From: Philippe Waroquiers Date: Fri, 11 Nov 2016 14:07:03 +0000 (+0000) Subject: Addition of the pub_tool_xtree.h and pub_tool_xtmemory.h modules, and of the --xtree... X-Git-Tag: svn/VALGRIND_3_13_0~298 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f9386afa8964673bf1576bd08750f94c96ad3bbe;p=thirdparty%2Fvalgrind.git Addition of the pub_tool_xtree.h and pub_tool_xtmemory.h modules, and of the --xtree-memory* options This commit is the bulk of the new code. There is however no functional impact yet : the new modules are not used by anybody. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@16123 --- diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am index d798015d74..41c5ef4931 100644 --- a/coregrind/Makefile.am +++ b/coregrind/Makefile.am @@ -234,6 +234,8 @@ noinst_HEADERS = \ pub_core_vkiscnums_asm.h\ pub_core_wordfm.h \ pub_core_xarray.h \ + pub_core_xtree.h \ + pub_core_xtmemory.h \ m_aspacemgr/priv_aspacemgr.h \ m_debuginfo/priv_misc.h \ m_debuginfo/priv_storage.h \ @@ -332,6 +334,8 @@ COREGRIND_SOURCES_COMMON = \ m_vkiscnums.c \ m_wordfm.c \ m_xarray.c \ + m_xtree.c \ + m_xtmemory.c \ m_aspacehl.c \ m_aspacemgr/aspacemgr-common.c \ m_aspacemgr/aspacemgr-linux.c \ diff --git a/coregrind/m_options.c b/coregrind/m_options.c index d5e07da850..591e40e7d9 100644 --- a/coregrind/m_options.c +++ b/coregrind/m_options.c @@ -112,6 +112,10 @@ Int VG_(clo_core_redzone_size) = CORE_REDZONE_DEFAULT_SZB; // A value != -1 overrides the tool-specific value // VG_(needs_malloc_replacement).tool_client_redzone_szB Int VG_(clo_redzone_size) = -1; +VgXTMemory VG_(clo_xtree_memory) = Vg_XTMemory_None; +const HChar* VG_(clo_xtree_memory_file) = "xtmemory.kcg.%p"; +Bool VG_(clo_xtree_compress_strings) = True; + Int VG_(clo_dump_error) = 0; Int VG_(clo_backtrace_size) = 12; Int VG_(clo_merge_recursive_frames) = 0; // default value: no merge diff --git a/coregrind/m_xtmemory.c b/coregrind/m_xtmemory.c new file mode 100644 index 0000000000..5e92caddf3 --- /dev/null +++ b/coregrind/m_xtmemory.c @@ -0,0 +1,341 @@ + +/*--------------------------------------------------------------------*/ +/*--- Support functions for xtree memory reports. m_xtmemory.c ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2016-2016 Philippe Waroquiers + + 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 "pub_core_libcassert.h" +#include "pub_core_libcbase.h" +#include "pub_core_libcprint.h" +#include "pub_core_libcproc.h" +#include "pub_core_mallocfree.h" +#include "pub_core_options.h" +#include "pub_core_xarray.h" +#include "pub_core_xtree.h" +#include "pub_core_xtmemory.h" /* self */ + +static void VG_(XT_Allocs_init)(void* xt_allocs) +{ + VG_(memset) (xt_allocs, 0, sizeof(XT_Allocs)); +} +static void VG_(XT_Allocs_add) (void* to, const void* xt_allocs) +{ + XT_Allocs* xto = to; + const XT_Allocs* xta = xt_allocs; + + xto->nbytes += xta->nbytes; + xto->nblocks += xta->nblocks; +} +static void VG_(XT_Allocs_sub) (void* from, const void* xt_allocs) +{ + XT_Allocs* xfrom = from; + const XT_Allocs* xta = xt_allocs; + + xfrom->nbytes -= xta->nbytes; + xfrom->nblocks -= xta->nblocks; +} +static const HChar* VG_(XT_Allocs_img) (const void* xt_allocs) +{ + static HChar buf[100]; + + const XT_Allocs* xta = xt_allocs; + + if (xta->nbytes > 0 || xta->nblocks > 0) { + VG_(sprintf) (buf, "%lu %lu", + xta->nbytes, xta->nblocks); + return buf; + } else { + return NULL; + } +} +const HChar* XT_Allocs_events = "curB : currently allocated Bytes" "," + "curBk : currently allocated Blocks"; + +/* Type and functions for full xtree memory profiling. */ +static XTree* full_xt; +typedef + struct _XT_Full { + // Current nr of bytes/blocks allocated by this ec + SizeT cur_alloc_nbytes; + SizeT cur_alloc_nblocks; + + // Total/cumulative nr of bytes/blocks allocated by this ec + ULong tot_alloc_nbytes; + ULong tot_alloc_nblocks; + + // Total/cumulative nr of bytes/blocks freed by this ec + ULong tot_freed_nbytes; + ULong tot_freed_nblocks; + } XT_Full; +/* Note: normally, an ec should never be used as both an alloc_ec and + a free_ec. This implies that we should never have a XT_Full that has + at the same time some alloc and some freed components > 0. + We however still will support this possibility, just in case very + strange ec are produced and/or given by the tool. */ + +static void VG_(XT_Full_init)(void* xtfull) +{ + VG_(memset) (xtfull, 0, sizeof(XT_Full)); +} +static void VG_(XT_Full_add) (void* to, const void* xtfull) +{ + XT_Full* xto = to; + const XT_Full* xtf = xtfull; + + xto->cur_alloc_nbytes += xtf->cur_alloc_nbytes; + xto->cur_alloc_nblocks += xtf->cur_alloc_nblocks; + xto->tot_alloc_nbytes += xtf->tot_alloc_nbytes; + xto->tot_alloc_nblocks += xtf->tot_alloc_nblocks; + xto->tot_freed_nbytes += xtf->tot_freed_nbytes; + xto->tot_freed_nblocks += xtf->tot_freed_nblocks; +} +static void VG_(XT_Full_sub) (void* from, const void* xtfull) +{ + XT_Full* xfrom = from; + const XT_Full* xtf = xtfull; + + xfrom->cur_alloc_nbytes -= xtf->cur_alloc_nbytes; + xfrom->cur_alloc_nblocks -= xtf->cur_alloc_nblocks; + xfrom->tot_alloc_nbytes -= xtf->tot_alloc_nbytes; + xfrom->tot_alloc_nblocks -= xtf->tot_alloc_nblocks; + xfrom->tot_freed_nbytes -= xtf->tot_freed_nbytes; + xfrom->tot_freed_nblocks -= xtf->tot_freed_nblocks; +} +static const HChar* VG_(XT_Full_img) (const void* xtfull) +{ + static HChar buf[300]; + + const XT_Full* xtf = xtfull; + + if ( xtf->cur_alloc_nbytes > 0 + || xtf->cur_alloc_nblocks > 0 + || xtf->tot_alloc_nbytes > 0 + || xtf->tot_alloc_nblocks > 0 + || xtf->tot_freed_nbytes > 0 + || xtf->tot_freed_nblocks > 0) { + VG_(sprintf) (buf, + "%lu %lu " + "%llu %llu " + "%llu %llu", + xtf->cur_alloc_nbytes, xtf->cur_alloc_nblocks, + xtf->tot_alloc_nbytes, xtf->tot_alloc_nblocks, + xtf->tot_freed_nbytes, xtf->tot_freed_nblocks); + return buf; + } else { + return NULL; + } +} +static const HChar* XT_Full_events = + "curB : currently allocated Bytes" "," + "curBk : currently allocated Blocks" "," + "totB : total allocated Bytes" "," + "totBk : total allocated Blocks" "," + "totFdB : total Freed Bytes" "," + "totFdBk : total Freed Blocks"; +void VG_(XTMemory_Full_init)(XT_filter_IPs_t filter_IPs_fn) +{ + full_xt = VG_(XT_create) (VG_(malloc), + "m_xtree.full_xt", + VG_(free), + sizeof(XT_Full), + VG_(XT_Full_init), + VG_(XT_Full_add), + VG_(XT_Full_sub), + filter_IPs_fn); +} +void VG_(XTMemory_Full_alloc)(SizeT szB, + ExeContext* ec_alloc) +{ + XT_Full xtf = {szB, 1, szB, 1, 0, 0}; + VG_(XT_add_to_ec)(full_xt, ec_alloc, &xtf); +} +void VG_(XTMemory_Full_free)(SizeT szB, + ExeContext* ec_alloc, + ExeContext* ec_free) +{ + // substract from ec_alloc the freed memory. + XT_Full xtf_sub = {szB, 1, 0, 0, 0, 0}; + VG_(XT_sub_from_ec)(full_xt, ec_alloc, &xtf_sub); + + // add to ec_free the freed memory + XT_Full xtf_add = {0, 0, 0, 0, szB, 1}; + VG_(XT_add_to_ec)(full_xt, ec_free, &xtf_add); +} + +void VG_(XTMemory_Full_resize_in_place)(SizeT oldSzB, SizeT newSzB, + ExeContext* ec_alloc) +{ + if (oldSzB > newSzB) { + XT_Full xtf = {oldSzB - newSzB, 0, oldSzB - newSzB, 0, 0, 0}; + VG_(XT_sub_from_ec)(full_xt, ec_alloc, &xtf); + } else { + XT_Full xtf = {newSzB - oldSzB, 0, newSzB - oldSzB, 0, 0, 0}; + VG_(XT_add_to_ec)(full_xt, ec_alloc, &xtf); + } +} + +// Indicates which event nr the report_value function must return. +static UInt event_report_value_id; +static ULong XT_Full_report_value(const void* xtfull) +{ + const XT_Full* xtf = xtfull; + switch (event_report_value_id) { + case 0: return (ULong) xtf->cur_alloc_nbytes; + case 1: return (ULong) xtf->cur_alloc_nblocks; + case 2: return xtf->tot_alloc_nbytes; + case 3: return xtf->tot_alloc_nblocks; + case 4: return xtf->tot_freed_nbytes; + case 5: return xtf->tot_freed_nblocks; + default: vg_assert(0); + } +} +static ULong XT_Allocs_report_value(const void* xt_allocs) +{ + const XT_Allocs* xta = xt_allocs; + switch (event_report_value_id) { + case 0: return (ULong) xta->nbytes; + case 1: return (ULong) xta->nblocks; + default: vg_assert(0); + } +} + +static void produce_report(XTree* xt, const HChar* filename, + const HChar* events, + const HChar* (*img_value) (const void* value), + ULong (*report_value)(const void* value)) +{ + /* The user can control the kind of report using filename extension. */ + if (VG_(strstr)(filename, ".ms")) { + /* If needed, some harcoded value below could become parameters. */ + MsFile* fp; + Massif_Header header = (Massif_Header) { + .snapshot_n = 0, + .time = VG_(read_millisecond_timer)(), + .sz_B = 0ul, + .extra_B = 0ul, + .stacks_B = 0ul, + .detailed = True, + .peak = False, + .top_node_desc = NULL, + .sig_threshold = 0.00000000000001 + // Currently, we take a very small float value to not output + // the 0 values, but still output all the rest. + }; + + // Variables to parse events + HChar strtok_events[VG_(strlen)(events)+1]; + HChar* e; + HChar* ssaveptr; + + fp = VG_(XT_massif_open)(filename, + "xtree.produce_report", + NULL, + "ms"); + + event_report_value_id = 0; + VG_(strcpy)(strtok_events, events); + for (e = VG_(strtok_r) (strtok_events, ",", &ssaveptr); + e != NULL; + e = VG_(strtok_r) (NULL, ",", &ssaveptr)) { + header.top_node_desc = e; + VG_(XT_massif_print)(fp, xt, &header, report_value); + header.snapshot_n++; + event_report_value_id++; + } + + VG_(XT_massif_close)(fp); + } else + VG_(XT_callgrind_print)(xt, + filename, + events, + img_value); +} + +void VG_(XTMemory_report) + (const HChar* filename, Bool fini, + void (*next_block)(XT_Allocs* xta, ExeContext** ec_alloc), + XT_filter_IPs_t filter_IPs_fn) +{ + HChar* expanded_filename; + + if (fini && VG_(clo_xtree_memory) == Vg_XTMemory_None) + return; + + expanded_filename + = VG_(expand_file_name)("--xtree-memory-file", + (filename == NULL) ? + (fini ? + VG_(clo_xtree_memory_file) : "xtmemory.kcg") + : filename); + + /* fini is False => even if user kept --xtree-memory=none, we + produce a report when explicitely requested e.g. via a monitor + command. */ + switch (VG_(clo_xtree_memory)) { + case Vg_XTMemory_None: + case Vg_XTMemory_Allocs: { + XTree* xt; + XT_Allocs xta; + ExeContext* ec_alloc; + + xt = VG_(XT_create) (VG_(malloc), + "VG_(XTMemory_report)", + VG_(free), + sizeof(XT_Allocs), + VG_(XT_Allocs_init), + VG_(XT_Allocs_add), + VG_(XT_Allocs_sub), + filter_IPs_fn); + (*next_block)(&xta, &ec_alloc); + while ( xta.nblocks > 0 ) { + VG_(XT_add_to_ec) (xt, ec_alloc, &xta); + (*next_block)(&xta, &ec_alloc); + } + + produce_report(xt, expanded_filename, + XT_Allocs_events, VG_(XT_Allocs_img), + XT_Allocs_report_value); + + VG_(XT_delete)(xt); + break; + } + case Vg_XTMemory_Full: + produce_report(full_xt, expanded_filename, + XT_Full_events, VG_(XT_Full_img), + XT_Full_report_value); + break; + default: + vg_assert(0); + } + if (VG_(clo_verbosity) >= 1 || !fini) + VG_(umsg)("xtree memory report: %s\n", expanded_filename); + + VG_(free)(expanded_filename); +} + +/*--------------------------------------------------------------------*/ +/*--- end m_xtree.c ---*/ +/*--------------------------------------------------------------------*/ diff --git a/coregrind/m_xtree.c b/coregrind/m_xtree.c new file mode 100644 index 0000000000..5da5986c57 --- /dev/null +++ b/coregrind/m_xtree.c @@ -0,0 +1,1013 @@ + +/*--------------------------------------------------------------------*/ +/*--- An xtree, tree of stacktraces with data m_xtree.c ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2016-2016 Philippe Waroquiers + + This code generalises the XTree idea that was implemented in + the massif tool in Valgrind versions <= 3.12, which is + Copyright (C) 2005-2015 Nicholas Nethercote + njn@valgrind.org + + The XTree implementation in this file is however implemented completely + differently. Some code has been re-used for the production of the + massif file header (e.g. FP_cmd function). + + 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 "pub_core_basics.h" +#include "pub_core_debuglog.h" +#include "pub_core_clientstate.h" +#include "pub_core_stacktrace.h" +#include "pub_core_execontext.h" +#include "pub_core_libcbase.h" +#include "pub_core_libcassert.h" +#include "pub_core_libcfile.h" +#include "pub_core_libcprint.h" +#include "pub_core_libcproc.h" +#include "pub_core_hashtable.h" +#include "pub_core_mallocfree.h" +#include "pub_core_options.h" +#include "pub_core_debuginfo.h" +#include "pub_core_deduppoolalloc.h" +#include "pub_core_xtree.h" /* self */ + +#define DMSG(level, ...) (level <= VG_(debugLog_getLevel)() ? \ + VG_(dmsg)(__VA_ARGS__) \ + : 0) + +/* Defines the relevant part of an ec. This is shared between an xt + and its snapshots (see XT_shared XArray of xec). */ +typedef + struct _xec { + ExeContext* ec; + UShort top; // The first ips of ec to take into account. + UShort n_ips_sel; // The nr of ips from top to take into account. + } + xec; + +/* XT_shared maintains the information shared between an XT and all + its snapshots. */ +typedef + struct _XT_shared { + UWord nrRef; /* nr of XTrees referencing this shared memory. */ + + void* (*alloc_fn)( const HChar*, SizeT ); /* alloc fn (nofail) */ + const HChar* cc; /* cost centre for alloc */ + void (*free_fn)( void* ); /* free fn */ + + /* The data associated to each ec is stored in 2 arrays: + an xec array, shared between an xt and all its snapshots. + a data array, private to each XTree. + For an ec with an ECU ecu, d4ecu2xecu[ecu/4] gives the offset in + xec and data arrays where the ec information is located (this + indirection is used to avoid huge xec and data arrays, in + case an XTree contains data only for a small number of ec. + The offset in the xec and data array is used as xtree ec unique + id i.e. an xecu. */ + + UInt d4ecu2xecu_sz; /* size of d4ecu2xecu (in nr of elements). */ + UInt* d4ecu2xecu; + + /* ec information common to an xt and its snapshots. */ + XArray* xec; /* XArray of xec, indexed by xecu (== d4ecu2xecu[ecu/4]). */ + + /* XArray of xecu, sorted by StackTrace ips[top..top+n_ips_sel-1]. + See ips_order_cmp. */ + XArray* ips_order_xecu; + } XT_shared; + +/* NO_OFFSET indicates in d4ecu2xecu there is no data (yet) for this ec + (with the index ecu/4). */ +#define NO_OFFSET 0xffffffff + +static XT_shared* new_XT_shared (void* (*alloc_fn)(const HChar*, SizeT), + const HChar* cc, + void (*free_fn)(void*)) +{ + XT_shared* shared; + + vg_assert(alloc_fn); + vg_assert(cc); + vg_assert(free_fn); + shared = alloc_fn(cc, sizeof(*shared)); + shared->nrRef = 0; + shared->alloc_fn = alloc_fn; + shared->cc = cc; + shared->free_fn = free_fn; + + shared->d4ecu2xecu_sz = 0; + shared->d4ecu2xecu = NULL; + shared->xec = VG_(newXA)(alloc_fn, cc, free_fn, sizeof(xec)); + shared->ips_order_xecu = NULL; // Allocated when needed. + + return shared; +} + +static void delete_XT_shared (XT_shared* shared) +{ + vg_assert(shared->nrRef == 0); + shared->free_fn(shared->d4ecu2xecu); + VG_(deleteXA)(shared->xec); + if (shared->ips_order_xecu != NULL) + VG_(deleteXA)(shared->ips_order_xecu); + shared->free_fn(shared); +} + +/* Compare 2 entries in ips_order_xecu by StackTrace elements. Note + that a not existing ips is considered smaller than any other + address. */ +static XArray* xec_data_for_sort; // Needed to translate an xecu into an xec +static Int ips_order_cmp(const void* vleft, const void* vright) +{ + const Xecu left_xecu = *(const Xecu*)vleft; + const Xecu right_xecu = *(const Xecu*)vright; + const xec* left = VG_(indexXA)(xec_data_for_sort, left_xecu); + const xec* right = VG_(indexXA)(xec_data_for_sort, right_xecu); + const StackTrace left_ips = VG_(get_ExeContext_StackTrace)(left->ec) + + left->top; + const StackTrace right_ips = VG_(get_ExeContext_StackTrace)(right->ec) + + right->top; + UInt i; + + const UInt c_n_ips_sel = left->n_ips_sel < right->n_ips_sel + ? left->n_ips_sel : right->n_ips_sel; + + // First see if we have a difference on the common nr of ips selected + for (i = 0; i < c_n_ips_sel; i++) { + if (left_ips[i] == right_ips[i]) continue; + if (left_ips[i] < right_ips[i]) return -1; + return 1; + } + // Common part is equal => compare lengths. + if (left->n_ips_sel < right->n_ips_sel) return -1; + if (left->n_ips_sel > right->n_ips_sel) return 1; + return 0; +} + +// If needed, build or refresh shared->ips_order_xecu +static void ensure_ips_order_xecu_valid(XT_shared* shared) +{ + UInt i; + UInt n_xecu; + + if (shared->ips_order_xecu == NULL) { + shared->ips_order_xecu = VG_(newXA)(shared->alloc_fn, shared->cc, + shared->free_fn, sizeof(Xecu)); + VG_(hintSizeXA)(shared->ips_order_xecu, VG_(sizeXA)(shared->xec)); + VG_(setCmpFnXA)(shared->ips_order_xecu, ips_order_cmp); + } + + if (VG_(sizeXA)(shared->xec) == VG_(sizeXA)(shared->ips_order_xecu)) + return; + + n_xecu = VG_(sizeXA)(shared->xec); + for (i = VG_(sizeXA)(shared->ips_order_xecu); i < n_xecu; i++) + VG_(addToXA)(shared->ips_order_xecu, &i); + + xec_data_for_sort = shared->xec; + VG_(sortXA)(shared->ips_order_xecu); +} + +static void addRef_XT_shared (XT_shared* shared) +{ + shared->nrRef++; +} + +static UWord release_XT_shared(XT_shared* shared) +{ + UWord nrRef; + + vg_assert(shared->nrRef > 0); + nrRef = --shared->nrRef; + if (nrRef == 0) + delete_XT_shared(shared); + return nrRef; +} + + +struct _XTree { + void* (*alloc_fn)( const HChar*, SizeT ); /* alloc fn (nofail) */ + const HChar* cc; /* cost centre for alloc */ + void (*free_fn)( void* ); /* free fn */ + Word dataSzB; /* data size in bytes */ + XT_init_data_t init_data_fn; + XT_add_data_t add_data_fn; + XT_sub_data_t sub_data_fn; + XT_filter_IPs_t filter_IPs_fn; + + XT_shared* shared; + + HChar* tmp_data; /* temporary buffer, to insert new elements. */ + XArray* data; /* of elements of size dataSzB */ +}; + + +XTree* VG_(XT_create) ( void*(*alloc_fn)(const HChar*, SizeT), + const HChar* cc, + void(*free_fn) (void*), + Word dataSzB, + XT_init_data_t init_data_fn, + XT_add_data_t add_data_fn, + XT_sub_data_t sub_data_fn, + XT_filter_IPs_t filter_IPs_fn) +{ + XTree* xt; + + /* check user-supplied info .. */ + vg_assert(alloc_fn); + vg_assert(free_fn); + vg_assert(dataSzB >= 0); + vg_assert(init_data_fn); + vg_assert(add_data_fn); + vg_assert(sub_data_fn); + + xt = alloc_fn(cc, sizeof(struct _XTree) ); + xt->alloc_fn = alloc_fn; + xt->cc = cc; + xt->free_fn = free_fn; + xt->dataSzB = dataSzB; + xt->init_data_fn = init_data_fn; + xt->add_data_fn = add_data_fn; + xt->sub_data_fn = sub_data_fn; + xt->filter_IPs_fn = filter_IPs_fn; + + xt->shared = new_XT_shared(alloc_fn, cc, free_fn); + addRef_XT_shared(xt->shared); + xt->tmp_data = alloc_fn(cc, xt->dataSzB); + xt->data = VG_(newXA)(alloc_fn, cc, free_fn, dataSzB); + + return xt; +} + +XTree* VG_(XT_snapshot)(XTree* xt) +{ + XTree* nxt; + + vg_assert(xt); + + nxt = xt->alloc_fn(xt->cc, sizeof(struct _XTree) ); + + *nxt = *xt; + addRef_XT_shared (nxt->shared); + nxt->tmp_data = nxt->alloc_fn(nxt->cc, nxt->dataSzB); + nxt->data = VG_(cloneXA)(nxt->cc, xt->data); + + return nxt; +} + +void VG_(XT_delete) ( XTree* xt ) +{ + vg_assert(xt); + + release_XT_shared(xt->shared); + xt->free_fn(xt->tmp_data); + VG_(deleteXA)(xt->data); + xt->free_fn(xt); +} + +static Xecu find_or_insert (XTree* xt, ExeContext* ec) +{ + + const UInt d4ecu = VG_(get_ECU_from_ExeContext)(ec) / 4; + XT_shared* shared = xt->shared; + + /* First grow the d4ecu2xecu array if needed. */ + if (d4ecu >= shared->d4ecu2xecu_sz) { + UInt old_sz = shared->d4ecu2xecu_sz; + UInt new_sz = (3 * d4ecu) / 2; + + if (new_sz < 1000) + new_sz = 1000; + shared->d4ecu2xecu = VG_(realloc)(xt->cc, shared->d4ecu2xecu, + new_sz * sizeof(UInt)); + shared->d4ecu2xecu_sz = new_sz; + for (UInt i = old_sz; i < new_sz; i++) + shared->d4ecu2xecu[i] = NO_OFFSET; + } + + if (shared->d4ecu2xecu[d4ecu] == NO_OFFSET) { + xec xe; + + xe.ec = ec; + if (xt->filter_IPs_fn == NULL) { + xe.top = 0; + xe.n_ips_sel = (UShort)VG_(get_ExeContext_n_ips)(xe.ec); + } else { + UInt top; + UInt n_ips_sel = VG_(get_ExeContext_n_ips)(xe.ec); + xt->filter_IPs_fn(VG_(get_ExeContext_StackTrace)(xe.ec), n_ips_sel, + &top, &n_ips_sel); + xe.top = (UShort)top; + xe.n_ips_sel = (UShort)n_ips_sel; + } + xt->init_data_fn(xt->tmp_data); + VG_(addToXA)(shared->xec, &xe); + shared->d4ecu2xecu[d4ecu] = (UInt)VG_(addToXA)(xt->data, xt->tmp_data); + } + + return shared->d4ecu2xecu[d4ecu]; +} + +Xecu VG_(XT_add_to_ec) (XTree* xt, ExeContext* ec, const void* value) +{ + Xecu xecu = find_or_insert(xt, ec); + void* data = VG_(indexXA)(xt->data, xecu); + + xt->add_data_fn(data, value); + return xecu; +} + +Xecu VG_(XT_sub_from_ec) (XTree* xt, ExeContext* ec, const void* value) +{ + Xecu xecu = find_or_insert(xt, ec); + void* data = VG_(indexXA)(xt->data, xecu); + + xt->sub_data_fn(data, value); + return xecu; +} + +void VG_(XT_add_to_xecu) (XTree* xt, Xecu xecu, const void* value) +{ + void* data = VG_(indexXA)(xt->data, xecu); + xt->add_data_fn(data, value); +} + +void VG_(XT_sub_from_xecu) (XTree* xt, Xecu xecu, const void* value) +{ + void* data = VG_(indexXA)(xt->data, xecu); + xt->sub_data_fn(data, value); +} + +UInt VG_(XT_n_ips_sel) (XTree* xt, Xecu xecu) +{ + xec* xe = (xec*)VG_(indexXA)(xt->shared->xec, xecu); + return (UInt)xe->n_ips_sel; +} + +ExeContext* VG_(XT_get_ec_from_xecu) (XTree* xt, Xecu xecu) +{ + xec* xe = (xec*)VG_(indexXA)(xt->shared->xec, xecu); + return xe->ec; +} + +static VgFile* xt_open (const HChar* outfilename) +{ + VgFile* fp; + + fp = VG_(fopen)(outfilename, VKI_O_CREAT|VKI_O_WRONLY|VKI_O_TRUNC, + VKI_S_IRUSR|VKI_S_IWUSR); + if (fp == NULL) { + VG_(message)(Vg_UserMsg, + "Error: can not open xtree output file `%s'\n", + outfilename); + } + return fp; +} + +#define FP(format, args...) ({ VG_(fprintf)(fp, format, ##args); }) + +// Print "cmd:" line. +static void FP_cmd(VgFile* fp) +{ + UInt i; + + FP("cmd: "); + FP("%s", VG_(args_the_exename)); + for (i = 0; i < VG_(sizeXA)(VG_(args_for_client)); i++) { + HChar* arg = * (HChar**) VG_(indexXA)(VG_(args_for_client), i); + FP(" %s", arg); + } + FP("\n"); +} + +/* ----------- Callgrind output ------------------------------------------- */ + +/* Output a callgrind file element in compressed or not compressed format, + according to VG_(clo_xtree_compress_strings). */ +static void FP_pos_str(VgFile* fp, const HChar* name, UInt pos, + const HChar* value, Bool value_new) +{ + if (!VG_(clo_xtree_compress_strings)) + FP("%s=%s\n", name, value); + else if (value_new) + FP("%s=(%d) %s\n", name, pos, value); + else + FP("%s=(%d)\n", name, pos); +} + +void VG_(XT_callgrind_print) + (XTree* xt, + const HChar* outfilename, + const HChar* events, + const HChar* (*img_value)(const void* value)) +{ + UInt n_xecu; + XT_shared* shared = xt->shared; + VgFile* fp = xt_open(outfilename); + DedupPoolAlloc* fnname_ddpa; + DedupPoolAlloc* filename_ddpa; + + if (fp == NULL) + return; + + fnname_ddpa = VG_(newDedupPA)(16000, 1, xt->alloc_fn, + "XT_callgrind_print.fn", xt->free_fn); + filename_ddpa = VG_(newDedupPA)(16000, 1, xt->alloc_fn, + "XT_callgrind_print.fl", xt->free_fn); + + FP("version: 1\n"); + FP("creator: xtree-1\n"); + FP("pid: %d\n", VG_(getpid)()); + FP_cmd(fp); + + /* Currently, we only need/support line positions. */ + FP("\npositions:%s\n", " line"); + + /* Produce one "event:" line for each event, and the "events:" line. */ + { + HChar strtok_events[VG_(strlen)(events)+1]; + HChar* e; + HChar* ssaveptr; + HChar* p; + + VG_(strcpy)(strtok_events, events); + for (e = VG_(strtok_r) (strtok_events, ",", &ssaveptr); + e != NULL; + e = VG_(strtok_r) (NULL, ",", &ssaveptr)) + FP("event: %s\n", e); + FP("events:"); + VG_(strcpy)(strtok_events, events); + for (e = VG_(strtok_r) (strtok_events, ",", &ssaveptr); + e != NULL; + e = VG_(strtok_r) (NULL, ",", &ssaveptr)) { + p = e; + while (*p) { + if (*p == ':') + *p = 0; + p++; + } + FP(" %s", e); + } + FP("\n"); + } + xt->init_data_fn(xt->tmp_data); // to compute totals + + n_xecu = VG_(sizeXA)(xt->data); + vg_assert (n_xecu <= VG_(sizeXA)(shared->xec)); + for (Xecu xecu = 0; xecu < n_xecu; xecu++) { + xec* xe = (xec*)VG_(indexXA)(shared->xec, xecu); + if (xe->n_ips_sel == 0) + continue; + + const HChar* img = img_value(VG_(indexXA)(xt->data, xecu)); + +#define CALLED_FLF(n) \ + if ((n) < 0 \ + || !VG_(get_filename_linenum)(ips[(n)], \ + &called_filename, \ + NULL, \ + &called_linenum)) { \ + called_filename = "file ???"; \ + called_linenum = 0; \ + } \ + if ((n) < 0 \ + || !VG_(get_fnname)(ips[(n)], &called_fnname)) { \ + called_fnname = "???"; \ + } \ + called_filename_nr = VG_(allocStrDedupPA)(filename_ddpa, \ + called_filename, \ + &called_filename_new); \ + called_fnname_nr = VG_(allocStrDedupPA)(fnname_ddpa, \ + called_fnname, \ + &called_fnname_new); + + /* Instead of unknown fnname ???, CALLED_FLF could use instead: + VG_(sprintf)(unknown_fn, "%p", (void*)ips[(n)]); + but that creates a lot of (useless) nodes at least for + valgrind self-hosting. */ + + if (img) { + const HChar* called_filename; + UInt called_filename_nr; + Bool called_filename_new; + const HChar* called_fnname; + UInt called_fnname_nr; + Bool called_fnname_new; + UInt called_linenum; + UInt prev_linenum; + + const Addr* ips = VG_(get_ExeContext_StackTrace)(xe->ec) + xe->top; + Int ips_idx = xe->n_ips_sel - 1; + + if (0) { + VG_(printf)("entry img %s\n", img); + VG_(pp_ExeContext)(xe->ec); + VG_(printf)("\n"); + } + xt->add_data_fn(xt->tmp_data, VG_(indexXA)(xt->data, xecu)); + CALLED_FLF(ips_idx); + for (; + ips_idx >= 0; + ips_idx--) { + FP_pos_str(fp, "fl", called_filename_nr, + called_filename, called_filename_new); + FP_pos_str(fp, "fn", called_fnname_nr, + called_fnname, called_fnname_new); + if (ips_idx == 0) + FP("%d %s\n", called_linenum, img); + else + FP("%d\n", called_linenum); //no self cost. + prev_linenum = called_linenum; + CALLED_FLF (ips_idx-1); + if (ips_idx >= 1) { + FP_pos_str(fp, "cfi", called_filename_nr, + called_filename, called_filename_new); + FP_pos_str(fp, "cfn", called_fnname_nr, + called_fnname, called_fnname_new); + called_filename_new = False; + called_fnname_new = False; + /* Giving a call count of 0 allows kcachegrind to hide the calls + column. A call count of 1 means kcachegrind would show in the + calls column the nr of stacktrace containing this arc, which + is very confusing. So, the less bad is to give a 0 call + count. */ + FP("calls=0 %d\n", called_linenum); + FP("%d %s\n", prev_linenum, img); + } + } + FP("\n"); + } + } + /* callgrind format is not really fully supporting (yet?) execution trees: + in an execution tree, self and inclusive costs are identical, and + cannot be added together. + If no totals: line is given, callgrind_annotate calculates the addition + of all costs, and so gives a wrong totals. + Giving a totals: line solves this, but the user must give the option + --inclusive=yes (kind of hack) to have all the functions given + in the output file. */ + FP("totals: %s\n", img_value(xt->tmp_data)); + VG_(fclose)(fp); + VG_(deleteDedupPA)(fnname_ddpa); + VG_(deleteDedupPA)(filename_ddpa); +} + + +/* ----------- Massif output ---------------------------------------------- */ + +/* For Massif output, some functions from the execontext are not output, a.o. + the allocation functions at the top of the stack and the functions below + main. So, the StackTrace of the execontexts in the xtree must be filtered. + The functions below main. + Ms_Ec defines the subset of the stacktrace relevant for the report. */ +typedef + struct { + StackTrace ips; // ips and n_ips provides the subset of the xtree ec + UInt n_ips; // to use for a massif report. + + SizeT report_value; // The value to report for this stack trace. + } Ms_Ec; + +/* Ms_Group defines (at a certain depth) a group of ec context that + have the same IPs at the given depth, and have the same 'parent'. + total is the sum of the values of all group elements. + A Ms_Group can also represent a set of ec contexts that do not + have the same IP, but that have each a total which is below the + significant size. Such a group has a NULL ms_ec, a zero group_io. + n_ec is the nr of insignificant ec that have been collected inside this + insignificant group, and total is the sum of all non significant ec + at the given depth. */ +typedef + struct { + Ms_Ec* ms_ec; // The group consists in ms_ec[0 .. n_ec-1] + Addr group_ip; + UInt n_ec; + SizeT total; + } Ms_Group; + +/* Compare 2 groups by total, to have bigger total first. */ +static Int ms_group_revcmp_total(const void* vleft, const void* vright) +{ + const Ms_Group* left = (const Ms_Group*)vleft; + const Ms_Group* right = (const Ms_Group*)vright; + + // First reverse compare total + if (left->total > right->total) return -1; + if (left->total < right->total) return 1; + + /* Equal size => compare IPs. + This (somewhat?) helps to have deterministic test results. + If this would change between platforms, then we should compare + function names/filename/linenr */ + if (left->group_ip < right->group_ip) return -1; + if (left->group_ip > right->group_ip) return 1; + return 0; +} + +/* Scan the addresses in ms_ec at the given depth. + On return, + *groups points to an array of Ms_Group sorted by total. + *n_groups is the nr of groups + The caller is responsible to free the allocated group array. */ +static void ms_make_groups (UInt depth, Ms_Ec* ms_ec, UInt n_ec, SizeT sig_sz, + UInt* n_groups, Ms_Group** groups) +{ + UInt i, g; + Addr cur_group_ip = 0; + + *n_groups = 0; + + /* Handle special case somewhat more efficiently */ + if (n_ec == 0) { + *groups = NULL; + return; + } + + /* Compute how many groups we have. */ + for (i = 0; i < n_ec; i++) { + if (ms_ec[i].n_ips > depth + && (*n_groups == 0 || cur_group_ip != ms_ec[i].ips[depth])) { + (*n_groups)++; + cur_group_ip = ms_ec[i].ips[depth]; + } + } + + /* make the group array. */ + *groups = VG_(malloc)("ms_make_groups", *n_groups * sizeof(Ms_Group)); + i = 0; + for (g = 0; g < *n_groups; g++) { + while (ms_ec[i].n_ips <= depth) + i++; + cur_group_ip = ms_ec[i].ips[depth]; + (*groups)[g].group_ip = cur_group_ip; + (*groups)[g].ms_ec = &ms_ec[i]; + (*groups)[g].n_ec = 1; + (*groups)[g].total = ms_ec[i].report_value; + i++; + while (i < n_ec + && ms_ec[i].n_ips > depth + && cur_group_ip == ms_ec[i].ips[depth]) { + (*groups)[g].total += ms_ec[i].report_value; + i++; + (*groups)[g].n_ec++; + } + } + + /* Search for insignificant groups, collect them all together + in the first insignificant group, and compact the group array. */ + { + UInt insig1; // Position of first insignificant group. + UInt n_insig = 0; // Nr of insignificant groups found. + + for (g = 0; g < *n_groups; g++) { + if ((*groups)[g].total < sig_sz) { + if (n_insig == 0) { + // First insig group => transform it into the special group + (*groups)[g].ms_ec = NULL; + (*groups)[g].group_ip = 0; + (*groups)[g].n_ec = 0; + // start the sum of insig total as total + insig1 = g; + } else { + // Add this insig group total into insig1 first group + (*groups)[insig1].total += (*groups)[g].total; + } + n_insig++; + } else { + if (n_insig > 1) + (*groups)[g - n_insig + 1] = (*groups)[g]; + } + } + if (n_insig > 0) { + (*groups)[insig1].n_ec = n_insig; + *n_groups -= n_insig - 1; + } + DMSG(1, "depth %u n_groups %u n_insig %u\n", depth, *n_groups, n_insig); + } + + /* Sort on total size, bigger size first. */ + VG_(ssort) (*groups, *n_groups, sizeof(Ms_Group), ms_group_revcmp_total); +} + +static void ms_output_group (VgFile* fp, UInt depth, Ms_Group* group, + SizeT sig_sz, double sig_pct_threshold) +{ + UInt i; + Ms_Group* groups; + UInt n_groups; + + // If this is an insignificant group, handle it specially + if (group->ms_ec == NULL) { + const HChar* s = ( 1 == group->n_ec? "," : "s, all" ); + vg_assert (group->group_ip == 0); + FP("%*sn0: %lu in %d place%s below massif's threshold (%.2f%%)\n", + depth+1, "", group->total, group->n_ec, s, sig_pct_threshold); + return; + } + + // Normal group => output the group and its subgroups. + ms_make_groups (depth+1, group->ms_ec, group->n_ec, sig_sz, + &n_groups, &groups); + + FP("%*s" "n%u: %ld %s\n", + depth + 1, "", + n_groups, + group->total, + VG_(describe_IP)(group->ms_ec->ips[depth] - 1, NULL)); + /* XTREE??? Massif original code removes 1 to get the IP description. I am + wondering if this is not something that predates revision r8818, + which introduced a -1 in the stack unwind (see m_stacktrace.c) + Kept for the moment to allow exact comparison with massif output, but + probably we should remove this, as we very probably end up 2 bytes before + the RA Return Address. */ + + /* Output sub groups of this group. */ + for (i = 0; i < n_groups; i++) + ms_output_group (fp, depth+1, &groups[i], sig_sz, sig_pct_threshold); + + VG_(free)(groups); +} + +/* Allocate and build an array of Ms_Ec sorted by addresses in the + Ms_Ec StackTrace. */ +static void prepare_ms_ec (XTree* xt, + ULong (*report_value)(const void* value), + ULong* top_total, Ms_Ec** vms_ec, UInt* vn_ec) +{ + XT_shared* shared = xt->shared; + const UInt n_xecu = VG_(sizeXA)(shared->xec); + const UInt n_data_xecu = VG_(sizeXA)(xt->data); + Ms_Ec* ms_ec = VG_(malloc)("XT_massif_print.ms_ec", n_xecu * sizeof(Ms_Ec)); + UInt n_xecu_sel = 0; // Nr of xecu that are selected for output. + + vg_assert (n_data_xecu <= n_xecu); + + // Ensure we have in shared->ips_order_xecu our xecu sorted by StackTrace. + ensure_ips_order_xecu_valid(shared); + + *top_total = 0; + DMSG(1, "iteration %u\n", n_xecu); + for (UInt i = 0; i < n_xecu; i++) { + Xecu xecu = *(Xecu*)VG_(indexXA)(shared->ips_order_xecu, i); + xec* xe = (xec*)VG_(indexXA)(shared->xec, xecu); + + if (xecu >= n_data_xecu) + continue; // No data for this xecu in xt->data. + ms_ec[n_xecu_sel].n_ips = xe->n_ips_sel; + if (ms_ec[n_xecu_sel].n_ips == 0) + continue; + + ms_ec[n_xecu_sel].ips = VG_(get_ExeContext_StackTrace)(xe->ec) + xe->top; + ms_ec[n_xecu_sel].report_value + = (*report_value)(VG_(indexXA)(xt->data, xecu)); + *top_total += ms_ec[n_xecu_sel].report_value; + + n_xecu_sel++; + } + vg_assert(n_xecu_sel <= n_xecu); + + *vms_ec = ms_ec; + *vn_ec = n_xecu_sel; +} + +MsFile* VG_(XT_massif_open) + (const HChar* outfilename, + const HChar* desc, + const XArray* desc_args, + const HChar* time_unit) +{ + UInt i; + VgFile* fp = xt_open(outfilename); + + if (fp == NULL) + return NULL; // xt_open reported the error. + + /* ------ file header ------------------------------- */ + FP("desc:"); + if (desc) + FP(" %s", desc); + i = 0; + if (desc_args) { + for (i = 0; i < VG_(sizeXA)(desc_args); i++) { + HChar* arg = *(HChar**)VG_(indexXA)(desc_args, i); + FP(" %s", arg); + } + } + if (0 == i && desc == NULL) FP(" (none)"); + FP("\n"); + + FP_cmd(fp); + + FP("time_unit: %s\n", time_unit); + + return fp; +} + +void VG_(XT_massif_close)(MsFile* fp) +{ + if (fp == NULL) + return; // Error should have been reported by VG_(XT_massif_open) + + VG_(fclose)(fp); +} + +void VG_(XT_massif_print) + (MsFile* fp, + XTree* xt, + const Massif_Header* header, + ULong (*report_value)(const void* value)) +{ + UInt i; + + if (fp == NULL) + return; // Normally VG_(XT_massif_open) already reported an error. + + /* Compute/prepare Snapshot totals/data/... */ + ULong top_total; + + /* Following variables only used for detailed snapshot. */ + UInt n_ec = 0; + Ms_Ec* ms_ec = NULL; + const HChar* kind = + header->detailed ? (header->peak ? "peak" : "detailed") : "empty"; + + DMSG(1, "XT_massif_print %s\n", kind); + if (header->detailed) { + /* Prepare the Ms_Ec sorted array of stacktraces and the groups + at level 0. */ + prepare_ms_ec(xt, report_value, &top_total, &ms_ec, &n_ec); + DMSG(1, "XT_print_massif ms_ec n_ec %u\n", n_ec); + } else if (xt == NULL) { + /* Non detailed, no xt => use the sz provided in the header. */ + top_total = header->sz_B; + } else { + /* For non detailed snapshot, compute total directly from the xec. */ + const XT_shared* shared = xt->shared; + const UInt n_xecu = VG_(sizeXA)(xt->data); + top_total = 0; + + for (UInt xecu = 0; xecu < n_xecu; xecu++) { + xec* xe = (xec*)VG_(indexXA)(shared->xec, xecu); + if (xe->n_ips_sel == 0) + continue; + top_total += (*report_value)(VG_(indexXA)(xt->data, xecu)); + } + } + + /* ------ snapshot header --------------------------- */ + FP("#-----------\n"); + FP("snapshot=%d\n", header->snapshot_n); + FP("#-----------\n"); + FP("time=%lld\n", header->time); + + FP("mem_heap_B=%llu\n", top_total); // without extra_B and without stacks_B + FP("mem_heap_extra_B=%llu\n", header->extra_B); + FP("mem_stacks_B=%llu\n", header->stacks_B); + FP("heap_tree=%s\n", kind); + + /* ------ detailed snapshot data ----------------------------- */ + if (header->detailed) { + UInt n_groups; + Ms_Group* groups; + + ULong sig_sz; + // Work out how big a child must be to be significant. If the current + // top_total is zero, then we set it to 1, which means everything will be + // judged insignificant -- this is sensible, as there's no point showing + // any detail for this case. Unless they used threshold=0, in which + // case we show them everything because that's what they asked for. + // + // Nb: We do this once now, rather than once per child, because if we do + // that the cost of all the divisions adds up to something significant. + if (0 == top_total && 0 != header->sig_threshold) + sig_sz = 1; + else + sig_sz = ((top_total + header->extra_B + header->stacks_B) + * header->sig_threshold) / 100; + + /* Produce the groups at depth 0 */ + DMSG(1, "XT_massif_print producing depth 0 groups\n"); + ms_make_groups (0, ms_ec, n_ec, sig_sz, &n_groups, &groups); + + /* Output the top node. */ + FP("n%u: %llu %s\n", n_groups, top_total, header->top_node_desc); + + /* Output depth 0 groups. */ + DMSG(1, "XT_massif_print outputing %u depth 0 groups\n", n_groups); + for (i = 0; i < n_groups; i++) + ms_output_group (fp, 0, &groups[i], sig_sz, header->sig_threshold); + + VG_(free)(groups); + VG_(free)(ms_ec); + } +} + +Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips) +{ + /* Search for main or below main function. + To limit the nr of ips to examine, we maintain the deepest + offset where main was found, and we first search main + from there. + If no main is found, we will then do a search for main or + below main function till the top. */ + static Int deepest_main = 0; + Vg_FnNameKind kind = Vg_FnNameNormal; + Int mbm = n_ips - 1; // Position of deepest main or below main. + Vg_FnNameKind mbmkind = Vg_FnNameNormal; + Int i; + + for (i = n_ips - 1 - deepest_main; + i < n_ips; + i++) { + mbmkind = VG_(get_fnname_kind_from_IP)(ips[i]); + if (mbmkind != Vg_FnNameNormal) { + mbm = i; + break; + } + } + + /* Search for main or below main function till top. */ + for (i = mbm - 1; + i >= 0 && mbmkind != Vg_FnNameMain; + i--) { + kind = VG_(get_fnname_kind_from_IP)(ips[i]); + if (kind != Vg_FnNameNormal) { + mbm = i; + mbmkind = kind; + } + } + if (Vg_FnNameMain == mbmkind || Vg_FnNameBelowMain == mbmkind) { + if (mbmkind == Vg_FnNameMain && (n_ips - 1 - mbm) > deepest_main) + deepest_main = n_ips - 1 - mbm; + return mbm; + } else + return n_ips-1; +} + +void VG_(XT_filter_1top_and_maybe_below_main) + (Addr* ips, Int n_ips, + UInt* top, UInt* n_ips_sel) +{ + Int mbm; + + *n_ips_sel = n_ips; + if (n_ips == 0) { + *top = 0; + return; + } + + /* Filter top function. */ + *top = 1; + + if (VG_(clo_show_below_main)) + mbm = n_ips - 1; + else + mbm = VG_(XT_offset_main_or_below_main)(ips, n_ips); + + *n_ips_sel = mbm - *top + 1; +} + +void VG_(XT_filter_maybe_below_main) + (Addr* ips, Int n_ips, + UInt* top, UInt* n_ips_sel) +{ + Int mbm; + + *n_ips_sel = n_ips; + *top = 0; + if (n_ips == 0) + return; + + if (VG_(clo_show_below_main)) + mbm = n_ips - 1; + else + mbm = VG_(XT_offset_main_or_below_main)(ips, n_ips); + + *n_ips_sel = mbm - *top + 1; +} + +/*--------------------------------------------------------------------*/ +/*--- end m_xtree.c ---*/ +/*--------------------------------------------------------------------*/ diff --git a/coregrind/pub_core_options.h b/coregrind/pub_core_options.h index 7a887fc0d9..f249cfad21 100644 --- a/coregrind/pub_core_options.h +++ b/coregrind/pub_core_options.h @@ -207,7 +207,6 @@ extern Bool VG_(clo_profile_heap); extern Int VG_(clo_core_redzone_size); // VG_(clo_redzone_size) has default value -1, indicating to keep // the tool provided value. -extern Int VG_(clo_redzone_size); /* DEBUG: display gory details for the k'th most popular error. default: Infinity. */ extern Int VG_(clo_dump_error); diff --git a/coregrind/pub_core_xtmemory.h b/coregrind/pub_core_xtmemory.h new file mode 100644 index 0000000000..03f2e06c9d --- /dev/null +++ b/coregrind/pub_core_xtmemory.h @@ -0,0 +1,42 @@ + +/*--------------------------------------------------------------------*/ +/*--- Support functions for xtree memory reports. pub_tool_xtmemory.h ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2016-2016 Philippe Waroquiers + + 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. +*/ + +#ifndef __PUB_CORE_XTMEMORY_H +#define __PUB_CORE_XTMEMORY_H + +// No core-only exports; everything in this module is visible to both +// the core and tools. + +#include "pub_tool_xtmemory.h" + +#endif // __PUB_CORE_XTMEMORY_H + +/*-----------------------------------------------------------------------*/ +/*--- end pub_core_xtmemory.h ---*/ +/*-----------------------------------------------------------------------*/ diff --git a/coregrind/pub_core_xtree.h b/coregrind/pub_core_xtree.h new file mode 100644 index 0000000000..3e8bcd9329 --- /dev/null +++ b/coregrind/pub_core_xtree.h @@ -0,0 +1,42 @@ + +/*--------------------------------------------------------------------*/ +/*--- An xtree, tree of stacktraces with data pub_core_xtree.h ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2015-2015 Philippe Waroquiers + + 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. +*/ + +#ifndef __PUB_CORE_XTREE_H +#define __PUB_CORE_XTREE_H + +// No core-only exports; everything in this module is visible to both +// the core and tools. + +#include "pub_tool_xtree.h" + +#endif // __PUB_CORE_XTREE_H + +/*--------------------------------------------------------------------*/ +/*--- end pub_core_xtree.h ---*/ +/*--------------------------------------------------------------------*/ diff --git a/include/Makefile.am b/include/Makefile.am index ccfcd2778f..11c7ca86db 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -43,6 +43,8 @@ nobase_pkginclude_HEADERS = \ pub_tool_vkiscnums_asm.h \ pub_tool_wordfm.h \ pub_tool_xarray.h \ + pub_tool_xtree.h \ + pub_tool_xtmemory.h \ valgrind.h \ vki/vki-linux.h \ vki/vki-darwin.h \ diff --git a/include/pub_tool_options.h b/include/pub_tool_options.h index 94ed5442fd..8574fc3366 100644 --- a/include/pub_tool_options.h +++ b/include/pub_tool_options.h @@ -223,6 +223,26 @@ extern const HChar* VG_(clo_xml_user_comment); extern VexControl VG_(clo_vex_control); extern VexRegisterUpdates VG_(clo_px_file_backed); +extern Int VG_(clo_redzone_size); + +typedef + enum { + Vg_XTMemory_None, // Do not do any xtree memory profiling. + Vg_XTMemory_Allocs, // Currently allocated size xtree memory profiling + Vg_XTMemory_Full, // Full profiling : Current allocated size, total + // allocated size, nr of blocks, total freed size, ... + } + VgXTMemory; +// Tools that replace malloc can optionally implement memory profiling +// following the value of VG_(clo_xtree_profile_memory) to produce a report +// at the end of execution. +extern VgXTMemory VG_(clo_xtree_memory); +/* Holds the filename to use for xtree memory profiling output, before expansion + of %p and %q templates. */ +extern const HChar* VG_(clo_xtree_memory_file); +/* Compress strings in xtree dumps. */ +extern Bool VG_(clo_xtree_compress_strings); + /* Number of parents of a backtrace. Default: 12 */ extern Int VG_(clo_backtrace_size); diff --git a/include/pub_tool_xtmemory.h b/include/pub_tool_xtmemory.h new file mode 100644 index 0000000000..b1282d5518 --- /dev/null +++ b/include/pub_tool_xtmemory.h @@ -0,0 +1,86 @@ + +/*-----------------------------------------------------------------------*/ +/*--- Support functions for xtree memory reports. pub_tool_xtmemory.h ---*/ +/*-----------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2016-2016 Philippe Waroquiers + + 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. +*/ + +#ifndef __PUB_TOOL_XTMEMORY_H +#define __PUB_TOOL_XTMEMORY_H + +/* Type to profile allocated size and nr of blocks, typically used for + --xtree-memory=allocs. */ +typedef + struct _XT_Allocs { + SizeT nbytes; + SizeT nblocks; + } XT_Allocs; + +/* Support functions to produce a full xtree memory profiling. */ +/* tool must call VG_(XTMemory_Full_init) to ini full xtree memory profiling. */ +extern void VG_(XTMemory_Full_init) (XT_filter_IPs_t filter_IPs_Fn); +/* Then each time a certain nr of blocks are allocated or freed, the below + functions must be called. The arguments are: + szB: nr of bytes for the allocated/freed block(s) + ec_alloc : ExeContext of the allocation (original allocation for + free and resize_in_place). + ec_free : ExeContext of the free. + The tool is responsible to properly provide the ExeContext for + the allocation and free. For VG_(XTMemory_Full_free), ec_alloc + must be the one that was used for the allocation of the just released + block. */ +extern void VG_(XTMemory_Full_alloc)(SizeT szB, + ExeContext* ec_alloc); +extern void VG_(XTMemory_Full_free)(SizeT szB, + ExeContext* ec_alloc, + ExeContext* ec_free); +extern void VG_(XTMemory_Full_resize_in_place)(SizeT oldSzB, SizeT newSzB, + ExeContext* ec_alloc); + +/* Handle the production of a xtree memory report, either during run (fini False + e.g. via a gdb monitor command), or at the end of execution (fini True). + + VG_(XTMemory_report) behaviour depends on the value of the command line + options --xtree-memory=none|allocs|full and --xtree-memory-file= : + If --xtree-memory=full, the report will be produced from the data + provided via the calls to void VG_(XTMemory_Full_*). + Otherwise, for --xtree-memory=allocs or for --xtree-memory=none (if fini + is False), next_block is used to get the data for the report: + next_block is called repetitively to get information about all allocated + blocks, till xta->nblocks is 0. + If filename is NULL, --xtree-memory-file is used to produce the name. + filter_IPs_fn : used for --xtree-memory=allocs/none filtering (see + VG_(XT_create) and XT_filter_IPs_t typdef for more information). */ +extern void VG_(XTMemory_report) + (const HChar* filename, Bool fini, + void (*next_block)(XT_Allocs* xta, ExeContext** ec_alloc), + XT_filter_IPs_t filter_IPs_fn); + +#endif // __PUB_TOOL_XTMEMORY_H + + +/*-----------------------------------------------------------------------*/ +/*--- end pub_tool_xtmemory.h ---*/ +/*-----------------------------------------------------------------------*/ diff --git a/include/pub_tool_xtree.h b/include/pub_tool_xtree.h new file mode 100644 index 0000000000..a976a38b8f --- /dev/null +++ b/include/pub_tool_xtree.h @@ -0,0 +1,248 @@ + +/*--------------------------------------------------------------------*/ +/*--- An xtree, tree of stacktraces with data pub_tool_xtree.h ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2015-2016 Philippe Waroquiers + + 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. +*/ + +#ifndef __PUB_TOOL_XTREE_H +#define __PUB_TOOL_XTREE_H + +#include "pub_tool_basics.h" +#include "pub_tool_execontext.h" + +//-------------------------------------------------------------------- +// PURPOSE: an XTree is conceptually a set of stacktraces organised +// as a tree structure. +// A stacktrace (an Addr* ips, i.e. an array of IPs : Instruction Pointers) +// can be added to the tree once transformed into an execontext (ec). +// Some data (typically one or more integer values) can be attached to +// leafs of the tree. +// Non-leaf nodes data is build by combining (typically adding together) +// the data of their children nodes. +// An XTree can be output in various formats. +// +//-------------------------------------------------------------------- + + +/* It's an abstract type. */ +typedef struct _XTree XTree; + +/* 3 functions types used by an xtree to manipulate the data attached to leafs + of an XTree. + XT_init_data_t function is used to initialise (typically to 0) the data + of a new node. + XT_add_data_t function is used to add 'value' to the data 'to'. + XT_sub_data_t function is used to substract 'value' from the data 'from'. + + Note that the add/sub functions can do whatever operations to + combine/integrate value with/into to or from. In other words, add/sub + functions are in fact equivalent to 'Reduce' functions. Add/sub is used + as it is assumed that this module will be mostly used to follow + resource consumption, which can be more clearly modelled with add/sub. + For such resource consumption usage, typically, a call to add means that + some additional resource has been allocated or consumed or ... by the + given ExeContext. Similarly, a call to sub means that some resource + has been released/freed/... by the given execontext. + + Note however that there is no constraints in what add (or sub) can do. For + example, the add function could maintain Min/Max values, or an histogram of + values, or ... */ +typedef void (*XT_init_data_t) (void* value); +typedef void (*XT_add_data_t) (void* to, const void* value); +typedef void (*XT_sub_data_t) (void* from, const void* value); + +/* If not NULL, the XT_filter_IPs_t function is called when a new ec is inserted + in the XTree. + It indicates to the XTree to filter a range of IPs at the top and/or at + the bottom of the ec Stacktrace : *top is the offset of the first IP to take + into account. *n_ips_sel is the nr of IPs selected starting from *top. + + If XT_filter_IPs_t gives *n_ips_sel equal to 0, then the inserted ec will + be fully ignored when outputting the xtree: + the ec value(s) will not be counted in the XTree total, + the ec will not be printed/shown. + Note however that the filtering only influences the output of an XTree : + the ec is still inserted in the XTree, and the XT_*_data_t functions are + called in any case for such filtered ec. */ +typedef void (*XT_filter_IPs_t) (Addr* ips, Int n_ips, + UInt* top, UInt* n_ips_sel); + +/* Create new XTree, using given allocation and free function. + This function never returns NULL. + cc is the allocation cost centre. + alloc_fn must not return NULL (that is, if it returns it must have + succeeded.). + See respective typedef for *_fn arguments. */ +extern XTree* VG_(XT_create) ( void*(*alloc_fn)(const HChar*, SizeT), + const HChar* cc, + void(*free_fn) (void*), + Word dataSzB, + XT_init_data_t init_data_fn, + XT_add_data_t add_data_fn, + XT_sub_data_t sub_data_fn, + XT_filter_IPs_t filter_IPs_fn); + + +/* General useful filtering functions. */ + +/* Filter functions below main, unless VG_(clo_show_below_main) is True. */ +extern void VG_(XT_filter_maybe_below_main) + (Addr* ips, Int n_ips, + UInt* top, UInt* n_ips_sel); +/* Same as VG_(XT_filter_maybe_below_main) but also filters one top function + (typically to ignore the top level malloc/new/... fn). */ +extern void VG_(XT_filter_1top_and_maybe_below_main) + (Addr* ips, Int n_ips, + UInt* top, UInt* n_ips_sel); + +/* Search in ips[0..n_ips-1] the first function which is main or below main + and return its offset. + If no main or below main is found, return n_ips-1 */ +extern Int VG_(XT_offset_main_or_below_main)(Addr* ips, Int n_ips); + + +/* Take a (frozen) snapshot of xt. + Note that the resulting XTree is 'read-only' : calls to + VG_(XT_add_to_*)/VG_(XT_sub_from_*) will assert. + + Note: to spare memory, some data is shared between an xt and all its + snapshots. This memory is released when the last XTree using this memory + is deleted. */ +extern XTree* VG_(XT_snapshot)(XTree* xt); + +/* Non frozen dup currently not needed : + extern XTree* VG_(XT_dup)(XTree* xt); */ + +/* Free all memory associated with an XTRee. */ +extern void VG_(XT_delete)(XTree* xt); + +/* an Xecu identifies an exe context+its associated data in an XTree. */ +typedef UInt Xecu; + +/* If not yet in xt, inserts the provided ec and initialises its + data by calling init_data_fn. + If already present (or after insertion), updates the data by calling + add_data_fn. */ +extern Xecu VG_(XT_add_to_ec)(XTree* xt, ExeContext* ec, const void* value); + +/* If not yet in xt, inserts the provided ec and initialises its + data by calling init_data_fn. + If already present (or after insertion), updates the data by calling + sub_data_fn to substract value from the data associated to ec. */ +extern Xecu VG_(XT_sub_from_ec)(XTree* xt, ExeContext* ec, const void* value); + +/* Same as (but more efficient than) VG_(XT_add_to_ec) and VG_(XT_sub_from_ec) + for an ec already inserted in xt. */ +extern void VG_(XT_add_to_xecu)(XTree* xt, Xecu xecu, const void* value); +extern void VG_(XT_sub_from_xecu)(XTree* xt, Xecu xecu, const void* value); + +/* Return the nr of IPs selected for xecu. 0 means fully filtered. */ +extern UInt VG_(XT_n_ips_sel)(XTree* xt, Xecu xecu); + +/* Return the ExeContext associated to the Xecu. */ +extern ExeContext* VG_(XT_get_ec_from_xecu) (XTree* xt, Xecu xecu); + +/* -------------------- CALLGRIND/KCACHEGRIND OUTPUT FORMAT --------------*/ +/* Prints xt in outfilename in callgrind/kcachegrind format. + events is a comma separated list of events, used by + kcachegrind/callgrind_annotate/... to name the value various components. + An event can optionally have a longer description, separated from the + event name by " : ", e.g. + "curB : currently allocated Bytes,curBk : Currently allocated Blocks" + img_value returns an image of the value. The image must be a space + separated set of integers, matching the corresponding event in events. + Note that the returned pointer can be static data. + img_value can return NULL if value (and its associated ExeContext) should + not be printed. +*/ +extern void VG_(XT_callgrind_print) + (XTree* xt, + const HChar* outfilename, + const HChar* events, + const HChar* (*img_value) (const void* value)); + + +/* -------------------- MASSIF OUTPUT FORMAT --------------*/ +// Time is measured either in i or ms or bytes, depending on the --time-unit +// option. It's a Long because it can exceed 32-bits reasonably easily, and +// because we need to allow negative values to represent unset times. +typedef Long Time; + +typedef void MsFile; + +/* Create a new file or truncate existing file for printing xtrees in + massif format. time_unit is a string describing the unit used + in Massif_Header time. + Produces a user error msg and returns NULL if file cannot be opened. + Caller must VG_(XT_massif_close) the returned file. */ +extern MsFile* VG_(XT_massif_open)(const HChar* outfilename, + const HChar* desc, // can be NULL + const XArray* desc_args, // can be NULL + const HChar* time_unit); + +extern void VG_(XT_massif_close)(MsFile* fp); + +typedef + struct { + int snapshot_n; // starting at 0. + Time time; + + ULong sz_B; // sum of values, only used when printing a NULL xt. + ULong extra_B; + ULong stacks_B; + + Bool detailed; + Bool peak; + + /* top_node_desc: description for the top node. + Typically for memory usage, give xt_heap_alloc_functions_desc. */ + const HChar* top_node_desc; + + /* children with less than sig_threshold * total xt sz will be aggregated + and printed as one single child. */ + double sig_threshold; + + } Massif_Header; + +/* Prints xt in outfilename in massif format. + If a NULL xt is provided, then only the header information is used + to produce the (necessarily not detailed) snapshot. + report_value must return the value to be used for the report production. + It will typically be the nr of bytes allocated stored with the execontext + but it could be anything measured with a ULong (e.g. the nr of blocks + allocated, or a number of calls, ...). +*/ +extern void VG_(XT_massif_print) + (MsFile* fp, + XTree* xt, + const Massif_Header* header, + ULong (*report_value)(const void* value)); + +#endif // __PUB_TOOL_XTREE_H + +/*--------------------------------------------------------------------*/ +/*--- end pub_tool_xtree.h ---*/ +/*--------------------------------------------------------------------*/