diff options
-rw-r--r-- | gdb/ChangeLog | 10 | ||||
-rw-r--r-- | gdb/arm-tdep.c | 250 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 6 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/thumb2-it.S | 43 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/thumb2-it.exp | 17 |
5 files changed, 308 insertions, 18 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 72866ca..b6ce4ad 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,15 @@ 2010-02-01 Daniel Jacobowitz <dan@codesourcery.com> + * 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. + +2010-02-01 Daniel Jacobowitz <dan@codesourcery.com> + * arm-linux-tdep.c (arm_linux_thumb2_be_breakpoint) (arm_linux_thumb2_le_breakpoint): New constants. (arm_linux_init_abi): Set thumb2_breakpoint and diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c index e740083..524e4ac 100644 --- a/gdb/arm-tdep.c +++ b/gdb/arm-tdep.c @@ -275,25 +275,14 @@ arm_compare_mapping_symbols (const struct arm_mapping_symbol *lhs, 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_info *frame) 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: @@ -6274,6 +6484,10 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) 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); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 05ecd88..00f4aec 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,11 @@ 2010-02-01 Daniel Jacobowitz <dan@codesourcery.com> + * gdb.arch/thumb2-it.S (it_breakpoints): New function. + * gdb.arch/thumb2-it.exp (test_it_break): New function. + (Top level): Call it. + +2010-02-01 Daniel Jacobowitz <dan@codesourcery.com> + * gdb.arch/thumb2-it.S, gdb.arch/thumb2-it.exp: New files. 2010-01-29 Daniel Jacobowitz <dan@codesourcery.com> diff --git a/gdb/testsuite/gdb.arch/thumb2-it.S b/gdb/testsuite/gdb.arch/thumb2-it.S index a5aab8c..700c92a 100644 --- a/gdb/testsuite/gdb.arch/thumb2-it.S +++ b/gdb/testsuite/gdb.arch/thumb2-it.S @@ -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__ */ diff --git a/gdb/testsuite/gdb.arch/thumb2-it.exp b/gdb/testsuite/gdb.arch/thumb2-it.exp index 5ef8475..5144ac1 100644 --- a/gdb/testsuite/gdb.arch/thumb2-it.exp +++ b/gdb/testsuite/gdb.arch/thumb2-it.exp @@ -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} +} |