diff options
Diffstat (limited to 'gdb/dwarf2-frame-tailcall.c')
-rw-r--r-- | gdb/dwarf2-frame-tailcall.c | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/gdb/dwarf2-frame-tailcall.c b/gdb/dwarf2-frame-tailcall.c new file mode 100644 index 0000000..3813115 --- /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 <http://www.gnu.org/licenses/>. */ + +#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); +} |