This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch/ARM] Add support for breakpoints in IT blocks


And the very last piece of the puzzle.  An IT block might span
multiple source lines.  It's impossible to reconstruct from the debug
info whether the user's intention was to place a breakpoint at a line
that is logically "not executed" or on a conditional statement that
was logically executed, e.g. the foo call in "x ? foo() : bar()".  So
to be consistent with behavior on other targets, we adjust
breakpoints that fall within an IT block to the start of the block.

I hope that in some distant future, we can hide this detail of
conditional execution from users.  I can't think of a way to do so
without more in the debug info though.

Tested on arm-none-linux-gnueabi.  Tests included.  I'll apply when
the previous patches are ready to go in.

-- 
Daniel Jacobowitz
CodeSourcery

2010-01-28  Daniel Jacobowitz  <dan@codesourcery.com>

	gdb/
	* arm-tdep.c (arm_find_mapping_symbol): New function, from
	arm_pc_is_thumb.
	(arm_pc_is_thumb): Use arm_find_mapping_symbol.
	(extend_buffer_earlier): New function.
	(MAX_IT_BLOCK_PREFIX, IT_SCAN_THRESHOLD): New constants.
	(arm_adjust_breakpoint_address): New function.
	(arm_gdbarch_init): Register arm_adjust_breakpoint_address.

	gdb/testsuite/
	* gdb.arch/thumb2-it.S (it_breakpoints): New function.
	* gdb.arch/thumb2-it.exp (test_it_break): New function.
	(Top level): Call it.

---
 gdb/arm-tdep.c                       |  250 ++++++++++++++++++++++++++++++++---
 gdb/testsuite/gdb.arch/thumb2-it.S   |   43 ++++++
 gdb/testsuite/gdb.arch/thumb2-it.exp |   17 ++
 3 files changed, 292 insertions(+), 18 deletions(-)

Index: gdb-mainline/gdb/arm-tdep.c
===================================================================
--- gdb-mainline.orig/gdb/arm-tdep.c	2010-01-13 12:49:28.000000000 -0800
+++ gdb-mainline/gdb/arm-tdep.c	2010-01-13 14:30:24.000000000 -0800
@@ -275,25 +275,14 @@ arm_compare_mapping_symbols (const struc
   return lhs->value < rhs->value;
 }
 
-/* Determine if the program counter specified in MEMADDR is in a Thumb
-   function.  This function should be called for addresses unrelated to
-   any executing frame; otherwise, prefer arm_frame_is_thumb.  */
+/* Search for the mapping symbol covering MEMADDR.  If one is found,
+   return its type.  Otherwise, return 0.  If START is non-NULL,
+   set *START to the location of the mapping symbol.  */
 
-static int
-arm_pc_is_thumb (CORE_ADDR memaddr)
+static char
+arm_find_mapping_symbol (CORE_ADDR memaddr, CORE_ADDR *start)
 {
   struct obj_section *sec;
-  struct minimal_symbol *sym;
-
-  /* If bit 0 of the address is set, assume this is a Thumb address.  */
-  if (IS_THUMB_ADDR (memaddr))
-    return 1;
-
-  /* If the user wants to override the symbol table, let him.  */
-  if (strcmp (arm_force_mode_string, "arm") == 0)
-    return 0;
-  if (strcmp (arm_force_mode_string, "thumb") == 0)
-    return 1;
 
   /* If there are mapping symbols, consult them.  */
   sec = find_pc_section (memaddr);
@@ -324,18 +313,53 @@ arm_pc_is_thumb (CORE_ADDR memaddr)
 		{
 		  map_sym = VEC_index (arm_mapping_symbol_s, map, idx);
 		  if (map_sym->value == map_key.value)
-		    return map_sym->type == 't';
+		    {
+		      if (start)
+			*start = map_sym->value + obj_section_addr (sec);
+		      return map_sym->type;
+		    }
 		}
 
 	      if (idx > 0)
 		{
 		  map_sym = VEC_index (arm_mapping_symbol_s, map, idx - 1);
-		  return map_sym->type == 't';
+		  if (start)
+		    *start = map_sym->value + obj_section_addr (sec);
+		  return map_sym->type;
 		}
 	    }
 	}
     }
 
+  return 0;
+}
+
+/* Determine if the program counter specified in MEMADDR is in a Thumb
+   function.  This function should be called for addresses unrelated to
+   any executing frame; otherwise, prefer arm_frame_is_thumb.  */
+
+static int
+arm_pc_is_thumb (CORE_ADDR memaddr)
+{
+  struct obj_section *sec;
+  struct minimal_symbol *sym;
+  char type;
+
+  /* If bit 0 of the address is set, assume this is a Thumb address.  */
+  if (IS_THUMB_ADDR (memaddr))
+    return 1;
+
+  /* If the user wants to override the symbol table, let him.  */
+  if (strcmp (arm_force_mode_string, "arm") == 0)
+    return 0;
+  if (strcmp (arm_force_mode_string, "thumb") == 0)
+    return 1;
+
+  /* If there are mapping symbols, consult them.  */
+  type = arm_find_mapping_symbol (memaddr, NULL);
+  if (type)
+    return type == 't';
+
   /* Thumb functions have a "special" bit set in minimal symbols.  */
   sym = lookup_minimal_symbol_by_pc (memaddr);
   if (sym)
@@ -2921,6 +2945,192 @@ arm_software_single_step (struct frame_i
   return 1;
 }
 
+/* Given BUF, which is OLD_LEN bytes ending at ENDADDR, expand
+   the buffer to be NEW_LEN bytes ending at ENDADDR.  Return
+   NULL if an error occurs.  BUF is freed.  */
+
+static gdb_byte *
+extend_buffer_earlier (gdb_byte *buf, CORE_ADDR endaddr,
+		       int old_len, int new_len)
+{
+  gdb_byte *new_buf, *middle;
+  int bytes_to_read = new_len - old_len;
+
+  new_buf = xmalloc (new_len);
+  memcpy (new_buf + bytes_to_read, buf, old_len);
+  xfree (buf);
+  if (target_read_memory (endaddr - new_len, new_buf, bytes_to_read) != 0)
+    {
+      xfree (new_buf);
+      return NULL;
+    }
+  return new_buf;
+}
+
+/* An IT block is at most the 2-byte IT instruction followed by
+   four 4-byte instructions.  The furthest back we must search to
+   find an IT block that affects the current instruction is thus
+   2 + 3 * 4 == 14 bytes.  */
+#define MAX_IT_BLOCK_PREFIX 14
+
+/* Use a quick scan if there are more than this many bytes of
+   code.  */
+#define IT_SCAN_THRESHOLD 32
+
+/* Adjust a breakpoint's address to move breakpoints out of IT blocks.
+   A breakpoint in an IT block may not be hit, depending on the
+   condition flags.  */
+static CORE_ADDR
+arm_adjust_breakpoint_address (struct gdbarch *gdbarch, CORE_ADDR bpaddr)
+{
+  gdb_byte *buf;
+  char map_type;
+  CORE_ADDR boundary, func_start;
+  int buf_len, buf2_len;
+  enum bfd_endian order = gdbarch_byte_order_for_code (gdbarch);
+  int i, any, last_it, last_it_count;
+
+  /* If we are using BKPT breakpoints, none of this is necessary.  */
+  if (gdbarch_tdep (gdbarch)->thumb2_breakpoint == NULL)
+    return bpaddr;
+
+  /* ARM mode does not have this problem.  */
+  if (!arm_pc_is_thumb (bpaddr))
+    return bpaddr;
+
+  /* We are setting a breakpoint in Thumb code that could potentially
+     contain an IT block.  The first step is to find how much Thumb
+     code there is; we do not need to read outside of known Thumb
+     sequences.  */
+  map_type = arm_find_mapping_symbol (bpaddr, &boundary);
+  if (map_type == 0)
+    /* Thumb-2 code must have mapping symbols to have a chance.  */
+    return bpaddr;
+
+  bpaddr = gdbarch_addr_bits_remove (gdbarch, bpaddr);
+
+  if (find_pc_partial_function (bpaddr, NULL, &func_start, NULL)
+      && func_start > boundary)
+    boundary = func_start;
+
+  /* Search for a candidate IT instruction.  We have to do some fancy
+     footwork to distinguish a real IT instruction from the second
+     half of a 32-bit instruction, but there is no need for that if
+     there's no candidate.  */
+  buf_len = min (bpaddr - boundary, MAX_IT_BLOCK_PREFIX);
+  if (buf_len == 0)
+    /* No room for an IT instruction.  */
+    return bpaddr;
+
+  buf = xmalloc (buf_len);
+  if (target_read_memory (bpaddr - buf_len, buf, buf_len) != 0)
+    return bpaddr;
+  any = 0;
+  for (i = 0; i < buf_len; i += 2)
+    {
+      unsigned short inst1 = extract_unsigned_integer (&buf[i], 2, order);
+      if ((inst1 & 0xff00) == 0xbf00 && (inst1 & 0x000f) != 0)
+	{
+	  any = 1;
+	  break;
+	}
+    }
+  if (any == 0)
+    {
+      xfree (buf);
+      return bpaddr;
+    }
+
+  /* OK, the code bytes before this instruction contain at least one
+     halfword which resembles an IT instruction.  We know that it's
+     Thumb code, but there are still two possibilities.  Either the
+     halfword really is an IT instruction, or it is the second half of
+     a 32-bit Thumb instruction.  The only way we can tell is to
+     scan forwards from a known instruction boundary.  */
+  if (bpaddr - boundary > IT_SCAN_THRESHOLD)
+    {
+      int definite;
+
+      /* There's a lot of code before this instruction.  Start with an
+	 optimistic search; it's easy to recognize halfwords that can
+	 not be the start of a 32-bit instruction, and use that to
+	 lock on to the instruction boundaries.  */
+      buf = extend_buffer_earlier (buf, bpaddr, buf_len, IT_SCAN_THRESHOLD);
+      if (buf == NULL)
+	return bpaddr;
+      buf_len = IT_SCAN_THRESHOLD;
+
+      definite = 0;
+      for (i = 0; i < buf_len - sizeof (buf) && ! definite; i += 2)
+	{
+	  unsigned short inst1 = extract_unsigned_integer (&buf[i], 2, order);
+	  if (thumb_insn_size (inst1) == 2)
+	    {
+	      definite = 1;
+	      break;
+	    }
+	}
+
+      /* At this point, if DEFINITE, BUF[I] is the first place we
+	 are sure that we know the instruction boundaries, and it is far
+	 enough from BPADDR that we could not miss an IT instruction
+	 affecting BPADDR.  If ! DEFINITE, give up - start from a
+	 known boundary.  */
+      if (! definite)
+	{
+	  buf = extend_buffer_earlier (buf, bpaddr, buf_len, bpaddr - boundary);
+	  if (buf == NULL)
+	    return bpaddr;
+	  buf_len = bpaddr - boundary;
+	  i = 0;
+	}
+    }
+  else
+    {
+      buf = extend_buffer_earlier (buf, bpaddr, buf_len, bpaddr - boundary);
+      if (buf == NULL)
+	return bpaddr;
+      buf_len = bpaddr - boundary;
+      i = 0;
+    }
+
+  /* Scan forwards.  Find the last IT instruction before BPADDR.  */
+  last_it = -1;
+  last_it_count = 0;
+  while (i < buf_len)
+    {
+      unsigned short inst1 = extract_unsigned_integer (&buf[i], 2, order);
+      last_it_count--;
+      if ((inst1 & 0xff00) == 0xbf00 && (inst1 & 0x000f) != 0)
+	{
+	  last_it = i;
+	  if (inst1 & 0x0001)
+	    last_it_count = 4;
+	  else if (inst1 & 0x0002)
+	    last_it_count = 3;
+	  else if (inst1 & 0x0004)
+	    last_it_count = 2;
+	  else
+	    last_it_count = 1;
+	}
+      i += thumb_insn_size (inst1);
+    }
+
+  xfree (buf);
+
+  if (last_it == -1)
+    /* There wasn't really an IT instruction after all.  */
+    return bpaddr;
+
+  if (last_it_count < 1)
+    /* It was too far away.  */
+    return bpaddr;
+
+  /* This really is a trouble spot.  Move the breakpoint to the IT
+     instruction.  */
+  return bpaddr - buf_len + last_it;
+}
+
 /* ARM displaced stepping support.
 
    Generally ARM displaced stepping works as follows:
@@ -6270,6 +6480,10 @@ arm_gdbarch_init (struct gdbarch_info in
 					 arm_coff_make_msymbol_special);
   set_gdbarch_record_special_symbol (gdbarch, arm_record_special_symbol);
 
+  /* Thumb-2 IT block support.  */
+  set_gdbarch_adjust_breakpoint_address (gdbarch,
+					 arm_adjust_breakpoint_address);
+
   /* Virtual tables.  */
   set_gdbarch_vbit_in_delta (gdbarch, 1);
 
Index: gdb-mainline/gdb/testsuite/gdb.arch/thumb2-it.S
===================================================================
--- gdb-mainline.orig/gdb/testsuite/gdb.arch/thumb2-it.S	2010-01-13 11:22:55.000000000 -0800
+++ gdb-mainline/gdb/testsuite/gdb.arch/thumb2-it.S	2010-01-13 13:43:38.000000000 -0800
@@ -136,4 +136,47 @@ it_8:
 	addlt	r0, #8	@ Not reached
 	bx	lr	@ Done, Check $r0 == 1
 
+	.type it_breakpoints,%function
+	.thumb_func
+it_breakpoints:
+	mov	r0, #0
+	cmp	r0, #0
+	it	eq	@ Location 1 @ Break 1
+	moveq	r0, #0
+
+	it	eq	@ Location 2
+	moveq	r0, #0	@ Break 2
+
+	it	ne	@ Location 3
+	movne	r0, #0	@ Break 3
+
+	@ An IT block of maximum size.
+	itttt	eq	@ Location 4
+	moveq.w	r0, #0
+	moveq.w	r0, #0
+	moveq.w	r0, #0
+	moveq.w	r0, #0	@ Break 4
+
+	@ Just outside an IT block.
+	it	eq
+	moveq	r0, #0
+	mov	r0, #0	@ Location 5 @ Break 5
+
+	@ After something that looks like an IT block, but
+	@ is the second half of an instruction.
+	.p2align 6
+	cmp	r0, r0
+	b	1f
+	b.w	.+0xe14	@ 0xf000 0xbf08 -> second half is IT EQ
+1:	mov	r0, #0	@ Location 6 @ Break 6
+
+	@ After something that looks like an IT block, but
+	@ is data.
+	.p2align 6
+	b	1f
+	.short	0xbf08
+1:	mov	r0, #0	@ Location 7 @ Break 7
+
+	bx	lr
+
 #endif /* __thumb2__ */
Index: gdb-mainline/gdb/testsuite/gdb.arch/thumb2-it.exp
===================================================================
--- gdb-mainline.orig/gdb/testsuite/gdb.arch/thumb2-it.exp	2010-01-13 11:22:55.000000000 -0800
+++ gdb-mainline/gdb/testsuite/gdb.arch/thumb2-it.exp	2010-01-13 13:40:03.000000000 -0800
@@ -125,6 +125,17 @@ proc test_it_block { func } {
     return
 }
 
+proc test_it_break { ndx } {
+    set line [gdb_get_line_number "@ Break ${ndx}"]
+
+    if { ! [gdb_breakpoint "${line}"] } {
+	unresolved "continue to breakpoint: test ${ndx}"
+	return
+    }
+
+    gdb_continue_to_breakpoint "test ${ndx}" ".*@ Location ${ndx}.*"
+}
+
 # If we are using software single-stepping in GDB, then GDB will not
 # stop at conditional instructions with a false predicate during stepi.
 # If we are using a simulator or debug interface with hardware single
@@ -138,3 +149,9 @@ if { [istarget arm*-linux*] } {
 for { set i 1 } { $i <= 8 } { incr i } {
     test_it_block it_${i}
 }
+
+gdb_breakpoint "*it_breakpoints"
+gdb_test "call it_breakpoints()" "Breakpoint.*"
+for { set i 1 } { $i <= 7 } { incr i } {
+    test_it_break ${i}
+}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]