+/* Displaced stepping. */
+
+/* Fix up the state of registers and memory after having single-stepped
+ a displaced instruction. */
+static void
+s390_displaced_step_fixup (struct gdbarch *gdbarch,
+ struct displaced_step_closure *closure,
+ CORE_ADDR from, CORE_ADDR to,
+ struct regcache *regs)
+{
+ /* Since we use simple_displaced_step_copy_insn, our closure is a
+ copy of the instruction. */
+ gdb_byte *insn = (gdb_byte *) closure;
+ static int s390_instrlen[] = { 2, 4, 4, 6 };
+ int insnlen = s390_instrlen[insn[0] >> 6];
+
+ /* Fields for various kinds of instructions. */
+ unsigned int b2, r1, r2, x2, r3;
+ int i2, d2;
+
+ /* Get current PC and addressing mode bit. */
+ CORE_ADDR pc = regcache_read_pc (regs);
+ ULONGEST amode = 0;
+
+ if (register_size (gdbarch, S390_PSWA_REGNUM) == 4)
+ {
+ regcache_cooked_read_unsigned (regs, S390_PSWA_REGNUM, &amode);
+ amode &= 0x80000000;
+ }
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog,
+ "displaced: (s390) fixup (%s, %s) pc %s len %d amode 0x%x\n",
+ paddress (gdbarch, from), paddress (gdbarch, to),
+ paddress (gdbarch, pc), insnlen, (int) amode);
+
+ /* Handle absolute branch and save instructions. */
+ if (is_rr (insn, op_basr, &r1, &r2)
+ || is_rx (insn, op_bas, &r1, &d2, &x2, &b2))
+ {
+ /* Recompute saved return address in R1. */
+ regcache_cooked_write_unsigned (regs, S390_R0_REGNUM + r1,
+ amode | (from + insnlen));
+ }
+
+ /* Handle absolute branch instructions. */
+ else if (is_rr (insn, op_bcr, &r1, &r2)
+ || is_rx (insn, op_bc, &r1, &d2, &x2, &b2)
+ || is_rr (insn, op_bctr, &r1, &r2)
+ || is_rre (insn, op_bctgr, &r1, &r2)
+ || is_rx (insn, op_bct, &r1, &d2, &x2, &b2)
+ || is_rxy (insn, op1_bctg, op2_brctg, &r1, &d2, &x2, &b2)
+ || is_rs (insn, op_bxh, &r1, &r3, &d2, &b2)
+ || is_rsy (insn, op1_bxhg, op2_bxhg, &r1, &r3, &d2, &b2)
+ || is_rs (insn, op_bxle, &r1, &r3, &d2, &b2)
+ || is_rsy (insn, op1_bxleg, op2_bxleg, &r1, &r3, &d2, &b2))
+ {
+ /* Update PC iff branch was *not* taken. */
+ if (pc == to + insnlen)
+ regcache_write_pc (regs, from + insnlen);
+ }
+
+ /* Handle PC-relative branch and save instructions. */
+ else if (is_ri (insn, op1_bras, op2_bras, &r1, &i2)
+ || is_ril (insn, op1_brasl, op2_brasl, &r1, &i2))
+ {
+ /* Update PC. */
+ regcache_write_pc (regs, pc - to + from);
+ /* Recompute saved return address in R1. */
+ regcache_cooked_write_unsigned (regs, S390_R0_REGNUM + r1,
+ amode | (from + insnlen));
+ }
+
+ /* Handle PC-relative branch instructions. */
+ else if (is_ri (insn, op1_brc, op2_brc, &r1, &i2)
+ || is_ril (insn, op1_brcl, op2_brcl, &r1, &i2)
+ || is_ri (insn, op1_brct, op2_brct, &r1, &i2)
+ || is_ri (insn, op1_brctg, op2_brctg, &r1, &i2)
+ || is_rsi (insn, op_brxh, &r1, &r3, &i2)
+ || is_rie (insn, op1_brxhg, op2_brxhg, &r1, &r3, &i2)
+ || is_rsi (insn, op_brxle, &r1, &r3, &i2)
+ || is_rie (insn, op1_brxlg, op2_brxlg, &r1, &r3, &i2))
+ {
+ /* Update PC. */
+ regcache_write_pc (regs, pc - to + from);
+ }
+
+ /* Handle LOAD ADDRESS RELATIVE LONG. */
+ else if (is_ril (insn, op1_larl, op2_larl, &r1, &i2))
+ {
+ /* Update PC. */
+ regcache_write_pc (regs, from + insnlen);
+ /* Recompute output address in R1. */
+ regcache_cooked_write_unsigned (regs, S390_R0_REGNUM + r1,
+ amode | (from + i2 * 2));
+ }
+
+ /* If we executed a breakpoint instruction, point PC right back at it. */
+ else if (insn[0] == 0x0 && insn[1] == 0x1)
+ regcache_write_pc (regs, from);
+
+ /* For any other insn, PC points right after the original instruction. */
+ else
+ regcache_write_pc (regs, from + insnlen);
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog,
+ "displaced: (s390) pc is now %s\n",
+ paddress (gdbarch, regcache_read_pc (regs)));
+}
+
+
+/* Helper routine to unwind pseudo registers. */
+
+static struct value *
+s390_unwind_pseudo_register (struct frame_info *this_frame, int regnum)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ struct type *type = register_type (gdbarch, regnum);
+
+ /* Unwind PC via PSW address. */
+ if (regnum == tdep->pc_regnum)
+ {
+ struct value *val;
+
+ val = frame_unwind_register_value (this_frame, S390_PSWA_REGNUM);
+ if (!value_optimized_out (val))
+ {
+ LONGEST pswa = value_as_long (val);
+
+ if (TYPE_LENGTH (type) == 4)
+ return value_from_pointer (type, pswa & 0x7fffffff);
+ else
+ return value_from_pointer (type, pswa);
+ }
+ }
+
+ /* Unwind CC via PSW mask. */
+ if (regnum == tdep->cc_regnum)
+ {
+ struct value *val;
+
+ val = frame_unwind_register_value (this_frame, S390_PSWM_REGNUM);
+ if (!value_optimized_out (val))
+ {
+ LONGEST pswm = value_as_long (val);
+
+ if (TYPE_LENGTH (type) == 4)
+ return value_from_longest (type, (pswm >> 12) & 3);
+ else
+ return value_from_longest (type, (pswm >> 44) & 3);
+ }
+ }
+
+ /* Unwind full GPRs to show at least the lower halves (as the
+ upper halves are undefined). */
+ if (regnum_is_gpr_full (tdep, regnum))
+ {
+ int reg = regnum - tdep->gpr_full_regnum;
+ struct value *val;
+
+ val = frame_unwind_register_value (this_frame, S390_R0_REGNUM + reg);
+ if (!value_optimized_out (val))
+ return value_cast (type, val);
+ }
+
+ return allocate_optimized_out_value (type);
+}
+
+static struct value *
+s390_trad_frame_prev_register (struct frame_info *this_frame,
+ struct trad_frame_saved_reg saved_regs[],
+ int regnum)
+{
+ if (regnum < S390_NUM_REGS)
+ return trad_frame_get_prev_register (this_frame, saved_regs, regnum);
+ else
+ return s390_unwind_pseudo_register (this_frame, regnum);
+}
+