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.
This commit is contained in:
Jan Kratochvil 2011-10-09 19:26:44 +00:00
parent bb984ff154
commit 111c64899c
14 changed files with 709 additions and 12 deletions

View File

@ -1,3 +1,37 @@
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.

View File

@ -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 \

View File

@ -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

View File

@ -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.

View File

@ -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);
}

View File

@ -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.

View File

@ -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 */

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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)));

View File

@ -1,3 +1,14 @@
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.

View File

@ -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;
}

View File

@ -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"