[PATCH v2 17/17] arm fast tracepoints: Relocation of Thumb 32-bits instructions

Antoine Tremblay
2016-06-09T12:57:35Z

This patch adds the possibility to relocate some common 32-bits
Thumb-mode instructions that may depend on the current value of the PC.

As with the ARM-mode instructions, branches are very frequent and
pc-relative. Therefore, we support relocating all kinds of branches.

Other instructions are rejected if they reference the PC.

gdb/gdbserver/ChangeLog:

* linux-arm-low.c (thumb32_reloc_alu_imm): Implement.
(thumb32_reloc_b_bl_blx): Likewise.
(thumb32_reloc_block_xfer): Likewise.
(thumb32_reloc_copro_load_store): Likewise.
(thumb32_reloc_load_literal): Likewise.
(thumb32_reloc_load_reg_imm): Likewise.
(thumb32_reloc_pc_relative_32bit): Likewise.
(thumb32_reloc_preload): Likewise.
(thumb32_reloc_undef): Likewise.
(thumb32_reloc_table_branch): Likewise.
(copy_instruction_thumb32): Assign orig_loc and new_loc..

gdb/testsuite/ChangeLog:

* gdb.trace/ftrace-arm-insn.S (func_thumb_b_imm): New function.
(func_thumb_b_imm_cond): Likewise.
(func_thumb_bl_imm): Likewise.
(func_thumb_blx_imm): Likewise.
(func_thumb_ldm): Likewise.
(func_thumb_ldm_pc): Likewise.
(func_thumb_stm): Likewise.
* gdb.trace/ftrace-arm-insn.c (thumb_b_imm): New function.
(thumb_b_imm_cond): Likewise.
(thumb_bl_imm): Likewise.
(thumb_blx_imm): Likewise.
(thumb_ldm): Likewise.
(thumb_ldm_pc): Likewise.
(thumb_stm): Likewise.
(main): Call the new functions.
* gdb.trace/ftrace-arm-insn.exp: Test Thumb instructions relocation.
---
gdb/NEWS | 4 +
gdb/gdbserver/linux-arm-low.c | 175 ++++++++++++++++++++++++++--
gdb/testsuite/gdb.trace/ftrace-arm-insn.S | 157 +++++++++++++++++++++++++
gdb/testsuite/gdb.trace/ftrace-arm-insn.c | 16 +++
gdb/testsuite/gdb.trace/ftrace-arm-insn.exp | 8 ++
5 files changed, 350 insertions(+), 10 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 00d9554..673e0b1 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@

*** Changes since GDB 7.11

+* Support for fast tracepoints on arm-linux was added in GDBserver,
+ including JIT compiling fast tracepoint's conditional expression
+ bytecode into native code.
+
* Support for tracepoints on arm-linux was added in GDBServer.

* Fortran: Support structures with fields of dynamic types and
diff --git a/gdb/gdbserver/linux-arm-low.c b/gdb/gdbserver/linux-arm-low.c
index 2e66caa..6602208 100644
--- a/gdb/gdbserver/linux-arm-low.c
+++ b/gdb/gdbserver/linux-arm-low.c
@@ -1310,35 +1310,167 @@ static int
thumb32_reloc_alu_imm (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ uint16_t rm = bits (insn2, 0, 3);
+ uint16_t rd = bits (insn2, 8, 11);
+
+ /* This is essentially about the MOV (register, Thumb) instruction. Allow
+ relocation if it doesn't reference the PC. */
+ if (rm == ARM_PC_REGNUM || rd == ARM_PC_REGNUM)
+ return arm_reloc_refs_pc_error (data);
+
+ return thumb32_reloc_others (insn1, insn2, "ALU imm", data);
}

static int
thumb32_reloc_b_bl_blx (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ int ret = 1;
+ long offset;
+ CORE_ADDR absolute_dest;
+ uint16_t link = bit (insn2, 14);
+
+ int j1 = bit (insn2, 13);
+ int j2 = bit (insn2, 11);
+ int i1 = !(j1 ^ bit (insn1, 10));
+ int i2 = !(j2 ^ bit (insn1, 10));
+ int s = sbits (insn1, 10, 10);
+
+ if (!link)
+ {
+ /* It's a b. */
+ int imm11 = bits (insn2, 0, 10);
+
+ if (!bit (insn2, 12))
+ {
+ /* Encoding T3, with a condition and smaller immediate. */
+
+ int imm6 = bits (insn1, 0, 5);
+ int cond = bits (insn1, 6, 9);
+
+ offset = ((imm11 << 1) |
+ (imm6 << 12) |
+ (j1 << 18) |
+ (j2 << 19) |
+ (s << 20)) + 4;
+
+ absolute_dest = data->orig_loc + offset;
+
+ arm_emit_thumb_bw_cond (data->insns.thumb32, cond,
+ arm_thumb_branch_relative_distance
+ (data->new_loc, absolute_dest));
+ }
+ else
+ {
+ /* Encoding T4, with no condition but a larger immediate */
+ int imm10 = bits (insn1, 0, 9);
+
+ offset = ((imm11 << 1) |
+ (imm10 << 12) |
+ (i2 << 22) |
+ (i1 << 23) |
+ (s << 24)) + 4;
+
+ absolute_dest = data->orig_loc + offset;
+
+ arm_emit_thumb_bw
+ (data->insns.thumb32, arm_thumb_branch_relative_distance
+ (data->new_loc, absolute_dest));
+ }
+
+ ret = 0;
+ }
+ else
+ {
+ int exchange = !bit (insn2, 12);
+
+ if (!exchange)
+ {
+ /* It's a bl. */
+ int imm11 = bits (insn2, 0, 10);
+ int imm10 = bits (insn1, 0, 9);
+
+ offset = ((imm11 << 1)
+ | (imm10 << 12)
+ | (i2 << 22)
+ | (i1 << 23)
+ | (s << 24)) + 4;
+
+ absolute_dest = data->orig_loc + offset;
+
+ arm_emit_thumb_bl (data->insns.thumb32,
+ arm_thumb_branch_relative_distance
+ (data->new_loc, absolute_dest));
+ ret = 0;
+ }
+ else
+ {
+ /* It's a blx. */
+ int imm10l = bits (insn2, 1, 10);
+ int imm10h = bits (insn1, 0, 9);
+
+ offset = ((imm10l << 2)
+ | (imm10h << 12)
+ | (i2 << 22)
+ | (i1 << 23)
+ | (s << 24)) + 4;
+
+ absolute_dest = data->orig_loc + offset;
+
+ arm_emit_thumb_blx (data->insns.thumb32,
+ immediate_operand
+ (arm_thumb_to_arm_branch_relative_distance
+ (data->new_loc, absolute_dest)));
+ ret = 0;
+ }
+ }
+
+ return ret;
}

static int
thumb32_reloc_block_xfer (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ uint16_t rn = bits (insn1, 0, 3);
+ uint16_t load = bit (insn1, 4);
+
+ /* For LDM/POP instructions, there is not problem executing out of line even
+ if PC is part of the register list. It will simply result in an absolute
+ jump to the address popped.
+
+ For STM/PUSH instructions in Thumb mode, PC can't be in the register list,
+ unlike in ARM mode. So we don't need to check if it's present.
+ */
+
+ return thumb32_reloc_others (insn1, insn2, "ldm/stm", data);
}

static int
thumb32_reloc_copro_load_store (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ uint16_t rn = bits (insn1, 0, 3);
+
+ if (rn == ARM_PC_REGNUM)
+ return arm_reloc_refs_pc_error (data);
+
+ return thumb32_reloc_others (insn1, insn2, "copro load/store", data);
}

static int
thumb32_reloc_load_literal (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data, int size)
{
- return 1;
+ /* It should be possible to relocate this. The difficulty is that the
+ 12 offset bits are likely not to be enough to encode the offset at the new
+ location. We might be able to replace it by multiple instructions, loading
+ the absolute offset in a register, and using LDR (immediate, Thumb). We
+ would need to take care of which register we use and save the relevant
+ registers value.
+ */
+
+ return arm_reloc_refs_pc_error (data);
}

static int
@@ -1346,27 +1478,45 @@ thumb32_reloc_load_reg_imm (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data, int writeback,
int immed)
{
- return 1;
+ uint16_t rn = bits (insn1, 0, 3);
+
+ /* If rn is the PC, we should end up in load_literal, and that means the
+ instruction decoder has a bug. */
+ gdb_assert (rn != ARM_PC_REGNUM);
+
+ /* The instruction is safe even if the destination register (rt) is the PC,
+ as it will simply result in a branch. */
+ return thumb32_reloc_others (insn1, insn2, "load reg", data);
}

static int
thumb32_reloc_pc_relative_32bit (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ /* Form PC-relative Address. We could re-encode this instruction with the
+ new PC of the instruction, if we determine that this instruction is common
+ enough for it to be worth it. */
+
+ return arm_reloc_refs_pc_error (data);
}

static int
thumb32_reloc_preload (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
+ unsigned int rn = bits (insn1, 0, 3);
+
+ if (rn != ARM_PC_REGNUM)
+ return arm_reloc_refs_pc_error (data);
+
+ return thumb32_reloc_others (insn1, insn2, "preload", data);
}

static int
thumb32_reloc_undef (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
+ data->err = "The instruction is undefined, couldn't relocate.";
return 1;
}

@@ -1374,9 +1524,12 @@ static int
thumb32_reloc_table_branch (uint16_t insn1, uint16_t insn2,
struct arm_insn_reloc_data *data)
{
- return 1;
-}
+ /* The values in the table are relative offsets to PC. We could generate a
+ sequence of instructions to reproduce the same behavior. but these
+ instructions are probably not common enough for it to be worth it. */

+ return arm_reloc_refs_pc_error (data);
+}

struct thumb_32bit_insn_reloc_visitor thumb_32bit_insns_reloc_visitor = {
thumb32_reloc_alu_imm,
@@ -1416,6 +1569,8 @@ copy_instruction_thumb32 (CORE_ADDR *to, CORE_ADDR from, const char **err)
/* Set a default generic error message, which can be overridden by the
relocation functions. */
data.err = "Error relocating instruction.";
+ data.orig_loc = from;
+ data.new_loc = *to;

ret = thumb_32bit_relocate_insn (insn1, insn2,
&thumb_32bit_insns_reloc_visitor, &data);
diff --git a/gdb/testsuite/gdb.trace/ftrace-arm-insn.S b/gdb/testsuite/gdb.trace/ftrace-arm-insn.S
index c123727..1460fa8 100644
--- a/gdb/testsuite/gdb.trace/ftrace-arm-insn.S
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.S
@@ -219,3 +219,160 @@ insn_arm_stm:

epilogue

+
+/* thumb b imm (should test the encoding T4) */
+.thumb
+.type func_thumb_b_imm, STT_FUNC
+.global func_thumb_b_imm
+func_thumb_b_imm:
+ prologue
+
+insn_thumb_b_imm:
+ b.w thumb_b_imm_jump
+
+thumb_b_imm_jump_back:
+ epilogue
+
+thumb_b_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ b thumb_b_imm_jump_back
+
+
+/* thumb b imm cond (should test the encoding T3) */
+.thumb
+.type func_thumb_b_imm_cond, STT_FUNC
+.global func_thumb_b_imm_cond
+func_thumb_b_imm_cond:
+ prologue
+
+ /* Make a comparison that evaluates to true */
+ cmp r0, r0
+
+insn_thumb_b_imm_cond:
+ bne.w thumb_b_imm_cond_jump
+
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+thumb_b_imm_cond_jump:
+ epilogue
+
+/* thumb bl imm */
+.thumb
+.type func_thumb_bl_imm, STT_FUNC
+.global func_thumb_bl_imm
+func_thumb_bl_imm:
+ prologue
+
+insn_thumb_bl_imm:
+ bl.w thumb_bl_imm_jump
+
+ epilogue
+
+thumb_bl_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ bx lr
+
+
+/* thumb blx imm */
+.thumb
+.type func_thumb_blx_imm, STT_FUNC
+.global func_thumb_blx_imm
+func_thumb_blx_imm:
+ prologue
+
+insn_thumb_blx_imm:
+ blx.w thumb_blx_imm_jump
+
+ epilogue
+
+.arm
+thumb_blx_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ bx lr
+
+
+/* thumb ldm */
+.thumb
+.type func_thumb_ldm, STT_FUNC
+.global func_thumb_ldm
+func_thumb_ldm:
+ prologue
+
+ /* Store magic number in r0. */
+ ldr r0, =magic_number
+ ldr r0, [r0]
+
+ /* Push the magic number on the stack. We need to use at least two
+ registers for the stm/ldm, otherwise the ldm will be encoded as an
+ ldr. */
+ stmfd sp!, {r0, r1}
+ mov r0, 0
+
+insn_thumb_ldm:
+ ldmfd.w sp!, {r0, r1}
+ ldr r1, =global_variable
+ str r0, [r1]
+
+ epilogue
+
+
+/* thumb ldm pc */
+.thumb
+.type func_thumb_ldm_pc, STT_FUNC
+.global func_thumb_ldm_pc
+func_thumb_ldm_pc:
+ prologue
+
+ bl thumb_ldm_pc_jump
+
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ epilogue
+
+thumb_ldm_pc_jump:
+ /* We need to specify at least two registers, otherwise the ldm instruction
+ will get encoded as an ldr. */
+ stmfd sp!, {r0, lr}
+insn_thumb_ldm_pc:
+ ldmfd.w sp!, {r0, pc}
+
+
+/* thumb stm */
+.thumb
+.type func_thumb_stm, STT_FUNC
+.global func_thumb_stm
+func_thumb_stm:
+ prologue
+
+ ldr r0, =magic_number
+ ldr r0, [r0]
+
+insn_thumb_stm:
+ stmfd.w sp!, {r0}
+
+ ldmfd sp!, {r1}
+ ldr r0, =global_variable
+ str r1, [r0]
+
+ epilogue
diff --git a/gdb/testsuite/gdb.trace/ftrace-arm-insn.c b/gdb/testsuite/gdb.trace/ftrace-arm-insn.c
index ec08298..cbceddf 100644
--- a/gdb/testsuite/gdb.trace/ftrace-arm-insn.c
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.c
@@ -61,6 +61,14 @@ DEF_TEST_FN (arm_ldm)
DEF_TEST_FN (arm_ldm_pc)
DEF_TEST_FN (arm_stm)

+DEF_TEST_FN (thumb_b_imm)
+DEF_TEST_FN (thumb_b_imm_cond)
+DEF_TEST_FN (thumb_bl_imm)
+DEF_TEST_FN (thumb_blx_imm)
+DEF_TEST_FN (thumb_ldm)
+DEF_TEST_FN (thumb_ldm_pc)
+DEF_TEST_FN (thumb_stm)
+
int
main (void)
{
@@ -74,5 +82,13 @@ main (void)
test_arm_ldm_pc ();
test_arm_stm ();

+ test_thumb_b_imm ();
+ test_thumb_b_imm_cond ();
+ test_thumb_bl_imm ();
+ test_thumb_blx_imm ();
+ test_thumb_ldm ();
+ test_thumb_ldm_pc ();
+ test_thumb_stm ();
+
return 0;
}
diff --git a/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp b/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp
index e78d484..35cb59d 100644
--- a/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp
@@ -101,3 +101,11 @@ do_test arm_blx_reg
do_test arm_ldm
do_test arm_ldm_pc
do_test arm_stm
+
+do_test thumb_b_imm
+do_test thumb_b_imm_cond
+do_test thumb_bl_imm
+do_test thumb_blx_imm
+do_test thumb_ldm
+do_test thumb_ldm_pc
+do_test thumb_stm
--
2.8.1