| /* Virtual tail call frames unwinder for GDB. |
| |
| Copyright (C) 2010-2012 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" |
| #include "dwarf2-frame.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; |
| |
| 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 = dwarf2_frame_cfa (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; |
| struct gdbarch *prev_gdbarch; |
| struct call_site_chain *chain = NULL; |
| 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 sp_regnum; |
| |
| prev_gdbarch = frame_unwind_arch (this_frame); |
| |
| /* Simulate frame_unwind_pc without setting this_frame->prev_pc.p. */ |
| prev_pc = gdbarch_unwind_pc (prev_gdbarch, this_frame); |
| |
| /* 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); |
| } |