diff mbox

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

Message ID 1465476975-25062-18-git-send-email-antoine.tremblay@ericsson.com
State New
Headers show

Commit Message

Antoine Tremblay June 9, 2016, 12:56 p.m. UTC
From: Simon Marchi <simon.marchi@ericsson.com>

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 mbox

Patch

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