From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 2405 invoked by alias); 29 Sep 2011 19:50:03 -0000 Received: (qmail 2318 invoked by uid 22791); 29 Sep 2011 19:49:52 -0000 X-SWARE-Spam-Status: No, hits=-2.5 required=5.0 tests=AWL,BAYES_50,KAM_STOCKTIP,RCVD_IN_DNSWL_HI,RP_MATCHES_RCVD,SPF_HELO_PASS,TW_BJ,TW_CP,TW_QU,TW_XZ X-Spam-Check-By: sourceware.org Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Thu, 29 Sep 2011 19:49:30 +0000 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id p8TJnUL4004262 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Thu, 29 Sep 2011 15:49:30 -0400 Received: from host1.jankratochvil.net (ovpn-116-16.ams2.redhat.com [10.36.116.16]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id p8TJnQS7027065 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Thu, 29 Sep 2011 15:49:28 -0400 Received: from host1.jankratochvil.net (localhost [127.0.0.1]) by host1.jankratochvil.net (8.14.4/8.14.4) with ESMTP id p8TJnPOQ027666 for ; Thu, 29 Sep 2011 21:49:25 +0200 Received: (from jkratoch@localhost) by host1.jankratochvil.net (8.14.4/8.14.4/Submit) id p8TJnPqu027665 for gdb-patches@sourceware.org; Thu, 29 Sep 2011 21:49:25 +0200 Date: Thu, 29 Sep 2011 19:50:00 -0000 From: Jan Kratochvil To: gdb-patches@sourceware.org Subject: [patch 04/12] entryval#3: Virtual tail call frames Message-ID: <20110929194925.GE26579@host1.jankratochvil.net> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2011-09/txt/msg00557.txt.bz2 Hi, [patch 04/12+doc] entryval#2: Virtual tail call frames http://sourceware.org/ml/gdb-patches/2011-09/msg00226.html doc updates by Eli, new examples in the doc, more output on `set debug entry-values', move the debugging output to earlier patch part. Thanks, Jan gdb/ 2011-09-13 Jan Kratochvil 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/ 2011-07-18 Jan Kratochvil Eli Zaretskii 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/ 2011-07-18 Jan Kratochvil 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. --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -695,6 +695,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 \ @@ -771,7 +772,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 \ @@ -879,7 +880,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 \ --- 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 : jmp 0x400640 +(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 @@ -23080,6 +23201,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. --- /dev/null +++ b/gdb/dwarf2-frame-tailcall.c @@ -0,0 +1,479 @@ +/* Virtual tail call frames unwinder for GDB. + + Copyright (C) 2010, 2011 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 3 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, see . */ + +#include "defs.h" +#include "gdb_assert.h" +#include "frame.h" +#include "dwarf2-frame-tailcall.h" +#include "dwarf2loc.h" +#include "frame-unwind.h" +#include "block.h" +#include "hashtab.h" +#include "exceptions.h" +#include "gdbtypes.h" +#include "regcache.h" +#include "value.h" + +/* Contains struct tailcall_cache indexed by next_bottom_frame. */ +static htab_t cache_htab; + +/* Associate structure of the unwinder to call_site_chain. Lifetime of this + structure is maintained by REFC decremented by dealloc_cache, all of them + get deleted during reinit_frame_cache. */ +struct tailcall_cache +{ + /* It must be the first one of this struct. It is the furthest callee. */ + struct frame_info *next_bottom_frame; + + /* Reference count. The whole chain of virtual tail call frames shares one + tailcall_cache. */ + int refc; + + /* Associated found virtual taill call frames chain, it is never NULL. */ + struct call_site_chain *chain; + + /* Cached pretended_chain_levels result. */ + int chain_levels; + + /* Unwound PC from the top (caller) frame, as it is not contained + in CHAIN. */ + CORE_ADDR prev_pc; + + /* Compensate SP in caller frames appropriately. prev_sp and + entry_cfa_sp_offset are valid only if PREV_SP_P. PREV_SP is SP at the top + (caller) frame. ENTRY_CFA_SP_OFFSET is shift of SP in tail call frames + against next_bottom_frame SP. */ + unsigned prev_sp_p : 1; + CORE_ADDR prev_sp; + LONGEST entry_cfa_sp_offset; +}; + +/* hash_f for htab_create_alloc of cache_htab. */ + +static hashval_t +cache_hash (const void *arg) +{ + const struct tailcall_cache *cache = arg; + + return htab_hash_pointer (cache->next_bottom_frame); +} + +/* eq_f for htab_create_alloc of cache_htab. */ + +static int +cache_eq (const void *arg1, const void *arg2) +{ + const struct tailcall_cache *cache1 = arg1; + const struct tailcall_cache *cache2 = arg2; + + return cache1->next_bottom_frame == cache2->next_bottom_frame; +} + +/* Create new tailcall_cache for NEXT_BOTTOM_FRAME, NEXT_BOTTOM_FRAME must not + yet have been indexed by cache_htab. Caller holds one reference of the new + tailcall_cache. */ + +static struct tailcall_cache * +cache_new_ref1 (struct frame_info *next_bottom_frame) +{ + struct tailcall_cache *cache; + void **slot; + + cache = xzalloc (sizeof (*cache)); + + cache->next_bottom_frame = next_bottom_frame; + cache->refc = 1; + + slot = htab_find_slot (cache_htab, cache, INSERT); + gdb_assert (*slot == NULL); + *slot = cache; + + return cache; +} + +/* Create new reference to CACHE. */ + +static void +cache_ref (struct tailcall_cache *cache) +{ + gdb_assert (cache->refc > 0); + + cache->refc++; +} + +/* Drop reference to CACHE, possibly fully freeing it and unregistering it from + cache_htab. */ + +static void +cache_unref (struct tailcall_cache *cache) +{ + gdb_assert (cache->refc > 0); + + if (!--cache->refc) + { + gdb_assert (htab_find_slot (cache_htab, cache, NO_INSERT) != NULL); + htab_remove_elt (cache_htab, cache); + + xfree (cache->chain); + xfree (cache); + } +} + +/* Return 1 if FI is a non-bottom (not the callee) tail call frame. Otherwise + return 0. */ + +static int +frame_is_tailcall (struct frame_info *fi) +{ + return frame_unwinder_is (fi, &dwarf2_tailcall_frame_unwind); +} + +/* Try to find tailcall_cache in cache_htab if FI is a part of its virtual tail + call chain. Otherwise return NULL. No new reference is created. */ + +static struct tailcall_cache * +cache_find (struct frame_info *fi) +{ + struct tailcall_cache *cache; + void **slot; + + while (frame_is_tailcall (fi)) + { + fi = get_next_frame (fi); + gdb_assert (fi != NULL); + } + + slot = htab_find_slot (cache_htab, &fi, NO_INSERT); + if (slot == NULL) + return NULL; + + cache = *slot; + gdb_assert (cache != NULL); + return cache; +} + +/* Number of virtual frames between THIS_FRAME and CACHE->NEXT_BOTTOM_FRAME. + If THIS_FRAME is CACHE-> NEXT_BOTTOM_FRAME return -1. */ + +static int +existing_next_levels (struct frame_info *this_frame, + struct tailcall_cache *cache) +{ + int retval = (frame_relative_level (this_frame) + - frame_relative_level (cache->next_bottom_frame) - 1); + + gdb_assert (retval >= -1); + + return retval; +} + +/* The number of virtual tail call frames in CHAIN. With no virtual tail call + frames the function would return 0 (but CHAIN does not exist in such + case). */ + +static int +pretended_chain_levels (struct call_site_chain *chain) +{ + int chain_levels; + + gdb_assert (chain != NULL); + + if (chain->callers == chain->length && chain->callees == chain->length) + return chain->length; + + chain_levels = chain->callers + chain->callees; + gdb_assert (chain_levels < chain->length); + + return chain_levels; +} + +/* Implementation of frame_this_id_ftype. THIS_CACHE must be already + initialized with tailcall_cache, THIS_FRAME must be a part of THIS_CACHE. + + Specific virtual tail call frames are tracked by INLINE_DEPTH. */ + +static void +tailcall_frame_this_id (struct frame_info *this_frame, void **this_cache, + struct frame_id *this_id) +{ + struct tailcall_cache *cache = *this_cache; + struct frame_info *next_frame; + + /* Tail call does not make sense for a sentinel frame. */ + next_frame = get_next_frame (this_frame); + gdb_assert (next_frame != NULL); + + *this_id = get_frame_id (next_frame); + (*this_id).code_addr = get_frame_pc (this_frame); + (*this_id).code_addr_p = 1; + (*this_id).inline_depth = (cache->chain_levels + - existing_next_levels (this_frame, cache)); + gdb_assert ((*this_id).inline_depth > 0); +} + +/* Find PC to be unwound from THIS_FRAME. THIS_FRAME must be a part of + CACHE. */ + +static CORE_ADDR +pretend_pc (struct frame_info *this_frame, struct tailcall_cache *cache) +{ + int next_levels = existing_next_levels (this_frame, cache); + struct call_site_chain *chain = cache->chain; + int caller_no; + + gdb_assert (chain != NULL); + + next_levels++; + gdb_assert (next_levels >= 0); + + if (next_levels < chain->callees) + return chain->call_site[chain->length - next_levels - 1]->pc; + next_levels -= chain->callees; + + /* Otherwise CHAIN->CALLEES are already covered by CHAIN->CALLERS. */ + if (chain->callees != chain->length) + { + if (next_levels < chain->callers) + return chain->call_site[chain->callers - next_levels - 1]->pc; + next_levels -= chain->callers; + } + + gdb_assert (next_levels == 0); + return cache->prev_pc; +} + +/* Implementation of frame_prev_register_ftype. If no specific register + override is supplied NULL is returned (this is incompatible with + frame_prev_register_ftype semantics). next_bottom_frame and tail call + frames unwind the NULL case differently. */ + +struct value * +dwarf2_tailcall_prev_register_first (struct frame_info *this_frame, + void **tailcall_cachep, int regnum) +{ + struct gdbarch *this_gdbarch = get_frame_arch (this_frame); + struct tailcall_cache *cache = *tailcall_cachep; + CORE_ADDR addr; + + if (regnum == gdbarch_pc_regnum (this_gdbarch)) + addr = pretend_pc (this_frame, cache); + else if (cache->prev_sp_p && regnum == gdbarch_sp_regnum (this_gdbarch)) + { + int next_levels = existing_next_levels (this_frame, cache); + + if (next_levels == cache->chain_levels - 1) + addr = cache->prev_sp; + else + addr = get_frame_base (this_frame) - cache->entry_cfa_sp_offset; + } + else + return NULL; + + return frame_unwind_got_address (this_frame, regnum, addr); +} + +/* Implementation of frame_prev_register_ftype for tail call frames. Register + set of virtual tail call frames is assumed to be the one of the top (caller) + frame - assume unchanged register value for NULL from + dwarf2_tailcall_prev_register_first. */ + +static struct value * +tailcall_frame_prev_register (struct frame_info *this_frame, + void **this_cache, int regnum) +{ + struct tailcall_cache *cache = *this_cache; + struct value *val; + + gdb_assert (this_frame != cache->next_bottom_frame); + + val = dwarf2_tailcall_prev_register_first (this_frame, this_cache, regnum); + if (val) + return val; + + return frame_unwind_got_register (this_frame, regnum, regnum); +} + +/* Implementation of frame_sniffer_ftype. It will never find a new chain, use + dwarf2_tailcall_sniffer_first for the bottom (callee) frame. It will find + all the predecessing virtual tail call frames, it will return false when + there exist no more tail call frames in this chain. */ + +static int +tailcall_frame_sniffer (const struct frame_unwind *self, + struct frame_info *this_frame, void **this_cache) +{ + struct frame_info *next_frame; + int next_levels; + struct tailcall_cache *cache; + + /* Inner tail call element does not make sense for a sentinel frame. */ + next_frame = get_next_frame (this_frame); + if (next_frame == NULL) + return 0; + + cache = cache_find (next_frame); + if (cache == NULL) + return 0; + + cache_ref (cache); + + next_levels = existing_next_levels (this_frame, cache); + + /* NEXT_LEVELS is -1 only in dwarf2_tailcall_sniffer_first. */ + gdb_assert (next_levels >= 0); + gdb_assert (next_levels <= cache->chain_levels); + + if (next_levels == cache->chain_levels) + { + cache_unref (cache); + return 0; + } + + *this_cache = cache; + return 1; +} + +/* The initial "sniffer" whether THIS_FRAME is a bottom (callee) frame of a new + chain to create. Keep TAILCALL_CACHEP NULL if it did not find any chain, + initialize it otherwise. No tail call chain is created if there are no + unambiguous virtual tail call frames to report. + + ENTRY_CFA_SP_OFFSETP is NULL if no special SP handling is possible, + otherwise *ENTRY_CFA_SP_OFFSETP is the number of bytes to subtract from tail + call frames frame base to get the SP value there - to simulate return + address pushed on the stack. */ + +void +dwarf2_tailcall_sniffer_first (struct frame_info *this_frame, + void **tailcall_cachep, + const LONGEST *entry_cfa_sp_offsetp) +{ + CORE_ADDR prev_pc = 0, prev_sp = 0; /* GCC warning. */ + int prev_sp_p = 0; + CORE_ADDR this_pc, pc; + struct gdbarch *prev_gdbarch; + struct call_site_chain *chain = NULL; + struct frame_info *fi; + struct tailcall_cache *cache; + volatile struct gdb_exception except; + + gdb_assert (*tailcall_cachep == NULL); + + this_pc = get_frame_pc (this_frame); + + /* Catch any unwinding errors. */ + TRY_CATCH (except, RETURN_MASK_ERROR) + { + int pc_regnum, sp_regnum; + + prev_gdbarch = frame_unwind_arch (this_frame); + pc_regnum = gdbarch_pc_regnum (prev_gdbarch); + if (pc_regnum == -1) + break; + + /* Simulate frame_unwind_pc without setting this_frame->prev_pc.p. */ + prev_pc = frame_unwind_register_unsigned (this_frame, pc_regnum); + + /* call_site_find_chain can throw an exception. */ + chain = call_site_find_chain (prev_gdbarch, prev_pc, this_pc); + + if (entry_cfa_sp_offsetp == NULL) + break; + sp_regnum = gdbarch_sp_regnum (prev_gdbarch); + if (sp_regnum == -1) + break; + prev_sp = frame_unwind_register_unsigned (this_frame, sp_regnum); + prev_sp_p = 1; + } + if (except.reason < 0) + { + if (entry_values_debug) + exception_print (gdb_stdout, except); + return; + } + + /* Ambiguous unwind or unambiguous unwind verified as matching. */ + if (chain == NULL || chain->length == 0) + { + xfree (chain); + return; + } + + cache = cache_new_ref1 (this_frame); + *tailcall_cachep = cache; + cache->chain = chain; + cache->prev_pc = prev_pc; + cache->chain_levels = pretended_chain_levels (chain); + cache->prev_sp_p = prev_sp_p; + if (cache->prev_sp_p) + { + cache->prev_sp = prev_sp; + cache->entry_cfa_sp_offset = *entry_cfa_sp_offsetp; + } + gdb_assert (cache->chain_levels > 0); +} + +/* Implementation of frame_dealloc_cache_ftype. It can be called even for the + bottom chain frame from dwarf2_frame_dealloc_cache which is not a real + TAILCALL_FRAME. */ + +static void +tailcall_frame_dealloc_cache (struct frame_info *self, void *this_cache) +{ + struct tailcall_cache *cache = this_cache; + + cache_unref (cache); +} + +/* Implementation of frame_prev_arch_ftype. We assume all the virtual tail + call frames have gdbarch of the bottom (callee) frame. */ + +static struct gdbarch * +tailcall_frame_prev_arch (struct frame_info *this_frame, + void **this_prologue_cache) +{ + struct tailcall_cache *cache = *this_prologue_cache; + + return get_frame_arch (cache->next_bottom_frame); +} + +/* Virtual tail call frame unwinder if dwarf2_tailcall_sniffer_first finds + a chain to create. */ + +const struct frame_unwind dwarf2_tailcall_frame_unwind = +{ + TAILCALL_FRAME, + default_frame_unwind_stop_reason, + tailcall_frame_this_id, + tailcall_frame_prev_register, + NULL, + tailcall_frame_sniffer, + tailcall_frame_dealloc_cache, + tailcall_frame_prev_arch +}; + +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_tailcall_frame; + +void +_initialize_tailcall_frame (void) +{ + cache_htab = htab_create_alloc (50, cache_hash, cache_eq, NULL, xcalloc, + xfree); +} --- /dev/null +++ b/gdb/dwarf2-frame-tailcall.h @@ -0,0 +1,39 @@ +/* Definitions for virtual tail call frames unwinder for GDB. + + Copyright (C) 2010, 2011 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 3 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, see . */ + +#ifndef DWARF2_FRAME_TAILCALL_H +#define DWARF2_FRAME_TAILCALL_H 1 + +struct frame_info; +struct frame_unwind; + +/* The tail call frame unwinder. */ + +extern void + dwarf2_tailcall_sniffer_first (struct frame_info *this_frame, + void **tailcall_cachep, + const LONGEST *entry_cfa_sp_offsetp); + +extern struct value * + dwarf2_tailcall_prev_register_first (struct frame_info *this_frame, + void **tailcall_cachep, int regnum); + +extern const struct frame_unwind dwarf2_tailcall_frame_unwind; + +#endif /* !DWARF2_FRAME_TAILCALL_H */ --- 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; @@ -398,7 +399,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) @@ -681,9 +686,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; } @@ -975,6 +985,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 * @@ -988,6 +1005,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; @@ -1039,8 +1060,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) @@ -1181,6 +1219,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; } @@ -1226,6 +1270,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: @@ -1295,6 +1355,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) @@ -1321,7 +1393,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 = @@ -1331,7 +1410,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 = @@ -1341,7 +1421,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. */ @@ -1349,6 +1432,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); } @@ -1400,7 +1487,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); } --- 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. --- a/gdb/dwarf2loc.h +++ b/gdb/dwarf2loc.h @@ -135,4 +135,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 */ --- 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; --- 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, --- 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); --- 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))); --- 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; } --- 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=, j=\\)\[^\r\n\]*\r\n#1 +0x\[0-9a-f\]+ in amb_z \\(i=\\)\[^\r\n\]*\r\n#2 +0x\[0-9a-f\]+ in amb_y \\(i=\\)\[^\r\n\]*\r\n#3 +0x\[0-9a-f\]+ in amb_x \\(i=\\)\[^\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"