From ce806ed31f1399815e7fa40f96f93894ab5afd2e Mon Sep 17 00:00:00 2001 From: Philippe Waroquiers Date: Thu, 26 Jan 2012 23:13:52 +0000 Subject: [PATCH] (fixes bug 289939 wish: complete monitor cmd 'leak_check' with details about leaked or reachable blocks) This patch implements two new memcheck gdbserver monitor commands: block_list after a leak search, shows the list of blocks of who_points_at [] shows places pointing inside (default 1) bytes at (with len 1, only shows "start pointers" pointing exactly to , with len > 1, will also show "interior pointers") Compiled and reg-tested on f12/x86, deb5/amd64, f16/ppc64. The 'block_list' command is implemented on top of the lr_array/lc_chunks/lc_extras arrays used during the last leak search. NB: no impact on the memory for the typical Valgrind usage where a leak search is only done at the end of the run. Printing the block_list of a loss record simply consists in scanning the lc_chunks to find back the chunks corresponding to the loss record for which block lists is requested. The 'who_points_at' command is implemented by doing a scan similar to (but simpler than) the leak search scan. lc_scan_memory has been enhanced to have a mode to search for a specific address, rather than to search for all allocated blocks. VG_(apply_to_GP_regs) has been enhanced to also provide the ThreadId and register name in the callback function. The patch touches multiple files (but most changes are easy/trivial or factorise existing code). Most significant changes are in memcheck/mc_leakcheck.c : * changed the LC_Extra struct to remember the clique for indirect leaks (size of structure not changed). * made lr_array a static global * changed lc_scan_memory: to have a search mode for a specific address (for who_points_at) (for leak search) to pass a 'current clique' in addition to the clique leader so as to have a proper clique hierarchy for indirectly leaked blocks. * print_results: reset values at the beginning of the print_result of the next leak search, rather than at the end of print_results of the previous leak search. This allows to continue showing the same info for loss records till a new leak search is done. * new function print_clique which recursively prints a group of leaked blocks, starting from the clique leader. * new function MC_(print_block_list) : calls print_clique for each clique leader found for the given loss record. * static void scan_memory_root_set : code extracted from MC_(detect_memory_leaks) (no relevant change) * void MC_(who_points_at) : calls scan_memory_root_set, lc_scan_memory and VG_(apply_to_GP_regs)(search_address_in_GP_reg) to search pointers to the given address. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@12357 --- NEWS | 6 + coregrind/m_gdbserver/README_DEVELOPERS | 8 +- coregrind/m_machine.c | 184 +++---- gdbserver_tests/Makefile.am | 4 + gdbserver_tests/filter_gdb | 2 +- gdbserver_tests/mcblocklistsearch.stderr.exp | 0 gdbserver_tests/mcblocklistsearch.stderrB.exp | 67 +++ gdbserver_tests/mcblocklistsearch.stdinB.gdb | 31 ++ gdbserver_tests/mcblocklistsearch.vgtest | 12 + gdbserver_tests/mchelp.stdoutB.exp | 12 + include/pub_tool_machine.h | 3 +- memcheck/docs/mc-manual.xml | 187 +++++++- memcheck/mc_errors.c | 168 ++++--- memcheck/mc_include.h | 17 +- memcheck/mc_leakcheck.c | 453 ++++++++++++++---- memcheck/mc_main.c | 38 +- memcheck/mc_malloc_wrappers.c | 6 + 17 files changed, 888 insertions(+), 310 deletions(-) create mode 100644 gdbserver_tests/mcblocklistsearch.stderr.exp create mode 100644 gdbserver_tests/mcblocklistsearch.stderrB.exp create mode 100644 gdbserver_tests/mcblocklistsearch.stdinB.gdb create mode 100644 gdbserver_tests/mcblocklistsearch.vgtest diff --git a/NEWS b/NEWS index 2bb6cfb47..79fd6cb1c 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,11 @@ Release 3.8.0 (????) - reduction of memory use for applications allocating many blocks and/or having many partially defined bytes. + - Addition of GDB server monitor command 'block_list' that lists + the addresses/sizes of the blocks of a leak search loss record. + + - Addition of GDB server monitor command 'who_points_at' that lists + the locations pointing at a block. * ==================== OTHER CHANGES ==================== @@ -43,6 +48,7 @@ where XXXXXX is the bug number as listed below. 286374 Running cachegrind with --branch-sim=yes on 64-bit PowerPC program fails 287858 VG_(strerror): unknown error 289699 vgdb connection in relay mode erroneously closed due to buffer overrun +289939 wish: complete monitor cmd 'leak_check' with details about leaked or reachable blocks Release 3.7.0 (5 November 2011) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/coregrind/m_gdbserver/README_DEVELOPERS b/coregrind/m_gdbserver/README_DEVELOPERS index 88aa6175b..55d0790f6 100644 --- a/coregrind/m_gdbserver/README_DEVELOPERS +++ b/coregrind/m_gdbserver/README_DEVELOPERS @@ -305,11 +305,9 @@ TODO and/or additional nice things to have its arguments like the startup options of m_main.c and tool clo processing. * have a memcheck monitor command - who_points_at
| - that would describe the addresses where a pointer is found - to address (or address leaked at loss_record_nr>) - This would allow to interactively searching who is "keeping" a piece - of memory. + show_dangling_pointers [last_n_recently_released_blocks] + showing which of the n last recently released blocks are still + referenced. These references are (potential) dangling pointers. * some GDBTD in the code diff --git a/coregrind/m_machine.c b/coregrind/m_machine.c index bbfaa1f83..7043aea9e 100644 --- a/coregrind/m_machine.c +++ b/coregrind/m_machine.c @@ -186,115 +186,115 @@ VG_(set_shadow_regs_area) ( ThreadId tid, } -static void apply_to_GPs_of_tid(VexGuestArchState* vex, void (*f)(Addr)) +static void apply_to_GPs_of_tid(ThreadId tid, void (*f)(ThreadId, HChar*, Addr)) { + VexGuestArchState* vex = &(VG_(get_ThreadState)(tid)->arch.vex); #if defined(VGA_x86) - (*f)(vex->guest_EAX); - (*f)(vex->guest_ECX); - (*f)(vex->guest_EDX); - (*f)(vex->guest_EBX); - (*f)(vex->guest_ESI); - (*f)(vex->guest_EDI); - (*f)(vex->guest_ESP); - (*f)(vex->guest_EBP); + (*f)(tid, "EAX", vex->guest_EAX); + (*f)(tid, "ECX", vex->guest_ECX); + (*f)(tid, "EDX", vex->guest_EDX); + (*f)(tid, "EBX", vex->guest_EBX); + (*f)(tid, "ESI", vex->guest_ESI); + (*f)(tid, "EDI", vex->guest_EDI); + (*f)(tid, "ESP", vex->guest_ESP); + (*f)(tid, "EBP", vex->guest_EBP); #elif defined(VGA_amd64) - (*f)(vex->guest_RAX); - (*f)(vex->guest_RCX); - (*f)(vex->guest_RDX); - (*f)(vex->guest_RBX); - (*f)(vex->guest_RSI); - (*f)(vex->guest_RDI); - (*f)(vex->guest_RSP); - (*f)(vex->guest_RBP); - (*f)(vex->guest_R8); - (*f)(vex->guest_R9); - (*f)(vex->guest_R10); - (*f)(vex->guest_R11); - (*f)(vex->guest_R12); - (*f)(vex->guest_R13); - (*f)(vex->guest_R14); - (*f)(vex->guest_R15); + (*f)(tid, "RAX", vex->guest_RAX); + (*f)(tid, "RCX", vex->guest_RCX); + (*f)(tid, "RDX", vex->guest_RDX); + (*f)(tid, "RBX", vex->guest_RBX); + (*f)(tid, "RSI", vex->guest_RSI); + (*f)(tid, "RDI", vex->guest_RDI); + (*f)(tid, "RSP", vex->guest_RSP); + (*f)(tid, "RBP", vex->guest_RBP); + (*f)(tid, "R8" , vex->guest_R8 ); + (*f)(tid, "R9" , vex->guest_R9 ); + (*f)(tid, "R10", vex->guest_R10); + (*f)(tid, "R11", vex->guest_R11); + (*f)(tid, "R12", vex->guest_R12); + (*f)(tid, "R13", vex->guest_R13); + (*f)(tid, "R14", vex->guest_R14); + (*f)(tid, "R15", vex->guest_R15); #elif defined(VGA_ppc32) || defined(VGA_ppc64) - (*f)(vex->guest_GPR0); - (*f)(vex->guest_GPR1); - (*f)(vex->guest_GPR2); - (*f)(vex->guest_GPR3); - (*f)(vex->guest_GPR4); - (*f)(vex->guest_GPR5); - (*f)(vex->guest_GPR6); - (*f)(vex->guest_GPR7); - (*f)(vex->guest_GPR8); - (*f)(vex->guest_GPR9); - (*f)(vex->guest_GPR10); - (*f)(vex->guest_GPR11); - (*f)(vex->guest_GPR12); - (*f)(vex->guest_GPR13); - (*f)(vex->guest_GPR14); - (*f)(vex->guest_GPR15); - (*f)(vex->guest_GPR16); - (*f)(vex->guest_GPR17); - (*f)(vex->guest_GPR18); - (*f)(vex->guest_GPR19); - (*f)(vex->guest_GPR20); - (*f)(vex->guest_GPR21); - (*f)(vex->guest_GPR22); - (*f)(vex->guest_GPR23); - (*f)(vex->guest_GPR24); - (*f)(vex->guest_GPR25); - (*f)(vex->guest_GPR26); - (*f)(vex->guest_GPR27); - (*f)(vex->guest_GPR28); - (*f)(vex->guest_GPR29); - (*f)(vex->guest_GPR30); - (*f)(vex->guest_GPR31); - (*f)(vex->guest_CTR); - (*f)(vex->guest_LR); + (*f)(tid, "GPR0" , vex->guest_GPR0 ); + (*f)(tid, "GPR1" , vex->guest_GPR1 ); + (*f)(tid, "GPR2" , vex->guest_GPR2 ); + (*f)(tid, "GPR3" , vex->guest_GPR3 ); + (*f)(tid, "GPR4" , vex->guest_GPR4 ); + (*f)(tid, "GPR5" , vex->guest_GPR5 ); + (*f)(tid, "GPR6" , vex->guest_GPR6 ); + (*f)(tid, "GPR7" , vex->guest_GPR7 ); + (*f)(tid, "GPR8" , vex->guest_GPR8 ); + (*f)(tid, "GPR9" , vex->guest_GPR9 ); + (*f)(tid, "GPR10", vex->guest_GPR10); + (*f)(tid, "GPR11", vex->guest_GPR11); + (*f)(tid, "GPR12", vex->guest_GPR12); + (*f)(tid, "GPR13", vex->guest_GPR13); + (*f)(tid, "GPR14", vex->guest_GPR14); + (*f)(tid, "GPR15", vex->guest_GPR15); + (*f)(tid, "GPR16", vex->guest_GPR16); + (*f)(tid, "GPR17", vex->guest_GPR17); + (*f)(tid, "GPR18", vex->guest_GPR18); + (*f)(tid, "GPR19", vex->guest_GPR19); + (*f)(tid, "GPR20", vex->guest_GPR20); + (*f)(tid, "GPR21", vex->guest_GPR21); + (*f)(tid, "GPR22", vex->guest_GPR22); + (*f)(tid, "GPR23", vex->guest_GPR23); + (*f)(tid, "GPR24", vex->guest_GPR24); + (*f)(tid, "GPR25", vex->guest_GPR25); + (*f)(tid, "GPR26", vex->guest_GPR26); + (*f)(tid, "GPR27", vex->guest_GPR27); + (*f)(tid, "GPR28", vex->guest_GPR28); + (*f)(tid, "GPR29", vex->guest_GPR29); + (*f)(tid, "GPR30", vex->guest_GPR30); + (*f)(tid, "GPR31", vex->guest_GPR31); + (*f)(tid, "CTR" , vex->guest_CTR ); + (*f)(tid, "LR" , vex->guest_LR ); #elif defined(VGA_arm) - (*f)(vex->guest_R0); - (*f)(vex->guest_R1); - (*f)(vex->guest_R2); - (*f)(vex->guest_R3); - (*f)(vex->guest_R4); - (*f)(vex->guest_R5); - (*f)(vex->guest_R6); - (*f)(vex->guest_R8); - (*f)(vex->guest_R9); - (*f)(vex->guest_R10); - (*f)(vex->guest_R11); - (*f)(vex->guest_R12); - (*f)(vex->guest_R13); - (*f)(vex->guest_R14); + (*f)(tid, "R0" , vex->guest_R0 ); + (*f)(tid, "R1" , vex->guest_R1 ); + (*f)(tid, "R2" , vex->guest_R2 ); + (*f)(tid, "R3" , vex->guest_R3 ); + (*f)(tid, "R4" , vex->guest_R4 ); + (*f)(tid, "R5" , vex->guest_R5 ); + (*f)(tid, "R6" , vex->guest_R6 ); + (*f)(tid, "R8" , vex->guest_R8 ); + (*f)(tid, "R9" , vex->guest_R9 ); + (*f)(tid, "R10", vex->guest_R10); + (*f)(tid, "R11", vex->guest_R11); + (*f)(tid, "R12", vex->guest_R12); + (*f)(tid, "R13", vex->guest_R13); + (*f)(tid, "R14", vex->guest_R14); #elif defined(VGA_s390x) - (*f)(vex->guest_r0); - (*f)(vex->guest_r1); - (*f)(vex->guest_r2); - (*f)(vex->guest_r3); - (*f)(vex->guest_r4); - (*f)(vex->guest_r5); - (*f)(vex->guest_r6); - (*f)(vex->guest_r7); - (*f)(vex->guest_r8); - (*f)(vex->guest_r9); - (*f)(vex->guest_r10); - (*f)(vex->guest_r11); - (*f)(vex->guest_r12); - (*f)(vex->guest_r13); - (*f)(vex->guest_r14); - (*f)(vex->guest_r15); + (*f)(tid, "r0" , vex->guest_r0 ); + (*f)(tid, "r1" , vex->guest_r1 ); + (*f)(tid, "r2" , vex->guest_r2 ); + (*f)(tid, "r3" , vex->guest_r3 ); + (*f)(tid, "r4" , vex->guest_r4 ); + (*f)(tid, "r5" , vex->guest_r5 ); + (*f)(tid, "r6" , vex->guest_r6 ); + (*f)(tid, "r7" , vex->guest_r7 ); + (*f)(tid, "r8" , vex->guest_r8 ); + (*f)(tid, "r9" , vex->guest_r9 ); + (*f)(tid, "r10", vex->guest_r10); + (*f)(tid, "r11", vex->guest_r11); + (*f)(tid, "r12", vex->guest_r12); + (*f)(tid, "r13", vex->guest_r13); + (*f)(tid, "r14", vex->guest_r14); + (*f)(tid, "r15", vex->guest_r15); #else # error Unknown arch #endif } -void VG_(apply_to_GP_regs)(void (*f)(UWord)) +void VG_(apply_to_GP_regs)(void (*f)(ThreadId, HChar*, UWord)) { ThreadId tid; for (tid = 1; tid < VG_N_THREADS; tid++) { if (VG_(is_valid_tid)(tid)) { - ThreadState* tst = VG_(get_ThreadState)(tid); - apply_to_GPs_of_tid(&(tst->arch.vex), f); + apply_to_GPs_of_tid(tid, f); } } } diff --git a/gdbserver_tests/Makefile.am b/gdbserver_tests/Makefile.am index 2891c8ba1..310baedb7 100644 --- a/gdbserver_tests/Makefile.am +++ b/gdbserver_tests/Makefile.am @@ -8,6 +8,10 @@ dist_noinst_SCRIPTS = \ EXTRA_DIST = \ README_DEVELOPERS \ + mcblocklistsearch.stderr.exp \ + mcblocklistsearch.stdinB.gdb \ + mcblocklistsearch.vgtest \ + mcblocklistsearch.stderrB.exp \ mcbreak.stderrB.exp \ mcbreak.stderr.exp \ mcbreak.stdinB.gdb \ diff --git a/gdbserver_tests/filter_gdb b/gdbserver_tests/filter_gdb index be74c787c..6f16dcf4c 100755 --- a/gdbserver_tests/filter_gdb +++ b/gdbserver_tests/filter_gdb @@ -10,7 +10,7 @@ $dir/filter_stderr | $dir/../tests/filter_addresses | # memcheck stuff -$dir/filter_memcheck_monitor | +$dir/filter_memcheck_monitor "$@" | # Anonymise or remove : diff --git a/gdbserver_tests/mcblocklistsearch.stderr.exp b/gdbserver_tests/mcblocklistsearch.stderr.exp new file mode 100644 index 000000000..e69de29bb diff --git a/gdbserver_tests/mcblocklistsearch.stderrB.exp b/gdbserver_tests/mcblocklistsearch.stderrB.exp new file mode 100644 index 000000000..eb4c7e44a --- /dev/null +++ b/gdbserver_tests/mcblocklistsearch.stderrB.exp @@ -0,0 +1,67 @@ +relaying data between gdb and process .... +vgdb-error value changed from 0 to 999999 +Breakpoint 1 at 0x........: file leak-tree.c, line 42. +Breakpoint 2 at 0x........: file leak-tree.c, line 67. +Continuing. +Breakpoint 1, f () at leak-tree.c:42 +42 t->l = mk(); // B +Continuing. +Breakpoint 2, main () at leak-tree.c:67 +67 PRINT_LEAK_COUNTS(stderr); +Searching for pointers to 0x........ +*0x........ points at 0x........ + Address 0x........ is 0 bytes inside data symbol "t" +full leak search +16 bytes in 1 blocks are still reachable in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:41) + by 0x........: main (leak-tree.c:63) +16 bytes in 1 blocks are still reachable in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:42) + by 0x........: main (leak-tree.c:63) +16 bytes in 1 blocks are still reachable in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:45) + by 0x........: main (leak-tree.c:63) +16 bytes in 1 blocks are indirectly lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:46) + by 0x........: main (leak-tree.c:63) +16 bytes in 1 blocks are indirectly lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:47) + by 0x........: main (leak-tree.c:63) +16 bytes in 1 blocks are definitely lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:44) + by 0x........: main (leak-tree.c:63) +48 (16 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:43) + by 0x........: main (leak-tree.c:63) +block list 6 D +16 bytes in 1 blocks are definitely lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:44) + by 0x........: main (leak-tree.c:63) +0x........[16] +block list 7 C F G +48 (16 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record ... of ... + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: mk (leak-tree.c:28) + by 0x........: f (leak-tree.c:43) + by 0x........: main (leak-tree.c:63) +0x........[16] + 0x........[16] indirect loss record 4 + 0x........[16] indirect loss record 5 +monitor command request to kill this process +Remote connection closed diff --git a/gdbserver_tests/mcblocklistsearch.stdinB.gdb b/gdbserver_tests/mcblocklistsearch.stdinB.gdb new file mode 100644 index 000000000..19fe0e531 --- /dev/null +++ b/gdbserver_tests/mcblocklistsearch.stdinB.gdb @@ -0,0 +1,31 @@ +# connect gdb to Valgrind gdbserver: +target remote | ./vgdb --wait=60 --vgdb-prefix=./vgdb-prefix-mcblocklistsearch +echo vgdb launched process attached\n +monitor v.set vgdb-error 999999 +# +# +# insert break after the allocation of A +break leak-tree.c:42 +# insert break after returning from function f +break leak-tree.c:67 +# +# continue till //1break: +continue +# save the value of A +set $0xA = t +# +# continue till 2nd break +continue +# +# check who points at A +eval "monitor who_points_at 0x%x 1", $0xA +# do a leak check, and then list the blocks lost +echo full leak search \n +monitor leak_check full reachable any +# +echo block list 6 D \n +monitor block_list 6 +echo block list 7 C F G \n +monitor block_list 7 +monitor v.kill +quit diff --git a/gdbserver_tests/mcblocklistsearch.vgtest b/gdbserver_tests/mcblocklistsearch.vgtest new file mode 100644 index 000000000..bced1cde8 --- /dev/null +++ b/gdbserver_tests/mcblocklistsearch.vgtest @@ -0,0 +1,12 @@ +# test the memcheck block_list and search monitor commands. +prog: ../memcheck/tests/leak-tree +vgopts: --tool=memcheck --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-mcblocklistsearch -q +prereq: test -e gdb.eval +stdout_filter: filter_make_empty +stderr_filter: filter_make_empty +progB: gdb +argsB: --quiet -l 60 --nx 1>&2 ../memcheck/tests/leak-tree +stdinB: mcblocklistsearch.stdinB.gdb +stdoutB_filter: filter_make_empty +stderrB_filter: filter_gdb +stderrB_filter_args: leak-tree.c diff --git a/gdbserver_tests/mchelp.stdoutB.exp b/gdbserver_tests/mchelp.stdoutB.exp index a12b4c4e5..20b2e7673 100644 --- a/gdbserver_tests/mchelp.stdoutB.exp +++ b/gdbserver_tests/mchelp.stdoutB.exp @@ -28,6 +28,12 @@ memcheck monitor commands: Examples: leak_check leak_check summary any leak_check full reachable any limited 100 + block_list + after a leak search, shows the list of blocks of + who_points_at [] + shows places pointing inside (default 1) bytes at + (with len 1, only shows "start pointers" pointing exactly to , + with len > 1, will also show "interior pointers") general valgrind monitor commands: help [debug] : monitor command help. With debug: + debugging commands @@ -67,5 +73,11 @@ memcheck monitor commands: Examples: leak_check leak_check summary any leak_check full reachable any limited 100 + block_list + after a leak search, shows the list of blocks of + who_points_at [] + shows places pointing inside (default 1) bytes at + (with len 1, only shows "start pointers" pointing exactly to , + with len > 1, will also show "interior pointers") monitor command request to kill this process diff --git a/include/pub_tool_machine.h b/include/pub_tool_machine.h index c86fff004..49c700d3e 100644 --- a/include/pub_tool_machine.h +++ b/include/pub_tool_machine.h @@ -121,7 +121,8 @@ void VG_(set_syscall_return_shadows) ( ThreadId tid, // current threads. // This is very Memcheck-specific -- it's used to find the roots when // doing leak checking. -extern void VG_(apply_to_GP_regs)(void (*f)(UWord val)); +extern void VG_(apply_to_GP_regs)(void (*f)(ThreadId tid, + HChar* regname, UWord val)); // This iterator lets you inspect each live thread's stack bounds. // Returns False at the end. 'tid' is the iterator and you can only diff --git a/memcheck/docs/mc-manual.xml b/memcheck/docs/mc-manual.xml index 64817e94c..3503b5979 100644 --- a/memcheck/docs/mc-manual.xml +++ b/memcheck/docs/mc-manual.xml @@ -1372,7 +1372,7 @@ $10 = 0x0 (default 1) bytes at <addr> has the specified accessibility. It then outputs a description of <addr>. In the following example, a detailed description is available because the - option was given Valgrind at + option was given at Valgrind startup: Note that when using Valgrind's gdbserver, it is not @@ -1481,6 +1481,143 @@ Address 0x8049E28 len 1 defined + + block_list <loss_record_nr> + shows the list of blocks belonging to <loss_record_nr>. + + + A leak search merges the allocated blocks in loss records : + a loss record re-groups all blocks having the same state (for + example, Definitely Lost) and the same allocation backtrace. + Each loss record is identified in the leak search result + by a loss record number. + The block_list command shows the loss record information + followed by the addresses and sizes of the blocks which have been + merged in the loss record. + + + If a directly lost block causes some other blocks to be indirectly + lost, the block_list command will also show these indirectly lost blocks. + The indirectly lost blocks will be indented according to the level of indirection + between the directly lost block and the indirectly lost block(s). + Each indirectly lost block is followed by the reference of its loss record. + + + The block_list command can be used on the results of a leak search as long + as no block has been freed after this leak search: as soon as the program frees + a block, a new leak search is needed before block_list can be used again. + + + + In the below example, the program leaks a tree structure by losing the pointer to + the block A (top of the tree). + So, the block A is directly lost, causing an indirect + loss of blocks B to G. The first block_list command shows the loss record of A + (a definitely lost block with address 0x4028028, size 16). The addresses and sizes + of the indirectly lost blocks due to block A are shown below the block A. + The second command shows the details of one of the indirect loss records output + by the first command. + + + + + + + + + who_points_at <addr> [<len>] + shows all the locations where a pointer to addr is found. + If len is equal to 1, the command only shows the locations pointing + exactly at addr (i.e. the "start pointers" to addr). + If len is > 1, "interior pointers" pointing at the len first bytes + will also be shown. + + + The locations searched for are the same as the locations + used in the leak search. So, who_points_at can a.o. + be used to show why the leak search still can reach a block, or can + search for dangling pointers to a freed block. + Each location pointing at addr (or pointing inside addr if interior pointers + are being searched for) will be described. + + + In the below example, the pointers to the 'tree block A' (see example + in command block_list) is shown before the tree was leaked. + The descriptions are detailed as the option + was given at Valgrind startup. The second call shows the pointers (start and interior + pointers) to block G. The block G (0x40281A8) is reachable via block C (0x40280a8) + and register ECX of tid 1 (tid is the Valgrind thread id). + It is "interior reachable" via the register EBX. + + + + + + diff --git a/memcheck/mc_errors.c b/memcheck/mc_errors.c index dedeeccdd..7c09804f2 100644 --- a/memcheck/mc_errors.c +++ b/memcheck/mc_errors.c @@ -447,6 +447,95 @@ char * MC_(snprintf_delta) (char * buf, Int size, return buf; } +static void pp_LossRecord(UInt n_this_record, UInt n_total_records, + LossRecord* lr, Bool xml) +{ + // char arrays to produce the indication of increase/decrease in case + // of delta_mode != LCD_Any + char d_bytes[20]; + char d_direct_bytes[20]; + char d_indirect_bytes[20]; + char d_num_blocks[20]; + + MC_(snprintf_delta) (d_bytes, 20, + lr->szB + lr->indirect_szB, + lr->old_szB + lr->old_indirect_szB, + MC_(detect_memory_leaks_last_delta_mode)); + MC_(snprintf_delta) (d_direct_bytes, 20, + lr->szB, + lr->old_szB, + MC_(detect_memory_leaks_last_delta_mode)); + MC_(snprintf_delta) (d_indirect_bytes, 20, + lr->indirect_szB, + lr->old_indirect_szB, + MC_(detect_memory_leaks_last_delta_mode)); + MC_(snprintf_delta) (d_num_blocks, 20, + (SizeT) lr->num_blocks, + (SizeT) lr->old_num_blocks, + MC_(detect_memory_leaks_last_delta_mode)); + + if (xml) { + emit(" %s\n", xml_leak_kind(lr->key.state)); + if (lr->indirect_szB > 0) { + emit( " \n" ); + emit( " %'lu%s (%'lu%s direct, %'lu%s indirect) bytes " + "in %'u%s blocks" + " are %s in loss record %'u of %'u\n", + lr->szB + lr->indirect_szB, d_bytes, + lr->szB, d_direct_bytes, + lr->indirect_szB, d_indirect_bytes, + lr->num_blocks, d_num_blocks, + str_leak_lossmode(lr->key.state), + n_this_record, n_total_records ); + // Nb: don't put commas in these XML numbers + emit( " %lu\n", + lr->szB + lr->indirect_szB ); + emit( " %u\n", lr->num_blocks ); + emit( " \n" ); + } else { + emit( " \n" ); + emit( " %'lu%s bytes in %'u%s blocks" + " are %s in loss record %'u of %'u\n", + lr->szB, d_direct_bytes, + lr->num_blocks, d_num_blocks, + str_leak_lossmode(lr->key.state), + n_this_record, n_total_records ); + emit( " %ld\n", lr->szB); + emit( " %d\n", lr->num_blocks); + emit( " \n" ); + } + VG_(pp_ExeContext)(lr->key.allocated_at); + } else { /* ! if (xml) */ + if (lr->indirect_szB > 0) { + emit( + "%'lu%s (%'lu%s direct, %'lu%s indirect) bytes in %'u%s blocks" + " are %s in loss record %'u of %'u\n", + lr->szB + lr->indirect_szB, d_bytes, + lr->szB, d_direct_bytes, + lr->indirect_szB, d_indirect_bytes, + lr->num_blocks, d_num_blocks, + str_leak_lossmode(lr->key.state), + n_this_record, n_total_records + ); + } else { + emit( + "%'lu%s bytes in %'u%s blocks are %s in loss record %'u of %'u\n", + lr->szB, d_direct_bytes, + lr->num_blocks, d_num_blocks, + str_leak_lossmode(lr->key.state), + n_this_record, n_total_records + ); + } + VG_(pp_ExeContext)(lr->key.allocated_at); + } /* if (xml) */ +} + +void MC_(pp_LossRecord)(UInt n_this_record, UInt n_total_records, + LossRecord* l) +{ + pp_LossRecord (n_this_record, n_total_records, l, /* xml */ False); +} + void MC_(pp_Error) ( Error* err ) { const Bool xml = VG_(clo_xml); /* a shorthand */ @@ -717,84 +806,7 @@ void MC_(pp_Error) ( Error* err ) UInt n_this_record = extra->Err.Leak.n_this_record; UInt n_total_records = extra->Err.Leak.n_total_records; LossRecord* lr = extra->Err.Leak.lr; - // char arrays to produce the indication of increase/decrease in case - // of delta_mode != LCD_Any - char d_bytes[20]; - char d_direct_bytes[20]; - char d_indirect_bytes[20]; - char d_num_blocks[20]; - - MC_(snprintf_delta) (d_bytes, 20, - lr->szB + lr->indirect_szB, - lr->old_szB + lr->old_indirect_szB, - MC_(detect_memory_leaks_last_delta_mode)); - MC_(snprintf_delta) (d_direct_bytes, 20, - lr->szB, - lr->old_szB, - MC_(detect_memory_leaks_last_delta_mode)); - MC_(snprintf_delta) (d_indirect_bytes, 20, - lr->indirect_szB, - lr->old_indirect_szB, - MC_(detect_memory_leaks_last_delta_mode)); - MC_(snprintf_delta) (d_num_blocks, 20, - (SizeT) lr->num_blocks, - (SizeT) lr->old_num_blocks, - MC_(detect_memory_leaks_last_delta_mode)); - - if (xml) { - emit(" %s\n", xml_leak_kind(lr->key.state)); - if (lr->indirect_szB > 0) { - emit( " \n" ); - emit( " %'lu%s (%'lu%s direct, %'lu%s indirect) bytes " - "in %'u%s blocks" - " are %s in loss record %'u of %'u\n", - lr->szB + lr->indirect_szB, d_bytes, - lr->szB, d_direct_bytes, - lr->indirect_szB, d_indirect_bytes, - lr->num_blocks, d_num_blocks, - str_leak_lossmode(lr->key.state), - n_this_record, n_total_records ); - // Nb: don't put commas in these XML numbers - emit( " %lu\n", - lr->szB + lr->indirect_szB ); - emit( " %u\n", lr->num_blocks ); - emit( " \n" ); - } else { - emit( " \n" ); - emit( " %'lu%s bytes in %'u%s blocks" - " are %s in loss record %'u of %'u\n", - lr->szB, d_direct_bytes, - lr->num_blocks, d_num_blocks, - str_leak_lossmode(lr->key.state), - n_this_record, n_total_records ); - emit( " %ld\n", lr->szB); - emit( " %d\n", lr->num_blocks); - emit( " \n" ); - } - VG_(pp_ExeContext)(lr->key.allocated_at); - } else { /* ! if (xml) */ - if (lr->indirect_szB > 0) { - emit( - "%'lu%s (%'lu%s direct, %'lu%s indirect) bytes in %'u%s blocks" - " are %s in loss record %'u of %'u\n", - lr->szB + lr->indirect_szB, d_bytes, - lr->szB, d_direct_bytes, - lr->indirect_szB, d_indirect_bytes, - lr->num_blocks, d_num_blocks, - str_leak_lossmode(lr->key.state), - n_this_record, n_total_records - ); - } else { - emit( - "%'lu%s bytes in %'u%s blocks are %s in loss record %'u of %'u\n", - lr->szB, d_direct_bytes, - lr->num_blocks, d_num_blocks, - str_leak_lossmode(lr->key.state), - n_this_record, n_total_records - ); - } - VG_(pp_ExeContext)(lr->key.allocated_at); - } /* if (xml) */ + pp_LossRecord (n_this_record, n_total_records, lr, xml); break; } diff --git a/memcheck/mc_include.h b/memcheck/mc_include.h index 21ec0df15..91921d22c 100644 --- a/memcheck/mc_include.h +++ b/memcheck/mc_include.h @@ -121,6 +121,8 @@ void MC_(make_mem_defined) ( Addr a, SizeT len ); void MC_(copy_address_range_state) ( Addr src, Addr dst, SizeT len ); void MC_(print_malloc_stats) ( void ); +/* nr of free operations done */ +SizeT MC_(get_cmalloc_n_frees) ( void ); void* MC_(malloc) ( ThreadId tid, SizeT n ); void* MC_(__builtin_new) ( ThreadId tid, SizeT n ); @@ -254,6 +256,7 @@ typedef } Reachedness; + /* For VALGRIND_COUNT_LEAKS client request */ extern SizeT MC_(bytes_leaked); extern SizeT MC_(bytes_indirect); @@ -324,6 +327,15 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams * lcp); // maintains the lcp.deltamode given in the last call to detect_memory_leaks extern LeakCheckDeltaMode MC_(detect_memory_leaks_last_delta_mode); +// prints the list of blocks corresponding to the given loss_record_nr. +// Returns True if loss_record_nr identifies a correct loss record from last leak search. +// Returns False otherwise. +Bool MC_(print_block_list) ( UInt loss_record_nr); + +// Prints the addresses/registers/... at which a pointer to +// the given range [address, address+szB[ is found. +void MC_(who_points_at) ( Addr address, SizeT szB); + // if delta_mode == LCD_Any, prints in buf an empty string // otherwise prints a delta in the layout " (+%'lu)" or " (-%'lu)" extern char * MC_(snprintf_delta) (char * buf, Int size, @@ -334,8 +346,9 @@ extern char * MC_(snprintf_delta) (char * buf, Int size, Bool MC_(is_valid_aligned_word) ( Addr a ); Bool MC_(is_within_valid_secondary) ( Addr a ); -void MC_(pp_LeakError)(UInt n_this_record, UInt n_total_records, - LossRecord* l); +// Prints as user msg a description of the given loss record. +void MC_(pp_LossRecord)(UInt n_this_record, UInt n_total_records, + LossRecord* l); /*------------------------------------------------------------*/ diff --git a/memcheck/mc_leakcheck.c b/memcheck/mc_leakcheck.c index 55a5b7bb1..e500375b7 100644 --- a/memcheck/mc_leakcheck.c +++ b/memcheck/mc_leakcheck.c @@ -426,19 +426,39 @@ typedef struct { UInt state:2; // Reachedness. UInt pending:1; // Scan pending. - SizeT indirect_szB : (sizeof(SizeT)*8)-3; // If Unreached, how many bytes - // are unreachable from here. + union { + SizeT indirect_szB : (sizeof(SizeT)*8)-3; // If Unreached, how many bytes + // are unreachable from here. + SizeT clique : (sizeof(SizeT)*8)-3; // if IndirectLeak, clique leader + // to which it belongs. + } IorC; } LC_Extra; // An array holding pointers to every chunk we're checking. Sorted by address. +// lc_chunks is initialised during leak search. It is kept after leak search +// to support printing the list of blocks belonging to a loss record. +// lc_chunk array can only be used validly till the next "free" operation +// (as a free operation potentially destroys one or more chunks). +// To detect lc_chunk is valid, we store the nr of frees operations done +// when lc_chunk was build : lc_chunks (and lc_extras) stays valid as +// long as no free operations has been done since lc_chunks building. static MC_Chunk** lc_chunks; // How many chunks we're dealing with. static Int lc_n_chunks; +static SizeT lc_chunks_n_frees_marker; +// This has the same number of entries as lc_chunks, and each entry +// in lc_chunks corresponds with the entry here (ie. lc_chunks[i] and +// lc_extras[i] describe the same block). +static LC_Extra* lc_extras; + // chunks will be converted and merged in loss record, maintained in lr_table // lr_table elements are kept from one leak_search to another to implement // the "print new/changed leaks" client request static OSet* lr_table; +// Array of sorted loss record (produced during last leak search). +static LossRecord** lr_array; + // DeltaMode used the last time we called detect_memory_leaks. // The recorded leak errors must be output using a logic based on this delta_mode. @@ -446,11 +466,6 @@ static OSet* lr_table; LeakCheckDeltaMode MC_(detect_memory_leaks_last_delta_mode); -// This has the same number of entries as lc_chunks, and each entry -// in lc_chunks corresponds with the entry here (ie. lc_chunks[i] and -// lc_extras[i] describe the same block). -static LC_Extra* lc_extras; - // Records chunks that are currently being processed. Each element in the // stack is an index into lc_chunks and lc_extras. Its size is // 'lc_n_chunks' because in the worst case that's how many chunks could be @@ -478,7 +493,6 @@ SizeT MC_(blocks_dubious) = 0; SizeT MC_(blocks_reachable) = 0; SizeT MC_(blocks_suppressed) = 0; - // Determines if a pointer is to a chunk. Returns the chunk number et al // via call-by-reference. static Bool @@ -587,7 +601,7 @@ lc_push_without_clique_if_a_chunk_ptr(Addr ptr, Bool is_prior_definite) } static void -lc_push_if_a_chunk_ptr_register(Addr ptr) +lc_push_if_a_chunk_ptr_register(ThreadId tid, HChar* regname, Addr ptr) { lc_push_without_clique_if_a_chunk_ptr(ptr, /*is_prior_definite*/True); } @@ -596,7 +610,7 @@ lc_push_if_a_chunk_ptr_register(Addr ptr) // before, push it onto the mark stack. Clique is the index of the // clique leader. static void -lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique) +lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique, Int cur_clique) { Int ch_no; MC_Chunk* ch; @@ -614,7 +628,6 @@ lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique) // Note that, unlike reachable blocks, we currently don't distinguish // between start-pointers and interior-pointers here. We probably // should, though. - ex->state = IndirectLeak; lc_push(ch_no, ch); // Add the block to the clique, and add its size to the @@ -622,28 +635,29 @@ lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique) // itself a clique leader, it isn't any more, so add its // indirect_szB to the new clique leader. if (VG_DEBUG_CLIQUE) { - if (ex->indirect_szB > 0) + if (ex->IorC.indirect_szB > 0) VG_(printf)(" clique %d joining clique %d adding %lu+%lu\n", ch_no, clique, (unsigned long)ch->szB, - (unsigned long)ex->indirect_szB); + (unsigned long)ex->IorC.indirect_szB); else VG_(printf)(" block %d joining clique %d adding %lu\n", ch_no, clique, (unsigned long)ch->szB); } - lc_extras[clique].indirect_szB += ch->szB; - lc_extras[clique].indirect_szB += ex->indirect_szB; - ex->indirect_szB = 0; // Shouldn't matter. + lc_extras[clique].IorC.indirect_szB += ch->szB; + lc_extras[clique].IorC.indirect_szB += ex->IorC.indirect_szB; + ex->state = IndirectLeak; + ex->IorC.clique = (SizeT) cur_clique; } } static void -lc_push_if_a_chunk_ptr(Addr ptr, Int clique, Bool is_prior_definite) +lc_push_if_a_chunk_ptr(Addr ptr, Int clique, Int cur_clique, Bool is_prior_definite) { if (-1 == clique) lc_push_without_clique_if_a_chunk_ptr(ptr, is_prior_definite); else - lc_push_with_clique_if_a_chunk_ptr(ptr, clique); + lc_push_with_clique_if_a_chunk_ptr(ptr, clique, cur_clique); } @@ -658,12 +672,38 @@ void scan_all_valid_memory_catcher ( Int sigNo, Addr addr ) VG_MINIMAL_LONGJMP(memscan_jmpbuf); } +// lc_scan_memory has 2 modes: +// +// 1. Leak check mode (searched == 0). +// ----------------------------------- // Scan a block of memory between [start, start+len). This range may // be bogus, inaccessable, or otherwise strange; we deal with it. For each // valid aligned word we assume it's a pointer to a chunk a push the chunk // onto the mark stack if so. +// clique is the "highest level clique" in which indirectly leaked blocks have +// to be collected. cur_clique is the current "lower" level clique through which +// the memory to be scanned has been found. +// Example: in the below tree if A is leaked, the top level clique will +// be A, while lower level cliques will be B and C. +/* + A + / \ + B C + / \ / \ + D E F G +*/ +// Proper handling of top and lowest level clique allows block_list of a loss +// record to describe the hierarchy of indirectly leaked blocks. +// +// 2. Search ptr mode (searched != 0). +// ----------------------------------- +// In this mode, searches for pointers to a specific address range +// In such a case, lc_scan_memory just scans [start..start+len[ for pointers to searched +// and outputs the places where searched is found. It does not recursively scans the +// found memory. static void -lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique) +lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique, Int cur_clique, + Addr searched, SizeT szB) { Addr ptr = VG_ROUNDUP(start, sizeof(Addr)); Addr end = VG_ROUNDDN(start+len, sizeof(Addr)); @@ -703,7 +743,18 @@ lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique) addr = *(Addr *)ptr; // If we get here, the scanned word is in valid memory. Now // let's see if its contents point to a chunk. - lc_push_if_a_chunk_ptr(addr, clique, is_prior_definite); + if (searched) { + if (addr >= searched && addr < searched + szB) { + if (addr == searched) + VG_(umsg)("*%#lx points at %#lx\n", ptr, searched); + else + VG_(umsg)("*%#lx interior points at %lu bytes inside %#lx\n", + ptr, (long unsigned) addr - searched, searched); + MC_(pp_describe_addr) (ptr); + } + } else { + lc_push_if_a_chunk_ptr(addr, clique, cur_clique, is_prior_definite); + } } else if (0 && VG_DEBUG_LEAKCHECK) { VG_(printf)("%#lx not valid\n", ptr); } @@ -735,7 +786,8 @@ static void lc_process_markstack(Int clique) is_prior_definite = ( Possible != lc_extras[top].state ); lc_scan_memory(lc_chunks[top]->data, lc_chunks[top]->szB, - is_prior_definite, clique); + is_prior_definite, clique, (clique == -1 ? -1 : top), + /*searched*/ 0, 0); } } @@ -782,6 +834,28 @@ static Int cmp_LossRecords(void* va, void* vb) return 0; } +// allocates or reallocates lr_array, and set its elements to the loss records +// contains in lr_table. +static Int get_lr_array_from_lr_table(void) { + Int i, n_lossrecords; + LossRecord* lr; + + n_lossrecords = VG_(OSetGen_Size)(lr_table); + + // (re-)create the array of pointers to the loss records. + // lr_array is kept to allow producing the block list from gdbserver. + if (lr_array != NULL) + VG_(free)(lr_array); + lr_array = VG_(malloc)("mc.pr.2", n_lossrecords * sizeof(LossRecord*)); + i = 0; + VG_(OSetGen_ResetIter)(lr_table); + while ( (lr = VG_(OSetGen_Next)(lr_table)) ) { + lr_array[i++] = lr; + } + tl_assert(i == n_lossrecords); + return n_lossrecords; +} + static void get_printing_rules(LeakCheckParams* lcp, LossRecord* lr, @@ -840,7 +914,6 @@ static void get_printing_rules(LeakCheckParams* lcp, static void print_results(ThreadId tid, LeakCheckParams* lcp) { Int i, n_lossrecords, start_lr_output_scan; - LossRecord** lr_array; LossRecord* lr; Bool is_suppressed; SizeT old_bytes_leaked = MC_(bytes_leaked); /* to report delta in summary */ @@ -866,6 +939,28 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) VG_(malloc), "mc.pr.1", VG_(free)); + // If we have loss records from a previous search, reset values to have + // proper printing of the deltas between previous search and this search. + n_lossrecords = get_lr_array_from_lr_table(); + for (i = 0; i < n_lossrecords; i++) { + if (lr_array[i]->num_blocks == 0) + // remove from lr_table the old loss_records with 0 bytes found + VG_(OSetGen_Remove) (lr_table, &lr_array[i]->key); + else { + // move the leak sizes to old_* and zero the current sizes + // for next leak search + lr_array[i]->old_szB = lr_array[i]->szB; + lr_array[i]->old_indirect_szB = lr_array[i]->indirect_szB; + lr_array[i]->old_num_blocks = lr_array[i]->num_blocks; + lr_array[i]->szB = 0; + lr_array[i]->indirect_szB = 0; + lr_array[i]->num_blocks = 0; + } + } + // lr_array now contains "invalid" loss records => free it. + // lr_array will be re-created below with the kept and new loss records. + VG_(free) (lr_array); + lr_array = NULL; // Convert the chunks into loss records, merging them where appropriate. for (i = 0; i < lc_n_chunks; i++) { @@ -882,7 +977,8 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) // loss record's details in-situ. This is safe because we don't // change the elements used as the OSet key. old_lr->szB += ch->szB; - old_lr->indirect_szB += ex->indirect_szB; + if (ex->state == Unreached) + old_lr->indirect_szB += ex->IorC.indirect_szB; old_lr->num_blocks++; } else { // No existing loss record matches this chunk. Create a new loss @@ -890,7 +986,10 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) lr = VG_(OSetGen_AllocNode)(lr_table, sizeof(LossRecord)); lr->key = lrkey; lr->szB = ch->szB; - lr->indirect_szB = ex->indirect_szB; + if (ex->state == Unreached) + lr->indirect_szB = ex->IorC.indirect_szB; + else + lr->indirect_szB = 0; lr->num_blocks = 1; lr->old_szB = 0; lr->old_indirect_szB = 0; @@ -898,16 +997,10 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) VG_(OSetGen_Insert)(lr_table, lr); } } - n_lossrecords = VG_(OSetGen_Size)(lr_table); - // Create an array of pointers to the loss records. - lr_array = VG_(malloc)("mc.pr.2", n_lossrecords * sizeof(LossRecord*)); - i = 0; - VG_(OSetGen_ResetIter)(lr_table); - while ( (lr = VG_(OSetGen_Next)(lr_table)) ) { - lr_array[i++] = lr; - } - tl_assert(i == n_lossrecords); + // (re-)create the array of pointers to the (new) loss records. + n_lossrecords = get_lr_array_from_lr_table (); + tl_assert(VG_(OSetGen_Size)(lr_table) == n_lossrecords); // Sort the array by loss record sizes. VG_(ssort)(lr_array, n_lossrecords, sizeof(LossRecord*), @@ -980,25 +1073,6 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) } } - for (i = 0; i < n_lossrecords; i++) - { - if (lr->num_blocks == 0) - // remove from lr_table the old loss_records with 0 bytes found - VG_(OSetGen_Remove) (lr_table, &lr_array[i]->key); - else - { - // move the leak sizes to old_* and zero the current sizes - // for next leak search - lr_array[i]->old_szB = lr_array[i]->szB; - lr_array[i]->old_indirect_szB = lr_array[i]->indirect_szB; - lr_array[i]->old_num_blocks = lr_array[i]->num_blocks; - lr_array[i]->szB = 0; - lr_array[i]->indirect_szB = 0; - lr_array[i]->num_blocks = 0; - } - } - VG_(free)(lr_array); - if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) { char d_bytes[20]; char d_blocks[20]; @@ -1053,6 +1127,157 @@ static void print_results(ThreadId tid, LeakCheckParams* lcp) } } +// print recursively all indirectly leaked blocks collected in clique. +static void print_clique (Int clique, UInt level) +{ + Int ind; + Int i, n_lossrecords;; + + n_lossrecords = VG_(OSetGen_Size)(lr_table); + + for (ind = 0; ind < lc_n_chunks; ind++) { + LC_Extra* ind_ex = &(lc_extras)[ind]; + if (ind_ex->state == IndirectLeak && ind_ex->IorC.clique == (SizeT) clique) { + MC_Chunk* ind_ch = lc_chunks[ind]; + LossRecord* ind_lr; + LossRecordKey ind_lrkey; + Int lr_i; + ind_lrkey.state = ind_ex->state; + ind_lrkey.allocated_at = ind_ch->where; + ind_lr = VG_(OSetGen_Lookup)(lr_table, &ind_lrkey); + for (lr_i = 0; lr_i < n_lossrecords; lr_i++) + if (ind_lr == lr_array[lr_i]) + break; + for (i = 0; i < level; i++) + VG_(umsg)(" "); + VG_(umsg)("%p[%lu] indirect loss record %d\n", + (void *)ind_ch->data, (unsigned long)ind_ch->szB, + lr_i+1); // lr_i+1 for user numbering. + if (lr_i >= n_lossrecords) + VG_(umsg) + ("error: no indirect loss record found for %p[%lu]?????\n", + (void *)ind_ch->data, (unsigned long)ind_ch->szB); + print_clique(ind, level+1); + } + } + } + +Bool MC_(print_block_list) ( UInt loss_record_nr) +{ + Int i, n_lossrecords; + LossRecord* lr; + + if (lr_table == NULL || lc_chunks == NULL || lc_extras == NULL) { + VG_(umsg)("Can't print block list : no valid leak search result\n"); + return False; + } + + if (lc_chunks_n_frees_marker != MC_(get_cmalloc_n_frees)()) { + VG_(umsg)("Can't print obsolete block list : redo a leak search first\n"); + return False; + } + + n_lossrecords = VG_(OSetGen_Size)(lr_table); + if (loss_record_nr >= n_lossrecords) + return False; // Invalid loss record nr. + + tl_assert (lr_array); + lr = lr_array[loss_record_nr]; + + // (re-)print the loss record details. + // (+1 on loss_record_nr as user numbering for loss records starts at 1). + MC_(pp_LossRecord)(loss_record_nr+1, n_lossrecords, lr); + + // Match the chunks with loss records. + for (i = 0; i < lc_n_chunks; i++) { + MC_Chunk* ch = lc_chunks[i]; + LC_Extra* ex = &(lc_extras)[i]; + LossRecord* old_lr; + LossRecordKey lrkey; + lrkey.state = ex->state; + lrkey.allocated_at = ch->where; + + old_lr = VG_(OSetGen_Lookup)(lr_table, &lrkey); + if (old_lr) { + // We found an existing loss record matching this chunk. + // If this is the loss record we are looking for, then output the pointer. + if (old_lr == lr_array[loss_record_nr]) { + VG_(umsg)("%p[%lu]\n", + (void *)ch->data, (unsigned long) ch->szB); + if (ex->state != Reachable) { + // We can print the clique in all states, except Reachable. + // In Unreached state, lc_chunk[i] is the clique leader. + // In IndirectLeak, lc_chunk[i] might have been a clique leader + // which was later collected in another clique. + // For Possible, lc_chunk[i] might be the top of a clique + // or an intermediate clique. + print_clique(i, 1); + } + } + } else { + // No existing loss record matches this chunk ??? + VG_(umsg)("error: no loss record found for %p[%lu]?????\n", + (void *)ch->data, (unsigned long) ch->szB); + } + } + return True; +} + +// If searched = 0, scan memory root set, pushing onto the mark stack the blocks +// encountered. +// Otherwise (searched != 0), scan the memory root set searching for ptr pointing +// inside [searched, searched+szB[. +static void scan_memory_root_set(Addr searched, SizeT szB) +{ + Int i; + Int n_seg_starts; + Addr* seg_starts = VG_(get_segment_starts)( &n_seg_starts ); + + tl_assert(seg_starts && n_seg_starts > 0); + + lc_scanned_szB = 0; + + // VG_(am_show_nsegments)( 0, "leakcheck"); + for (i = 0; i < n_seg_starts; i++) { + SizeT seg_size; + NSegment const* seg = VG_(am_find_nsegment)( seg_starts[i] ); + tl_assert(seg); + + if (seg->kind != SkFileC && seg->kind != SkAnonC) continue; + if (!(seg->hasR && seg->hasW)) continue; + if (seg->isCH) continue; + + // Don't poke around in device segments as this may cause + // hangs. Exclude /dev/zero just in case someone allocated + // memory by explicitly mapping /dev/zero. + if (seg->kind == SkFileC + && (VKI_S_ISCHR(seg->mode) || VKI_S_ISBLK(seg->mode))) { + HChar* dev_name = VG_(am_get_filename)( (NSegment*)seg ); + if (dev_name && 0 == VG_(strcmp)(dev_name, "/dev/zero")) { + // Don't skip /dev/zero. + } else { + // Skip this device mapping. + continue; + } + } + + if (0) + VG_(printf)("ACCEPT %2d %#lx %#lx\n", i, seg->start, seg->end); + + // Scan the segment. We use -1 for the clique number, because this + // is a root-set. + seg_size = seg->end - seg->start + 1; + if (VG_(clo_verbosity) > 2) { + VG_(message)(Vg_DebugMsg, + " Scanning root segment: %#lx..%#lx (%lu)\n", + seg->start, seg->end, seg_size); + } + lc_scan_memory(seg->start, seg_size, /*is_prior_definite*/True, + /*clique*/-1, /*cur_clique*/-1, + searched, szB); + } +} + /*------------------------------------------------------------*/ /*--- Top-level entry point. ---*/ /*------------------------------------------------------------*/ @@ -1066,16 +1291,22 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp) MC_(detect_memory_leaks_last_delta_mode) = lcp->deltamode; // Get the chunks, stop if there were none. + if (lc_chunks) { + VG_(free)(lc_chunks); + lc_chunks = NULL; + } lc_chunks = find_active_chunks(&lc_n_chunks); + lc_chunks_n_frees_marker = MC_(get_cmalloc_n_frees)(); if (lc_n_chunks == 0) { tl_assert(lc_chunks == NULL); if (lr_table != NULL) { - // forget the previous recorded LossRecords as next leak search will in any case - // just create new leaks. + // forget the previous recorded LossRecords as next leak search + // can in any case just create new leaks. // Maybe it would be better to rather call print_result ? - // (at least when leak decrease are requested) + // (at least when leak decreases are requested) // This will then output all LossRecords with a size decreasing to 0 VG_(OSetGen_Destroy) (lr_table); + lr_table = NULL; } if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) { VG_(umsg)("All heap blocks were freed -- no leaks are possible\n"); @@ -1152,11 +1383,15 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp) } // Initialise lc_extras. + if (lc_extras) { + VG_(free)(lc_extras); + lc_extras = NULL; + } lc_extras = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(LC_Extra) ); for (i = 0; i < lc_n_chunks; i++) { lc_extras[i].state = Unreached; lc_extras[i].pending = False; - lc_extras[i].indirect_szB = 0; + lc_extras[i].IorC.indirect_szB = 0; } // Initialise lc_markstack. @@ -1174,52 +1409,7 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp) // Scan the memory root-set, pushing onto the mark stack any blocks // pointed to. - { - Int n_seg_starts; - Addr* seg_starts = VG_(get_segment_starts)( &n_seg_starts ); - - tl_assert(seg_starts && n_seg_starts > 0); - - lc_scanned_szB = 0; - - // VG_(am_show_nsegments)( 0, "leakcheck"); - for (i = 0; i < n_seg_starts; i++) { - SizeT seg_size; - NSegment const* seg = VG_(am_find_nsegment)( seg_starts[i] ); - tl_assert(seg); - - if (seg->kind != SkFileC && seg->kind != SkAnonC) continue; - if (!(seg->hasR && seg->hasW)) continue; - if (seg->isCH) continue; - - // Don't poke around in device segments as this may cause - // hangs. Exclude /dev/zero just in case someone allocated - // memory by explicitly mapping /dev/zero. - if (seg->kind == SkFileC - && (VKI_S_ISCHR(seg->mode) || VKI_S_ISBLK(seg->mode))) { - HChar* dev_name = VG_(am_get_filename)( (NSegment*)seg ); - if (dev_name && 0 == VG_(strcmp)(dev_name, "/dev/zero")) { - // Don't skip /dev/zero. - } else { - // Skip this device mapping. - continue; - } - } - - if (0) - VG_(printf)("ACCEPT %2d %#lx %#lx\n", i, seg->start, seg->end); - - // Scan the segment. We use -1 for the clique number, because this - // is a root-set. - seg_size = seg->end - seg->start + 1; - if (VG_(clo_verbosity) > 2) { - VG_(message)(Vg_DebugMsg, - " Scanning root segment: %#lx..%#lx (%lu)\n", - seg->start, seg->end, seg_size); - } - lc_scan_memory(seg->start, seg_size, /*is_prior_definite*/True, -1); - } - } + scan_memory_root_set(/*searched*/0, 0); // Scan GP registers for chunk pointers. VG_(apply_to_GP_regs)(lc_push_if_a_chunk_ptr_register); @@ -1256,7 +1446,7 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp) // Push this Unreached block onto the stack and process it. lc_push(i, ch); - lc_process_markstack(i); + lc_process_markstack(/*clique*/i); tl_assert(lc_markstack_top == -1); tl_assert(ex->state == Unreached); @@ -1265,9 +1455,62 @@ void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp) print_results( tid, lcp); - VG_(free) ( lc_chunks ); - VG_(free) ( lc_extras ); VG_(free) ( lc_markstack ); + lc_markstack = NULL; + // lc_chunks, lc_extras, lr_array and lr_table are kept (needed if user + // calls MC_(print_block_list)). lr_table also used for delta leak reporting + // between this leak search and the next leak search. +} + +static Addr searched_wpa; +static SizeT searched_szB; +static void +search_address_in_GP_reg(ThreadId tid, HChar* regname, Addr addr_in_reg) +{ + if (addr_in_reg >= searched_wpa + && addr_in_reg < searched_wpa + searched_szB) { + if (addr_in_reg == searched_wpa) + VG_(umsg) + ("tid %d register %s pointing at %#lx\n", + tid, regname, searched_wpa); + else + VG_(umsg) + ("tid %d register %s interior pointing %lu bytes inside %#lx\n", + tid, regname, (long unsigned) addr_in_reg - searched_wpa, + searched_wpa); + } +} + +void MC_(who_points_at) ( Addr address, SizeT szB) +{ + MC_Chunk** chunks; + Int n_chunks; + Int i; + + if (szB == 1) + VG_(umsg) ("Searching for pointers to %#lx\n", address); + else + VG_(umsg) ("Searching for pointers pointing in %lu bytes from %#lx\n", + szB, address); + + // Scan memory root-set, searching for ptr pointing in address[szB] + scan_memory_root_set(address, szB); + + // Scan active malloc-ed chunks + chunks = find_active_chunks(&n_chunks); + for (i = 0; i < n_chunks; i++) { + lc_scan_memory(chunks[i]->data, chunks[i]->szB, + /*is_prior_definite*/True, + /*clique*/-1, /*cur_clique*/-1, + address, szB); + } + VG_(free) ( chunks ); + + // Scan GP registers for pointers to address range. + searched_wpa = address; + searched_szB = szB; + VG_(apply_to_GP_regs)(search_address_in_GP_reg); + } /*--------------------------------------------------------------------*/ diff --git a/memcheck/mc_main.c b/memcheck/mc_main.c index e6dd6a4c2..fcbfe3522 100644 --- a/memcheck/mc_main.c +++ b/memcheck/mc_main.c @@ -5027,6 +5027,12 @@ static void print_monitor_help ( void ) " Examples: leak_check\n" " leak_check summary any\n" " leak_check full reachable any limited 100\n" +" block_list \n" +" after a leak search, shows the list of blocks of \n" +" who_points_at []\n" +" shows places pointing inside (default 1) bytes at \n" +" (with len 1, only shows \"start pointers\" pointing exactly to ,\n" +" with len > 1, will also show \"interior pointers\")\n" "\n"); } @@ -5044,7 +5050,8 @@ static Bool handle_gdb_monitor_command (ThreadId tid, Char *req) starts with the same first letter(s) as an already existing command. This ensures a shorter abbreviation for the user. */ switch (VG_(keyword_id) - ("help get_vbits leak_check make_memory check_memory", + ("help get_vbits leak_check make_memory check_memory " + "block_list who_points_at", wcmd, kwd_report_duplicated_matches)) { case -2: /* multiple matches */ return True; @@ -5249,6 +5256,35 @@ static Bool handle_gdb_monitor_command (ThreadId tid, Char *req) return True; } + case 5: { /* block_list */ + Char* wl; + Char *endptr; + UInt lr_nr = 0; + wl = VG_(strtok_r) (NULL, " ", &ssaveptr); + lr_nr = VG_(strtoull10) (wl, &endptr); + if (wl != NULL && *endptr != '\0') { + VG_(gdb_printf) ("malformed integer\n"); + } else { + // lr_nr-1 as what is shown to the user is 1 more than the index in lr_array. + if (lr_nr == 0 || ! MC_(print_block_list) (lr_nr-1)) + VG_(gdb_printf) ("invalid loss record nr\n"); + } + return True; + } + + case 6: { /* who_points_at */ + Addr address; + SizeT szB = 1; + + VG_(strtok_get_address_and_size) (&address, &szB, &ssaveptr); + if (address == (Addr) 0) { + VG_(gdb_printf) ("Cannot search who points at 0x0\n"); + return True; + } + MC_(who_points_at) (address, szB); + return True; + } + default: tl_assert(0); return False; diff --git a/memcheck/mc_malloc_wrappers.c b/memcheck/mc_malloc_wrappers.c index fc37e609b..bf67571ff 100644 --- a/memcheck/mc_malloc_wrappers.c +++ b/memcheck/mc_malloc_wrappers.c @@ -1018,6 +1018,12 @@ void MC_(print_malloc_stats) ( void ) ); } +SizeT MC_(get_cmalloc_n_frees) ( void ) +{ + return cmalloc_n_frees; +} + + /*--------------------------------------------------------------------*/ /*--- end ---*/ /*--------------------------------------------------------------------*/