/* This file is part of Callgrind, a Valgrind skin for call graph profiling programs. Copyright (C) 2002-2004, Josef Weidendorfer (Josef.Weidendorfer@gmx.de) This skin is derived from and contains lot of code from Cachegrind Copyright (C) 2002 Nicholas Nethercote (njn25@cam.ac.uk) 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" /*------------------------------------------------------------*/ /*--- Function specific configuration options ---*/ /*------------------------------------------------------------*/ #define CONFIG_DEFAULT -1 #define CONFIG_FALSE 0 #define CONFIG_TRUE 1 /* Logging configuration for a function */ struct _fn_config { Char* name; Int dump_before; Int dump_after; Int zero_before; Int toggle_collect; Int skip; /* Handle CALL to this function as JMP (= Skip)? */ Int group; /* don't change caller dependency inside group !=0 */ Int separate_callers; /* separate logging dependent on caller */ Int separate_recursions; /* separate logging of rec. levels */ #if CT_ENABLE_DEBUG Int verbosity; /* Change debug verbosity level while in function */ #endif fn_config* next; }; /* Configurations for function name prefix patterns. * Currently, only very limit patterns are possible: * Exact prefix patterns and "*::" are allowed. * E.g. * - "abc" matches all functions starting with "abc". * - "abc*::def" matches all functions starting with "abc" and * starting with "def" after the first "::" separator. * - "*::print(" matches C++ methods "print" in all classes * without namespace. I.e. "*" doesn't match a "::". * * We build a trie from patterns, and for a given function, we * go down the tree and apply all non-default configurations. */ #define NODE_DEGREE 30 /* node of compressed trie search structure */ typedef struct _config_node config_node; struct _config_node { Char* name; Int length; fn_config* config; config_node* sub_node[NODE_DEGREE]; config_node* cpp_sep; }; /* root of trie */ static config_node* fn_configs = 0; static __inline__ fn_config* new_fnc(Char* name) { fn_config* new = VG_(malloc)(sizeof(fn_config)); new->name = VG_(strdup)(name); new->next = 0; new->dump_before = CONFIG_DEFAULT; new->dump_after = CONFIG_DEFAULT; new->zero_before = CONFIG_DEFAULT; new->toggle_collect = CONFIG_DEFAULT; new->skip = CONFIG_DEFAULT; new->group = CONFIG_DEFAULT; new->separate_callers = CONFIG_DEFAULT; new->separate_recursions = CONFIG_DEFAULT; #if CT_ENABLE_DEBUG new->verbosity = CONFIG_DEFAULT; #endif CT_DEBUG(2, " new_fnc('%s')\n", name); return new; } static fn_config* get_fnc1(fn_config** pfnc, Char* name) { fn_config* fnc = *pfnc; while(fnc) { if (VG_(strcmp)(fnc->name,name) == 0) break; fnc = fnc->next; } if (!fnc) { fnc = new_fnc(name); fnc->next = *pfnc; *pfnc = fnc; } return fnc; } static config_node* new_config(Char* name, int length) { int i; config_node* node = VG_(malloc)(sizeof(config_node)); node->name = name; node->length = length; node->config = 0; for(i=0;isub_node[i] = 0; node->cpp_sep = 0; CT_DEBUG(2, " new_config('%s', len %d)\n", name, length); return node; } /* Search first wildcard, with optionally appended cpp separator ("::"). * if not found, *(return) is 0 */ static Char* next_wild_cpp_sep(Char* n) { while(*n!=0) { if (*n == '*') { n++; if (*n == ':') { n++; if (*n == ':') { n++; } } return n; } n++; } return n; } /* Get function config for a specific name in search tree */ static fn_config* get_fnc2(config_node** pnode, Char* name, int offset) { config_node* node; fn_config* fnc; Char* cpp_sep; CT_ASSERT(pnode != 0); node = *pnode; if (node == 0) { fnc = new_fnc(name); cpp_sep = next_wild_cpp_sep(name+offset); if (*cpp_sep) { /* found wildcard */ int newOffset = cpp_sep - name; node = new_config(name, offset); node->config = fnc; *pnode = node; CT_DEBUG(3, " get_config('%s', off %d, new %d): cpp_sep\n", name, offset, newOffset); /* recurse */ return get_fnc2( &(node->cpp_sep), cpp_sep, newOffset); } node = new_config(fnc->name, VG_(strlen)(fnc->name)); node->config = fnc; *pnode = node; CT_DEBUG(3, " get_config('%s', offset %d): new\n", name, offset); return fnc; } if (name[offset] == 0) { CT_DEBUG(3, " get_config('%s', off %d): found\n", name, offset); return get_fnc1(&node->config, name); } if (name[offset] == '*') { /* the "::" is optional in the pattern */ offset++; if (name[offset] == ':') { offset++; if (name[offset] == ':') offset++; } CT_DEBUG(3, " get_config('%s', off %d): cpp_sep\n", name, offset); return get_fnc2( &(node->cpp_sep), name+offset, 0); } /* FIXME: Should handle wildcard */ while(offset < node->length) { if ((name[offset] == 0) || ((name[offset]%NODE_DEGREE) != (node->name[offset]%NODE_DEGREE))) break; offset++; } /* name is shorter or different as nodes name */ if (offset < node->length) { config_node* new; /* split up this node */ fnc = new_fnc(name); new = new_config(node->name, offset); new->sub_node[ node->name[offset]%NODE_DEGREE ] = node; if (name[offset]==0) { /* no subnode, insert into new node */ new->config = fnc; } else { config_node* new2 = new_config(node->name, VG_(strlen)(name)); new->sub_node[ name[offset]%NODE_DEGREE ] = new2; new2->config = fnc; } *pnode = new; CT_DEBUG(3, " get_config('%s', off %d): splitted\n", name, offset); return fnc; } /* name and node name are congruent */ if (name[offset] == 0) { CT_DEBUG(3, " get_config('%s', off %d): found\n", name, offset); return get_fnc1(&node->config, name); } /* name is longer than the nodes name: append new node */ CT_DEBUG(3, " get_config('%s', off %d): next\n", name, offset); /* recurse */ return get_fnc2( &(node->sub_node[ name[offset]%NODE_DEGREE ]), name, offset); } /* get a function config for a given prefix name */ static fn_config* get_fnc(Char* name) { return get_fnc2(&fn_configs, name, 0); } static void update_fn_config1(fn_node* fn, fn_config* fnc) { if (fnc->dump_before != CONFIG_DEFAULT) fn->dump_before = (fnc->dump_before == CONFIG_TRUE); if (fnc->dump_after != CONFIG_DEFAULT) fn->dump_after = (fnc->dump_after == CONFIG_TRUE); if (fnc->zero_before != CONFIG_DEFAULT) fn->zero_before = (fnc->zero_before == CONFIG_TRUE); if (fnc->toggle_collect != CONFIG_DEFAULT) fn->toggle_collect = (fnc->toggle_collect == CONFIG_TRUE); if (fnc->skip != CONFIG_DEFAULT) fn->skip = (fnc->skip == CONFIG_TRUE); if (fnc->group != CONFIG_DEFAULT) fn->group = fnc->group; if (fnc->separate_callers != CONFIG_DEFAULT) fn->separate_callers = fnc->separate_callers; if (fnc->separate_recursions != CONFIG_DEFAULT) fn->separate_recursions = fnc->separate_recursions; #if CT_ENABLE_DEBUG if (fnc->verbosity != CONFIG_DEFAULT) fn->verbosity = fnc->verbosity; #endif } /* Search first cpp separator ("::"). * if not found, m[return] is 0 */ static Int next_cpp_sep(Char* n) { Int p = 0; while(n[p]!=0) { if (n[p] == ':') { p++; if (n[p] == ':') { p++; return p; } } p++; } return p; } static void update_fn_config2(fn_node* fn, char* name, config_node* node) { fn_config* fnc; int len, cpp_sep_pos = -1; len = next_cpp_sep(name); if (name[len]) { cpp_sep_pos = len; while(name[len]) len++; CT_DEBUG(3, " update_fn_config('%s', off %d): cpp_sep\n", name, cpp_sep_pos); } while(node && (node->length <= len)) { fnc = node->config; while(fnc) { if (VG_(strncmp)(fnc->name,name,node->length) == 0) break; fnc = fnc->next; } if (fnc) { CT_DEBUG(3, " update_fn_config('%s', len %d): found\n", fnc->name, node->length); update_fn_config1(fn, fnc); /* recurse on wildcard patterns (depth first search). * Better would be to build a state machine... */ if (node->cpp_sep && cpp_sep_pos>0) update_fn_config2(fn, name + cpp_sep_pos, node->cpp_sep); } if (name[node->length] == 0) break; node = node->sub_node[ name[node->length]%NODE_DEGREE ]; } } /* Update function config according to configs of name prefixes */ void SK_(update_fn_config)(fn_node* fn) { CT_DEBUG(3, " update_fn_config('%s')\n", fn->name); update_fn_config2(fn, fn->name, fn_configs); } /*--------------------------------------------------------------------*/ /*--- Command line processing ---*/ /*--------------------------------------------------------------------*/ #define UNDEFINED_CACHE ((cache_t) { -1, -1, -1 }) static cache_t clo_I1_cache = UNDEFINED_CACHE; static cache_t clo_D1_cache = UNDEFINED_CACHE; static cache_t clo_L2_cache = UNDEFINED_CACHE; static void parse_cache_opt ( cache_t* cache, char* orig_opt, int opt_len ) { int i1, i2, i3; int i; char *opt = VG_(strdup)(orig_opt); i = i1 = opt_len; /* Option looks like "--I1=65536,2,64". * Find commas, replace with NULs to make three independent * strings, then extract numbers. Yuck. */ while (VG_(isdigit)(opt[i])) i++; if (',' == opt[i]) { opt[i++] = '\0'; i2 = i; } else goto bad; while (VG_(isdigit)(opt[i])) i++; if (',' == opt[i]) { opt[i++] = '\0'; i3 = i; } else goto bad; while (VG_(isdigit)(opt[i])) i++; if ('\0' != opt[i]) goto bad; cache->size = (Int)VG_(atoll)(opt + i1); cache->assoc = (Int)VG_(atoll)(opt + i2); cache->line_size = (Int)VG_(atoll)(opt + i3); VG_(free)(opt); return; bad: VG_(bad_option)(orig_opt); } static Char* getUInt(Char* s, UInt* pn) { UInt n = 0; while((*s >='0') && (*s <='9')) { n = 10*n + (*s-'0'); s++; } if (pn) *pn = n; return s; } Bool SK_(process_cmd_line_option)(Char* arg) { if (0 == VG_(strcmp)(arg, "--skip-plt=yes")) SK_(clo).skip_plt = True; else if (0 == VG_(strcmp)(arg, "--skip-plt=no")) SK_(clo).skip_plt = False; else if (0 == VG_(strcmp)(arg, "--collect-jumps=yes")) SK_(clo).collect_jumps = True; else if (0 == VG_(strcmp)(arg, "--collect-jumps=no")) SK_(clo).collect_jumps = False; /* compatibility alias, deprecated option */ else if (0 == VG_(strcmp)(arg, "--trace-jump=yes")) SK_(clo).collect_jumps = True; else if (0 == VG_(strcmp)(arg, "--trace-jump=no")) SK_(clo).collect_jumps = False; else if (0 == VG_(strcmp)(arg, "--separate-dumps=yes")) SK_(clo).separate_dumps = True; else if (0 == VG_(strcmp)(arg, "--separate-dumps=no")) SK_(clo).separate_dumps = False; else if (0 == VG_(strcmp)(arg, "--simulate-cache=yes")) SK_(clo).simulate_cache = True; else if (0 == VG_(strcmp)(arg, "--simulate-cache=no")) SK_(clo).simulate_cache = False; else if (0 == VG_(strcmp)(arg, "--collect-data=yes")) SK_(clo).collect_data = True; else if (0 == VG_(strcmp)(arg, "--collect-data=no")) SK_(clo).collect_data = False; #ifdef CT_HWPREFETCH else if (0 == VG_(strcmp)(arg, "--simulate-hwpref=yes")) SK_(clo).simulate_hwpref = True; else if (0 == VG_(strcmp)(arg, "--simulate-hwpref=no")) SK_(clo).simulate_hwpref = False; #endif else if (0 == VG_(strcmp)(arg, "--collect-atstart=yes")) SK_(clo).collect_atstart = True; else if (0 == VG_(strcmp)(arg, "--collect-atstart=no")) SK_(clo).collect_atstart = False; else if (0 == VG_(strcmp)(arg, "--instr-atstart=yes")) SK_(clo).instrument_atstart = True; else if (0 == VG_(strcmp)(arg, "--instr-atstart=no")) SK_(clo).instrument_atstart = False; else if (0 == VG_(strcmp)(arg, "--separate-threads=yes")) SK_(clo).separate_threads = True; else if (0 == VG_(strcmp)(arg, "--separate-threads=no")) SK_(clo).separate_threads = False; /* compatibility alias, deprecated option */ else if (0 == VG_(strcmp)(arg, "--dump-threads=yes")) SK_(clo).separate_threads = True; else if (0 == VG_(strcmp)(arg, "--dump-threads=no")) SK_(clo).separate_threads = False; else if (0 == VG_(strcmp)(arg, "--compress-strings=yes")) SK_(clo).compress_strings = True; else if (0 == VG_(strcmp)(arg, "--compress-strings=no")) SK_(clo).compress_strings = False; else if (0 == VG_(strcmp)(arg, "--compress-mangled=yes")) SK_(clo).compress_mangled = True; else if (0 == VG_(strcmp)(arg, "--compress-mangled=no")) SK_(clo).compress_mangled = False; else if (0 == VG_(strcmp)(arg, "--compress-pos=yes")) SK_(clo).compress_pos = True; else if (0 == VG_(strcmp)(arg, "--compress-pos=no")) SK_(clo).compress_pos = False; else if (0 == VG_(strncmp)(arg, "--fn-skip=", 10)) { fn_config* fnc = get_fnc(arg+10); fnc->skip = CONFIG_TRUE; } else if (0 == VG_(strncmp)(arg, "--dump-before=", 14)) { fn_config* fnc = get_fnc(arg+14); fnc->dump_before = CONFIG_TRUE; } else if (0 == VG_(strncmp)(arg, "--zero-before=", 14)) { fn_config* fnc = get_fnc(arg+14); fnc->zero_before = CONFIG_TRUE; } else if (0 == VG_(strncmp)(arg, "--dump-after=", 13)) { fn_config* fnc = get_fnc(arg+13); fnc->dump_after = CONFIG_TRUE; } else if (0 == VG_(strncmp)(arg, "--toggle-collect=", 17)) { fn_config* fnc = get_fnc(arg+17); fnc->toggle_collect = CONFIG_TRUE; /* defaults to initial collection off */ SK_(clo).collect_atstart = False; } else if (0 == VG_(strncmp)(arg, "--separate-recs=", 16)) SK_(clo).separate_recursions = (Int)VG_(atoll)(&arg[16]); #if CT_ENABLE_DEBUG else if (0 == VG_(strncmp)(arg, "--ct-verbose=", 13)) SK_(clo).verbose = (Int)VG_(atoll)(&arg[13]); else if (0 == VG_(strncmp)(arg, "--ct-vstart=", 12)) SK_(clo).verbose_start = (ULong)VG_(atoll)(&arg[12]); else if (0 == VG_(strncmp)(arg, "--ct-verbose", 12)) { UInt n; fn_config* fnc; Char* s = getUInt(arg+12, &n); if ((n == 0) || *s != '=') return False; fnc = get_fnc(s+1); fnc->verbosity = n; } #endif else if (0 == VG_(strncmp)(arg, "--separate-callers=", 19)) SK_(clo).separate_callers = (Int)VG_(atoll)(&arg[19]); else if (0 == VG_(strncmp)(arg, "--fn-group", 10)) { UInt n; fn_config* fnc; Char* s = getUInt(arg+10, &n); if ((n == 0) || *s != '=') return False; fnc = get_fnc(s+1); fnc->group = n; } else if (0 == VG_(strncmp)(arg, "--separate-callers", 18)) { UInt n; fn_config* fnc; Char* s = getUInt(arg+18, &n); if ((n == 0) || *s != '=') return False; fnc = get_fnc(s+1); fnc->separate_callers = n; } else if (0 == VG_(strncmp)(arg, "--separate-recs", 15)) { UInt n; fn_config* fnc; Char* s = getUInt(arg+15, &n); if ((n == 0) || *s != '=') return False; fnc = get_fnc(s+1); fnc->separate_recursions = n; } else if (0 == VG_(strncmp)(arg, "--base=", 7)) SK_(clo).filename_base = VG_(strdup)(arg+7); else if (0 == VG_(strcmp)(arg, "--mangle-names=yes")) SK_(clo).mangle_names = True; else if (0 == VG_(strcmp)(arg, "--mangle-names=no")) SK_(clo).mangle_names = False; else if (0 == VG_(strcmp)(arg, "--skip-direct-rec=yes")) SK_(clo).skip_direct_recursion = True; else if (0 == VG_(strcmp)(arg, "--skip-direct-rec=no")) SK_(clo).skip_direct_recursion = False; else if (0 == VG_(strcmp)(arg, "--dump-bbs=yes")) SK_(clo).dump_bbs = True; else if (0 == VG_(strcmp)(arg, "--dump-bbs=no")) SK_(clo).dump_bbs = False; else if (0 == VG_(strcmp)(arg, "--dump-line=yes")) SK_(clo).dump_line = True; else if (0 == VG_(strcmp)(arg, "--dump-line=no")) SK_(clo).dump_line = False; else if (0 == VG_(strcmp)(arg, "--dump-instr=yes")) SK_(clo).dump_instr = True; else if (0 == VG_(strcmp)(arg, "--dump-instr=no")) SK_(clo).dump_instr = False; else if (0 == VG_(strcmp)(arg, "--dump-bb=yes")) SK_(clo).dump_bb = True; else if (0 == VG_(strcmp)(arg, "--dump-bb=no")) SK_(clo).dump_bb = False; else if (0 == VG_(strncmp)(arg, "--dump-every-bb=", 16)) SK_(clo).dump_every_bb = (Int)VG_(atoll)(&arg[16]); else if (0 == VG_(strcmp)(arg, "--collect-alloc=yes")) SK_(clo).collect_alloc = True; else if (0 == VG_(strcmp)(arg, "--collect-alloc=no")) SK_(clo).collect_alloc = False; else if (0 == VG_(strcmp)(arg, "--collect-systime=yes")) SK_(clo).collect_systime = True; else if (0 == VG_(strcmp)(arg, "--collect-systime=no")) SK_(clo).collect_systime = False; /* 5 is length of "--I1=" */ else if (0 == VG_(strncmp)(arg, "--I1=", 5)) parse_cache_opt(&clo_I1_cache, arg, 5); else if (0 == VG_(strncmp)(arg, "--D1=", 5)) parse_cache_opt(&clo_D1_cache, arg, 5); else if (0 == VG_(strncmp)(arg, "--L2=", 5)) parse_cache_opt(&clo_L2_cache, arg, 5); else return False; return True; } void SK_(print_usage)(void) { VG_(printf)( " --base= Prefix for profile files [" DEFAULT_DUMPNAME "]\n" " --simulate-cache=no|yes Full cache simulation [yes]\n" #if CT_HWPREFETCH " --simulate-hwpref=no|yes Simulate Hardware Prefetch [no]\n" #endif " --instr-atstart=no|yes Do instrumentation at callgrind start [yes]\n" " --collect-atstart=no|yes Collect at process/thread start [yes]\n" " --collect-jumps=no|yes Collect jumps? [no]\n" " --collect-alloc=no|yes Collect memory allocation info? [no]\n" " --collect-systime=no|yes Collect system call time info? [no]\n" #if CT_DATA " --collect-data=no|yes Collect data relation of events [no]\n" #endif " --skip-plt=no|yes Ignore calls to/from PLT sections? [yes]\n" " --fn-skip= Ignore calls to/from function?\n" #if CT_EXPERIMENTAL " --fn-group= Put function into separation group \n" #endif " --separate-threads=no|yes Separate data per thread [no]\n" " --separate-recs= Separate function recursions upto level [2]\n" " --separate-callers= Separate functions by call chain length [0]\n" " --separate-recs= Separate recursions for function \n" " --separate-callers= Separate callers for function \n" " --dump-before= Dump when entering function\n" " --zero-before= Zero all costs when entering function\n" " --dump-after= Dump when leaving function\n" " --toggle-collect= Toggle collection on enter/leave function\n" " --skip-direct-rec=no|yes Ignore direct recursions? [yes]\n" #if CT_EXPERIMENTAL " --mangle-names=no|yes Mangle separation into names? [yes]\n" #endif " --compress-strings=no|yes Compress strings in profile dump? [yes]\n" #if CT_EXPERIMENTAL " --compress-events=no|yes Compress events in profile dump? [no]\n" #endif " --compress-pos=no|yes Compress positions in profile dump? [yes]\n" " --dump-line=no|yes Dump source lines of costs? [yes]\n" " --dump-instr=no|yes Dump instruction address of costs? [no]\n" #if CT_EXPERIMENTAL " --dump-bb=no|yes Dump basic block address of costs? [no]\n" " --dump-bbs=no|yes Dump basic block info? [no]\n" " --dump-skipped=no|yes Dump info on skipped functions in calls? [no]\n" #endif " --dump-every-bb= Dump every basic blocks [0=never]\n" #if CT_EXPERIMENTAL " --separate-dumps=no|yes Write each dump into separate file [yes]\n" #endif " --I1=,, set I1 cache manually\n" " --D1=,, set D1 cache manually\n" " --L2=,, set L2 cache manually\n" "\n see " VG_LIBDIR"/../share/doc/valgrind/ct_main.html for full documentation.\n\n" ); } void SK_(print_debug_usage)(void) { VG_(printf)( #if CT_ENABLE_DEBUG " --ct-verbose= Verbosity of standard debug output [0]\n" " --ct-vstart= Only be verbose after basic block [0]\n" " --ct-verbose= Verbosity while in \n" #else " (none)\n" #endif ); } void SK_(set_clo_defaults)(void) { /* Default values for command line arguments */ /* dump options */ SK_(clo).filename_base = 0; SK_(clo).separate_dumps = True; SK_(clo).compress_strings = True; SK_(clo).compress_mangled = False; SK_(clo).compress_events = False; SK_(clo).compress_pos = True; SK_(clo).mangle_names = True; SK_(clo).dump_line = True; SK_(clo).dump_instr = False; SK_(clo).dump_bb = False; SK_(clo).dump_bbs = False; SK_(clo).dump_every_bb = 0; /* Collection */ SK_(clo).separate_threads = False; SK_(clo).collect_data = False; SK_(clo).collect_atstart = True; SK_(clo).collect_jumps = False; SK_(clo).collect_alloc = False; SK_(clo).collect_systime = False; SK_(clo).skip_plt = True; SK_(clo).separate_callers = 0; SK_(clo).separate_recursions = 2; SK_(clo).skip_direct_recursion = False; /* Simulation */ SK_(clo).simulate_cache = True; SK_(clo).simulate_hwpref = False; SK_(clo).instrument_atstart = True; #if CT_ENABLE_DEBUG SK_(clo).verbose = 0; SK_(clo).verbose_start = 0; #endif }