diff options
author | Paul Brook <paul@codesourcery.com> | 2006-03-21 02:13:36 +0000 |
---|---|---|
committer | Paul Brook <paul@codesourcery.com> | 2006-03-21 02:13:36 +0000 |
commit | 570a3f89cf92e4b1ca7fa6fb61f7c035ce24e412 (patch) | |
tree | 5f367027da40bf59f3bb82c38f5650acd5cc0747 | |
parent | bef0bb7e54b807a8050679167be7a732538762b2 (diff) | |
download | binutils-gdb-570a3f89cf92e4b1ca7fa6fb61f7c035ce24e412.tar.gz |
2006-03-21 Paul Brook <paul@codesourcery.com>
Daniel Jacobowitz <dan@codesourcery.com>
Jim Blandy <jimb@codesourcery.com>
* gdb/Makefile.in: Add prologue_value.{c,h}
* gdb/arm-tdep.c: Include prologue-value.h.
(thumb_skip_prologue): Remove.
(thumb_analyze_prologue): New function.
(arm_skip_prologue): Use thumb_analyze_prologue.
(thumb_scan_prologue): Ditto.
* gdb/prologue-value.c: New file.
* gdb/prologue-value.h: New file.
* gdb/config/arm/embed.mt (TDEPFILES): Add prologue-value.o.
* gdb/config/arm/linux.mt (TDEPFILES): Add prologue-value.o.
* gdb/config/arm/nbsd.mt (TDEPFILES): Add prologue-value.o.
* gdb/config/arm/wince.mt (TDEPFILES): Add prologue-value.o.
* gdb/testsuite/gdb.arch/thumb-prologue.c: New file.
* gdb/testsuite/gdb.arch/thumb-prologue.exp: New file.
-rw-r--r-- | ChangeLog.csl | 21 | ||||
-rw-r--r-- | gdb/Makefile.in | 5 | ||||
-rw-r--r-- | gdb/arm-tdep.c | 273 | ||||
-rw-r--r-- | gdb/config/arm/embed.mt | 2 | ||||
-rw-r--r-- | gdb/config/arm/linux.mt | 2 | ||||
-rw-r--r-- | gdb/config/arm/nbsd.mt | 2 | ||||
-rw-r--r-- | gdb/config/arm/wince.mt | 2 | ||||
-rw-r--r-- | gdb/prologue-value.c | 591 | ||||
-rw-r--r-- | gdb/prologue-value.h | 293 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/thumb-prologue.c | 96 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/thumb-prologue.exp | 59 |
11 files changed, 1201 insertions, 145 deletions
diff --git a/ChangeLog.csl b/ChangeLog.csl index 4954e97ace0..8c8e3564852 100644 --- a/ChangeLog.csl +++ b/ChangeLog.csl @@ -1,3 +1,24 @@ +2006-03-21 Paul Brook <paul@codesourcery.com> + Daniel Jacobowitz <dan@codesourcery.com> + Jim Blandy <jimb@codesourcery.com> + + * gdb/Makefile.in: Add prologue_value.{c,h} + * gdb/arm-tdep.c: Include prologue-value.h. + (thumb_skip_prologue): Remove. + (thumb_analyze_prologue): New function. + (arm_skip_prologue): Use thumb_analyze_prologue. + (thumb_scan_prologue): Ditto. + * gdb/prologue-value.c: New file. + * gdb/prologue-value.h: New file. + * gdb/config/arm/embed.mt (TDEPFILES): Add prologue-value.o. + * gdb/config/arm/linux.mt (TDEPFILES): Add prologue-value.o. + * gdb/config/arm/nbsd.mt (TDEPFILES): Add prologue-value.o. + * gdb/config/arm/wince.mt (TDEPFILES): Add prologue-value.o. + * gdb/testsuite/gdb.arch/thumb-prologue.c: New file. + * gdb/testsuite/gdb.arch/thumb-prologue.exp: New file. + +2006-03-20 Mark Mitchell <mark@codesourcery.com> + * configure.tgt (arm*-stellaris-*): Use armv7m configuration. * config/arm/armv7m.mt: New file. * config/arm/tm-armv7m.h: Likewise. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 99aa888c29f..59280dce309 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -542,6 +542,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c \ objc-exp.y objc-lang.c \ objfiles.c osabi.c observer.c \ p-exp.y p-lang.c p-typeprint.c p-valprint.c parse.c printcmd.c \ + prologue-value.h \ regcache.c reggroups.c remote.c remote-fileio.c \ scm-exp.c scm-lang.c scm-valprint.c \ sentinel-frame.c \ @@ -756,6 +757,7 @@ ppcnbsd_tdep_h = ppcnbsd-tdep.h ppcobsd_tdep_h = ppcobsd-tdep.h ppc_tdep_h = ppc-tdep.h proc_utils_h = proc-utils.h +prologue_value_h = prologue-value.h regcache_h = regcache.h reggroups_h = reggroups.h regset_h = regset.h @@ -1442,6 +1444,7 @@ ALLDEPFILES = \ ppcnbsd-nat.c ppcnbsd-tdep.c \ ppcobsd-nat.c ppcobsd-tdep.c \ procfs.c \ + prologue-value.c \ remote-e7000.c \ remote-hms.c remote-m32r-sdi.c remote-mips.c \ remote-rdp.c remote-sim.c \ @@ -2432,6 +2435,8 @@ procfs.o: procfs.c $(defs_h) $(inferior_h) $(target_h) $(gdbcore_h) \ proc-service.o: proc-service.c $(defs_h) $(gdb_proc_service_h) $(inferior_h) \ $(symtab_h) $(target_h) $(gregset_h) proc-why.o: proc-why.c $(defs_h) $(proc_utils_h) +prologue-value.o: prologue-value.c $(defs_h) $(gdb_string_h) $(gdb_assert_h) \ + $(prologue_value_h) $(regcache_h) p-typeprint.o: p-typeprint.c $(defs_h) $(gdb_obstack_h) $(bfd_h) $(symtab_h) \ $(gdbtypes_h) $(expression_h) $(value_h) $(gdbcore_h) $(target_h) \ $(language_h) $(p_lang_h) $(typeprint_h) $(gdb_string_h) diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c index d9e4401775d..54d1bccfa46 100644 --- a/gdb/arm-tdep.c +++ b/gdb/arm-tdep.c @@ -40,6 +40,7 @@ #include "trad-frame.h" #include "objfiles.h" #include "dwarf2-frame.h" +#include "prologue-value.h" #include "arm-tdep.h" #include "gdb/sim-arm.h" @@ -233,84 +234,152 @@ arm_saved_pc_after_call (struct frame_info *frame) return ADDR_BITS_REMOVE (read_register (ARM_LR_REGNUM)); } -/* A typical Thumb prologue looks like this: - push {r7, lr} - add sp, sp, #-28 - add r7, sp, #12 - Sometimes the latter instruction may be replaced by: - mov r7, sp - - or like this: - push {r7, lr} - mov r7, sp - sub sp, #12 - - or, on tpcs, like this: - sub sp,#16 - push {r7, lr} - (many instructions) - mov r7, sp - sub sp, #12 - - There is always one instruction of three classes: - 1 - push - 2 - setting of r7 - 3 - adjusting of sp - - When we have found at least one of each class we are done with the prolog. - Note that the "sub sp, #NN" before the push does not count. - */ - -static CORE_ADDR -thumb_skip_prologue (CORE_ADDR pc, CORE_ADDR func_end) +CORE_ADDR +thumb_analyze_prologue (struct gdbarch *gdbarch, + CORE_ADDR start, CORE_ADDR limit, + struct arm_prologue_cache *cache) { - CORE_ADDR current_pc; - /* findmask: - bit 0 - push { rlist } - bit 1 - mov r7, sp OR add r7, sp, #imm (setting of r7) - bit 2 - sub sp, #simm OR add sp, #simm (adjusting of sp) - */ - int findmask = 0; + int i; + pv_t regs[16]; + struct pv_area *stack; + struct cleanup *back_to; + CORE_ADDR offset; - for (current_pc = pc; - current_pc + 2 < func_end && current_pc < pc + 40; - current_pc += 2) + for (i = 0; i < 16; i++) + regs[i] = pv_register (i, 0); + stack = make_pv_area (ARM_SP_REGNUM); + back_to = make_cleanup_free_pv_area (stack); + + /* The call instruction saved PC in LR, and the current PC is not + interesting. Due to this file's conventions, we want the value + of LR at this function's entry, not at the call site, so we do + not record the save of the PC - when the ARM prologue analyzer + has also been converted to the pv mechanism, we could record the + save here and remove the hack in prev_register. */ + regs[ARM_PC_REGNUM] = pv_unknown (); + + while (start < limit) { - unsigned short insn = read_memory_unsigned_integer (current_pc, 2); + unsigned short insn; - if ((insn & 0xfe00) == 0xb400) /* push { rlist } */ + insn = read_memory_unsigned_integer (start, 2); + + if ((insn & 0xfe00) == 0xb400) { - findmask |= 1; /* push found */ + int regno; + int mask; + int stop = 0; + + /* Bits 0-7 contain a mask for registers R0-R7. Bit 8 says + whether to save LR (R14). */ + mask = (insn & 0xff) | ((insn & 0x100) << 6); + + /* Calculate offsets of saved R0-R7 and LR. */ + for (regno = ARM_LR_REGNUM; regno >= 0; regno--) + if (mask & (1 << regno)) + { + if (pv_area_store_would_trash (stack, regs[ARM_SP_REGNUM])) + { + stop = 1; + break; + } + + regs[ARM_SP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], + -4); + pv_area_store (stack, regs[ARM_SP_REGNUM], 4, regs[regno]); + } + + if (stop) + break; } else if ((insn & 0xff00) == 0xb000) /* add sp, #simm OR sub sp, #simm */ { - if ((findmask & 1) == 0) /* before push ? */ - continue; + offset = (insn & 0x7f) << 2; /* get scaled offset */ + if (insn & 0x80) /* Check for SUB. */ + regs[ARM_SP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], + -offset); else - findmask |= 4; /* add/sub sp found */ + regs[ARM_SP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], + offset); } else if ((insn & 0xff00) == 0xaf00) /* add r7, sp, #imm */ + regs[THUMB_FP_REGNUM] = pv_add_constant (regs[ARM_SP_REGNUM], + (insn & 0xff) << 2); + else if ((insn & 0xff00) == 0x4600) /* mov hi, lo or mov lo, hi */ { - findmask |= 2; /* setting of r7 found */ + int dst_reg = (insn & 0x7) + ((insn & 0x80) >> 4); + int src_reg = (insn & 0x78) >> 3; + regs[dst_reg] = regs[src_reg]; } - else if (insn == 0x466f) /* mov r7, sp */ + else if ((insn & 0xf800) == 0x9000) /* str rd, [sp, #off] */ { - findmask |= 2; /* setting of r7 found */ + /* Handle stores to the stack. Normally pushes are used, + but with GCC -mtpcs-frame, there may be other stores + in the prologue to create the frame. */ + int regno = (insn >> 8) & 0x7; + pv_t addr; + + offset = (insn & 0xff) << 2; + addr = pv_add_constant (regs[ARM_SP_REGNUM], offset); + + if (pv_area_store_would_trash (stack, addr)) + break; + + pv_area_store (stack, addr, 4, regs[regno]); } - else if (findmask == (4+2+1)) + else { - /* We have found one of each type of prologue instruction */ + /* We don't know what this instruction is. We're finished + scanning. NOTE: Recognizing more safe-to-ignore + instructions here will improve support for optimized + code. */ break; } - else - /* Something in the prolog that we don't care about or some - instruction from outside the prolog scheduled here for - optimization. */ - continue; + + start += 2; + } + + if (cache == NULL) + { + do_cleanups (back_to); + return start; + } + + /* frameoffset is unused for this unwinder. */ + cache->frameoffset = 0; + + if (pv_is_register (regs[ARM_FP_REGNUM], ARM_SP_REGNUM)) + { + /* Frame pointer is fp. Frame size is constant. */ + cache->framereg = ARM_FP_REGNUM; + cache->framesize = -regs[ARM_FP_REGNUM].k; + } + else if (pv_is_register (regs[THUMB_FP_REGNUM], ARM_SP_REGNUM)) + { + /* Frame pointer is r7. Frame size is constant. */ + cache->framereg = THUMB_FP_REGNUM; + cache->framesize = -regs[THUMB_FP_REGNUM].k; + } + else if (pv_is_register (regs[ARM_SP_REGNUM], ARM_SP_REGNUM)) + { + /* Try the stack pointer... this is a bit desperate. */ + cache->framereg = ARM_SP_REGNUM; + cache->framesize = -regs[ARM_SP_REGNUM].k; + } + else + { + /* We're just out of luck. We don't know where the frame is. */ + cache->framereg = -1; + cache->framesize = 0; } - return current_pc; + for (i = 0; i < 16; i++) + if (pv_area_find_reg (stack, gdbarch, i, &offset)) + cache->saved_regs[i].addr = offset; + + do_cleanups (back_to); + return start; } /* Advance the PC across any function entry prologue instructions to @@ -358,10 +427,6 @@ arm_skip_prologue (CORE_ADDR pc) } } - /* Check if this is Thumb code. */ - if (arm_pc_is_thumb (pc)) - return thumb_skip_prologue (pc, func_end); - /* Can't find the prologue end in the symbol table, try it the hard way by disassembling the instructions. */ @@ -369,6 +434,10 @@ arm_skip_prologue (CORE_ADDR pc) if (func_end == 0 || func_end > pc + 64) func_end = pc + 64; + /* Check if this is Thumb code. */ + if (arm_pc_is_thumb (pc)) + return thumb_analyze_prologue (current_gdbarch, pc, func_end, NULL); + for (skip_pc = pc; skip_pc < func_end; skip_pc += 4) { inst = read_memory_unsigned_integer (skip_pc, 4); @@ -483,86 +552,8 @@ thumb_scan_prologue (CORE_ADDR prev_pc, struct arm_prologue_cache *cache) prologue_end = min (prologue_end, prev_pc); - /* Initialize the saved register map. When register H is copied to - register L, we will put H in saved_reg[L]. */ - for (i = 0; i < 16; i++) - saved_reg[i] = i; - - /* Search the prologue looking for instructions that set up the - frame pointer, adjust the stack pointer, and save registers. - Do this until all basic prolog instructions are found. */ - - cache->framesize = 0; - for (current_pc = prologue_start; - (current_pc < prologue_end) && ((findmask & 7) != 7); - current_pc += 2) - { - unsigned short insn; - int regno; - int offset; - - insn = read_memory_unsigned_integer (current_pc, 2); - - if ((insn & 0xfe00) == 0xb400) /* push { rlist } */ - { - int mask; - findmask |= 1; /* push found */ - /* Bits 0-7 contain a mask for registers R0-R7. Bit 8 says - whether to save LR (R14). */ - mask = (insn & 0xff) | ((insn & 0x100) << 6); - - /* Calculate offsets of saved R0-R7 and LR. */ - for (regno = ARM_LR_REGNUM; regno >= 0; regno--) - if (mask & (1 << regno)) - { - cache->framesize += 4; - cache->saved_regs[saved_reg[regno]].addr = -cache->framesize; - /* Reset saved register map. */ - saved_reg[regno] = regno; - } - } - else if ((insn & 0xff00) == 0xb000) /* add sp, #simm OR - sub sp, #simm */ - { - if ((findmask & 1) == 0) /* before push? */ - continue; - else - findmask |= 4; /* add/sub sp found */ - - offset = (insn & 0x7f) << 2; /* get scaled offset */ - if (insn & 0x80) /* is it signed? (==subtracting) */ - { - cache->frameoffset += offset; - offset = -offset; - } - cache->framesize -= offset; - } - else if ((insn & 0xff00) == 0xaf00) /* add r7, sp, #imm */ - { - findmask |= 2; /* setting of r7 found */ - cache->framereg = THUMB_FP_REGNUM; - /* get scaled offset */ - cache->frameoffset = (insn & 0xff) << 2; - } - else if (insn == 0x466f) /* mov r7, sp */ - { - findmask |= 2; /* setting of r7 found */ - cache->framereg = THUMB_FP_REGNUM; - cache->frameoffset = 0; - saved_reg[THUMB_FP_REGNUM] = ARM_SP_REGNUM; - } - else if ((insn & 0xffc0) == 0x4640) /* mov r0-r7, r8-r15 */ - { - int lo_reg = insn & 7; /* dest. register (r0-r7) */ - int hi_reg = ((insn >> 3) & 7) + 8; /* source register (r8-15) */ - saved_reg[lo_reg] = hi_reg; /* remember hi reg was saved */ - } - else - /* Something in the prolog that we don't care about or some - instruction from outside the prolog scheduled here for - optimization. */ - continue; - } + thumb_analyze_prologue (current_gdbarch, prologue_start, prologue_end, + cache); } /* This function decodes an ARM function prologue to determine: diff --git a/gdb/config/arm/embed.mt b/gdb/config/arm/embed.mt index 45f09c87b7d..84b293febd9 100644 --- a/gdb/config/arm/embed.mt +++ b/gdb/config/arm/embed.mt @@ -1,5 +1,5 @@ # Target: ARM embedded system -TDEPFILES= arm-tdep.o +TDEPFILES= arm-tdep.o prologue-value.o DEPRECATED_TM_FILE= tm-embed.h SIM_OBS = remote-sim.o diff --git a/gdb/config/arm/linux.mt b/gdb/config/arm/linux.mt index 6521a0b997d..e0501aa5ddf 100644 --- a/gdb/config/arm/linux.mt +++ b/gdb/config/arm/linux.mt @@ -1,3 +1,3 @@ # Target: ARM based machine running GNU/Linux DEPRECATED_TM_FILE= tm-linux.h -TDEPFILES= arm-tdep.o arm-linux-tdep.o glibc-tdep.o solib.o solib-svr4.o solib-legacy.o symfile-mem.o +TDEPFILES= arm-tdep.o arm-linux-tdep.o glibc-tdep.o solib.o solib-svr4.o solib-legacy.o symfile-mem.o prologue-value.o diff --git a/gdb/config/arm/nbsd.mt b/gdb/config/arm/nbsd.mt index e9dd0d01496..30a84ed777d 100644 --- a/gdb/config/arm/nbsd.mt +++ b/gdb/config/arm/nbsd.mt @@ -1,3 +1,3 @@ # Target: ARM running NetBSD -TDEPFILES= arm-tdep.o armnbsd-tdep.o solib.o solib-svr4.o nbsd-tdep.o +TDEPFILES= arm-tdep.o armnbsd-tdep.o solib.o solib-svr4.o nbsd-tdep.o prologue-value.o DEPRECATED_TM_FILE=solib.h diff --git a/gdb/config/arm/wince.mt b/gdb/config/arm/wince.mt index 9cdc07ded5e..e2f41992ded 100644 --- a/gdb/config/arm/wince.mt +++ b/gdb/config/arm/wince.mt @@ -1,5 +1,5 @@ # Target: Acorn RISC machine (ARM) with simulator -TDEPFILES= arm-tdep.o wince.o +TDEPFILES= arm-tdep.o wince.o prologue-value.o DEPRECATED_TM_FILE= tm-wince.h MT_CFLAGS=-DARM -U_X86_ -U_M_IX86 -U__i386__ -U__i486__ -U__i586__ -U__i686__ -DUNICODE -D_WIN32_WCE -DWINCE_STUB='"${target_alias}-stub.exe"' TM_CLIBS=-lrapi diff --git a/gdb/prologue-value.c b/gdb/prologue-value.c new file mode 100644 index 00000000000..a27509aee41 --- /dev/null +++ b/gdb/prologue-value.c @@ -0,0 +1,591 @@ +/* Prologue value handling for GDB. + Copyright 2003, 2004, 2005 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 2 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, write to: + + Free Software Foundation, Inc. + 51 Franklin St - Fifth Floor + Boston, MA 02110-1301 + USA */ + +#include "defs.h" +#include "gdb_string.h" +#include "gdb_assert.h" +#include "prologue-value.h" +#include "regcache.h" + + +/* Constructors. */ + +pv_t +pv_unknown (void) +{ + pv_t v = { pvk_unknown, 0, 0 }; + + return v; +} + + +pv_t +pv_constant (CORE_ADDR k) +{ + pv_t v; + + v.kind = pvk_constant; + v.reg = -1; /* for debugging */ + v.k = k; + + return v; +} + + +pv_t +pv_register (int reg, CORE_ADDR k) +{ + pv_t v; + + v.kind = pvk_register; + v.reg = reg; + v.k = k; + + return v; +} + + + +/* Arithmetic operations. */ + +/* If one of *A and *B is a constant, and the other isn't, swap the + values as necessary to ensure that *B is the constant. This can + reduce the number of cases we need to analyze in the functions + below. */ +static void +constant_last (pv_t *a, pv_t *b) +{ + if (a->kind == pvk_constant + && b->kind != pvk_constant) + { + pv_t temp = *a; + *a = *b; + *b = temp; + } +} + + +pv_t +pv_add (pv_t a, pv_t b) +{ + constant_last (&a, &b); + + /* We can add a constant to a register. */ + if (a.kind == pvk_register + && b.kind == pvk_constant) + return pv_register (a.reg, a.k + b.k); + + /* We can add a constant to another constant. */ + else if (a.kind == pvk_constant + && b.kind == pvk_constant) + return pv_constant (a.k + b.k); + + /* Anything else we don't know how to add. We don't have a + representation for, say, the sum of two registers, or a multiple + of a register's value (adding a register to itself). */ + else + return pv_unknown (); +} + + +pv_t +pv_add_constant (pv_t v, CORE_ADDR k) +{ + /* Rather than thinking of all the cases we can and can't handle, + we'll just let pv_add take care of that for us. */ + return pv_add (v, pv_constant (k)); +} + + +pv_t +pv_subtract (pv_t a, pv_t b) +{ + /* This isn't quite the same as negating B and adding it to A, since + we don't have a representation for the negation of anything but a + constant. For example, we can't negate { pvk_register, R1, 10 }, + but we do know that { pvk_register, R1, 10 } minus { pvk_register, + R1, 5 } is { pvk_constant, <ignored>, 5 }. + + This means, for example, that we could subtract two stack + addresses; they're both relative to the original SP. Since the + frame pointer is set based on the SP, its value will be the + original SP plus some constant (probably zero), so we can use its + value just fine, too. */ + + constant_last (&a, &b); + + /* We can subtract two constants. */ + if (a.kind == pvk_constant + && b.kind == pvk_constant) + return pv_constant (a.k - b.k); + + /* We can subtract a constant from a register. */ + else if (a.kind == pvk_register + && b.kind == pvk_constant) + return pv_register (a.reg, a.k - b.k); + + /* We can subtract a register from itself, yielding a constant. */ + else if (a.kind == pvk_register + && b.kind == pvk_register + && a.reg == b.reg) + return pv_constant (a.k - b.k); + + /* We don't know how to subtract anything else. */ + else + return pv_unknown (); +} + + +pv_t +pv_logical_and (pv_t a, pv_t b) +{ + constant_last (&a, &b); + + /* We can 'and' two constants. */ + if (a.kind == pvk_constant + && b.kind == pvk_constant) + return pv_constant (a.k & b.k); + + /* We can 'and' anything with the constant zero. */ + else if (b.kind == pvk_constant + && b.k == 0) + return pv_constant (0); + + /* We can 'and' anything with ~0. */ + else if (b.kind == pvk_constant + && b.k == ~ (CORE_ADDR) 0) + return a; + + /* We can 'and' a register with itself. */ + else if (a.kind == pvk_register + && b.kind == pvk_register + && a.reg == b.reg + && a.k == b.k) + return a; + + /* Otherwise, we don't know. */ + else + return pv_unknown (); +} + + + +/* Examining prologue values. */ + +int +pv_is_identical (pv_t a, pv_t b) +{ + if (a.kind != b.kind) + return 0; + + switch (a.kind) + { + case pvk_unknown: + return 1; + case pvk_constant: + return (a.k == b.k); + case pvk_register: + return (a.reg == b.reg && a.k == b.k); + default: + gdb_assert (0); + } +} + + +int +pv_is_constant (pv_t a) +{ + return (a.kind == pvk_constant); +} + + +int +pv_is_register (pv_t a, int r) +{ + return (a.kind == pvk_register + && a.reg == r); +} + + +int +pv_is_register_k (pv_t a, int r, CORE_ADDR k) +{ + return (a.kind == pvk_register + && a.reg == r + && a.k == k); +} + + +enum pv_boolean +pv_is_array_ref (pv_t addr, CORE_ADDR size, + pv_t array_addr, CORE_ADDR array_len, + CORE_ADDR elt_size, + int *i) +{ + /* Note that, since .k is a CORE_ADDR, and CORE_ADDR is unsigned, if + addr is *before* the start of the array, then this isn't going to + be negative... */ + pv_t offset = pv_subtract (addr, array_addr); + + if (offset.kind == pvk_constant) + { + /* This is a rather odd test. We want to know if the SIZE bytes + at ADDR don't overlap the array at all, so you'd expect it to + be an || expression: "if we're completely before || we're + completely after". But with unsigned arithmetic, things are + different: since it's a number circle, not a number line, the + right values for offset.k are actually one contiguous range. */ + if (offset.k <= -size + && offset.k >= array_len * elt_size) + return pv_definite_no; + else if (offset.k % elt_size != 0 + || size != elt_size) + return pv_maybe; + else + { + *i = offset.k / elt_size; + return pv_definite_yes; + } + } + else + return pv_maybe; +} + + + +/* Areas. */ + + +/* A particular value known to be stored in an area. + + Entries form a ring, sorted by unsigned offset from the area's base + register's value. Since entries can straddle the wrap-around point, + unsigned offsets form a circle, not a number line, so the list + itself is structured the same way --- there is no inherent head. + The entry with the lowest offset simply follows the entry with the + highest offset. Entries may abut, but never overlap. The area's + 'entry' pointer points to an arbitrary node in the ring. */ +struct area_entry +{ + /* Links in the doubly-linked ring. */ + struct area_entry *prev, *next; + + /* Offset of this entry's address from the value of the base + register. */ + CORE_ADDR offset; + + /* The size of this entry. Note that an entry may wrap around from + the end of the address space to the beginning. */ + CORE_ADDR size; + + /* The value stored here. */ + pv_t value; +}; + + +struct pv_area +{ + /* This area's base register. */ + int base_reg; + + /* The mask to apply to addresses, to make the wrap-around happen at + the right place. */ + CORE_ADDR addr_mask; + + /* An element of the doubly-linked ring of entries, or zero if we + have none. */ + struct area_entry *entry; +}; + + +struct pv_area * +make_pv_area (int base_reg) +{ + struct pv_area *a = (struct pv_area *) xmalloc (sizeof (*a)); + + memset (a, 0, sizeof (*a)); + + a->base_reg = base_reg; + a->entry = 0; + + /* Remember that shift amounts equal to the type's width are + undefined. */ + a->addr_mask = ((((CORE_ADDR) 1 << (TARGET_ADDR_BIT - 1)) - 1) << 1) | 1; + + return a; +} + + +/* Delete all entries from AREA. */ +static void +clear_entries (struct pv_area *area) +{ + struct area_entry *e = area->entry; + + if (e) + { + /* This needs to be a do-while loop, in order to actually + process the node being checked for in the terminating + condition. */ + do + { + struct area_entry *next = e->next; + xfree (e); + } + while (e != area->entry); + + area->entry = 0; + } +} + + +void +free_pv_area (struct pv_area *area) +{ + clear_entries (area); + xfree (area); +} + + +static void +do_free_pv_area_cleanup (void *arg) +{ + free_pv_area ((struct pv_area *) arg); +} + + +struct cleanup * +make_cleanup_free_pv_area (struct pv_area *area) +{ + return make_cleanup (do_free_pv_area_cleanup, (void *) area); +} + + +int +pv_area_store_would_trash (struct pv_area *area, pv_t addr) +{ + /* It may seem odd that pvk_constant appears here --- after all, + that's the case where we know the most about the address! But + pv_areas are always relative to a register, and we don't know the + value of the register, so we can't compare entry addresses to + constants. */ + return (addr.kind == pvk_unknown + || addr.kind == pvk_constant + || (addr.kind == pvk_register && addr.reg != area->base_reg)); +} + + +/* Return a pointer to the first entry we hit in AREA starting at + OFFSET and going forward. + + This may return zero, if AREA has no entries. + + And since the entries are a ring, this may return an entry that + entirely preceeds OFFSET. This is the correct behavior: depending + on the sizes involved, we could still overlap such an area, with + wrap-around. */ +static struct area_entry * +find_entry (struct pv_area *area, CORE_ADDR offset) +{ + struct area_entry *e = area->entry; + + if (! e) + return 0; + + /* If the next entry would be better than the current one, then scan + forward. Since we use '<' in this loop, it always terminates. + + Note that, even setting aside the addr_mask stuff, we must not + simplify this, in high school algebra fashion, to + (e->next->offset < e->offset), because of the way < interacts + with wrap-around. We have to subtract offset from both sides to + make sure both things we're comparing are on the same side of the + discontinuity. */ + while (((e->next->offset - offset) & area->addr_mask) + < ((e->offset - offset) & area->addr_mask)) + e = e->next; + + /* If the previous entry would be better than the current one, then + scan backwards. */ + while (((e->prev->offset - offset) & area->addr_mask) + < ((e->offset - offset) & area->addr_mask)) + e = e->prev; + + /* In case there's some locality to the searches, set the area's + pointer to the entry we've found. */ + area->entry = e; + + return e; +} + + +/* Return non-zero if the SIZE bytes at OFFSET would overlap ENTRY; + return zero otherwise. AREA is the area to which ENTRY belongs. */ +static int +overlaps (struct pv_area *area, + struct area_entry *entry, + CORE_ADDR offset, + CORE_ADDR size) +{ + /* Think carefully about wrap-around before simplifying this. */ + return (((entry->offset - offset) & area->addr_mask) < size + || ((offset - entry->offset) & area->addr_mask) < entry->size); +} + + +void +pv_area_store (struct pv_area *area, + pv_t addr, + CORE_ADDR size, + pv_t value) +{ + /* Remove any (potentially) overlapping entries. */ + if (pv_area_store_would_trash (area, addr)) + clear_entries (area); + else + { + CORE_ADDR offset = addr.k; + struct area_entry *e = find_entry (area, offset); + + /* Delete all entries that we would overlap. */ + while (e && overlaps (area, e, offset, size)) + { + struct area_entry *next = (e->next == e) ? 0 : e->next; + e->prev->next = e->next; + e->next->prev = e->prev; + + xfree (e); + e = next; + } + + /* Move the area's pointer to the next remaining entry. This + will also zero the pointer if we've deleted all the entries. */ + area->entry = e; + } + + /* Now, there are no entries overlapping us, and area->entry is + either zero or pointing at the closest entry after us. We can + just insert ourselves before that. + + But if we're storing an unknown value, don't bother --- that's + the default. */ + if (value.kind == pvk_unknown) + return; + else + { + CORE_ADDR offset = addr.k; + struct area_entry *e = (struct area_entry *) xmalloc (sizeof (*e)); + e->offset = offset; + e->size = size; + e->value = value; + + if (area->entry) + { + e->prev = area->entry->prev; + e->next = area->entry; + e->prev->next = e->next->prev = e; + } + else + { + e->prev = e->next = e; + area->entry = e; + } + } +} + + +pv_t +pv_area_fetch (struct pv_area *area, pv_t addr, CORE_ADDR size) +{ + /* If we have no entries, or we can't decide how ADDR relates to the + entries we do have, then the value is unknown. */ + if (! area->entry + || pv_area_store_would_trash (area, addr)) + return pv_unknown (); + else + { + CORE_ADDR offset = addr.k; + struct area_entry *e = find_entry (area, offset); + + /* If this entry exactly matches what we're looking for, then + we're set. Otherwise, say it's unknown. */ + if (e->offset == offset && e->size == size) + return e->value; + else + return pv_unknown (); + } +} + + +int +pv_area_find_reg (struct pv_area *area, + struct gdbarch *gdbarch, + int reg, + CORE_ADDR *offset_p) +{ + struct area_entry *e = area->entry; + + if (e) + do + { + if (e->value.kind == pvk_register + && e->value.reg == reg + && e->value.k == 0 + && e->size == register_size (gdbarch, reg)) + { + if (offset_p) + *offset_p = e->offset; + return 1; + } + + e = e->next; + } + while (e != area->entry); + + return 0; +} + + +void +pv_area_scan (struct pv_area *area, + void (*func) (void *closure, + pv_t addr, + CORE_ADDR size, + pv_t value), + void *closure) +{ + struct area_entry *e = area->entry; + pv_t addr; + + addr.kind = pvk_register; + addr.reg = area->base_reg; + + if (e) + do + { + addr.k = e->offset; + func (closure, addr, e->size, e->value); + e = e->next; + } + while (e != area->entry); +} diff --git a/gdb/prologue-value.h b/gdb/prologue-value.h new file mode 100644 index 00000000000..9a1fd096c0b --- /dev/null +++ b/gdb/prologue-value.h @@ -0,0 +1,293 @@ +/* Interface to prologue value handling for GDB. + Copyright 2003, 2004, 2005 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 2 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, write to: + + Free Software Foundation, Inc. + 51 Franklin St - Fifth Floor + Boston, MA 02110-1301 + USA */ + +#ifndef PROLOGUE_VALUE_H +#define PROLOGUE_VALUE_H + +/* When we analyze a prologue, we're really doing 'abstract + interpretation' or 'pseudo-evaluation': running the function's code + in simulation, but using conservative approximations of the values + it would have when it actually runs. For example, if our function + starts with the instruction: + + addi r1, 42 # add 42 to r1 + + we don't know exactly what value will be in r1 after executing this + instruction, but we do know it'll be 42 greater than its original + value. + + If we then see an instruction like: + + addi r1, 22 # add 22 to r1 + + we still don't know what r1's value is, but again, we can say it is + now 64 greater than its original value. + + If the next instruction were: + + mov r2, r1 # set r2 to r1's value + + then we can say that r2's value is now the original value of r1 + plus 64. + + It's common for prologues to save registers on the stack, so we'll + need to track the values of stack frame slots, as well as the + registers. So after an instruction like this: + + mov (fp+4), r2 + + Then we'd know that the stack slot four bytes above the frame + pointer holds the original value of r1 plus 64. + + And so on. + + Of course, this can only go so far before it gets unreasonable. If + we wanted to be able to say anything about the value of r1 after + the instruction: + + xor r1, r3 # exclusive-or r1 and r3, place result in r1 + + then things would get pretty complex. But remember, we're just + doing a conservative approximation; if exclusive-or instructions + aren't relevant to prologues, we can just say r1's value is now + 'unknown'. We can ignore things that are too complex, if that loss + of information is acceptable for our application. + + So when I say "conservative approximation" here, what I mean is an + approximation that is either accurate, or marked "unknown", but + never inaccurate. + + Once you've reached the current PC, or an instruction that you + don't know how to simulate, you stop. Now you can examine the + state of the registers and stack slots you've kept track of. + + - To see how large your stack frame is, just check the value of the + stack pointer register; if it's the original value of the SP + minus a constant, then that constant is the stack frame's size. + If the SP's value has been marked as 'unknown', then that means + the prologue has done something too complex for us to track, and + we don't know the frame size. + + - To see where we've saved the previous frame's registers, we just + search the values we've tracked --- stack slots, usually, but + registers, too, if you want --- for something equal to the + register's original value. If the ABI suggests a standard place + to save a given register, then we can check there first, but + really, anything that will get us back the original value will + probably work. + + Sure, this takes some work. But prologue analyzers aren't + quick-and-simple pattern patching to recognize a few fixed prologue + forms any more; they're big, hairy functions. Along with inferior + function calls, prologue analysis accounts for a substantial + portion of the time needed to stabilize a GDB port. So I think + it's worthwhile to look for an approach that will be easier to + understand and maintain. In the approach used here: + + - It's easier to see that the analyzer is correct: you just see + whether the analyzer properly (albiet conservatively) simulates + the effect of each instruction. + + - It's easier to extend the analyzer: you can add support for new + instructions, and know that you haven't broken anything that + wasn't already broken before. + + - It's orthogonal: to gather new information, you don't need to + complicate the code for each instruction. As long as your domain + of conservative values is already detailed enough to tell you + what you need, then all the existing instruction simulations are + already gathering the right data for you. + + A 'struct prologue_value' is a conservative approximation of the + real value the register or stack slot will have. */ + +struct prologue_value { + + /* What sort of value is this? This determines the interpretation + of subsequent fields. */ + enum { + + /* We don't know anything about the value. This is also used for + values we could have kept track of, when doing so would have + been too complex and we don't want to bother. The bottom of + our lattice. */ + pvk_unknown, + + /* A known constant. K is its value. */ + pvk_constant, + + /* The value that register REG originally had *UPON ENTRY TO THE + FUNCTION*, plus K. If K is zero, this means, obviously, just + the value REG had upon entry to the function. REG is a GDB + register number. Before we start interpreting, we initialize + every register R to { pvk_register, R, 0 }. */ + pvk_register, + + } kind; + + /* The meanings of the following fields depend on 'kind'; see the + comments for the specific 'kind' values. */ + int reg; + CORE_ADDR k; +}; + +typedef struct prologue_value pv_t; + + +/* Return the unknown prologue value --- { pvk_unknown, ?, ? }. */ +pv_t pv_unknown (void); + +/* Return the prologue value representing the constant K. */ +pv_t pv_constant (CORE_ADDR k); + +/* Return the prologue value representing the original value of + register REG, plus the constant K. */ +pv_t pv_register (int reg, CORE_ADDR k); + + +/* Return conservative approximations of the results of the following + operations. */ +pv_t pv_add (pv_t a, pv_t b); /* a + b */ +pv_t pv_add_constant (pv_t v, CORE_ADDR k); /* a + k */ +pv_t pv_subtract (pv_t a, pv_t b); /* a - b */ +pv_t pv_logical_and (pv_t a, pv_t b); /* a & b */ + + +/* Return non-zero iff A and B are identical expressions. + + This is not the same as asking if the two values are equal; the + result of such a comparison would have to be a pv_boolean, and + asking whether two 'unknown' values were equal would give you + pv_maybe. Same for comparing, say, { pvk_register, R1, 0 } and { + pvk_register, R2, 0}. + + Instead, this function asks whether the two representations are the + same. */ +int pv_is_identical (pv_t a, pv_t b); + + +/* Return non-zero if A is known to be a constant. */ +int pv_is_constant (pv_t a); + +/* Return non-zero if A is the original value of register number R + plus some constant, zero otherwise. */ +int pv_is_register (pv_t a, int r); + + +/* Return non-zero if A is the original value of register R plus the + constant K. */ +int pv_is_register_k (pv_t a, int r, CORE_ADDR k); + +/* A conservative boolean type, including "maybe", when we can't + figure out whether something is true or not. */ +enum pv_boolean { + pv_maybe, + pv_definite_yes, + pv_definite_no, +}; + + +/* Decide whether a reference to SIZE bytes at ADDR refers exactly to + an element of an array. The array starts at ARRAY_ADDR, and has + ARRAY_LEN values of ELT_SIZE bytes each. If ADDR definitely does + refer to an array element, set *I to the index of the referenced + element in the array, and return pv_definite_yes. If it definitely + doesn't, return pv_definite_no. If we can't tell, return pv_maybe. + + If the reference does touch the array, but doesn't fall exactly on + an element boundary, or doesn't refer to the whole element, return + pv_maybe. */ +enum pv_boolean pv_is_array_ref (pv_t addr, CORE_ADDR size, + pv_t array_addr, CORE_ADDR array_len, + CORE_ADDR elt_size, + int *i); + + +/* A 'struct pv_area' keeps track of values stored in a particular + region of memory. */ +struct pv_area; + +/* Create a new area, tracking stores relative to BASE_REG. Stores to + constant addresses, unknown addresses, or to addresses relative to + registers other than BASE_REG will trash this area; see + pv_area_store_would_trash. */ +struct pv_area *make_pv_area (int base_reg); + +/* Free AREA. */ +void free_pv_area (struct pv_area *area); + + +/* Register a cleanup to free AREA. */ +struct cleanup *make_cleanup_free_pv_area (struct pv_area *area); + + +/* Store the SIZE-byte value VALUE at ADDR in AREA. + + If ADDR is not relative to the same base register we used in + creating AREA, then we can't tell which values here the stored + value might overlap, and we'll have to mark everything as + unknown. */ +void pv_area_store (struct pv_area *area, + pv_t addr, + CORE_ADDR size, + pv_t value); + +/* Return the SIZE-byte value at ADDR in AREA. This may return + pv_unknown (). */ +pv_t pv_area_fetch (struct pv_area *area, pv_t addr, CORE_ADDR size); + +/* Return true if storing to address ADDR in AREA would force us to + mark the contents of the entire area as unknown. This could happen + if, say, ADDR is unknown, since we could be storing anywhere. Or, + it could happen if ADDR is relative to a different register than + the other stores base register, since we don't know the relative + values of the two registers. + + If you've reached such a store, it may be better to simply stop the + prologue analysis, and return the information you've gathered, + instead of losing all that information, most of which is probably + okay. */ +int pv_area_store_would_trash (struct pv_area *area, pv_t addr); + + +/* Search AREA for the original value of REGISTER. If we can't find + it, return zero; if we can find it, return a non-zero value, and if + OFFSET_P is non-zero, set *OFFSET_P to the register's offset within + AREA. GDBARCH is the architecture of which REGISTER is a member. */ +int pv_area_find_reg (struct pv_area *area, + struct gdbarch *gdbarch, + int register, + CORE_ADDR *offset_p); + + +/* For every part of AREA whose value we know, apply FUNC to CLOSURE, + the value's address, its size, and the value itself. */ +void pv_area_scan (struct pv_area *area, + void (*func) (void *closure, + pv_t addr, + CORE_ADDR size, + pv_t value), + void *closure); + + +#endif /* PROLOGUE_VALUE_H */ diff --git a/gdb/testsuite/gdb.arch/thumb-prologue.c b/gdb/testsuite/gdb.arch/thumb-prologue.c new file mode 100644 index 00000000000..f6189634641 --- /dev/null +++ b/gdb/testsuite/gdb.arch/thumb-prologue.c @@ -0,0 +1,96 @@ +/* Unwinder test program. + + Copyright 2006 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +void tpcs_frame (void); + +int +main (void) +{ + tpcs_frame (); + return 0; +} + +/* Normally Thumb functions use r7 as the frame pointer. However, + with the GCC option -mtpcs-frame, they may use fp instead. */ + +asm(".text\n" + " .align 2\n" + " .thumb_func\n" + " .code 16\n" + "tpcs_frame_1:\n" + " sub sp, #16\n" + " push {r7}\n" + " add r7, sp, #20\n" + " str r7, [sp, #8]\n" + " mov r7, pc\n" + " str r7, [sp, #16]\n" + " mov r7, fp\n" + " str r7, [sp, #4]\n" + " mov r7, lr\n" + " str r7, [sp, #12]\n" + " add r7, sp, #16\n" + " mov fp, r7\n" + " mov r7, sl\n" + " push {r7}\n" + + /* Trap. */ + " .short 0xdffe\n" + + " pop {r2}\n" + " mov sl, r2\n" + " pop {r7}\n" + " pop {r1, r2}\n" + " mov fp, r1\n" + " mov sp, r2\n" + " bx lr\n" + + " .align 2\n" + " .thumb_func\n" + " .code 16\n" + "tpcs_frame:\n" + " sub sp, #16\n" + " push {r7}\n" + " add r7, sp, #20\n" + " str r7, [sp, #8]\n" + " mov r7, pc\n" + " str r7, [sp, #16]\n" + " mov r7, fp\n" + " str r7, [sp, #4]\n" + " mov r7, lr\n" + " str r7, [sp, #12]\n" + " add r7, sp, #16\n" + " mov fp, r7\n" + " mov r7, sl\n" + " push {r7}\n" + + /* Clobber saved regs. */ + " mov r7, #0\n" + " mov lr, r7\n" + " bl tpcs_frame_1\n" + + " pop {r2}\n" + " mov sl, r2\n" + " pop {r7}\n" + " pop {r1, r2}\n" + " mov fp, r1\n" + " mov sp, r2\n" + " bx lr\n" +); diff --git a/gdb/testsuite/gdb.arch/thumb-prologue.exp b/gdb/testsuite/gdb.arch/thumb-prologue.exp new file mode 100644 index 00000000000..58a080fdb22 --- /dev/null +++ b/gdb/testsuite/gdb.arch/thumb-prologue.exp @@ -0,0 +1,59 @@ +# Copyright 2006 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Test ARM/Thumb prologue analyzer. + +if {![istarget arm*-*]} then { + verbose "Skipping ARM prologue tests." + return +} + +set testfile "thumb-prologue" +set srcfile ${testfile}.c +set binfile ${objdir}/${subdir}/${testfile} + +# Don't use "debug", so that we don't have line information for the assembly +# fragments. +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {"additional_flags=-mthumb"}] != "" } { + untested "ARM prologue tests" + return -1 +} + + +gdb_exit +gdb_start +gdb_reinitialize_dir $srcdir/$subdir +gdb_load ${binfile} + +# +# Run to `main' where we begin our tests. +# + +if ![runto_main] then { + gdb_suppress_tests +} + +# Testcase for TPCS prologue. + +gdb_test "continue" "Program received signal SIG.*" "continue to TPCS" + +gdb_test "backtrace 10" \ + "#0\[ \t\]*$hex in tpcs_frame_1 .*\r\n#1\[ \t\]*$hex in tpcs_frame .*\r\n#2\[ \t\]*$hex in main.*" \ + "backtrace in TPCS" + +gdb_test "info frame" \ + ".*Saved registers:.*r7 at.*r10 at.*r11 at.*lr at.*pc at .*" \ + "saved registers in TPCS" |