Re: [patch 3/3] Displaced stepping for 16-bit Thumb instructions

Yao Qi
2010-12-25T14:22:07Z

[-- Attachment #1: Type: text/plain, Size: 296 bytes --]

Patch 3 is about supporting 16-bit Thumb displaced stepping. In this
patch, we decode 16-bit instruction, and process them. We also leave a
slot for 32-bit Thumb instructions, and put an error there.

Test cases are updated accordingly for some PC-related instructions.

--
Yao (é½å°§)

[-- Attachment #2: arm_disp_step_thumb_16bit.patch --]
[-- Type: text/x-patch, Size: 28664 bytes --]

gdb/
2010-12-25 Yao Qi <yao@codesourcery.com>

Displaced stepping support for 16-bit Thumb insns.

* gdb/arm-tdep.c (THUMB_NOP): New macro.
(displaced_read_reg): Support Thumb mode.
(thumb_copy_unmodified_16bit): New.
(cleanup_branch): Move some code to ...
(cleanup_branch_1): ... here. New. Support Thumb mode.
(cleanup_cbz_cbnz): New.
(copy_b_bl_blx): Move some code to ...
(arm_copy_b_bl_blx): ... here. New.
(thumb_copy_b): New.
(copy_bx_blx_reg): Move some code to ...
(arm_copy_bx_blx_reg): ... here. New.
(thumb_copy_bx_blx_reg): New.
(decode_unconditional): Update caller.
(decode_miscellaneous): Likewise.
(decode_b_bl_ldmstm): Likewise.
(copy_ldr_str_ldrb_strb): Replace magic number with macro.
(thumb_decode_dp): New.
(thumb_decode_pc_relative): New.
(thumb_copy_16bit_ldr_literal): New.
(thumb_copy_cbnz_cbz): New.
(thumb_process_displaced_16bit_insn): New.
(thumb_process_displaced_32bit_insn): New.

gdb/testsuite/
2010-12-25 Yao Qi <yao@codesourcery.com>

* gdb.arch/arm-disp-step.S: Test cbnz/cbz, adr and ldr.
* gdb.arch/arm-disp-step.exp: Likewise.

diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index 93c4e50..4d766c9 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -4326,6 +4326,9 @@ arm_adjust_breakpoint_address (struct gdbarch *gdbarch, CORE_ADDR bpaddr)

/* NOP instruction (mov r0, r0). */
#define ARM_NOP 0xe1a00000
+#define THUMB_NOP 0x4600
+
+static int displaced_in_arm_mode (struct regcache *regs);

/* Helper for register reads for displaced stepping. In particular, this
returns the PC as it would be seen by the instruction at its original
@@ -4338,10 +4341,15 @@ displaced_read_reg (struct regcache *regs, CORE_ADDR from, int regno)

if (regno == 15)
{
+ if (displaced_in_arm_mode (regs))
+ from += 8;
+ else
+ from += 6;
+
if (debug_displaced)
fprintf_unfiltered (gdb_stdlog, "displaced: read pc value %.8lx\n",
- (unsigned long) from + 8);
- return (ULONGEST) from + 8; /* Pipeline offset. */
+ (unsigned long) from);
+ return (ULONGEST) from; /* Pipeline offset. */
}
else
{
@@ -4530,6 +4538,21 @@ copy_unmodified (struct gdbarch *gdbarch, uint32_t insn,
return 0;
}

+static int
+thumb_copy_unmodified_16bit (struct gdbarch *gdbarch, unsigned int insn,
+ const char *iname,
+ struct displaced_step_closure *dsc)
+{
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying insn %.4x, "
+ "opcode/class '%s' unmodified\n", insn,
+ iname);
+
+ RECORD_MOD_16BIT_INSN (0, insn);
+
+ return 0;
+}
+
/* Preload instructions with immediate offset. */

static void
@@ -4668,16 +4691,11 @@ copy_copro_load_store (struct gdbarch *gdbarch, uint32_t insn,
return 0;
}

-/* Clean up branch instructions (actually perform the branch, by setting
- PC). */
-
static void
-cleanup_branch (struct gdbarch *gdbarch, struct regcache *regs,
- struct displaced_step_closure *dsc)
+cleanup_branch_1 (struct gdbarch *gdbarch, struct regcache *regs,
+ struct displaced_step_closure *dsc, unsigned int branch_taken)
{
ULONGEST from = dsc->insn_addr;
- uint32_t status = displaced_read_reg (regs, from, ARM_PS_REGNUM);
- int branch_taken = condition_true (dsc->u.branch.cond, status);
enum pc_write_style write_pc = dsc->u.branch.exchange
? BX_WRITE_PC : BRANCH_WRITE_PC;

@@ -4687,29 +4705,45 @@ cleanup_branch (struct gdbarch *gdbarch, struct regcache *regs,
if (dsc->u.branch.link)
{
ULONGEST pc = displaced_read_reg (regs, from, ARM_PC_REGNUM);
- displaced_write_reg (regs, dsc, ARM_LR_REGNUM, pc - 4, CANNOT_WRITE_PC);
+
+ if (displaced_in_arm_mode (regs))
+ displaced_write_reg (regs, dsc, ARM_LR_REGNUM, pc - 4, CANNOT_WRITE_PC);
+ else
+ displaced_write_reg (regs, dsc, ARM_LR_REGNUM, (pc - 2) | 1u,
+ CANNOT_WRITE_PC);
}

displaced_write_reg (regs, dsc, ARM_PC_REGNUM, dsc->u.branch.dest, write_pc);
}

+/* Clean up branch instructions (actually perform the branch, by setting
+ PC). */
+static void
+cleanup_branch(struct gdbarch *gdbarch, struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ ULONGEST from = dsc->insn_addr;
+ uint32_t status = displaced_read_reg (regs, from, ARM_PS_REGNUM);
+ int branch_taken = condition_true (dsc->u.branch.cond, status);
+
+ cleanup_branch_1 (gdbarch, regs, dsc, branch_taken);
+}
+
+static void
+cleanup_cbz_cbnz(struct gdbarch *gdbarch, struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ cleanup_branch_1 (gdbarch, regs, dsc, dsc->u.branch.cond);
+}
+
/* Copy B/BL/BLX instructions with immediate destinations. */

static int
-copy_b_bl_blx (struct gdbarch *gdbarch, uint32_t insn,
- struct regcache *regs, struct displaced_step_closure *dsc)
+copy_b_bl_blx (struct gdbarch *gdbarch, unsigned int cond, int exchange,
+ int link, long offset, struct regcache *regs,
+ struct displaced_step_closure *dsc)
{
- unsigned int cond = bits (insn, 28, 31);
- int exchange = (cond == 0xf);
- int link = exchange || bit (insn, 24);
CORE_ADDR from = dsc->insn_addr;
- long offset;
-
- if (debug_displaced)
- fprintf_unfiltered (gdb_stdlog, "displaced: copying %s immediate insn "
- "%.8lx\n", (exchange) ? "blx" : (link) ? "bl" : "b",
- (unsigned long) insn);
-
/* Implement "BL<cond> " as:

Preparation: cond <- instruction condition
@@ -4718,6 +4752,40 @@ copy_b_bl_blx (struct gdbarch *gdbarch, uint32_t insn,

B<cond> similar, but don't set r14 in cleanup. */

+
+ dsc->u.branch.cond = cond;
+ dsc->u.branch.link = link;
+ dsc->u.branch.exchange = exchange;
+
+ if (arm_pc_is_thumb (gdbarch, from))
+ {
+ /* Plus the size of THUMB_NOP and B/BL/BLX. */
+ dsc->u.branch.dest = from + 2 + 4 + offset;
+ RECORD_MOD_16BIT_INSN (0, THUMB_NOP);
+ }
+ else
+ {
+ dsc->u.branch.dest = from + 8 + offset;
+ RECORD_MOD_32BIT_INSN (0, ARM_NOP);
+ }
+
+ dsc->cleanup = &cleanup_branch;
+
+ return 0;
+}
+static int
+arm_copy_b_bl_blx (struct gdbarch *gdbarch, uint32_t insn,
+ struct regcache *regs, struct displaced_step_closure *dsc)
+{
+ unsigned int cond = bits (insn, 28, 31);
+ int exchange = (cond == 0xf);
+ int link = exchange || bit (insn, 24);
+ long offset;
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying %s immediate insn "
+ "%.8lx\n", (exchange) ? "blx" : (link) ? "bl" : "b",
+ (unsigned long) insn);
if (exchange)
/* For BLX, set bit 0 of the destination. The cleanup_branch function will
then arrange the switch into Thumb mode. */
@@ -4728,12 +4796,40 @@ copy_b_bl_blx (struct gdbarch *gdbarch, uint32_t insn,
if (bit (offset, 25))
offset = offset | ~0x3ffffff;

+ return copy_b_bl_blx (gdbarch, cond, exchange, link, offset, regs, dsc);
+}
+
+static int
+thumb_copy_b (struct gdbarch *gdbarch, unsigned short insn,
+ struct displaced_step_closure *dsc)
+{
+ unsigned int cond = 0;
+ int offset = 0;
+ unsigned short bit_12_15 = bits (insn, 12, 15);
+ CORE_ADDR from = dsc->insn_addr;
+
+ if (bit_12_15 == 0xd)
+ {
+ offset = sbits (insn, 0, 7);
+ cond = bits (insn, 8, 11);
+ }
+ else if (bit_12_15 == 0xe)
+ {
+ offset = sbits (insn, 0, 10);
+ cond = INST_AL;
+ }
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog,
+ "displaced: copying b immediate insn %.4x "
+ "with offset %d\n", insn, offset);
+
dsc->u.branch.cond = cond;
- dsc->u.branch.link = link;
- dsc->u.branch.exchange = exchange;
- dsc->u.branch.dest = from + 8 + offset;
+ dsc->u.branch.link = 0;
+ dsc->u.branch.exchange = 0;
+ dsc->u.branch.dest = from + 4 + offset;

- RECORD_MOD_32BIT_INSN (0, ARM_NOP);
+ RECORD_MOD_16BIT_INSN (0, THUMB_NOP);

dsc->cleanup = &cleanup_branch;

@@ -4743,19 +4839,12 @@ copy_b_bl_blx (struct gdbarch *gdbarch, uint32_t insn,
/* Copy BX/BLX with register-specified destinations. */

static int
-copy_bx_blx_reg (struct gdbarch *gdbarch, uint32_t insn,
- struct regcache *regs, struct displaced_step_closure *dsc)
+copy_bx_blx_reg (struct gdbarch *gdbarch, unsigned int cond, int link,
+ unsigned int rm, struct regcache *regs,
+ struct displaced_step_closure *dsc)
{
- unsigned int cond = bits (insn, 28, 31);
- /* BX: x12xxx1x
- BLX: x12xxx3x. */
- int link = bit (insn, 5);
- unsigned int rm = bits (insn, 0, 3);
CORE_ADDR from = dsc->insn_addr;
-
- if (debug_displaced)
- fprintf_unfiltered (gdb_stdlog, "displaced: copying %s register insn "
- "%.8lx\n", (link) ? "blx" : "bx", (unsigned long) insn);
+ int is_thumb = arm_pc_is_thumb (gdbarch, from);

/* Implement {BX,BLX}<cond> <reg>" as:

@@ -4767,16 +4856,56 @@ copy_bx_blx_reg (struct gdbarch *gdbarch, uint32_t insn,

dsc->u.branch.dest = displaced_read_reg (regs, from, rm);

- dsc->u.branch.cond = cond;
dsc->u.branch.link = link;
dsc->u.branch.exchange = 1;

- RECORD_MOD_32BIT_INSN (0, ARM_NOP);
+ if (is_thumb)
+ {
+ /* Always true for thumb. */
+ dsc->u.branch.cond = INST_AL;
+ RECORD_MOD_16BIT_INSN (0, THUMB_NOP);
+ }
+ else
+ {
+ dsc->u.branch.cond = cond;
+ RECORD_MOD_32BIT_INSN (0, ARM_NOP);
+ }

dsc->cleanup = &cleanup_branch;

return 0;
}
+static int
+arm_copy_bx_blx_reg (struct gdbarch *gdbarch, uint32_t insn,
+ struct regcache *regs, struct displaced_step_closure *dsc)
+{
+ unsigned int cond = bits (insn, 28, 31);
+ /* BX: x12xxx1x
+ BLX: x12xxx3x. */
+ int link = bit (insn, 5);
+ unsigned int rm = bits (insn, 0, 3);
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying %s register insn "
+ "%.8lx\n", (link) ? "blx" : "bx", (unsigned long) insn);
+
+ return copy_bx_blx_reg (gdbarch, cond, link, rm, regs, dsc);
+}
+
+static int
+thumb_copy_bx_blx_reg (struct gdbarch *gdbarch, uint16_t insn,
+ struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ int link = bit (insn, 7);
+ unsigned int rm = bits (insn, 3, 6);
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying %s register insn "
+ "%.4x\n", (link) ? "blx" : "bx", (unsigned short) insn);
+
+ return copy_bx_blx_reg (gdbarch, INST_AL, link, rm, regs, dsc);
+}

/* Copy/cleanup arithmetic/logic instruction with immediate RHS. */

@@ -5171,7 +5300,7 @@ copy_ldr_str_ldrb_strb (struct gdbarch *gdbarch, uint32_t insn,
Otherwise we don't know what value to write for PC, since the offset is
architecture-dependent (sometimes PC+8, sometimes PC+12). */

- if (load || rt != 15)
+ if (load || rt != ARM_PC_REGNUM)
{
dsc->u.ldst.restore_r4 = 0;

@@ -5694,7 +5823,7 @@ decode_unconditional (struct gdbarch *gdbarch, uint32_t insn,
return copy_unmodified (gdbarch, insn, "rfe", dsc);

case 0x4: case 0x5: case 0x6: case 0x7:
- return copy_b_bl_blx (gdbarch, insn, regs, dsc);
+ return arm_copy_b_bl_blx (gdbarch, insn, regs, dsc);

case 0x8:
switch ((insn & 0xe00000) >> 21)
@@ -5777,7 +5906,7 @@ decode_miscellaneous (struct gdbarch *gdbarch, uint32_t insn,

case 0x1:
if (op == 0x1) /* bx. */
- return copy_bx_blx_reg (gdbarch, insn, regs, dsc);
+ return arm_copy_bx_blx_reg (gdbarch, insn, regs, dsc);
else if (op == 0x3)
return copy_unmodified (gdbarch, insn, "clz", dsc);
else
@@ -5791,8 +5920,8 @@ decode_miscellaneous (struct gdbarch *gdbarch, uint32_t insn,
return copy_undef (gdbarch, insn, dsc);

case 0x3:
- if (op == 0x1)
- return copy_bx_blx_reg (gdbarch, insn, regs, dsc); /* blx register. */
+ if (op == 0x1) /* blx register. */
+ return arm_copy_bx_blx_reg (gdbarch, insn, regs, dsc);
else
return copy_undef (gdbarch, insn, dsc);

@@ -5955,7 +6084,7 @@ decode_b_bl_ldmstm (struct gdbarch *gdbarch, int32_t insn,
struct regcache *regs, struct displaced_step_closure *dsc)
{
if (bit (insn, 25))
- return copy_b_bl_blx (gdbarch, insn, regs, dsc);
+ return arm_copy_b_bl_blx (gdbarch, insn, regs, dsc);
else
return copy_block_xfer (gdbarch, insn, regs, dsc);
}
@@ -6036,12 +6165,231 @@ decode_svc_copro (struct gdbarch *gdbarch, uint32_t insn, CORE_ADDR to,
return copy_undef (gdbarch, insn, dsc); /* Possibly unreachable. */
}

+static int
+thumb_decode_dp (struct gdbarch *gdbarch, unsigned short insn,
+ struct displaced_step_closure *dsc)
+{
+ /* 16-bit data-processing insns are not related to PC. */
+ return thumb_copy_unmodified_16bit (gdbarch, insn,"data-processing", dsc);
+}
+
+static int
+thumb_decode_pc_relative (struct gdbarch *gdbarch, unsigned short insn,
+ struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ unsigned int rd = bits (insn, 8, 10);
+ unsigned int imm8 = bits (insn, 0, 7);
+ CORE_ADDR from = dsc->insn_addr;
+ int val;
+
+ /* ADR Rd, #imm8
+
+ Rewrite as:
+
+ Preparation: Rd <- PC
+ Insn: ADD Rd, #imm8
+ Cleanup: Null.
+ */
+
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog,
+ "displaced: copying thumb adr r%d, #%d insn %.4x\n",
+ rd, imm8, insn);
+
+ /* Rd <- PC */
+ val = displaced_read_reg (regs, from, ARM_PC_REGNUM);
+ displaced_write_reg (regs, dsc, rd, val, CANNOT_WRITE_PC);
+
+ /* ADDS Rd, #imm8 */
+ RECORD_MOD_32BIT_INSN (0, 0x3000 | (rd << 8) | imm8);
+
+ return 0;
+}
+
+static int
+thumb_copy_16bit_ldr_literal (struct gdbarch *gdbarch, unsigned short insn1,
+ struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ unsigned int rt = bits (insn1, 8, 7);
+ unsigned int pc;
+ int imm8 = sbits (insn1, 0, 7);
+ CORE_ADDR from = dsc->insn_addr;
+
+ /* LDR Rd, #imm8
+
+ Rwrite as:
+
+ Preparation: tmp2 <- R2, tmp3 <- R3, R2 <- PC, R3 <- #imm8;
+ if (Rd is not R0) tmp0 <- R0;
+ Insn: LDR R0, [R2, R3];
+ Cleanup: R2 <- tmp2, R3 <- tmp3,
+ if (Rd is not R0) Rd <- R0, R0 <- tmp0 */
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying thumb ldr literal "
+ "insn %.4x\n", insn1);
+
+ dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+ dsc->tmp[2] = displaced_read_reg (regs, from, 2);
+ dsc->tmp[3] = displaced_read_reg (regs, from, 3);
+ pc = displaced_read_reg (regs, from, ARM_PC_REGNUM);
+
+ displaced_write_reg (regs, dsc, 2, pc, CANNOT_WRITE_PC);
+ displaced_write_reg (regs, dsc, 3, imm8, CANNOT_WRITE_PC);
+
+ dsc->rd = rt;
+ dsc->u.ldst.xfersize = 4;
+ dsc->u.ldst.rn = 0;
+ dsc->u.ldst.immed = 0;
+ dsc->u.ldst.writeback = 0;
+ dsc->u.ldst.restore_r4 = 0;
+
+ RECORD_MOD_16BIT_INSN (0, 0x58d0); /* ldr r0, [r2, r3]*/
+
+ dsc->cleanup = &cleanup_load;
+
+ return 0;
+}
+
+static int
+thumb_copy_cbnz_cbz (struct gdbarch *gdbarch, unsigned short insn1,
+ struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ int non_zero = bit (insn1, 11);
+ unsigned int imm5 = (bit (insn1, 9) << 6) | (bits (insn1, 3, 7) << 1);
+ CORE_ADDR from = dsc->insn_addr;
+ int rn = bits (insn1, 0, 2);
+ int rn_val = displaced_read_reg (regs, from, rn);
+
+ dsc->u.branch.cond = (rn_val && non_zero) || (!rn_val && !non_zero);
+ dsc->u.branch.link = 0;
+ dsc->u.branch.exchange = 0;
+
+ dsc->u.branch.dest = from + 4 + imm5;
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: copying %s [r%d = 0x%x]"
+ " insn %.4x to %.8lx\n", non_zero ? "cbnz" : "cbz",
+ rn, rn_val, insn1, dsc->u.branch.dest);
+
+ RECORD_MOD_16BIT_INSN (0, THUMB_NOP);
+
+ dsc->cleanup = &cleanup_cbz_cbnz;
+ return 0;
+}
+
+static void
+thumb_process_displaced_16bit_insn (struct gdbarch *gdbarch,
+ unsigned short insn1, CORE_ADDR to,
+ struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ unsigned short op_bit_12_15 = bits (insn1, 12, 15);
+ unsigned short op_bit_10_11 = bits (insn1, 10, 11);
+ int err = 0;
+
+ /* 16-bit thumb instructions. */
+ switch (op_bit_12_15)
+ {
+ /* Shift (imme), add, subtract, move and compare*/
+ case 0: case 1: case 2: case 3:
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"", dsc);
+ break;
+ case 4:
+ switch (op_bit_10_11)
+ {
+ case 0: /* Data-processing */
+ err = thumb_decode_dp (gdbarch, insn1, dsc);
+ break;
+ case 1: /* Special data instructions and branch and exchange */
+ {
+ unsigned short op = bits (insn1, 7, 9);
+ if (op == 6 || op == 7) /* BX or BLX */
+ err = thumb_copy_bx_blx_reg (gdbarch, insn1, regs, dsc);
+ else
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1, "special data",
+ dsc);
+ }
+ break;
+ default: /* LDR (literal) */
+ err = thumb_copy_16bit_ldr_literal (gdbarch, insn1, regs, dsc);
+ }
+ break;
+ case 5: case 6: case 7: case 8: case 9: /* Load/Store single data item */
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"ldr/str", dsc);
+ break;
+ case 10:
+ if (op_bit_10_11 < 2) /* Generate PC-relative address */
+ err = thumb_decode_pc_relative (gdbarch, insn1, regs, dsc);
+ else /* Generate SP-relative address */
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"sp-relative", dsc);
+ break;
+ case 11: /* Misc 16-bit instructions */
+ {
+ switch (bits (insn1, 8, 11))
+ {
+ case 1: case 3: case 9: case 11: /* CBNZ, CBZ */
+ err = thumb_copy_cbnz_cbz (gdbarch, insn1, regs, dsc);
+ break;
+ default:
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"", dsc);
+ }
+ }
+ break;
+ case 12:
+ if (op_bit_10_11 < 2) /* Store multiple registers */
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"stm", dsc);
+ else /* Load multiple registers */
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"ldm", dsc);
+ break;
+ case 13: /* Conditional branch and supervisor call */
+ if (bits (insn1, 9, 11) != 7) /* conditional branch */
+ err = thumb_copy_b (gdbarch, insn1, dsc);
+ else
+ err = thumb_copy_unmodified_16bit (gdbarch, insn1,"svc", dsc);
+ break;
+ case 14: /* Unconditional branch */
+ err = thumb_copy_b (gdbarch, insn1, dsc);
+ break;
+ default:
+ internal_error (__FILE__, __LINE__,
+ _("thumb_process_displaced_insn: Instruction decode error"));
+ }
+
+ if (err)
+ internal_error (__FILE__, __LINE__,
+ _("thumb_process_displaced_insn: Instruction decode error"));
+}
+
+static void
+thumb_process_displaced_32bit_insn (struct gdbarch *gdbarch, CORE_ADDR from,
+ CORE_ADDR to, struct regcache *regs,
+ struct displaced_step_closure *dsc)
+{
+ error (_("Displaced stepping is only supported in ARM mode and Thumb 16bit instructions"));
+}
+
static void
thumb_process_displaced_insn (struct gdbarch *gdbarch, CORE_ADDR from,
CORE_ADDR to, struct regcache *regs,
struct displaced_step_closure *dsc)
{
- error (_("Displaced stepping is only supported in ARM mode"));
+ enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
+ unsigned short insn1
+ = read_memory_unsigned_integer (from, 2, byte_order_for_code);
+
+ if (debug_displaced)
+ fprintf_unfiltered (gdb_stdlog, "displaced: process thumb insn %.4x "
+ "at %.8lx\n", insn1, (unsigned long) from);
+
+ if ((bits (insn1, 13, 15) == 7) && (bits (insn1, 11, 12)))
+ thumb_process_displaced_32bit_insn(gdbarch, from, to, regs, dsc);
+ else
+ thumb_process_displaced_16bit_insn(gdbarch, insn1, to, regs, dsc);
}

void
diff --git a/gdb/testsuite/gdb.arch/arm-disp-step.S b/gdb/testsuite/gdb.arch/arm-disp-step.S
index d748718..2e5b7ba 100644
--- a/gdb/testsuite/gdb.arch/arm-disp-step.S
+++ b/gdb/testsuite/gdb.arch/arm-disp-step.S
@@ -48,6 +48,26 @@ test_ret_end:
bl test_ldm_stm_pc
#endif

+ /* Test ldrX literal in ARM and Thumb-2 */
+#if !defined (__thumb__)
+ bl test_ldr_literal
+#endif
+
+ /* Test ldr literal in Thumb */
+#if defined(__thumb__)
+ bl test_ldr_literal_16
+#endif
+
+ /* Test cbnz/cbz in Thumb-2 */
+#if defined(__thumb2__)
+ bl test_cbz_cbnz
+#endif
+
+ /* Test adr in Thumb and Thumb-2 */
+#if defined(__thumb) || defined(__thumb2__)
+ bl test_adr
+#endif
+
/* Return */
mov sp, r7
sub sp, sp, #4
@@ -118,3 +138,75 @@ test_ldm_stm_pc_ret:
.word test_ldm_stm_pc_ret
.size test_ldm_stm_pc, .-test_ldm_stm_pc
#endif
+
+#if !defined (__thumb__)
+ .global test_ldr_literal
+ .type test_ldr_literal, %function
+test_ldr_literal:
+ ldrh r0, [pc]
+ .global test_ldrsb_literal
+test_ldrsb_literal:
+ ldrsb r0, [pc]
+ .global test_ldrsh_literal
+test_ldrsh_literal:
+ ldrsh r0, [pc]
+ .global test_ldr_literal_end
+test_ldr_literal_end:
+ bx lr
+ .size test_ldr_literal, .-test_ldr_literal
+#endif
+
+#if defined(__thumb__)
+ .global test_ldr_literal_16
+ .code 16
+ .thumb_func
+test_ldr_literal_16:
+ ldr r0, .L2
+ .global test_ldr_literal_16_end
+test_ldr_literal_16_end:
+ bx lr
+ .align 2
+.L2:
+ .word test_ldr_literal_16
+ .size test_ldr_literal_16, .-test_ldr_literal_16
+#endif
+
+#if defined(__thumb2__)
+ .global test_cbz_cbnz
+ .code 16
+ .thumb_func
+test_cbz_cbnz:
+ movs r0, #0
+ .global test_zero_cbnz
+test_zero_cbnz:
+ cbnz r0, .L3
+ .global test_zero_cbz
+test_zero_cbz:
+ cbz r0, .L3
+.L3:
+ movs r0, #1
+ .global test_non_zero_cbz
+test_non_zero_cbz:
+ cbz r0, .L4
+ .global test_non_zero_cbnz
+test_non_zero_cbnz:
+ cbnz r0, .L4
+ nop
+.L4:
+ .global test_cbz_cbnz_end
+test_cbz_cbnz_end:
+ bx lr
+ .size test_cbz_cbnz, .-test_cbz_cbnz
+#endif
+
+#if defined(__thumb) || defined(__thumb2__)
+ .global test_adr
+ .code 16
+ .thumb_func
+test_adr:
+ adr r0, #1
+ .global test_adr_end
+test_adr_end:
+ bx lr
+ .size test_adr, .-test_adr
+#endif
\ No newline at end of file
diff --git a/gdb/testsuite/gdb.arch/arm-disp-step.exp b/gdb/testsuite/gdb.arch/arm-disp-step.exp
index 826f728..51b7951 100644
--- a/gdb/testsuite/gdb.arch/arm-disp-step.exp
+++ b/gdb/testsuite/gdb.arch/arm-disp-step.exp
@@ -37,6 +37,22 @@ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list
return -1
}

+# Try to resume program with displaced stepping. If displaced stepping is
+# not supported, turn it off, and continue.
+
+proc try_continue_with_displaced_step { msg loc } {
+ gdb_test_no_output "set displaced-stepping on"
+ gdb_test_multiple "continue" "continue to test_call_end" {
+ -re ".*$loc.*" {
+ pass "continue to $msg"
+ }
+ -re "Displaced stepping is only supported in.*" {
+ gdb_test_no_output "set displaced-stepping off"
+ gdb_test "continue" ".*"
+ kfail "gdb/NNNN" $msg
+ }
+ }
+}

#########################################
# Test ldm/stm related to PC.
@@ -50,7 +66,11 @@ proc test_ldm_stm_pc {} {
}
-re "Function \"test_ldm_stm_pc\" not defined\..*Make breakpoint pending on future shared library load.*y or .n.. $" {
gdb_test "n" "" "Test case is compiled in Thumb mode"
- return
+ return 0
+ }
+ -re "No symbol.*" {
+ pass "break test_ldm_stm_pc"
+ return 0
}
}

@@ -68,10 +88,75 @@ proc test_ldm_stm_pc {} {
gdb_continue_to_breakpoint "continue to test_ldm_stm_pc_ret" \
".*bx lr.*"
}
+
+#########################################
+# Test ldrX literal
+proc test_ldr_literal {} {
+ global srcfile
+ # Try to set breakpoint on test_ldm_stm_pc. If symbol 'test_ldm_stm_pc'
+ # can't be resolved, test case is compiled in Thumb mode, skip it.
+ gdb_test_multiple "break *test_ldr_literal" "break test_ldr_literal" {
+ -re "Breakpoint.*at.* file .*$srcfile, line.*" {
+ pass "break test_ldr_literal"
+ }
+ -re "Function \"test_ldr_literal\" not defined\..*Make breakpoint pending on future shared library load.*y or .n.. $" {
+ gdb_test "n" "" "Test case is compiled in Thumb mode"
+ return 0
+ }
+ -re "No symbol.*" {
+ return 0
+ }
+ }
+
+ gdb_test "break *test_ldrsb_literal" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_ldrsb_literal"
+ gdb_test "break *test_ldrsh_literal" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_ldrsh_literal"
+ gdb_test "break *test_ldr_literal_end" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_test_ldr_literal_end"
+
+ gdb_continue_to_breakpoint "continue to test_ldr_literal" \
+ ".*ldrh.*r0\,.*\[pc\].*"
+ gdb_continue_to_breakpoint "continue to test_ldrsb_literal" \
+ ".*ldrsb.*r0\,.*\[pc\].*"
+ gdb_continue_to_breakpoint "continue to test_ldrsh_literal" \
+ ".*ldrsh.*r0\,.*\[pc\].*"
+ gdb_continue_to_breakpoint "continue to test_ldr_literal_ret" \
+ ".*bx lr.*"
+
+ gdb_test_multiple "break test_ldr_literal_16" "break test_ldr_literal_16" {
+ -re "Breakpoint.*at.* file .*$srcfile, line.*" {
+ pass "break test_ldr_literal"
+ }
+ -re "Function \"test_ldr_literal_16\" not defined\..*Make breakpoint pending on future shared library load.*y or .n.. $" {
+ gdb_test "n" "" "skip"
+ return 0
+ }
+ }
+
+ gdb_test "break *test_ldr_literal_16_end" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_test_ldr_literal_16_end"
+
+ gdb_continue_to_breakpoint "continue to test_ldr_literal" \
+ ".*ldr.*r0\,.*L2.*"
+ gdb_continue_to_breakpoint "continue to test_ldrsb_literal" \
+ ".*bx lr.*"
+}
+
##########################################
# Test call/ret.
proc test_call_ret {} {
global srcfile
+ global testfile
+
+ gdb_test "break *test_call" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_call"
+
gdb_test "break *test_call_end" \
"Breakpoint.*at.* file .*$srcfile, line.*" \
"break test_call_end"
@@ -82,8 +167,10 @@ proc test_call_ret {} {
"Breakpoint.*at.* file .*$srcfile, line.*" \
"break test_ret_end"

- gdb_continue_to_breakpoint "continue to test_call_end" \
- ".*@ Location test_call_end.*"
+ try_continue_with_displaced_step "test_call" "bl test_call_subr"
+ try_continue_with_displaced_step "test_call_end" \
+ "@ Location test_call_end"
+
gdb_continue_to_breakpoint "continue to test_ret" \
".*bx lr.*"
gdb_continue_to_breakpoint "continue to test_ret_end" \
@@ -122,7 +209,66 @@ proc test_ldr_from_pc {} {

gdb_continue_to_breakpoint "continue to test_ldr_pc" \
".*ldr.*r1\,.*\[pc, #0\].*"
- gdb_continue_to_breakpoint "continue to Lbranch" \
+ gdb_continue_to_breakpoint "continue to test_ldr_pc_ret" \
+ ".*bx lr.*"
+}
+
+#########################################
+
+# Test cbz and cbnz
+proc test_cbz_cbnz {} {
+ global srcfile
+
+ gdb_test_multiple "break *test_zero_cbnz" "break test_zero_cbnz" {
+ -re "Breakpoint.*at.* file .*$srcfile, line.*" {
+ pass "break test_ldr_literal"
+ }
+ -re "No symbol.*" {
+ return 0
+ }
+ }
+
+ gdb_test "break *test_zero_cbz" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_zero_cbz"
+ gdb_test "break *test_non_zero_cbnz" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_non_zero_cbnz"
+ gdb_test "break *test_non_zero_cbz" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_non_zero_cbz"
+
+ gdb_continue_to_breakpoint "continue to test_zero_cbnz" \
+ ".*cbnz.*r0\,.*\.L3.*"
+ gdb_continue_to_breakpoint "continue to test_zero_cbz" \
+ ".*cbz.*r0\,.*\.L3.*"
+ gdb_continue_to_breakpoint "continue to test_non_zero_cbz" \
+ ".*cbz.*r0\,.*\.L4.*"
+ gdb_continue_to_breakpoint "continue to test_non_zero_cbnz" \
+ ".*cbnz.*r0\,.*\.L4.*"
+}
+
+# Test adr
+
+proc test_adr {} {
+ global srcfile
+
+ gdb_test_multiple "break *test_adr" "break test_adr" {
+ -re "Breakpoint.*at.* file .*$srcfile, line.*" {
+ pass "break test_adr"
+ }
+ -re "No symbol.*" {
+ return 0
+ }
+ }
+
+ gdb_test "break *test_adr_end" \
+ "Breakpoint.*at.* file .*$srcfile, line.*" \
+ "break test_adr_end"
+
+ gdb_continue_to_breakpoint "continue to test_adr" \
+ ".*adr.*r0\,.*#1.*"
+ gdb_continue_to_breakpoint "continue to test_adr_end" \
".*bx lr.*"
}

@@ -143,20 +289,6 @@ if ![runto_main] then {
gdb_test_no_output "set displaced-stepping on"
gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"

-gdb_test "break *test_call" \
- "Breakpoint.*at.* file .*$srcfile, line.*" \
- "break test_call"
-
-gdb_test_multiple "continue" "continue to test_call" {
- -re ".*bl test_call_subr.*" {
- pass "continue to test_call"
- }
- -re "Displaced stepping is only supported in" {
- kfail "gdb/NNNN" $testfile
- return
- }
- }
-
test_call_ret

test_branch
@@ -165,6 +297,11 @@ test_ldr_from_pc

test_ldm_stm_pc

+test_ldr_literal
+
+test_cbz_cbnz
+
+test_adr
##########################################

# Done, run program to exit.