[PATCH v2 16/17] arm fast tracepoints: Relocation of ARM instructions

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

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

Branches are very frequent and are always pc-relative, so we make sure
to support pretty much all cases. We calculate the new relative offset,
given the old and new instruction locations.

Other instructions are rejected as soon as they reference the PC. We
judged these cases are not frequent enough to be worth the effort of
relocating.

gdb/gdbserver/ChangeLog:

* linux-arm-low.c (struct arm_insn_reloc_data): Add orig_loc and
new_loc fields.
(arm_reloc_refs_pc_error): New function.
(arm_reloc_alu_imm): Implement.
(arm_reloc_alu_reg): Implement.
(arm_reloc_alu_shifted_reg): Implement.
(arm_reloc_b_bl_blx): Implement.
(arm_reloc_block_xfer): Implement.
(arm_reloc_bx_blx_reg): Implement.
(arm_reloc_copro_load_store): Implement.
(arm_reloc_extra_ld_st): Implement.
(arm_reloc_ldr_str_ldrb_strb): Implement.
(arm_reloc_preload): Implement.
(arm_reloc_preload_reg): Implement.
(arm_reloc_svc): Implement.
(arm_reloc_undef): Implement.
(arm_reloc_unpred): Implement.
(copy_instruction_arm): Assign orig_loc and new_loc.

gdb/testsuite/ChangeLog:

* gdb.trace/ftrace-arm-insn.S: New file.
* gdb.trace/ftrace-arm-insn.c: New file.
* gdb.trace/ftrace-arm-insn.exp: New file.
---
gdb/gdbserver/linux-arm-low.c | 121 +++++++++++++--
gdb/testsuite/gdb.trace/ftrace-arm-insn.S | 221 ++++++++++++++++++++++++++++
gdb/testsuite/gdb.trace/ftrace-arm-insn.c | 78 ++++++++++
gdb/testsuite/gdb.trace/ftrace-arm-insn.exp | 103 +++++++++++++
4 files changed, 511 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.trace/ftrace-arm-insn.S
create mode 100644 gdb/testsuite/gdb.trace/ftrace-arm-insn.c
create mode 100644 gdb/testsuite/gdb.trace/ftrace-arm-insn.exp

diff --git a/gdb/gdbserver/linux-arm-low.c b/gdb/gdbserver/linux-arm-low.c
index 4b0e567..2e66caa 100644
--- a/gdb/gdbserver/linux-arm-low.c
+++ b/gdb/gdbserver/linux-arm-low.c
@@ -1053,11 +1053,24 @@ struct arm_insn_reloc_data
uint16_t thumb32[2];
} insns;

+ /* The original PC of the instruction. */
+ CORE_ADDR orig_loc;
+
+ /* The PC where the modified instruction(s) will start. */
+ CORE_ADDR new_loc;
+
/* Error message to return to the client in case the relocation fails. */
const char *err;
};

static int
+arm_reloc_refs_pc_error (struct arm_insn_reloc_data *data)
+{
+ data->err = "The instruction references the PC, couldn't relocate.";
+ return 1;
+}
+
+static int
arm_reloc_others (uint32_t insn, const char *iname,
struct arm_insn_reloc_data *data)
{
@@ -1069,86 +1082,168 @@ arm_reloc_others (uint32_t insn, const char *iname,
static int
arm_reloc_alu_imm (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000f0000ul))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "ALU immediate", data);
}

static int
arm_reloc_alu_reg (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000ff00ful))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "ALU reg", data);
}

static int
arm_reloc_alu_shifted_reg (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000fff0ful))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "ALU shifted reg", data);
}

static int
arm_reloc_b_bl_blx (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ uint32_t cond = bits (insn, 28, 31);
+ int exchange = (cond == 0xf);
+
+ if (exchange)
+ {
+ /* blx */
+ CORE_ADDR absolute_dest = BranchDest (data->orig_loc, insn);
+
+ /* Adjust the address if the H bit is set. */
+ if (bit(insn, 24))
+ absolute_dest |= (1 << 1);
+ arm_emit_arm_blx
+ (&data->insns.arm, cond,
+ immediate_operand (arm_arm_branch_relative_distance (data->new_loc,
+ absolute_dest)));
+ }
+ else
+ {
+ /* b or bl */
+ CORE_ADDR absolute_dest = BranchDest (data->orig_loc, insn);
+ int link = exchange || bit (insn, 24);
+
+ if (link)
+ {
+ arm_emit_arm_bl (&data->insns.arm, cond,
+ arm_arm_branch_relative_distance (data->new_loc,
+ absolute_dest));
+ }
+ else
+ {
+ arm_emit_arm_b (&data->insns.arm, cond,
+ arm_arm_branch_relative_distance (data->new_loc,
+ absolute_dest));
+ }
+ }
+
+ return 0;
}

static int
arm_reloc_block_xfer (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (bit (insn, 20) == 1)
+ {
+ /* it's an LDM/POP-kind instruction. If it mentions PC, the instruction
+ will result in a jump. The destination address is absolute, so it
+ won't change the result if we execute it out of line. */
+ return arm_reloc_others (insn, "ldm", data);
+ }
+ else
+ {
+ /* It's an STM/PUSH-kind instruction. If it mentions PC, we will store
+ the wrong value of PC in memory. Therefore, error out if PC is
+ mentioned. */
+ if ((insn & (1 << 15)) != 0)
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "stm", data);
+ }
}

static int
arm_reloc_bx_blx_reg (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ /* These instructions take an absolute address in a register, so there's
+ nothing special to do. */
+ return arm_reloc_others(insn, "bx/blx", data);
}

static int
arm_reloc_copro_load_store (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000f0000ul))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "copro load/store", data);
}

static int
arm_reloc_extra_ld_st (uint32_t insn, struct arm_insn_reloc_data *data,
int unprivileged)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000ff00ful))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "extra load/store", data);
}

static int
arm_reloc_ldr_str_ldrb_strb (uint32_t insn, struct arm_insn_reloc_data *data,
int load, int size, int usermode)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000ff00ful))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "load/store", data);
}

static int
arm_reloc_preload (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000f0000ul))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "preload", data);
}

static int
arm_reloc_preload_reg (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ if (arm_insn_references_pc (insn, 0x000f000ful))
+ return arm_reloc_refs_pc_error (data);
+
+ return arm_reloc_others (insn, "preload reg", data);
}

static int
arm_reloc_svc (uint32_t insn, struct arm_insn_reloc_data *data)
{
- return 1;
+ /* There is nothing PC-relative in the SVC instruction. */
+ return arm_reloc_others(insn, "svc", data);
}

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

static int
arm_reloc_unpred (uint32_t insn, struct arm_insn_reloc_data *data)
{
+ data->err = "The instruction is unpredictable, couldn't relocate.";
return 1;
}

@@ -1186,6 +1281,8 @@ copy_instruction_arm (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 = arm_relocate_insn (insn, &arm_insn_reloc_visitor, &data);
if (ret != 0)
diff --git a/gdb/testsuite/gdb.trace/ftrace-arm-insn.S b/gdb/testsuite/gdb.trace/ftrace-arm-insn.S
new file mode 100644
index 0000000..c123727
--- /dev/null
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.S
@@ -0,0 +1,221 @@
+.syntax unified
+
+.text
+
+.macro prologue
+push {lr}
+.endm
+
+.macro epilogue
+pop {pc}
+.endm
+
+/* arm b imm */
+.arm
+.type func_arm_b_imm, STT_FUNC
+.global func_arm_b_imm
+func_arm_b_imm:
+ prologue
+
+insn_arm_b_imm:
+ b arm_b_imm_jump
+
+arm_b_imm_jump_back:
+ epilogue
+
+arm_b_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ b arm_b_imm_jump_back
+
+
+/* arm b imm with a false condition */
+.arm
+.type func_arm_b_imm_cond, STT_FUNC
+.global func_arm_b_imm_cond
+func_arm_b_imm_cond:
+ prologue
+
+ /* Force a false condition. If we mess up the condition and the branch is
+ taken, the magic number won't get written and the test will fail. */
+ mov r0, 0
+ cmp r0, 0
+
+insn_arm_b_imm_cond:
+ bne arm_b_imm_jump_cond
+
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+arm_b_imm_jump_cond:
+
+ epilogue
+
+
+/* arm bl imm */
+.arm
+.type func_arm_bl_imm, STT_FUNC
+.global func_arm_bl_imm
+func_arm_bl_imm:
+ prologue
+
+insn_arm_bl_imm:
+ bl arm_bl_imm_jump
+
+ epilogue
+
+arm_bl_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ bx lr
+
+
+/* arm blx imm */
+.arm
+.type func_arm_blx_imm, STT_FUNC
+.global func_arm_blx_imm
+func_arm_blx_imm:
+ prologue
+
+insn_arm_blx_imm:
+ blx arm_blx_imm_jump
+
+ epilogue
+
+.thumb
+arm_blx_imm_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ bx lr
+
+
+/* arm bx reg */
+.arm
+.type func_arm_bx_reg, STT_FUNC
+.global func_arm_bx_reg
+func_arm_bx_reg:
+ prologue
+
+ ldr r0, =arm_bx_reg_jump
+
+insn_arm_bx_reg:
+ bx r0
+
+arm_bx_reg_jump_back:
+ epilogue
+
+.thumb_func
+.thumb
+arm_bx_reg_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ ldr r0, =arm_bx_reg_jump_back
+ bx r0
+
+
+/* arm blx reg */
+.arm
+.type func_arm_blx_reg, STT_FUNC
+.global func_arm_blx_reg
+func_arm_blx_reg:
+ prologue
+
+ ldr r0, =arm_blx_reg_jump
+
+insn_arm_blx_reg:
+ blx r0
+
+ epilogue
+
+.thumb_func
+.thumb
+arm_blx_reg_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+
+ # return to the function
+ bx lr
+
+
+/* arm ldm */
+.arm
+.type func_arm_ldm, STT_FUNC
+.global func_arm_ldm
+func_arm_ldm:
+ prologue
+
+ ldr r0, =magic_number
+ ldr r0, [r0]
+ stmfd sp!, {r0}
+
+insn_arm_ldm:
+ ldmfd sp!, {r1}
+ ldr r0, =global_variable
+ str r1, [r0]
+
+ epilogue
+
+
+/* arm ldm pc */
+.arm
+.type func_arm_ldm_pc, STT_FUNC
+.global func_arm_ldm_pc
+func_arm_ldm_pc:
+ prologue
+
+ ldr r0, =arm_ldm_pc_jump
+ stmfd sp!, {r0}
+
+insn_arm_ldm_pc:
+ ldmfd sp!, {pc}
+
+arm_ldm_pc_jump_back:
+ epilogue
+
+arm_ldm_pc_jump:
+ ldr r0, =global_variable
+ ldr r1, =magic_number
+ ldr r1, [r1]
+ str r1, [r0]
+ b arm_ldm_pc_jump_back
+
+
+/* arm stm */
+.arm
+.type func_arm_stm, STT_FUNC
+.global func_arm_stm
+func_arm_stm:
+ prologue
+
+ ldr r0, =magic_number
+ ldr r0, [r0]
+
+insn_arm_stm:
+ stmfd 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
new file mode 100644
index 0000000..ec08298
--- /dev/null
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.c
@@ -0,0 +1,78 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2015 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* Magic number chosen at random. */
+const uint32_t magic_number = 0x2eb2944b;
+
+static void
+break_here (void)
+{
+}
+
+void
+fail (void)
+{
+ exit (1);
+}
+
+int global_variable;
+
+#define DEF_TEST_FN(name) \
+ static void \
+ test_##name (void) \
+ { \
+ void func_##name (void); \
+ \
+ global_variable = 0; \
+ \
+ func_##name (); \
+ \
+ if (global_variable != magic_number) \
+ fail (); \
+ \
+ break_here (); \
+ }
+
+DEF_TEST_FN (arm_b_imm)
+DEF_TEST_FN (arm_b_imm_cond)
+DEF_TEST_FN (arm_bl_imm)
+DEF_TEST_FN (arm_blx_imm)
+DEF_TEST_FN (arm_bx_reg)
+DEF_TEST_FN (arm_blx_reg)
+DEF_TEST_FN (arm_ldm)
+DEF_TEST_FN (arm_ldm_pc)
+DEF_TEST_FN (arm_stm)
+
+int
+main (void)
+{
+ test_arm_b_imm ();
+ test_arm_b_imm_cond ();
+ test_arm_bl_imm ();
+ test_arm_blx_imm ();
+ test_arm_bx_reg ();
+ test_arm_blx_reg ();
+ test_arm_ldm ();
+ test_arm_ldm_pc ();
+ test_arm_stm ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp b/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp
new file mode 100644
index 0000000..e78d484
--- /dev/null
+++ b/gdb/testsuite/gdb.trace/ftrace-arm-insn.exp
@@ -0,0 +1,103 @@
+# Copyright 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+load_lib "trace-support.exp"
+
+standard_testfile
+set expfile $testfile.exp
+
+# Some targets have leading underscores on assembly symbols.
+set additional_flags [gdb_target_symbol_prefix_flags]
+
+set c_file "$srcdir/$subdir/$srcfile"
+set asm_file "$srcdir/$subdir/$testfile.S"
+set source_files "${c_file} ${asm_file}"
+
+if { ![istarget "arm*-*-*"] } {
+ verbose "Skipping ARM-specific test."
+ return
+}
+
+if [prepare_for_testing $expfile $binfile $source_files \
+ [list debug $additional_flags]] {
+ untested "failed to prepare for trace tests"
+ return -1
+}
+
+if ![runto_main] {
+ fail "Can't run to main to check for trace support"
+ return -1
+}
+
+if ![gdb_target_supports_trace] {
+ unsupported "target does not support trace"
+ return -1
+}
+
+if {![gdb_target_supports_fast_trace]} {
+ unsupported "Target does not support fast tracepoints."
+ return -1
+}
+
+set libipa [get_in_proc_agent]
+gdb_load_shlib $libipa
+
+if ![runto_main] {
+ untested "could not run to main"
+ return -1
+}
+
+if { [gdb_compile $source_files $binfile \
+ executable [list debug $additional_flags shlib=$libipa] ] != "" } {
+ untested "failed to compile ftrace tests"
+ return -1
+}
+
+proc do_test { insn } {
+ global binfile
+ global decimal
+ global hex
+ global asm_file
+
+ with_test_prefix $insn {
+ # The test function for this instruction
+ set test_function "test_${insn}"
+ # The instruction on which we want to install a fast tracepoint
+ set insn_location "insn_${insn}"
+
+ clean_restart ${binfile}
+ if ![runto $test_function] {
+ fail "Can't run to $test_function"
+ return 0
+ }
+ gdb_breakpoint "break_here"
+
+ gdb_test "ftrace ${insn_location}" "Fast tracepoint ${decimal} at ${hex}: file ${asm_file}, line ${decimal}."
+ gdb_test_no_output "tstart"
+ gdb_continue "break_here"
+ gdb_test_no_output "tstop"
+ gdb_test "tstatus" ".*Collected 1 trace frames.*"
+ }
+}
+
+do_test arm_b_imm
+do_test arm_b_imm_cond
+do_test arm_bl_imm
+do_test arm_blx_imm
+do_test arm_bx_reg
+do_test arm_blx_reg
+do_test arm_ldm
+do_test arm_ldm_pc
+do_test arm_stm
--
2.8.1