(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 <loss_record_nr>
        after a leak search, shows the list of blocks of <loss_record_nr>
  who_points_at <addr> [<len>]
        shows places pointing inside <len> (default 1) bytes at <addr>
        (with len 1, only shows "start pointers" pointing exactly to <addr>,
         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
This commit is contained in:
Philippe Waroquiers 2012-01-26 23:13:52 +00:00
parent af76b5a089
commit ce806ed31f
17 changed files with 888 additions and 310 deletions

6
NEWS
View File

@ -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)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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 <address> | <loss_record_nr>
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

View File

@ -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);
}
}
}

View File

@ -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 \

View File

@ -10,7 +10,7 @@ $dir/filter_stderr |
$dir/../tests/filter_addresses |
# memcheck stuff
$dir/filter_memcheck_monitor |
$dir/filter_memcheck_monitor "$@" |
# Anonymise or remove :

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,6 +28,12 @@ memcheck monitor commands:
Examples: leak_check
leak_check summary any
leak_check full reachable any limited 100
block_list <loss_record_nr>
after a leak search, shows the list of blocks of <loss_record_nr>
who_points_at <addr> [<len>]
shows places pointing inside <len> (default 1) bytes at <addr>
(with len 1, only shows "start pointers" pointing exactly to <addr>,
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 <loss_record_nr>
after a leak search, shows the list of blocks of <loss_record_nr>
who_points_at <addr> [<len>]
shows places pointing inside <len> (default 1) bytes at <addr>
(with len 1, only shows "start pointers" pointing exactly to <addr>,
with len > 1, will also show "interior pointers")
monitor command request to kill this process

View File

@ -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

View File

@ -1372,7 +1372,7 @@ $10 = 0x0
(default 1) bytes at &lt;addr&gt; has the specified accessibility.
It then outputs a description of &lt;addr&gt;. In the following
example, a detailed description is available because the
option <option>--read-var-info=yes</option> was given Valgrind at
option <option>--read-var-info=yes</option> was given at Valgrind
startup:
</para>
<programlisting><![CDATA[
@ -1443,31 +1443,31 @@ Address 0x8049E28 len 1 defined
there was no increase since the previous leak search.</para>
<programlisting><![CDATA[
(gdb) monitor leak_check full possibleleak increased
==14729== 16 (+16) bytes in 1 (+1) blocks are possibly lost in loss record 13 of 16
==14729== at 0x4006E9E: malloc (vg_replace_malloc.c:236)
==14729== by 0x80484D5: mk (leak-cases.c:52)
==14729== by 0x804855F: f (leak-cases.c:81)
==14729== by 0x80488F5: main (leak-cases.c:107)
==14729==
==14729== LEAK SUMMARY:
==14729== definitely lost: 32 (+0) bytes in 2 (+0) blocks
==14729== indirectly lost: 16 (+0) bytes in 1 (+0) blocks
==14729== possibly lost: 32 (+16) bytes in 2 (+1) blocks
==14729== still reachable: 96 (+16) bytes in 6 (+1) blocks
==14729== suppressed: 0 (+0) bytes in 0 (+0) blocks
==14729== Reachable blocks (those to which a pointer was found) are not shown.
==14729== To see them, add 'reachable any' args to leak_check
==14729==
==19520== 16 (+16) bytes in 1 (+1) blocks are possibly lost in loss record 9 of 12
==19520== at 0x40070B4: malloc (vg_replace_malloc.c:263)
==19520== by 0x80484D5: mk (leak-cases.c:52)
==19520== by 0x804855F: f (leak-cases.c:81)
==19520== by 0x80488E0: main (leak-cases.c:107)
==19520==
==19520== LEAK SUMMARY:
==19520== definitely lost: 32 (+0) bytes in 2 (+0) blocks
==19520== indirectly lost: 16 (+0) bytes in 1 (+0) blocks
==19520== possibly lost: 32 (+16) bytes in 2 (+1) blocks
==19520== still reachable: 96 (+16) bytes in 6 (+1) blocks
==19520== suppressed: 0 (+0) bytes in 0 (+0) blocks
==19520== Reachable blocks (those to which a pointer was found) are not shown.
==19520== To see them, add 'reachable any' args to leak_check
==19520==
(gdb) mo l
==14729== LEAK SUMMARY:
==14729== definitely lost: 32 (+0) bytes in 2 (+0) blocks
==14729== indirectly lost: 16 (+0) bytes in 1 (+0) blocks
==14729== possibly lost: 32 (+0) bytes in 2 (+0) blocks
==14729== still reachable: 96 (+0) bytes in 6 (+0) blocks
==14729== suppressed: 0 (+0) bytes in 0 (+0) blocks
==14729== Reachable blocks (those to which a pointer was found) are not shown.
==14729== To see them, add 'reachable any' args to leak_check
==14729==
==19520== LEAK SUMMARY:
==19520== definitely lost: 32 (+0) bytes in 2 (+0) blocks
==19520== indirectly lost: 16 (+0) bytes in 1 (+0) blocks
==19520== possibly lost: 32 (+0) bytes in 2 (+0) blocks
==19520== still reachable: 96 (+0) bytes in 6 (+0) blocks
==19520== suppressed: 0 (+0) bytes in 0 (+0) blocks
==19520== Reachable blocks (those to which a pointer was found) are not shown.
==19520== To see them, add 'reachable any' args to leak_check
==19520==
(gdb)
]]></programlisting>
<para>Note that when using Valgrind's gdbserver, it is not
@ -1481,6 +1481,143 @@ Address 0x8049E28 len 1 defined
</para>
</listitem>
<listitem>
<para><varname>block_list &lt;loss_record_nr&gt; </varname>
shows the list of blocks belonging to &lt;loss_record_nr&gt;.
</para>
<para> 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 <varname>block_list</varname> command shows the loss record information
followed by the addresses and sizes of the blocks which have been
merged in the loss record.
</para>
<para> 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.
</para>
<para> 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.
</para>
<para>
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.
</para>
<programlisting><![CDATA[
A
/ \
B C
/ \ / \
D E F G
]]></programlisting>
<programlisting><![CDATA[
(gdb) bt
#0 main () at leak-tree.c:69
(gdb) monitor leak_check full any
==19552== 112 (16 direct, 96 indirect) bytes in 1 blocks are definitely lost in loss record 7 of 7
==19552== at 0x40070B4: malloc (vg_replace_malloc.c:263)
==19552== by 0x80484D5: mk (leak-tree.c:28)
==19552== by 0x80484FC: f (leak-tree.c:41)
==19552== by 0x8048856: main (leak-tree.c:63)
==19552==
==19552== LEAK SUMMARY:
==19552== definitely lost: 16 bytes in 1 blocks
==19552== indirectly lost: 96 bytes in 6 blocks
==19552== possibly lost: 0 bytes in 0 blocks
==19552== still reachable: 0 bytes in 0 blocks
==19552== suppressed: 0 bytes in 0 blocks
==19552==
(gdb) monitor block_list 7
==19552== 112 (16 direct, 96 indirect) bytes in 1 blocks are definitely lost in loss record 7 of 7
==19552== at 0x40070B4: malloc (vg_replace_malloc.c:263)
==19552== by 0x80484D5: mk (leak-tree.c:28)
==19552== by 0x80484FC: f (leak-tree.c:41)
==19552== by 0x8048856: main (leak-tree.c:63)
==19552== 0x4028028[16]
==19552== 0x4028068[16] indirect loss record 1
==19552== 0x40280E8[16] indirect loss record 3
==19552== 0x4028128[16] indirect loss record 4
==19552== 0x40280A8[16] indirect loss record 2
==19552== 0x4028168[16] indirect loss record 5
==19552== 0x40281A8[16] indirect loss record 6
(gdb) mo b 2
==19552== 16 bytes in 1 blocks are indirectly lost in loss record 2 of 7
==19552== at 0x40070B4: malloc (vg_replace_malloc.c:263)
==19552== by 0x80484D5: mk (leak-tree.c:28)
==19552== by 0x8048519: f (leak-tree.c:43)
==19552== by 0x8048856: main (leak-tree.c:63)
==19552== 0x40280A8[16]
==19552== 0x4028168[16] indirect loss record 5
==19552== 0x40281A8[16] indirect loss record 6
(gdb)
]]></programlisting>
</listitem>
<listitem>
<para><varname>who_points_at &lt;addr&gt; [&lt;len&gt;]</varname>
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 &gt; 1, "interior pointers" pointing at the len first bytes
will also be shown.
</para>
<para>The locations searched for are the same as the locations
used in the leak search. So, <varname>who_points_at</varname> 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.
</para>
<para>In the below example, the pointers to the 'tree block A' (see example
in command <varname>block_list</varname>) is shown before the tree was leaked.
The descriptions are detailed as the option <option>--read-var-info=yes</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.
</para>
<programlisting><![CDATA[
(gdb) monitor who_points_at 0x4028028
==20852== Searching for pointers to 0x4028028
==20852== *0x8049e20 points at 0x4028028
==20852== Location 0x8049e20 is 0 bytes inside global var "t"
==20852== declared at leak-tree.c:35
(gdb) monitor who_points_at 0x40281A8 16
==20852== Searching for pointers pointing in 16 bytes from 0x40281a8
==20852== *0x40280ac points at 0x40281a8
==20852== Address 0x40280ac is 4 bytes inside a block of size 16 alloc'd
==20852== at 0x40070B4: malloc (vg_replace_malloc.c:263)
==20852== by 0x80484D5: mk (leak-tree.c:28)
==20852== by 0x8048519: f (leak-tree.c:43)
==20852== by 0x8048856: main (leak-tree.c:63)
==20852== tid 1 register ECX points at 0x40281a8
==20852== tid 1 register EBX interior points at 2 bytes inside 0x40281a8
(gdb)
]]></programlisting>
</listitem>
</itemizedlist>
</sect1>

View File

@ -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(" <kind>%s</kind>\n", xml_leak_kind(lr->key.state));
if (lr->indirect_szB > 0) {
emit( " <xwhat>\n" );
emit( " <text>%'lu%s (%'lu%s direct, %'lu%s indirect) bytes "
"in %'u%s blocks"
" are %s in loss record %'u of %'u</text>\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( " <leakedbytes>%lu</leakedbytes>\n",
lr->szB + lr->indirect_szB );
emit( " <leakedblocks>%u</leakedblocks>\n", lr->num_blocks );
emit( " </xwhat>\n" );
} else {
emit( " <xwhat>\n" );
emit( " <text>%'lu%s bytes in %'u%s blocks"
" are %s in loss record %'u of %'u</text>\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( " <leakedbytes>%ld</leakedbytes>\n", lr->szB);
emit( " <leakedblocks>%d</leakedblocks>\n", lr->num_blocks);
emit( " </xwhat>\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(" <kind>%s</kind>\n", xml_leak_kind(lr->key.state));
if (lr->indirect_szB > 0) {
emit( " <xwhat>\n" );
emit( " <text>%'lu%s (%'lu%s direct, %'lu%s indirect) bytes "
"in %'u%s blocks"
" are %s in loss record %'u of %'u</text>\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( " <leakedbytes>%lu</leakedbytes>\n",
lr->szB + lr->indirect_szB );
emit( " <leakedblocks>%u</leakedblocks>\n", lr->num_blocks );
emit( " </xwhat>\n" );
} else {
emit( " <xwhat>\n" );
emit( " <text>%'lu%s bytes in %'u%s blocks"
" are %s in loss record %'u of %'u</text>\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( " <leakedbytes>%ld</leakedbytes>\n", lr->szB);
emit( " <leakedblocks>%d</leakedblocks>\n", lr->num_blocks);
emit( " </xwhat>\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;
}

View File

@ -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);
/*------------------------------------------------------------*/

View File

@ -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);
}
/*--------------------------------------------------------------------*/

View File

@ -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 <loss_record_nr>\n"
" after a leak search, shows the list of blocks of <loss_record_nr>\n"
" who_points_at <addr> [<len>]\n"
" shows places pointing inside <len> (default 1) bytes at <addr>\n"
" (with len 1, only shows \"start pointers\" pointing exactly to <addr>,\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;

View File

@ -1018,6 +1018,12 @@ void MC_(print_malloc_stats) ( void )
);
}
SizeT MC_(get_cmalloc_n_frees) ( void )
{
return cmalloc_n_frees;
}
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/