/*--------------------------------------------------------------------*/ /*--- Callgrind ---*/ /*--- ct_callstack.c ---*/ /*--------------------------------------------------------------------*/ /* This file is part of Callgrind, a Valgrind tool for call tracing. Copyright (C) 2002-2004, Josef Weidendorfer (Josef.Weidendorfer@gmx.de) 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. The GNU General Public License is contained in the file COPYING. */ #include "ct_include.h" /*------------------------------------------------------------*/ /*--- Call stack, operations ---*/ /*------------------------------------------------------------*/ /* Stack of current thread. Gets initialized when switching to 1st thread. * * The artificial call stack is an array of call_entry's, representing * stack frames of the executing program. * Array call_stack and call_stack_esp have same size and grow on demand. * Array call_stack_esp holds ESPs of corresponding stack frames. * */ #define N_CALL_STACK_INITIAL_ENTRIES 500 call_stack SK_(current_call_stack); void SK_(init_call_stack)(call_stack* s) { Int i; CT_ASSERT(s != 0); s->size = N_CALL_STACK_INITIAL_ENTRIES; s->entry = (call_entry*) VG_(malloc)(s->size * sizeof(call_entry)); s->sp = 0; s->entry[0].cxt = 0; /* for assertion in push_cxt() */ for(i=0; isize; i++) s->entry[i].enter_cost = 0; } call_entry* SK_(get_call_entry)(Int sp) { CT_ASSERT(sp <= SK_(current_call_stack).sp); return &(SK_(current_call_stack).entry[sp]); } void SK_(copy_current_call_stack)(call_stack* dst) { CT_ASSERT(dst != 0); dst->size = SK_(current_call_stack).size; dst->entry = SK_(current_call_stack).entry; dst->sp = SK_(current_call_stack).sp; } void SK_(set_current_call_stack)(call_stack* s) { CT_ASSERT(s != 0); SK_(current_call_stack).size = s->size; SK_(current_call_stack).entry = s->entry; SK_(current_call_stack).sp = s->sp; } static __inline__ void ensure_stack_size(Int i) { Int oldsize; call_stack *cs = &SK_(current_call_stack); if (i < cs->size) return; oldsize = cs->size; cs->size *= 2; while (i > cs->size) cs->size *= 2; cs->entry = (call_entry*) VG_(realloc)(cs->entry, cs->size * sizeof(call_entry)); for(i=oldsize; isize; i++) cs->entry[i].enter_cost = 0; SK_(stat).call_stack_resizes++; CT_DEBUGIF(2) VG_(printf)(" call stack enlarged to %d entries\n", SK_(current_call_stack).size); } /* Called when function entered nonrecursive */ static void function_entered(fn_node* fn, BBCC* to) { CT_ASSERT(fn != 0); #if CT_ENABLE_DEBUG if (fn->verbosity >=0) { Int old = SK_(clo).verbose; SK_(clo).verbose = fn->verbosity; fn->verbosity = old; VG_(message)(Vg_DebugMsg, "Entering %s: Verbosity set to %d", fn->name, SK_(clo).verbose); } #endif if (fn->dump_before) { Char trigger[FN_NAME_LEN]; VG_(sprintf)(trigger, "--dump-before=%s", fn->name); SK_(dump_profile)(trigger, True); } else if (fn->zero_before) { SK_(zero_all_cost)(True); } if (fn->toggle_collect) { SK_(current_state).collect = !SK_(current_state).collect; CT_DEBUG(2," entering %s: toggled collection state to %s\n", fn->name, SK_(current_state).collect ? "ON" : "OFF"); } /* FIXME: Use Bit in fn_node struct + generalize */ if (SK_(clo).collect_alloc && (VG_(strcmp)(fn->name, "malloc")==0)) { Int o = SK_(sets).off_full_user; Int* sp = (Int*) VG_(get_stack_pointer)(); Int v1 = sp ? sp[1]:0; CT_DEBUG(3," entering malloc (cost at %d, sp %x, [1]=%x)\n", o, sp, v1); if (o>0) { SK_(current_state).cost[o] ++; SK_(current_state).cost[o+1] += v1; if (!to->skipped) SK_(init_cost_lz)(SK_(sets).full, &(to->skipped)); to->skipped[o] ++; to->skipped[o+1] += v1; } } } /* Called when function left (no recursive level active) */ static void function_left(fn_node* fn) { CT_ASSERT(fn != 0); if (fn->dump_after) { Char trigger[FN_NAME_LEN]; VG_(sprintf)(trigger, "--dump-after=%s", fn->name); SK_(dump_profile)(trigger, True); } if (fn->toggle_collect) { SK_(current_state).collect = !SK_(current_state).collect; CT_DEBUG(2," leaving %s: toggled collection state to %s\n", fn->name, SK_(current_state).collect ? "ON" : "OFF"); } #if CT_ENABLE_DEBUG if (fn->verbosity >=0) { Int old = SK_(clo).verbose; SK_(clo).verbose = fn->verbosity; fn->verbosity = old; VG_(message)(Vg_DebugMsg, "Leaving %s: Verbosity set back to %d", fn->name, SK_(clo).verbose); } #endif } /* Push call on call stack. * * Increment the usage count for the function called. * A jump from to , with . * If is true, this is a call to a function to be skipped; * for this, we set jcc = 0. */ void SK_(push_call_stack)(BBCC* from, BBCC* to, Addr esp, Bool skip) { jCC* jcc; UInt* pdepth; call_entry* current_entry; /* Ensure a call stack of size +1. * The +1 is needed as push_cxt will store the * context at [current_sp] */ ensure_stack_size(SK_(current_call_stack).sp +1); current_entry = &(SK_(current_call_stack).entry[SK_(current_call_stack).sp]); if (skip) { jcc = 0; } else { fn_node* to_fn = to->cxt->fn[0]; if (SK_(current_state).nonskipped) { /* this is a jmp from skipped to nonskipped */ CT_ASSERT(SK_(current_state).nonskipped == from); } /* As push_cxt() has to be called before push_call_stack if not * skipping, the old context should already be saved on the stack */ CT_ASSERT(current_entry->cxt != 0); SK_(copy_cost_lz)( SK_(sets).full, &(current_entry->enter_cost), SK_(current_state).cost ); jcc = SK_(get_jcc)(from, to); CT_ASSERT(jcc != 0); pdepth = SK_(get_fn_entry)(to_fn->number); if (SK_(clo).skip_direct_recursion) { /* only increment depth if another function is called */ if (jcc->from->cxt->fn[0] != to_fn) (*pdepth)++; } else (*pdepth)++; if (*pdepth>1) SK_(stat).rec_call_counter++; jcc->call_counter++; SK_(stat).call_counter++; if (*pdepth == 1) function_entered(to_fn, to); } /* put jcc on call stack */ current_entry->jcc = jcc; current_entry->esp = esp; current_entry->nonskipped = SK_(current_state).nonskipped; SK_(current_call_stack).sp++; /* To allow for above assertion we set context of next frame to 0 */ CT_ASSERT(SK_(current_call_stack).sp < SK_(current_call_stack).size); current_entry++; current_entry->cxt = 0; if (!skip) SK_(current_state).nonskipped = 0; else if (!SK_(current_state).nonskipped) { /* a call from nonskipped to skipped */ SK_(current_state).nonskipped = from; if (!SK_(current_state).nonskipped->skipped) { SK_(init_cost_lz)( SK_(sets).full, &SK_(current_state).nonskipped->skipped); SK_(stat).distinct_skips++; } } #if CT_ENABLE_DEBUG CT_DEBUGIF(1) { if (SK_(clo).verbose<4) { VG_(printf)("+ %2d ", SK_(current_call_stack).sp); SK_(print_short_jcc)(jcc); VG_(printf)(", ESP %x\n", esp); } else { VG_(printf)(" Pushed "); SK_(print_stackentry)(3, SK_(current_call_stack).sp-1); } } #endif } /* Pop call stack and update inclusive sums. * Returns modified fcc. * * If the JCC becomes inactive, call entries are freed if possible */ void SK_(pop_call_stack)() { jCC* jcc; Int depth = 0; call_entry* lower_entry = &(SK_(current_call_stack).entry[SK_(current_call_stack).sp-1]); CT_DEBUG(4,"+ pop_call_stack: frame %d, jcc 0x%p\n", SK_(current_call_stack).sp, lower_entry->jcc); /* jCC item not any more on real stack: pop */ jcc = lower_entry->jcc; SK_(current_state).nonskipped = lower_entry->nonskipped; if (jcc) { fn_node* to_fn = jcc->to->cxt->fn[0]; UInt* pdepth = SK_(get_fn_entry)(to_fn->number); if (SK_(clo).skip_direct_recursion) { /* only decrement depth if another function was called */ if (jcc->from->cxt->fn[0] != to_fn) (*pdepth)--; } else (*pdepth)--; depth = *pdepth; /* add cost difference to sum */ if ( SK_(add_diff_cost_lz)( SK_(sets).full, &(jcc->cost), lower_entry->enter_cost, SK_(current_state).cost) ) { /* only count this call if it attributed some cost. * the ret_counter is used to check if a BBCC dump is needed. */ jcc->from->ret_counter++; } SK_(stat).ret_counter++; /* restore context */ SK_(current_state).cxt = lower_entry->cxt; SK_(current_fn_stack).top = SK_(current_fn_stack).bottom + lower_entry->fn_sp; CT_ASSERT(SK_(current_state).cxt != 0); if (depth == 0) function_left(to_fn); } /* To allow for an assertion in push_call_stack() */ lower_entry->cxt = 0; SK_(current_call_stack).sp--; #if CT_ENABLE_DEBUG CT_DEBUGIF(1) { if (SK_(clo).verbose<4) { if (jcc) { /* popped JCC target first */ VG_(printf)("- %2d %x => ", SK_(current_call_stack).sp, bb_addr(jcc->to->bb)); SK_(print_addr)(bb_jmpaddr(jcc->from->bb)); VG_(printf)(", ESP %x\n", SK_(current_call_stack).entry[SK_(current_call_stack).sp].esp); SK_(print_cost)(10, SK_(sets).full, jcc->cost); } else VG_(printf)("- %2d [Skipped JCC], ESP %x\n", SK_(current_call_stack).sp, SK_(current_call_stack).entry[SK_(current_call_stack).sp].esp); } else { VG_(printf)(" Popped "); SK_(print_stackentry)(7, SK_(current_call_stack).sp); if (jcc) { VG_(printf)(" returned to "); SK_(print_addr_ln)(bb_jmpaddr(jcc->from->bb)); } } } #endif } /* remove CallStack items to sync with current ESP */ Int SK_(unwind_call_stack)(Addr esp) { Int old_current_sp = SK_(current_call_stack).sp; CT_DEBUG(4,"+ unwind_call_stack: esp 0x%x, frame %d\n", esp, SK_(current_call_stack).sp); /* We pop old stack frames. * For a call, be p the stack address with return address. * - call_stack_esp[] has ESP after the CALL: p-4 * - current esp is after a RET: >= p */ while((SK_(current_call_stack).sp >0) && (SK_(current_call_stack).entry[SK_(current_call_stack).sp-1].esp < esp)) SK_(pop_call_stack)(); CT_DEBUG(4,"- unwind_call_stack: diff %d\n", old_current_sp - SK_(current_call_stack).sp); return old_current_sp - SK_(current_call_stack).sp; }