diff options
author | Jan Kratochvil <jan.kratochvil@redhat.com> | 2011-10-09 19:26:44 +0000 |
---|---|---|
committer | Jan Kratochvil <jan.kratochvil@redhat.com> | 2011-10-09 19:26:44 +0000 |
commit | 111c64899c7b1811c3a04c711f4a0dc95776eecc (patch) | |
tree | ceae0851734071568fa8693459000c13eac48cf1 /gdb | |
parent | bb984ff154d9e532deccdb52b3527496852c5930 (diff) | |
download | gdb-111c64899c7b1811c3a04c711f4a0dc95776eecc.zip gdb-111c64899c7b1811c3a04c711f4a0dc95776eecc.tar.gz gdb-111c64899c7b1811c3a04c711f4a0dc95776eecc.tar.bz2 |
gdb/
Recognize virtual tail call frames.
* Makefile.in (SFILES): Add dwarf2-frame-tailcall.c.
(HFILES_NO_SRCDIR): Add dwarf2-frame-tailcall.h.
(COMMON_OBS): Add dwarf2-frame-tailcall.o.
* dwarf2-frame-tailcall.c: New file.
* dwarf2-frame-tailcall.h: New file.
* dwarf2-frame.c: Include dwarf2-frame-tailcall.h.
(execute_cfa_program): New function comment. Return INSN_PTR. Reset
REGS.PREV only after CIE execution.
(struct dwarf2_frame_cache): New field tailcall_cache.
(dwarf2_frame_cache): New variables entry_pc, entry_cfa_sp_offset,
entry_cfa_sp_offset_p and instr. Execute FDE instructions in two
parts, try to find entry_cfa_sp_offset. Call
dwarf2_tailcall_sniffer_first.
(dwarf2_frame_prev_register): Call dwarf2_tailcall_prev_register_first
when appropriate.
(dwarf2_frame_dealloc_cache): New function.
(dwarf2_frame_sniffer): Preinitialize cache by dwarf2_frame_cache.
(dwarf2_frame_unwind): Install dwarf2_frame_dealloc_cache.
(dwarf2_signal_frame_unwind): Do not install dwarf2_frame_dealloc_cache.
(dwarf2_append_unwinders): Add dwarf2_tailcall_frame_unwind.
(dwarf2_frame_cfa): Support also dwarf2_tailcall_frame_unwind.
* dwarf2loc.c (func_addr_to_tail_call_list)
(tailcall_dump, call_sitep, VEC (call_sitep), chain_candidate)
(call_site_find_chain_1, call_site_find_chain): New.
* dwarf2loc.h (struct call_site_chain): New.
(call_site_find_chain): New declaration.
* frame.c (get_frame_address_in_block): Support also TAILCALL_FRAME.
* frame.h (enum frame_type): New entry TAILCALL_FRAME.
* python/py-frame.c (gdbpy_initialize_frames): Add TAILCALL_FRAME.
* stack.c (frame_info): Support also TAILCALL_FRAME.
gdb/doc/
Recognize virtual tail call frames.
* gdb.texinfo (Optimized Code): Add reference to Tail Call Frames.
(Tail Call Frames): New node.
(Frames In Python): Add gdb.TAILCALL_FRAME.
gdb/testsuite/
Recognize virtual tail call frames.
* gdb.arch/amd64-entry-value.cc (c, a, b, amb_z, amb_y, amb_x, amb)
(amb_b, amb_a): New.
(main): Call a and b.
* gdb.arch/amd64-entry-value.exp (tailcall: breakhere, tailcall: bt)
(tailcall: p i, tailcall: p j, set $sp0=$sp, up, p $sp0 == $sp, frame 3)
(p $sp0 + sizeof (void *) == $sp, ambiguous: breakhere, ambiguous: bt):
New tests.
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 34 | ||||
-rw-r--r-- | gdb/Makefile.in | 5 | ||||
-rw-r--r-- | gdb/doc/ChangeLog | 8 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 124 | ||||
-rw-r--r-- | gdb/dwarf2-frame.c | 106 | ||||
-rw-r--r-- | gdb/dwarf2loc.c | 315 | ||||
-rw-r--r-- | gdb/dwarf2loc.h | 19 | ||||
-rw-r--r-- | gdb/frame.c | 4 | ||||
-rw-r--r-- | gdb/frame.h | 2 | ||||
-rw-r--r-- | gdb/python/py-frame.c | 1 | ||||
-rw-r--r-- | gdb/stack.c | 2 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 11 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/amd64-entry-value.cc | 62 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/amd64-entry-value.exp | 28 |
14 files changed, 709 insertions, 12 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 8908251..76e62f2 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,39 @@ 2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com> + Recognize virtual tail call frames. + * Makefile.in (SFILES): Add dwarf2-frame-tailcall.c. + (HFILES_NO_SRCDIR): Add dwarf2-frame-tailcall.h. + (COMMON_OBS): Add dwarf2-frame-tailcall.o. + * dwarf2-frame-tailcall.c: New file. + * dwarf2-frame-tailcall.h: New file. + * dwarf2-frame.c: Include dwarf2-frame-tailcall.h. + (execute_cfa_program): New function comment. Return INSN_PTR. Reset + REGS.PREV only after CIE execution. + (struct dwarf2_frame_cache): New field tailcall_cache. + (dwarf2_frame_cache): New variables entry_pc, entry_cfa_sp_offset, + entry_cfa_sp_offset_p and instr. Execute FDE instructions in two + parts, try to find entry_cfa_sp_offset. Call + dwarf2_tailcall_sniffer_first. + (dwarf2_frame_prev_register): Call dwarf2_tailcall_prev_register_first + when appropriate. + (dwarf2_frame_dealloc_cache): New function. + (dwarf2_frame_sniffer): Preinitialize cache by dwarf2_frame_cache. + (dwarf2_frame_unwind): Install dwarf2_frame_dealloc_cache. + (dwarf2_signal_frame_unwind): Do not install dwarf2_frame_dealloc_cache. + (dwarf2_append_unwinders): Add dwarf2_tailcall_frame_unwind. + (dwarf2_frame_cfa): Support also dwarf2_tailcall_frame_unwind. + * dwarf2loc.c (func_addr_to_tail_call_list) + (tailcall_dump, call_sitep, VEC (call_sitep), chain_candidate) + (call_site_find_chain_1, call_site_find_chain): New. + * dwarf2loc.h (struct call_site_chain): New. + (call_site_find_chain): New declaration. + * frame.c (get_frame_address_in_block): Support also TAILCALL_FRAME. + * frame.h (enum frame_type): New entry TAILCALL_FRAME. + * python/py-frame.c (gdbpy_initialize_frames): Add TAILCALL_FRAME. + * stack.c (frame_info): Support also TAILCALL_FRAME. + +2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com> + Tail call sites reader implementation. * dwarf2read.c (read_call_site_scope): Recognize DW_AT_GNU_tail_call, fill in TYPE_TAIL_CALL_LIST. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 986588b..4dd324d 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -697,6 +697,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \ cp-name-parser.y \ dbxread.c demangle.c dictionary.c disasm.c doublest.c dummy-frame.c \ dwarf2expr.c dwarf2loc.c dwarf2read.c dwarf2-frame.c \ + dwarf2-frame-tailcall.c \ elfread.c environ.c eval.c event-loop.c event-top.c \ exceptions.c expprint.c \ f-exp.y f-lang.c f-typeprint.c f-valprint.c filesystem.c \ @@ -773,7 +774,7 @@ cli/cli-decode.h cli/cli-cmds.h cli/cli-dump.h cli/cli-utils.h \ cli/cli-script.h macrotab.h symtab.h version.h gnulib/wchar.in.h \ gnulib/string.in.h gnulib/str-two-way.h \ gnulib/stdint.in.h remote.h gdb.h sparc-nat.h \ -gdbthread.h dwarf2-frame.h nbsd-nat.h dcache.h \ +gdbthread.h dwarf2-frame.h dwarf2-frame-tailcall.h nbsd-nat.h dcache.h \ amd64-nat.h s390-tdep.h arm-linux-tdep.h exceptions.h macroscope.h \ gdbarch.h bsd-uthread.h gdb_stat.h memory-map.h memrange.h \ mdebugread.h m88k-tdep.h stabsread.h hppa-linux-offsets.h linux-fork.h \ @@ -881,7 +882,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \ bcache.o objfiles.o observer.o minsyms.o maint.o demangle.o \ dbxread.o coffread.o coff-pe-read.o \ dwarf2read.o mipsread.o stabsread.o corefile.o \ - dwarf2expr.o dwarf2loc.o dwarf2-frame.o \ + dwarf2expr.o dwarf2loc.o dwarf2-frame.o dwarf2-frame-tailcall.o \ ada-lang.o c-lang.o d-lang.o f-lang.o objc-lang.o \ ada-tasks.o \ ui-out.o cli-out.o \ diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 5a6e468..8c9d9b5 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,11 @@ +2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com> + Eli Zaretskii <eliz@gnu.org> + + Recognize virtual tail call frames. + * gdb.texinfo (Optimized Code): Add reference to Tail Call Frames. + (Tail Call Frames): New node. + (Frames In Python): Add gdb.TAILCALL_FRAME. + 2011-10-07 Doug Evans <dje@google.com> * gdb.texinfo (gdb.printing): Document new `replace' arg to diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index af39ee9..fdf66c3 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -9486,6 +9486,7 @@ please report it to us as a bug (including a test case!). @menu * Inline Functions:: How @value{GDBN} presents inlining +* Tail Call Frames:: @value{GDBN} analysis of jumps to functions @end menu @node Inline Functions @@ -9553,6 +9554,126 @@ and print a variable where your program stored the return value. @end itemize +@node Tail Call Frames +@section Tail Call Frames +@cindex tail call frames, debugging + +Function @code{B} can call function @code{C} in its very last statement. In +unoptimized compilation the call of @code{C} is immediately followed by return +instruction at the end of @code{B} code. Optimizing compiler may replace the +call and return in function @code{B} into one jump to function @code{C} +instead. Such use of a jump instruction is called @dfn{tail call}. + +During execution of function @code{C}, there will be no indication in the +function call stack frames that it was tail-called from @code{B}. If function +@code{A} regularly calls function @code{B} which tail-calls function @code{C}, +then @value{GDBN} will see @code{A} as the caller of @code{C}. However, in +some cases @value{GDBN} can determine that @code{C} was tail-called from +@code{B}, and it will then create fictitious call frame for that, with the +return address set up as if @code{B} called @code{C} normally. + +This functionality is currently supported only by DWARF 2 debugging format and +the compiler has to produce @samp{DW_TAG_GNU_call_site} tags. With +@value{NGCC}, you need to specify @option{-O -g} during compilation, to get +this information. + +@kbd{info frame} command (@pxref{Frame Info}) will indicate the tail call frame +kind by text @code{tail call frame} such as in this sample @value{GDBN} output: + +@smallexample +(gdb) x/i $pc - 2 + 0x40066b <b(int, double)+11>: jmp 0x400640 <c(int, double)> +(gdb) info frame +Stack level 1, frame at 0x7fffffffda30: + rip = 0x40066d in b (amd64-entry-value.cc:59); saved rip 0x4004c5 + tail call frame, caller of frame at 0x7fffffffda30 + source language c++. + Arglist at unknown address. + Locals at unknown address, Previous frame's sp is 0x7fffffffda30 +@end smallexample + +The detection of all the possible code path executions can find them ambiguous. +There is no execution history stored (possible @ref{Reverse Execution} is never +used for this purpose) and the last known caller could have reached the known +callee by multiple different jump sequences. In such case @value{GDBN} still +tries to show at least all the unambiguous top tail callers and all the +unambiguous bottom tail calees, if any. + +@table @code +@item set debug entry-values +@kindex set debug entry-values +When set to on, enables printing of analysis messages for both frame argument +values at function entry and tail calls. It will show all the possible valid +tail calls code paths it has considered. It will also print the intersection +of them with the final unambiguous (possibly partial or even empty) code path +result. + +@item show debug entry-values +@kindex show debug entry-values +Show the current state of analysis messages printing for both frame argument +values at function entry and tail calls. +@end table + +The analysis messages for tail calls can for example show why the virtual tail +call frame for function @code{c} has not been recognized (due to the indirect +reference by variable @code{x}): + +@smallexample +static void __attribute__((noinline, noclone)) c (void); +void (*x) (void) = c; +static void __attribute__((noinline, noclone)) a (void) @{ x++; @} +static void __attribute__((noinline, noclone)) c (void) @{ a (); @} +int main (void) @{ x (); return 0; @} + +Breakpoint 1, DW_OP_GNU_entry_value resolving cannot find +DW_TAG_GNU_call_site 0x40039a in main +a () at t.c:3 +3 static void __attribute__((noinline, noclone)) a (void) @{ x++; @} +(gdb) bt +#0 a () at t.c:3 +#1 0x000000000040039a in main () at t.c:5 +@end smallexample + +Another possibility is an ambiguous virtual tail call frames resolution: + +@smallexample +int i; +static void __attribute__((noinline, noclone)) f (void) @{ i++; @} +static void __attribute__((noinline, noclone)) e (void) @{ f (); @} +static void __attribute__((noinline, noclone)) d (void) @{ f (); @} +static void __attribute__((noinline, noclone)) c (void) @{ d (); @} +static void __attribute__((noinline, noclone)) b (void) +@{ if (i) c (); else e (); @} +static void __attribute__((noinline, noclone)) a (void) @{ b (); @} +int main (void) @{ a (); return 0; @} + +tailcall: initial: 0x4004d2(a) 0x4004ce(b) 0x4004b2(c) 0x4004a2(d) +tailcall: compare: 0x4004d2(a) 0x4004cc(b) 0x400492(e) +tailcall: reduced: 0x4004d2(a) | +(gdb) bt +#0 f () at t.c:2 +#1 0x00000000004004d2 in a () at t.c:8 +#2 0x0000000000400395 in main () at t.c:9 +@end smallexample + +Frames #0 and #2 are real, #1 is a virtual tail call frame. The code can have +possible execution paths +@code{main@arrow{}a@arrow{}b@arrow{}c@arrow{}d@arrow{}f} or +@code{main@arrow{}a@arrow{}b@arrow{}e@arrow{}f}, @value{GDBN} cannot find which +one from the inferior state. + +@code{initial:} state shows some random possible calling sequence @value{GDBN} +has found. It then finds another possible calling sequcen - that one is +prefixed by @code{compare:}. The non-ambiguous intersection of these two is +printed as the @code{reduced:} calling sequence. That one could have many +futher @code{compare:} and @code{reduced:} statements as long as there remain +any non-ambiguous sequence entries. + +For the frame of function @code{b} in both cases there are different possible +@code{$pc} values (@code{0x4004cc} or @code{0x4004ce}), therefore this frame is +also ambigous. The only non-ambiguous frame is the one for function @code{a}, +therefore this one is displayed to the user while the ambiguous frames are +omitted. @node Macros @chapter C Preprocessor Macros @@ -23099,6 +23220,9 @@ inferior function call. A frame representing an inlined function. The function was inlined into a @code{gdb.NORMAL_FRAME} that is older than this one. +@item gdb.TAILCALL_FRAME +A frame representing a tail call. @xref{Tail Call Frames}. + @item gdb.SIGTRAMP_FRAME A signal trampoline frame. This is the frame created by the OS when it calls into a signal handler. diff --git a/gdb/dwarf2-frame.c b/gdb/dwarf2-frame.c index cd62529..265383f 100644 --- a/gdb/dwarf2-frame.c +++ b/gdb/dwarf2-frame.c @@ -41,6 +41,7 @@ #include "ax.h" #include "dwarf2loc.h" #include "exceptions.h" +#include "dwarf2-frame-tailcall.h" struct comp_unit; @@ -399,7 +400,11 @@ Not implemented: computing unwound register using explicit value operator")); } -static void +/* Execute FDE program from INSN_PTR possibly up to INSN_END or up to inferior + PC. Modify FS state accordingly. Return current INSN_PTR where the + execution has stopped, one can resume it on the next call. */ + +static const gdb_byte * execute_cfa_program (struct dwarf2_fde *fde, const gdb_byte *insn_ptr, const gdb_byte *insn_end, struct gdbarch *gdbarch, CORE_ADDR pc, struct dwarf2_frame_state *fs) @@ -682,9 +687,14 @@ bad CFI data; mismatched DW_CFA_restore_state at %s"), } } - /* Don't allow remember/restore between CIE and FDE programs. */ - dwarf2_frame_state_free_regs (fs->regs.prev); - fs->regs.prev = NULL; + if (fs->initial.reg == NULL) + { + /* Don't allow remember/restore between CIE and FDE programs. */ + dwarf2_frame_state_free_regs (fs->regs.prev); + fs->regs.prev = NULL; + } + + return insn_ptr; } @@ -976,6 +986,13 @@ struct dwarf2_frame_cache /* The .text offset. */ CORE_ADDR text_offset; + + /* If not NULL then this frame is the bottom frame of a TAILCALL_FRAME + sequence. If NULL then it is a normal case with no TAILCALL_FRAME + involved. Non-bottom frames of a virtual tail call frames chain use + dwarf2_tailcall_frame_unwind unwinder so this field does not apply for + them. */ + void *tailcall_cache; }; static struct dwarf2_frame_cache * @@ -989,6 +1006,10 @@ dwarf2_frame_cache (struct frame_info *this_frame, void **this_cache) struct dwarf2_frame_state *fs; struct dwarf2_fde *fde; volatile struct gdb_exception ex; + CORE_ADDR entry_pc; + LONGEST entry_cfa_sp_offset; + int entry_cfa_sp_offset_p = 0; + const gdb_byte *instr; if (*this_cache) return *this_cache; @@ -1040,8 +1061,25 @@ dwarf2_frame_cache (struct frame_info *this_frame, void **this_cache) fs->initial = fs->regs; fs->initial.reg = dwarf2_frame_state_copy_regs (&fs->regs); + if (get_frame_func_if_available (this_frame, &entry_pc)) + { + /* Decode the insns in the FDE up to the entry PC. */ + instr = execute_cfa_program (fde, fde->instructions, fde->end, gdbarch, + entry_pc, fs); + + if (fs->regs.cfa_how == CFA_REG_OFFSET + && (gdbarch_dwarf2_reg_to_regnum (gdbarch, fs->regs.cfa_reg) + == gdbarch_sp_regnum (gdbarch))) + { + entry_cfa_sp_offset = fs->regs.cfa_offset; + entry_cfa_sp_offset_p = 1; + } + } + else + instr = fde->instructions; + /* Then decode the insns in the FDE up to our target PC. */ - execute_cfa_program (fde, fde->instructions, fde->end, gdbarch, + execute_cfa_program (fde, instr, fde->end, gdbarch, get_frame_pc (this_frame), fs); TRY_CATCH (ex, RETURN_MASK_ERROR) @@ -1182,6 +1220,12 @@ incomplete CFI data; unspecified registers (e.g., %s) at %s"), do_cleanups (old_chain); + /* Try to find a virtual tail call frames chain with bottom (callee) frame + starting at THIS_FRAME. */ + dwarf2_tailcall_sniffer_first (this_frame, &cache->tailcall_cache, + (entry_cfa_sp_offset_p + ? &entry_cfa_sp_offset : NULL)); + return cache; } @@ -1227,6 +1271,22 @@ dwarf2_frame_prev_register (struct frame_info *this_frame, void **this_cache, CORE_ADDR addr; int realnum; + /* Non-bottom frames of a virtual tail call frames chain use + dwarf2_tailcall_frame_unwind unwinder so this code does not apply for + them. If dwarf2_tailcall_prev_register_first does not have specific value + unwind the register, tail call frames are assumed to have the register set + of the top caller. */ + if (cache->tailcall_cache) + { + struct value *val; + + val = dwarf2_tailcall_prev_register_first (this_frame, + &cache->tailcall_cache, + regnum); + if (val) + return val; + } + switch (cache->reg[regnum].how) { case DWARF2_FRAME_REG_UNDEFINED: @@ -1296,6 +1356,18 @@ dwarf2_frame_prev_register (struct frame_info *this_frame, void **this_cache, } } +/* Proxy for tailcall_frame_dealloc_cache for bottom frame of a virtual tail + call frames chain. */ + +static void +dwarf2_frame_dealloc_cache (struct frame_info *self, void *this_cache) +{ + struct dwarf2_frame_cache *cache = dwarf2_frame_cache (self, &this_cache); + + if (cache->tailcall_cache) + dwarf2_tailcall_frame_unwind.dealloc_cache (self, cache->tailcall_cache); +} + static int dwarf2_frame_sniffer (const struct frame_unwind *self, struct frame_info *this_frame, void **this_cache) @@ -1322,7 +1394,14 @@ dwarf2_frame_sniffer (const struct frame_unwind *self, this_frame)) return self->type == SIGTRAMP_FRAME; - return self->type != SIGTRAMP_FRAME; + if (self->type != NORMAL_FRAME) + return 0; + + /* Preinitializa the cache so that TAILCALL_FRAME can find the record by + dwarf2_tailcall_sniffer_first. */ + dwarf2_frame_cache (this_frame, this_cache); + + return 1; } static const struct frame_unwind dwarf2_frame_unwind = @@ -1332,7 +1411,8 @@ static const struct frame_unwind dwarf2_frame_unwind = dwarf2_frame_this_id, dwarf2_frame_prev_register, NULL, - dwarf2_frame_sniffer + dwarf2_frame_sniffer, + dwarf2_frame_dealloc_cache }; static const struct frame_unwind dwarf2_signal_frame_unwind = @@ -1342,7 +1422,10 @@ static const struct frame_unwind dwarf2_signal_frame_unwind = dwarf2_frame_this_id, dwarf2_frame_prev_register, NULL, - dwarf2_frame_sniffer + dwarf2_frame_sniffer, + + /* TAILCALL_CACHE can never be in such frame to need dealloc_cache. */ + NULL }; /* Append the DWARF-2 frame unwinders to GDBARCH's list. */ @@ -1350,6 +1433,10 @@ static const struct frame_unwind dwarf2_signal_frame_unwind = void dwarf2_append_unwinders (struct gdbarch *gdbarch) { + /* TAILCALL_FRAME must be first to find the record by + dwarf2_tailcall_sniffer_first. */ + frame_unwind_append_unwinder (gdbarch, &dwarf2_tailcall_frame_unwind); + frame_unwind_append_unwinder (gdbarch, &dwarf2_frame_unwind); frame_unwind_append_unwinder (gdbarch, &dwarf2_signal_frame_unwind); } @@ -1401,7 +1488,8 @@ dwarf2_frame_cfa (struct frame_info *this_frame) /* This restriction could be lifted if other unwinders are known to compute the frame base in a way compatible with the DWARF unwinder. */ - if (! frame_unwinder_is (this_frame, &dwarf2_frame_unwind)) + if (!frame_unwinder_is (this_frame, &dwarf2_frame_unwind) + && !frame_unwinder_is (this_frame, &dwarf2_tailcall_frame_unwind)) error (_("can't compute CFA for this frame")); return get_frame_base (this_frame); } diff --git a/gdb/dwarf2loc.c b/gdb/dwarf2loc.c index 980e9ab..1661d9f 100644 --- a/gdb/dwarf2loc.c +++ b/gdb/dwarf2loc.c @@ -399,6 +399,321 @@ call_site_to_target_addr (struct gdbarch *call_site_gdbarch, } } +/* Convert function entry point exact address ADDR to the function which is + compliant with TAIL_CALL_LIST_COMPLETE condition. Throw + NO_ENTRY_VALUE_ERROR otherwise. */ + +static struct symbol * +func_addr_to_tail_call_list (struct gdbarch *gdbarch, CORE_ADDR addr) +{ + struct symbol *sym = find_pc_function (addr); + struct type *type; + + if (sym == NULL || BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) != addr) + throw_error (NO_ENTRY_VALUE_ERROR, + _("DW_TAG_GNU_call_site resolving failed to find function " + "name for address %s"), + paddress (gdbarch, addr)); + + type = SYMBOL_TYPE (sym); + gdb_assert (TYPE_CODE (type) == TYPE_CODE_FUNC); + gdb_assert (TYPE_SPECIFIC_FIELD (type) == TYPE_SPECIFIC_FUNC); + + return sym; +} + +/* Print user readable form of CALL_SITE->PC to gdb_stdlog. Used only for + ENTRY_VALUES_DEBUG. */ + +static void +tailcall_dump (struct gdbarch *gdbarch, const struct call_site *call_site) +{ + CORE_ADDR addr = call_site->pc; + struct minimal_symbol *msym = lookup_minimal_symbol_by_pc (addr - 1); + + fprintf_unfiltered (gdb_stdlog, " %s(%s)", paddress (gdbarch, addr), + msym == NULL ? "???" : SYMBOL_PRINT_NAME (msym)); + +} + +/* vec.h needs single word type name, typedef it. */ +typedef struct call_site *call_sitep; + +/* Define VEC (call_sitep) functions. */ +DEF_VEC_P (call_sitep); + +/* Intersect RESULTP with CHAIN to keep RESULTP unambiguous, keep in RESULTP + only top callers and bottom callees which are present in both. GDBARCH is + used only for ENTRY_VALUES_DEBUG. RESULTP is NULL after return if there are + no remaining possibilities to provide unambiguous non-trivial result. + RESULTP should point to NULL on the first (initialization) call. Caller is + responsible for xfree of any RESULTP data. */ + +static void +chain_candidate (struct gdbarch *gdbarch, struct call_site_chain **resultp, + VEC (call_sitep) *chain) +{ + struct call_site_chain *result = *resultp; + long length = VEC_length (call_sitep, chain); + int callers, callees, idx; + + if (result == NULL) + { + /* Create the initial chain containing all the passed PCs. */ + + result = xmalloc (sizeof (*result) + sizeof (*result->call_site) + * (length - 1)); + result->length = length; + result->callers = result->callees = length; + memcpy (result->call_site, VEC_address (call_sitep, chain), + sizeof (*result->call_site) * length); + *resultp = result; + + if (entry_values_debug) + { + fprintf_unfiltered (gdb_stdlog, "tailcall: initial:"); + for (idx = 0; idx < length; idx++) + tailcall_dump (gdbarch, result->call_site[idx]); + fputc_unfiltered ('\n', gdb_stdlog); + } + + return; + } + + if (entry_values_debug) + { + fprintf_unfiltered (gdb_stdlog, "tailcall: compare:"); + for (idx = 0; idx < length; idx++) + tailcall_dump (gdbarch, VEC_index (call_sitep, chain, idx)); + fputc_unfiltered ('\n', gdb_stdlog); + } + + /* Intersect callers. */ + + callers = min (result->callers, length); + for (idx = 0; idx < callers; idx++) + if (result->call_site[idx] != VEC_index (call_sitep, chain, idx)) + { + result->callers = idx; + break; + } + + /* Intersect callees. */ + + callees = min (result->callees, length); + for (idx = 0; idx < callees; idx++) + if (result->call_site[result->length - 1 - idx] + != VEC_index (call_sitep, chain, length - 1 - idx)) + { + result->callees = idx; + break; + } + + if (entry_values_debug) + { + fprintf_unfiltered (gdb_stdlog, "tailcall: reduced:"); + for (idx = 0; idx < result->callers; idx++) + tailcall_dump (gdbarch, result->call_site[idx]); + fputs_unfiltered (" |", gdb_stdlog); + for (idx = 0; idx < result->callees; idx++) + tailcall_dump (gdbarch, result->call_site[result->length + - result->callees + idx]); + fputc_unfiltered ('\n', gdb_stdlog); + } + + if (result->callers == 0 && result->callees == 0) + { + /* There are no common callers or callees. It could be also a direct + call (which has length 0) with ambiguous possibility of an indirect + call - CALLERS == CALLEES == 0 is valid during the first allocation + but any subsequence processing of such entry means ambiguity. */ + xfree (result); + *resultp = NULL; + return; + } + + /* See call_site_find_chain_1 why there is no way to reach the bottom callee + PC again. In such case there must be two different code paths to reach + it, therefore some of the former determined intermediate PCs must differ + and the unambiguous chain gets shortened. */ + gdb_assert (result->callers + result->callees < result->length); +} + +/* Create and return call_site_chain for CALLER_PC and CALLEE_PC. All the + assumed frames between them use GDBARCH. Use depth first search so we can + keep single CHAIN of call_site's back to CALLER_PC. Function recursion + would have needless GDB stack overhead. Caller is responsible for xfree of + the returned result. Any unreliability results in thrown + NO_ENTRY_VALUE_ERROR. */ + +static struct call_site_chain * +call_site_find_chain_1 (struct gdbarch *gdbarch, CORE_ADDR caller_pc, + CORE_ADDR callee_pc) +{ + struct func_type *func_specific; + struct obstack addr_obstack; + struct cleanup *back_to_retval, *back_to_workdata; + struct call_site_chain *retval = NULL; + struct call_site *call_site; + + /* Mark CALL_SITEs so we do not visit the same ones twice. */ + htab_t addr_hash; + + /* CHAIN contains only the intermediate CALL_SITEs. Neither CALLER_PC's + call_site nor any possible call_site at CALLEE_PC's function is there. + Any CALL_SITE in CHAIN will be iterated to its siblings - via + TAIL_CALL_NEXT. This is inappropriate for CALLER_PC's call_site. */ + VEC (call_sitep) *chain = NULL; + + /* We are not interested in the specific PC inside the callee function. */ + callee_pc = get_pc_function_start (callee_pc); + if (callee_pc == 0) + throw_error (NO_ENTRY_VALUE_ERROR, _("Unable to find function for PC %s"), + paddress (gdbarch, callee_pc)); + + back_to_retval = make_cleanup (free_current_contents, &retval); + + obstack_init (&addr_obstack); + back_to_workdata = make_cleanup_obstack_free (&addr_obstack); + addr_hash = htab_create_alloc_ex (64, core_addr_hash, core_addr_eq, NULL, + &addr_obstack, hashtab_obstack_allocate, + NULL); + make_cleanup_htab_delete (addr_hash); + + make_cleanup (VEC_cleanup (call_sitep), &chain); + + /* Do not push CALL_SITE to CHAIN. Push there only the first tail call site + at the target's function. All the possible tail call sites in the + target's function will get iterated as already pushed into CHAIN via their + TAIL_CALL_NEXT. */ + call_site = call_site_for_pc (gdbarch, caller_pc); + + while (call_site) + { + CORE_ADDR target_func_addr; + struct call_site *target_call_site; + + /* CALLER_FRAME with registers is not available for tail-call jumped + frames. */ + target_func_addr = call_site_to_target_addr (gdbarch, call_site, NULL); + + if (target_func_addr == callee_pc) + { + chain_candidate (gdbarch, &retval, chain); + if (retval == NULL) + break; + + /* There is no way to reach CALLEE_PC again as we would prevent + entering it twice as being already marked in ADDR_HASH. */ + target_call_site = NULL; + } + else + { + struct symbol *target_func; + + target_func = func_addr_to_tail_call_list (gdbarch, target_func_addr); + target_call_site = TYPE_TAIL_CALL_LIST (SYMBOL_TYPE (target_func)); + } + + do + { + /* Attempt to visit TARGET_CALL_SITE. */ + + if (target_call_site) + { + void **slot; + + slot = htab_find_slot (addr_hash, &target_call_site->pc, INSERT); + if (*slot == NULL) + { + /* Successfully entered TARGET_CALL_SITE. */ + + *slot = &target_call_site->pc; + VEC_safe_push (call_sitep, chain, target_call_site); + break; + } + } + + /* Backtrack (without revisiting the originating call_site). Try the + callers's sibling; if there isn't any try the callers's callers's + sibling etc. */ + + target_call_site = NULL; + while (!VEC_empty (call_sitep, chain)) + { + call_site = VEC_pop (call_sitep, chain); + + gdb_assert (htab_find_slot (addr_hash, &call_site->pc, + NO_INSERT) != NULL); + htab_remove_elt (addr_hash, &call_site->pc); + + target_call_site = call_site->tail_call_next; + if (target_call_site) + break; + } + } + while (target_call_site); + + if (VEC_empty (call_sitep, chain)) + call_site = NULL; + else + call_site = VEC_last (call_sitep, chain); + } + + if (retval == NULL) + { + struct minimal_symbol *msym_caller, *msym_callee; + + msym_caller = lookup_minimal_symbol_by_pc (caller_pc); + msym_callee = lookup_minimal_symbol_by_pc (callee_pc); + throw_error (NO_ENTRY_VALUE_ERROR, + _("There are no unambiguously determinable intermediate " + "callers or callees between caller function \"%s\" at %s " + "and callee function \"%s\" at %s"), + (msym_caller == NULL + ? "???" : SYMBOL_PRINT_NAME (msym_caller)), + paddress (gdbarch, caller_pc), + (msym_callee == NULL + ? "???" : SYMBOL_PRINT_NAME (msym_callee)), + paddress (gdbarch, callee_pc)); + } + + do_cleanups (back_to_workdata); + discard_cleanups (back_to_retval); + return retval; +} + +/* Create and return call_site_chain for CALLER_PC and CALLEE_PC. All the + assumed frames between them use GDBARCH. If valid call_site_chain cannot be + constructed return NULL. Caller is responsible for xfree of the returned + result. */ + +struct call_site_chain * +call_site_find_chain (struct gdbarch *gdbarch, CORE_ADDR caller_pc, + CORE_ADDR callee_pc) +{ + volatile struct gdb_exception e; + struct call_site_chain *retval = NULL; + + TRY_CATCH (e, RETURN_MASK_ERROR) + { + retval = call_site_find_chain_1 (gdbarch, caller_pc, callee_pc); + } + if (e.reason < 0) + { + if (e.error == NO_ENTRY_VALUE_ERROR) + { + if (entry_values_debug) + exception_print (gdb_stdout, e); + + return NULL; + } + else + throw_exception (e); + } + return retval; +} + /* Fetch call_site_parameter from caller matching the parameters. FRAME is for callee. See DWARF_REG and FB_OFFSET description at struct dwarf_expr_context_funcs->push_dwarf_reg_entry_value. diff --git a/gdb/dwarf2loc.h b/gdb/dwarf2loc.h index 9efe196..32aa0af 100644 --- a/gdb/dwarf2loc.h +++ b/gdb/dwarf2loc.h @@ -139,4 +139,23 @@ extern void dwarf2_compile_expr_to_ax (struct agent_expr *expr, const gdb_byte *op_end, struct dwarf2_per_cu_data *per_cu); +/* Determined tail calls for constructing virtual tail call frames. */ + +struct call_site_chain + { + /* Initially CALLERS == CALLEES == LENGTH. For partially ambiguous result + CALLERS + CALLEES < LENGTH. */ + int callers, callees, length; + + /* Variably sized array with LENGTH elements. Later [0..CALLERS-1] contain + top (GDB "prev") sites and [LENGTH-CALLEES..LENGTH-1] contain bottom + (GDB "next") sites. One is interested primarily in the PC field. */ + struct call_site *call_site[1]; + }; + +struct call_site_stuff; +extern struct call_site_chain *call_site_find_chain (struct gdbarch *gdbarch, + CORE_ADDR caller_pc, + CORE_ADDR callee_pc); + #endif /* dwarf2loc.h */ diff --git a/gdb/frame.c b/gdb/frame.c index fc581fb..5824020 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2035,8 +2035,10 @@ get_frame_address_in_block (struct frame_info *this_frame) while (get_frame_type (next_frame) == INLINE_FRAME) next_frame = next_frame->next; - if (get_frame_type (next_frame) == NORMAL_FRAME + if ((get_frame_type (next_frame) == NORMAL_FRAME + || get_frame_type (next_frame) == TAILCALL_FRAME) && (get_frame_type (this_frame) == NORMAL_FRAME + || get_frame_type (this_frame) == TAILCALL_FRAME || get_frame_type (this_frame) == INLINE_FRAME)) return pc - 1; diff --git a/gdb/frame.h b/gdb/frame.h index a2052c0..6a1e711 100644 --- a/gdb/frame.h +++ b/gdb/frame.h @@ -206,6 +206,8 @@ enum frame_type /* A frame representing an inlined function, associated with an upcoming (prev, outer, older) NORMAL_FRAME. */ INLINE_FRAME, + /* A virtual frame of a tail call - see dwarf2_tailcall_frame_unwind. */ + TAILCALL_FRAME, /* In a signal handler, various OSs handle this in various ways. The main thing is that the frame may be far from normal. */ SIGTRAMP_FRAME, diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c index 9143367..75aa44e 100644 --- a/gdb/python/py-frame.c +++ b/gdb/python/py-frame.c @@ -595,6 +595,7 @@ gdbpy_initialize_frames (void) PyModule_AddIntConstant (gdb_module, "NORMAL_FRAME", NORMAL_FRAME); PyModule_AddIntConstant (gdb_module, "DUMMY_FRAME", DUMMY_FRAME); PyModule_AddIntConstant (gdb_module, "INLINE_FRAME", INLINE_FRAME); + PyModule_AddIntConstant (gdb_module, "TAILCALL_FRAME", TAILCALL_FRAME); PyModule_AddIntConstant (gdb_module, "SIGTRAMP_FRAME", SIGTRAMP_FRAME); PyModule_AddIntConstant (gdb_module, "ARCH_FRAME", ARCH_FRAME); PyModule_AddIntConstant (gdb_module, "SENTINEL_FRAME", SENTINEL_FRAME); diff --git a/gdb/stack.c b/gdb/stack.c index 15666ee..cf15512 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -1086,6 +1086,8 @@ frame_info (char *addr_exp, int from_tty) printf_filtered (_(" Outermost frame: %s\n"), frame_stop_reason_string (reason)); } + else if (get_frame_type (fi) == TAILCALL_FRAME) + puts_filtered (" tail call frame"); else if (get_frame_type (fi) == INLINE_FRAME) printf_filtered (" inlined into frame %d", frame_relative_level (get_prev_frame (fi))); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 52e544e..ce9800c 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,16 @@ 2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com> + Recognize virtual tail call frames. + * gdb.arch/amd64-entry-value.cc (c, a, b, amb_z, amb_y, amb_x, amb) + (amb_b, amb_a): New. + (main): Call a and b. + * gdb.arch/amd64-entry-value.exp (tailcall: breakhere, tailcall: bt) + (tailcall: p i, tailcall: p j, set $sp0=$sp, up, p $sp0 == $sp, frame 3) + (p $sp0 + sizeof (void *) == $sp, ambiguous: breakhere, ambiguous: bt): + New tests. + +2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com> + Implement basic support for DW_TAG_GNU_call_site. * gdb.arch/Makefile.in (EXECUTABLES): Add amd64-entry-value. * gdb.arch/amd64-entry-value.cc: New file. diff --git a/gdb/testsuite/gdb.arch/amd64-entry-value.cc b/gdb/testsuite/gdb.arch/amd64-entry-value.cc index b492231..9e09c8a 100644 --- a/gdb/testsuite/gdb.arch/amd64-entry-value.cc +++ b/gdb/testsuite/gdb.arch/amd64-entry-value.cc @@ -34,9 +34,71 @@ asm ("breakhere:"); e (v, v); } +static void __attribute__((noinline, noclone)) +c (int i, double j) +{ + d (i * 10, j * 10); +} + +static void __attribute__((noinline, noclone)) +a (int i, double j) +{ + c (i + 1, j + 1); +} + +static void __attribute__((noinline, noclone)) +b (int i, double j) +{ + c (i + 2, j + 2); +} + +static void __attribute__((noinline, noclone)) +amb_z (int i) +{ + d (i + 7, i + 7.5); +} + +static void __attribute__((noinline, noclone)) +amb_y (int i) +{ + amb_z (i + 6); +} + +static void __attribute__((noinline, noclone)) +amb_x (int i) +{ + amb_y (i + 5); +} + +static void __attribute__((noinline, noclone)) +amb (int i) +{ + if (i < 0) + amb_x (i + 3); + else + amb_x (i + 4); +} + +static void __attribute__((noinline, noclone)) +amb_b (int i) +{ + amb (i + 2); +} + +static void __attribute__((noinline, noclone)) +amb_a (int i) +{ + amb_b (i + 1); +} + int main () { d (30, 30.5); + if (v) + a (1, 1.25); + else + b (5, 5.25); + amb_a (100); return 0; } diff --git a/gdb/testsuite/gdb.arch/amd64-entry-value.exp b/gdb/testsuite/gdb.arch/amd64-entry-value.exp index e73254b..6abc7ab 100644 --- a/gdb/testsuite/gdb.arch/amd64-entry-value.exp +++ b/gdb/testsuite/gdb.arch/amd64-entry-value.exp @@ -45,3 +45,31 @@ gdb_test "bt" "^bt\r\n#0 +d *\\(i=31, j=31\\.5\\) \[^\r\n\]*\r\n#1 +0x\[0-9a-f\] "entry: bt" gdb_test "p i" " = 31" "entry: p i" gdb_test "p j" { = 31\.5} "entry: p j" + + +# Test virtual tail call frames. + +gdb_continue_to_breakpoint "tailcall: breakhere" + +gdb_test "bt" "^bt\r\n#0 +d *\\(i=71, j=73\\.5\\) \[^\r\n\]*\r\n#1 +0x\[0-9a-f\]+ in c \\(i=7, j=7\\.25\\) \[^\r\n\]*\r\n#2 +0x\[0-9a-f\]+ in b \\(i=5, j=5\\.25\\) \[^\r\n\]*\r\n#3 +0x\[0-9a-f\]+ in main \[^\r\n\]*" \ + "tailcall: bt" +gdb_test "p i" " = 71" "tailcall: p i" +gdb_test "p j" " = 73\\.5" "tailcall: p j" + +# Test $sp simulation for tail call frames. +#gdb_test {p/x $sp} " = 0x.*" +#gdb_test {p/x $pc} " = 0x.*" +gdb_test_no_output {set $sp0=$sp} +gdb_test "up" "\r\n#1 .*" +#gdb_test {p/x $sp} " = 0x.*" +gdb_test {p $sp0 == $sp} " = true" +gdb_test "frame 3" "\r\n#3 .*" +gdb_test {p $sp0 + sizeof (void *) == $sp} " = true" + + +# Test partial-ambiguous virtual tail call frames chain. + +gdb_continue_to_breakpoint "ambiguous: breakhere" + +gdb_test "bt" "^bt\r\n#0 +d \\(i=<optimized out>, j=<optimized out>\\)\[^\r\n\]*\r\n#1 +0x\[0-9a-f\]+ in amb_z \\(i=<optimized out>\\)\[^\r\n\]*\r\n#2 +0x\[0-9a-f\]+ in amb_y \\(i=<optimized out>\\)\[^\r\n\]*\r\n#3 +0x\[0-9a-f\]+ in amb_x \\(i=<optimized out>\\)\[^\r\n\]*\r\n#4 +0x\[0-9a-f\]+ in amb_b \\(i=101\\)\[^\r\n\]*\r\n#5 +0x\[0-9a-f\]+ in amb_a \\(i=100\\)\[^\r\n\]*\r\n#6 +0x\[0-9a-f\]+ in main \\(\\)\[^\r\n\]*" \ + "ambiguous: bt" |