<option><![CDATA[--undef-value-errors=<yes|no> [default: yes] ]]></option>
</term>
<listitem>
- <para>Controls whether <constant>memcheck</constant> reports
+ <para>Controls whether Memcheck reports
uses of undefined value errors. Set this to
<varname>no</varname> if you don't want to see undefined value
errors. It also has the side effect of speeding up
- <constant>memcheck</constant> somewhat.
+ memcheck somewhat.
</para>
</listitem>
</varlistentry>
<option><![CDATA[--track-origins=<yes|no> [default: no] ]]></option>
</term>
<listitem>
- <para>Controls whether <constant>memcheck</constant> tracks
+ <para>Controls whether Memcheck tracks
the origin of uninitialised values. By default, it does not,
which means that although it can tell you that an
uninitialised value is being used in a dangerous way, it
often makes it difficult to track down the root problem.
</para>
<para>When set
- to <varname>yes</varname>, <constant>memcheck</constant> keeps
+ to <varname>yes</varname>, Memcheck keeps
track of the origins of all uninitialised values. Then, when
an uninitialised value error is
- reported, <constant>memcheck</constant> will try to show the
+ reported, Memcheck will try to show the
origin of the value. An origin can be one of the following
four places: a heap block, a stack allocation, a client
request, or miscellaneous other sources (eg, a call
to <varname>brk</varname>).
</para>
<para>For uninitialised values originating from a heap
- block, <constant>memcheck</constant> shows where the block was
+ block, Memcheck shows where the block was
allocated. For uninitialised values originating from a stack
- allocation, <constant>memcheck</constant> can tell you which
+ allocation, Memcheck can tell you which
function allocated the value, but no more than that -- typically
it shows you the source location of the opening brace of the
function. So you should carefully check that all of the
function's local variables are initialised properly.
</para>
<para>Performance overhead: origin tracking is expensive. It
- halves <constant>memcheck</constant>'s speed and increases
+ halves Memcheck's speed and increases
memory use by a minimum of 100MB, and possibly more.
Nevertheless it can drastically reduce the effort required to
identify the root cause of uninitialised value errors, and so
is often a programmer productivity win, despite running
more slowly.
</para>
- <para>Accuracy: <constant>memcheck</constant> tracks origins
+ <para>Accuracy: Memcheck tracks origins
quite accurately. To avoid very large space and time
overheads, some approximations are made. It is possible,
- although unlikely, that
- <constant>memcheck</constant> will report an incorrect origin,
- or not be able to identify any origin.
+ although unlikely, that Memcheck will report an incorrect origin, or
+ not be able to identify any origin.
</para>
<para>Note that the combination
<option>--track-origins=yes</option>
and <option>--undef-value-errors=no</option> is
- nonsensical. <constant>memcheck</constant> checks for and
+ nonsensical. Memcheck checks for and
rejects this combination at startup.
</para>
<para>Origin tracking is a new feature, introduced in Valgrind
</term>
<listitem>
<para>When enabled, search for memory leaks when the client
- program finishes. A memory leak means a malloc'd block, which has
- not yet been free'd, but to which no pointer can be found. Such a
- block can never be free'd by the program, since no pointer to it
- exists. If set to <varname>summary</varname>, it says how many
- leaks occurred. If set to <varname>full</varname> or
- <varname>yes</varname>, it gives details of each individual
+ program finishes. If set to <varname>summary</varname>, it says how
+ many leaks occurred. If set to <varname>full</varname> or
+ <varname>yes</varname>, it also gives details of each individual
leak.</para>
</listitem>
</varlistentry>
<option><![CDATA[--show-reachable=<yes|no> [default: no] ]]></option>
</term>
<listitem>
- <para>When disabled, the memory leak detector only shows blocks
- for which it cannot find a pointer to at all, or it can only find
- a pointer to the middle of. These blocks are prime candidates for
- memory leaks. When enabled, the leak detector also reports on
- blocks which it could find a pointer to. Your program could, at
- least in principle, have freed such blocks before exit. Contrast
- this to blocks for which no pointer, or only an interior pointer
- could be found: they are more likely to indicate memory leaks,
- because you do not actually have a pointer to the start of the
- block which you can hand to <function>free</function>, even if you
- wanted to.</para>
+ <para>When disabled, the memory leak detector only shows "definitely
+ lost" and "possibly lost" blocks. When enabled, the leak detector also
+ shows "reachable" and "indirectly lost" blocks. (In other words, it
+ shows all blocks, except suppressed ones, so
+ <computeroutput>--show-all</computeroutput> would be a better name for
+ it.)
</listitem>
</varlistentry>
</term>
<listitem>
<para>When doing leak checking, determines how willing
- <constant>memcheck</constant> is to consider different backtraces to
+ Memcheck is to consider different backtraces to
be the same. When set to <varname>low</varname>, only the first
two entries need match. When <varname>med</varname>, four entries
have to match. When <varname>high</varname>, all entries need to
<option>--leak-resolution=high</option> together with
<option>--num-callers=40</option> or some such large number. Note
however that this can give an overwhelming amount of information,
- which is why the defaults are 4 callers and low-resolution
- matching.</para>
+ which is why the defaults give less information.
+ </para>
<para>Note that the <option>--leak-resolution=</option> setting
- does not affect <constant>memcheck's</constant> ability to find
+ does not affect Memcheck's ability to find
leaks. It only changes how the results are presented.</para>
</listitem>
</varlistentry>
and placed in a queue of freed blocks. The purpose is to defer as
long as possible the point at which freed-up memory comes back
into circulation. This increases the chance that
- <constant>memcheck</constant> will be able to detect invalid
+ Memcheck will be able to detect invalid
accesses to blocks for some significant period of time after they
have been freed.</para>
<para>This flag specifies the maximum total size, in bytes, of the
blocks in the queue. The default value is ten million bytes.
Increasing this increases the total amount of memory used by
- <constant>memcheck</constant> but may detect invalid uses of freed
+ Memcheck but may detect invalid uses of freed
blocks which would otherwise go undetected.</para>
</listitem>
</varlistentry>
<option><![CDATA[--partial-loads-ok=<yes|no> [default: no] ]]></option>
</term>
<listitem>
- <para>Controls how <constant>memcheck</constant> handles word-sized,
+ <para>Controls how Memcheck handles word-sized,
word-aligned loads from addresses for which some bytes are
addressable and others are not. When <varname>yes</varname>, such
loads do not produce an address error. Instead, loaded bytes
</para>
<para>If <option>--leak-check</option> is set appropriately, for each
-remaining block, Memcheck scans the entire address space of the process,
-looking for pointers to the block. Each block fits into one of the
-three following categories.</para>
+remaining block, Memcheck determines if the block is reachable from pointers
+within the root-set. The root-set consists of (a) general purpose registers
+of all threads, and (b) initialised, aligned, pointer-sized data words in
+accessible client memory, including stacks.</para>
+
+<para>There are two ways a block can be reached. The first is with a
+"start-pointer", i.e. a pointer to the start of the block. The second is
+with an "interior-pointer", i.e. a pointer to the middle of the block. The
+pointer might have originally been a start-pointer and have been moved
+along, or it might be entirely unrelated, just a coincidence. It's unclear
+whether such a pointer should be considered as genuinely pointing to the
+block.</para>
+
+<para>With that in mind, consider the nine possible cases described by the
+following figure.</para>
+
+<programlisting><![CDATA[
+ Pointer chain AAA Category BBB Category
+ ------------- ------------ ------------
+(1) RRR ------------> BBB DR
+(2) RRR ---> AAA ---> BBB DR IR
+(3) RRR BBB DL
+(4) RRR AAA ---> BBB DL IL
+(5) RRR ------?-----> BBB (y)DR, (n)DL
+(6) RRR ---> AAA -?-> BBB DR (y)IR, (n)DL
+(7) RRR -?-> AAA ---> BBB (y)DR, (n)DL (y)IR, (n)IL
+(8) RRR -?-> AAA -?-> BBB (y)DR, (n)DL (y,y)IR, (n,y)IL, (_,n)DL
+(9) RRR AAA -?-> BBB DL (y)IL, (n)DL
+
+Pointer chain legend:
+- RRR: a root set node or DR block
+- AAA, BBB: heap blocks
+- --->: a start-pointer
+- -?->: an interior-pointer
+
+Category legend:
+- DR: Directly reachable
+- IR: Indirectly reachable
+- DL: Directly lost
+- IL: Indirectly lost
+- (y)XY: it's XY if the interior-pointer is a real pointer
+- (n)XY: it's XY if the interior-pointer is not a real pointer
+- (_)XY: it's XY in either case
+]]></programlisting>
+
+<para>Every possible case can be reduced to one of the above nine. Memcheck
+merges some of these cases in its output, resulting in the following four
+categories.</para>
+
<itemizedlist>
<listitem>
- <para>Still reachable: A pointer to the start of the block is found.
- This usually indicates programming sloppiness. Since the block is
- still pointed at, the programmer could, at least in principle, free
- it before program exit. Because these are very common and arguably
- not a problem, Memcheck won't report such blocks unless
- <option>--show-reachable=yes</option> is specified.</para>
+ <para>"Still reachable". This covers cases 1 and 2 (for the BBB blocks)
+ above. A start-pointer or chain of start-pointers to the block is
+ found. Since the block is still pointed at, the programmer could, at
+ least in principle, have freed it before program exit. Because these
+ are very common and arguably not a problem, Memcheck won't report such
+ blocks individually unless <option>--show-reachable=yes</option> is
+ specified.</para>
</listitem>
<listitem>
- <para>Possibly lost, or "dubious": A pointer to the interior of the
- block is found. The pointer might originally have pointed to the
- start and have been moved along, or it might be entirely unrelated.
- Memcheck deems such a block as "dubious", because it's unclear
- whether or not a pointer to it still exists.</para>
+ <para>"Definitely lost". This covers case 3 (for the BBB blocks) above.
+ This means that no pointer to the block can be found. The block is
+ classified as "lost", because the programmer could not possibly have
+ freed it at program exit, since no pointer to it exists. This is likely
+ a symptom of having lost the pointer at some earlier point in the
+ program. Such cases should be fixed by the programmer.</para>
+ </listitem>
+
+ <listitem>
+ <para>"Indirectly lost". This covers cases 4 and 9 (for the BBB blocks)
+ above. This means that the block is lost, not because there are no
+ pointers to it, but rather because all the blocks that point to it are
+ themselves lost. For example, if you have a binary tree and the root
+ node is lost, all its children nodes will be indirectly lost. Because
+ the problem will disappear if the definitely lost block that caused the
+ indirect leak is fixed, Memcheck won't report such blocks individually
+ unless <option>--show-reachable=yes</option> is specified.</para>
</listitem>
<listitem>
- <para>Definitely lost, or "leaked": The worst outcome is that no
- pointer to the block can be found. The block is classified as
- "leaked", because the programmer could not possibly have freed it at
- program exit, since no pointer to it exists. This is likely a
- symptom of having lost the pointer at some earlier point in the
- program.</para>
- </listitem>
+ <para>"Possibly lost". This covers cases 5--8 (for the BBB blocks)
+ above. This means that a chain of one or more pointers to the block has
+ been found, but at least one of the pointers is an interior-pointer.
+ This could just be a random value in memory that happens to point into a
+ block, and so you shouldn't consider this ok unless you know you have
+ interior-pointers.</para>
+ </listitem>
</itemizedlist>
-<para>For each block mentioned, Memcheck will also tell you where the
-block was allocated. It cannot tell you how or why the pointer to a
-leaked block has been lost; you have to work that out for yourself. In
-general, you should attempt to ensure your programs do not have any
-leaked or dubious blocks at exit.</para>
+<para>(Note: This mapping of the nine possible cases onto four categories is
+not necessarily the best way that leaks could be reported; in particular,
+interior-pointers are treated inconsistently. It is possible the
+categorisation may be improved in the future.)</para>
+
+<para>Furthermore, if suppressions exists for a block, it will be reported
+as "suppressed" no matter what which of the above four categories it belongs
+to.</para>
+
+
+<para>The following is an example leak summary.</para>
+
+<programlisting><![CDATA[
+LEAK SUMMARY:
+ definitely lost: 48 bytes in 3 blocks.
+ indirectly lost: 32 bytes in 2 blocks.
+ possibly lost: 96 bytes in 6 blocks.
+ still reachable: 64 bytes in 4 blocks.
+ suppressed: 0 bytes in 0 blocks.
+]]></programlisting>
+
+<para>If <computeroutput>--leak-check=full</computeroutput> is specified,
+Memcheck will give details for each definitely lost or possibly lost block,
+including where it was allocated. It cannot tell you when or how or why the
+pointer to a leaked block was lost; you have to work that out for yourself.
+In general, you should attempt to ensure your programs do not have any
+definitely lost or possibly lost blocks at exit.</para>
<para>For example:</para>
<programlisting><![CDATA[
by 0x........: mk (leak-tree.c:11)
by 0x........: main (leak-tree.c:39)
-88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost
- in loss record 13 of 14
+88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 14
at 0x........: malloc (vg_replace_malloc.c:...)
by 0x........: mk (leak-tree.c:11)
by 0x........: main (leak-tree.c:25)
]]></programlisting>
<para>The first message describes a simple case of a single 8 byte block
-that has been definitely lost. The second case mentions both "direct"
-and "indirect" leaks. The distinction is that a direct leak is a block
-which has no pointers to it. An indirect leak is a block which is only
-pointed to by other leaked blocks. Both kinds of leak are bad.</para>
-
-<para>The precise area of memory in which Memcheck searches for pointers
-is: all naturally-aligned machine-word-sized words found in memory
-that Memcheck's records indicate is both accessible and initialised.
-</para>
+that has been definitely lost. The second case mentions another 8 byte
+block that has been definitely lost; the difference is that a further 80
+bytes in other blocks are indirectly lost because of this lost block.</para>
+
+<para>If you specify <computeroutput>--show-reachable=yes</computeroutput>,
+reachable and indirectly lost blocks will also be shown, as the following
+two examples show.</para>
+
+<programlisting><![CDATA[
+64 bytes in 4 blocks are still reachable in loss record 2 of 4
+ at 0x........: malloc (vg_replace_malloc.c:177)
+ by 0x........: mk (leak-cases.c:52)
+ by 0x........: main (leak-cases.c:74)
+
+32 bytes in 2 blocks are indirectly lost in loss record 1 of 4
+ at 0x........: malloc (vg_replace_malloc.c:177)
+ by 0x........: mk (leak-cases.c:52)
+ by 0x........: main (leak-cases.c:80)
+]]></programlisting>
</sect2>
<para><varname>VALGRIND_CHECK_VALUE_IS_DEFINED</varname>: a quick and easy
way to find out whether Valgrind thinks a particular value
(lvalue, to be precise) is addressable and defined. Prints an error
- message if not. Returns no value.</para>
+ message if not. It has no return value.</para>
+ </listitem>
+
+ <listitem>
+ <para><varname>VALGRIND_DO_LEAK_CHECK</varname>: does a full memory leak
+ check (like <computeroutput>--leak-check=full</computeroutput> right now.
+ This is useful for incrementally checking for leaks between arbitrary
+ places in the program's execution. It has no return value.</para>
</listitem>
<listitem>
- <para><varname>VALGRIND_DO_LEAK_CHECK</varname>: runs the memory
- leak detector right now. Is useful for incrementally checking for
- leaks between arbitrary places in the program's execution. Returns
- no value.</para>
+ <para><varname>VALGRIND_DO_QUICK_LEAK_CHECK</varname>: like
+ <varname>VALGRIND_DO_LEAK_CHECK</varname>, except it produces only a leak
+ summary (like <computeroutput>--leak-check=summary</computeroutput>).
+ It has no return value.</para>
</listitem>
<listitem>
<para><varname>VALGRIND_COUNT_LEAKS</varname>: fills in the four
arguments with the number of bytes of memory found by the previous
- leak check to be leaked, dubious, reachable and suppressed. Again,
- useful in test harness code, after calling
- <varname>VALGRIND_DO_LEAK_CHECK</varname>.</para>
+ leak check to be leaked (i.e. the sum of direct leaks and indirect leaks),
+ dubious, reachable and suppressed. Again, useful in test harness code,
+ after calling <varname>VALGRIND_DO_LEAK_CHECK</varname> or
+ <varname>VALGRIND_DO_QUICK_LEAK_CHECK</varname>.</para>
</listitem>
<listitem>
#include <setjmp.h> // For jmp_buf
-
-/* Define to debug the memory-leak-detector. */
-#define VG_DEBUG_LEAKCHECK 0
-#define VG_DEBUG_CLIQUE 0
-
/*------------------------------------------------------------*/
-/*--- Low-level address-space scanning, for the leak ---*/
-/*--- detector. ---*/
+/*--- An overview of leak checking. ---*/
/*------------------------------------------------------------*/
-static
-jmp_buf memscan_jmpbuf;
-
-
-static
-void scan_all_valid_memory_catcher ( Int sigNo, Addr addr )
-{
- if (0)
- VG_(printf)("OUCH! sig=%d addr=%#lx\n", sigNo, addr);
- if (sigNo == VKI_SIGSEGV || sigNo == VKI_SIGBUS)
- __builtin_longjmp(memscan_jmpbuf, 1);
-}
-
-
-/* TODO: GIVE THIS A PROPER HOME
- TODO: MERGE THIS WITH DUPLICATE IN m_main.c and coredump-elf.c.
- Extract from aspacem a vector of the current segment start
- addresses. The vector is dynamically allocated and should be freed
- by the caller when done. REQUIRES m_mallocfree to be running.
- Writes the number of addresses required into *n_acquired. */
-
-static Addr* get_seg_starts ( /*OUT*/Int* n_acquired )
-{
- Addr* starts;
- Int n_starts, r = 0;
+// Leak-checking is a directed-graph traversal problem. The graph has
+// two kinds of nodes:
+// - root-set nodes:
+// - GP registers of all threads;
+// - valid, aligned, pointer-sized data words in valid client memory,
+// including stacks, but excluding words within client heap-allocated
+// blocks (they are excluded so that later on we can differentiate
+// between heap blocks that are indirectly leaked vs. directly leaked).
+// - heap-allocated blocks. A block is a mempool chunk or a malloc chunk
+// that doesn't contain a mempool chunk. Nb: the terms "blocks" and
+// "chunks" are used interchangeably below.
+//
+// There are two kinds of edges:
+// - start-pointers, i.e. pointers to the start of a block;
+// - interior-pointers, i.e. pointers to the interior of a block.
+//
+// We use "pointers" rather than "edges" below.
+//
+// Root set nodes only point to blocks. Blocks only point to blocks;
+// a block can point to itself.
+//
+// The aim is to traverse the graph and determine the status of each block.
+//
+// There are 9 distinct cases. See memcheck/docs/mc-manual.xml for details.
+// Presenting all nine categories to the user is probably too much.
+// Currently we do this:
+// - definitely lost: case 3
+// - indirectly lost: case 4, 9
+// - possibly lost: cases 5..8
+// - still reachable: cases 1, 2
+//
+// It's far from clear that this is the best possible categorisation; it's
+// accreted over time without any central guiding principle.
- n_starts = 1;
- while (True) {
- starts = VG_(malloc)( "mc.gss.1", n_starts * sizeof(Addr) );
- if (starts == NULL)
- break;
- r = VG_(am_get_segment_starts)( starts, n_starts );
- if (r >= 0)
- break;
- VG_(free)(starts);
- n_starts *= 2;
- }
+/*------------------------------------------------------------*/
+/*--- XXX: Thoughts for improvement. ---*/
+/*------------------------------------------------------------*/
- if (starts == NULL) {
- *n_acquired = 0;
- return NULL;
- }
+// From the user's point of view:
+// - If they aren't using interior-pointers, they just have to fix the
+// directly lost blocks, and the indirectly lost ones will be fixed as
+// part of that. Any possibly lost blocks will just be due to random
+// pointer garbage and can be ignored.
+//
+// - If they are using interior-pointers, the fact that they currently are not
+// being told which ones might be directly lost vs. indirectly lost makes
+// it hard to know where to begin.
+//
+// All this makes me wonder if new option is warranted:
+// --follow-interior-pointers. By default it would be off, the leak checker
+// wouldn't follow interior-pointers and there would only be 3 categories:
+// R, DL, IL.
+//
+// If turned on, then it would show 7 categories (R, DL, IL, DR/DL, IR/IL,
+// IR/IL/DL, IL/DL). That output is harder to understand but it's your own
+// damn fault for using interior-pointers...
+//
+// ----
+//
+// Also, why are two blank lines printed between each loss record?
+//
+// ----
+//
+// Also, --show-reachable is a bad name because it also turns on the showing
+// of indirectly leaked blocks(!) It would be better named --show-all or
+// --show-all-heap-blocks, because that's the end result.
+//
+// ----
+//
+// Also, the VALGRIND_LEAK_CHECK and VALGRIND_QUICK_LEAK_CHECK aren't great
+// names. VALGRIND_FULL_LEAK_CHECK and VALGRIND_SUMMARY_LEAK_CHECK would be
+// better.
+//
+// ----
+//
+// Also, VALGRIND_COUNT_LEAKS and VALGRIND_COUNT_LEAK_BLOCKS aren't great as
+// they combine direct leaks and indirect leaks into one. New, more precise
+// ones (they'll need new names) would be good. If more categories are
+// used, as per the --follow-interior-pointers option, they should be
+// updated accordingly. And they should use a struct to return the values.
+//
+// ----
+//
+// Also, for this case:
+//
+// (4) p4 BBB ---> AAA
+//
+// BBB is definitely directly lost. AAA is definitely indirectly lost.
+// Here's the relevant loss records printed for a full check (each block is
+// 16 bytes):
+//
+// ==20397== 16 bytes in 1 blocks are indirectly lost in loss record 9 of 15
+// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397== by 0x400521: mk (leak-cases.c:49)
+// ==20397== by 0x400578: main (leak-cases.c:72)
+//
+// ==20397== 32 (16 direct, 16 indirect) bytes in 1 blocks are definitely
+// lost in loss record 14 of 15
+// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397== by 0x400521: mk (leak-cases.c:49)
+// ==20397== by 0x400580: main (leak-cases.c:72)
+//
+// The first one is fine -- it describes AAA.
+//
+// The second one is for BBB. It's correct in that 16 bytes in 1 block are
+// directly lost. It's also correct that 16 are indirectly lost as a result,
+// but it means that AAA is being counted twice in the loss records. (It's
+// not, thankfully, counted twice in the summary counts). Argh.
+//
+// This would be less confusing for the second one:
+//
+// ==20397== 16 bytes in 1 blocks are definitely lost in loss record 14
+// of 15 (and 16 bytes in 1 block are indirectly lost as a result; they
+// are mentioned elsewhere (if --show-reachable=yes is given!))
+// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397== by 0x400521: mk (leak-cases.c:49)
+// ==20397== by 0x400580: main (leak-cases.c:72)
+//
+// But ideally we'd present the loss record for the directly lost block and
+// then the resultant indirectly lost blocks and make it clear the
+// dependence. Double argh.
- *n_acquired = r;
- return starts;
-}
+/*------------------------------------------------------------*/
+/*--- The actual algorithm. ---*/
+/*------------------------------------------------------------*/
+// - Find all the blocks (a.k.a. chunks) to check. Mempool chunks require
+// some special treatment because they can be within malloc'd blocks.
+// - Scan every word in the root set (GP registers and valid
+// non-heap memory words).
+// - First, we skip if it doesn't point to valid memory.
+// - Then, we see if it points to the start or interior of a block. If
+// so, we push the block onto the mark stack and mark it as having been
+// reached.
+// - Then, we process the mark stack, repeating the scanning for each block;
+// this can push more blocks onto the mark stack. We repeat until the
+// mark stack is empty. Each block is marked as definitely or possibly
+// reachable, depending on whether interior-pointers were required to
+// reach it.
+// - At this point we know for every block if it's reachable or not.
+// - We then push each unreached block onto the mark stack, using the block
+// number as the "clique" number.
+// - We process the mark stack again, this time grouping blocks into cliques
+// in order to facilitate the directly/indirectly lost categorisation.
+// - We group blocks by their ExeContexts and categorisation, and print them
+// if --leak-check=full. We also print summary numbers.
+//
+// A note on "cliques":
+// - A directly lost block is one with no pointers to it. An indirectly
+// lost block is one that is pointed to by a directly or indirectly lost
+// block.
+// - Each directly lost block has zero or more indirectly lost blocks
+// hanging off it. All these blocks together form a "clique". The
+// directly lost block is called the "clique leader". The clique number
+// is the number (in lc_chunks[]) of the clique leader.
+// - Actually, a directly lost block may be pointed to if it's part of a
+// cycle. In that case, there may be more than one choice for the clique
+// leader, and the choice is arbitrary. Eg. if you have A-->B and B-->A
+// either A or B could be the clique leader.
+// - Cliques cannot overlap, and will be truncated to avoid this. Eg. if we
+// have A-->C and B-->C, the two cliques will be {A,C} and {B}, or {A} and
+// {B,C} (again the choice is arbitrary). This is because we don't want
+// to count a block as indirectly lost more than once.
+//
+// A note on 'is_prior_definite':
+// - This is a boolean used in various places that indicates if the chain
+// up to the prior node (prior to the one being considered) is definite.
+// - In the clique == -1 case:
+// - if True it means that the prior node is a root-set node, or that the
+// prior node is a block which is reachable from the root-set via
+// start-pointers.
+// - if False it means that the prior node is a block that is only
+// reachable from the root-set via a path including at least one
+// interior-pointer.
+// - In the clique != -1 case, currently it's always True because we treat
+// start-pointers and interior-pointers the same for direct/indirect leak
+// checking. If we added a PossibleIndirectLeak state then this would
+// change.
+
+
+// Define to debug the memory-leak-detector.
+#define VG_DEBUG_LEAKCHECK 0
+#define VG_DEBUG_CLIQUE 0
+
+#define UMSG(args...) VG_(message)(Vg_UserMsg, ##args)
/*------------------------------------------------------------*/
-/*--- Detecting leaked (unreachable) malloc'd blocks. ---*/
+/*--- Getting the initial chunks, and searching them. ---*/
/*------------------------------------------------------------*/
-/* An entry in the mark stack */
-typedef
- struct {
- Int next:30; /* Index of next in mark stack */
- UInt state:2; /* Reachedness */
- SizeT indirect; /* if Unreached, how much is unreachable from here */
- }
- MarkStack;
-
-/* Find the i such that ptr points at or inside the block described by
- shadows[i]. Return -1 if none found. This assumes that shadows[]
- has been sorted on the ->data field. */
+// Compare the MC_Chunks by 'data' (i.e. the address of the block).
+static Int compare_MC_Chunks(void* n1, void* n2)
+{
+ MC_Chunk* mc1 = *(MC_Chunk**)n1;
+ MC_Chunk* mc2 = *(MC_Chunk**)n2;
+ if (mc1->data < mc2->data) return -1;
+ if (mc1->data > mc2->data) return 1;
+ return 0;
+}
#if VG_DEBUG_LEAKCHECK
-/* Used to sanity-check the fast binary-search mechanism. */
+// Used to sanity-check the fast binary-search mechanism.
static
-Int find_shadow_for_OLD ( Addr ptr,
- MC_Chunk** shadows,
- Int n_shadows )
+Int find_chunk_for_OLD ( Addr ptr,
+ MC_Chunk** chunks,
+ Int n_chunks )
{
Int i;
Addr a_lo, a_hi;
- PROF_EVENT(70, "find_shadow_for_OLD");
- for (i = 0; i < n_shadows; i++) {
- PROF_EVENT(71, "find_shadow_for_OLD(loop)");
- a_lo = shadows[i]->data;
- a_hi = ((Addr)shadows[i]->data) + shadows[i]->szB;
+ PROF_EVENT(70, "find_chunk_for_OLD");
+ for (i = 0; i < n_chunks; i++) {
+ PROF_EVENT(71, "find_chunk_for_OLD(loop)");
+ a_lo = chunks[i]->data;
+ a_hi = ((Addr)chunks[i]->data) + chunks[i]->szB;
if (a_lo <= ptr && ptr < a_hi)
return i;
}
}
#endif
-
+// Find the i such that ptr points at or inside the block described by
+// chunks[i]. Return -1 if none found. This assumes that chunks[]
+// has been sorted on the 'data' field.
static
-Int find_shadow_for ( Addr ptr,
- MC_Chunk** shadows,
- Int n_shadows )
+Int find_chunk_for ( Addr ptr,
+ MC_Chunk** chunks,
+ Int n_chunks )
{
Addr a_mid_lo, a_mid_hi;
Int lo, mid, hi, retVal;
- /* VG_(printf)("find shadow for %p = ", ptr); */
+ // VG_(printf)("find chunk for %p = ", ptr);
retVal = -1;
lo = 0;
- hi = n_shadows-1;
+ hi = n_chunks-1;
while (True) {
- /* invariant: current unsearched space is from lo to hi, inclusive. */
- if (lo > hi) break; /* not found */
+ // Invariant: current unsearched space is from lo to hi, inclusive.
+ if (lo > hi) break; // not found
mid = (lo + hi) / 2;
- a_mid_lo = shadows[mid]->data;
- a_mid_hi = shadows[mid]->data + shadows[mid]->szB;
- /* Extent of block 'mid' is [a_mid_lo .. a_mid_hi).
- Special-case zero-sized blocks - treat them as if they had
- size 1. Not doing so causes them to not cover any address
- range at all and so will never be identified as the target of
- any pointer, which causes them to be incorrectly reported as
- definitely leaked. */
- if (shadows[mid]->szB == 0)
+ a_mid_lo = chunks[mid]->data;
+ a_mid_hi = chunks[mid]->data + chunks[mid]->szB;
+ // Extent of block 'mid' is [a_mid_lo .. a_mid_hi).
+ // Special-case zero-sized blocks - treat them as if they had
+ // size 1. Not doing so causes them to not cover any address
+ // range at all and so will never be identified as the target of
+ // any pointer, which causes them to be incorrectly reported as
+ // definitely leaked.
+ if (chunks[mid]->szB == 0)
a_mid_hi++;
if (ptr < a_mid_lo) {
}
# if VG_DEBUG_LEAKCHECK
- tl_assert(retVal == find_shadow_for_OLD ( ptr, shadows, n_shadows ));
+ tl_assert(retVal == find_chunk_for_OLD ( ptr, chunks, n_chunks ));
# endif
- /* VG_(printf)("%d\n", retVal); */
+ // VG_(printf)("%d\n", retVal);
return retVal;
}
-/* Globals, for the following callback used by VG_(detect_memory_leaks). */
-static MC_Chunk** lc_shadows;
-static Int lc_n_shadows;
-static MarkStack* lc_markstack;
-static Int lc_markstack_top;
-static Addr lc_min_mallocd_addr;
-static Addr lc_max_mallocd_addr;
-static SizeT lc_scanned;
-static Bool (*lc_is_within_valid_secondary) (Addr addr);
-static Bool (*lc_is_valid_aligned_word) (Addr addr);
+static MC_Chunk**
+find_active_chunks(UInt* pn_chunks)
+{
+ // Our goal is to construct a set of chunks that includes every
+ // mempool chunk, and every malloc region that *doesn't* contain a
+ // mempool chunk.
+ MC_Mempool *mp;
+ MC_Chunk **mallocs, **chunks, *mc;
+ UInt n_mallocs, n_chunks, m, s;
+ Bool *malloc_chunk_holds_a_pool_chunk;
+
+ // First we collect all the malloc chunks into an array and sort it.
+ // We do this because we want to query the chunks by interior
+ // pointers, requiring binary search.
+ mallocs = (MC_Chunk**) VG_(HT_to_array)( MC_(malloc_list), &n_mallocs );
+ if (n_mallocs == 0) {
+ tl_assert(mallocs == NULL);
+ *pn_chunks = 0;
+ return NULL;
+ }
+ VG_(ssort)(mallocs, n_mallocs, sizeof(VgHashNode*), compare_MC_Chunks);
+
+ // Then we build an array containing a Bool for each malloc chunk,
+ // indicating whether it contains any mempools.
+ malloc_chunk_holds_a_pool_chunk = VG_(calloc)( "mc.fas.1",
+ n_mallocs, sizeof(Bool) );
+ n_chunks = n_mallocs;
+
+ // Then we loop over the mempool tables. For each chunk in each
+ // pool, we set the entry in the Bool array corresponding to the
+ // malloc chunk containing the mempool chunk.
+ VG_(HT_ResetIter)(MC_(mempool_list));
+ while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
+ VG_(HT_ResetIter)(mp->chunks);
+ while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
+
+ // We'll need to record this chunk.
+ n_chunks++;
+
+ // Possibly invalidate the malloc holding the beginning of this chunk.
+ m = find_chunk_for(mc->data, mallocs, n_mallocs);
+ if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
+ tl_assert(n_chunks > 0);
+ n_chunks--;
+ malloc_chunk_holds_a_pool_chunk[m] = True;
+ }
+
+ // Possibly invalidate the malloc holding the end of this chunk.
+ if (mc->szB > 1) {
+ m = find_chunk_for(mc->data + (mc->szB - 1), mallocs, n_mallocs);
+ if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
+ tl_assert(n_chunks > 0);
+ n_chunks--;
+ malloc_chunk_holds_a_pool_chunk[m] = True;
+ }
+ }
+ }
+ }
+ tl_assert(n_chunks > 0);
+
+ // Create final chunk array.
+ chunks = VG_(malloc)("mc.fas.2", sizeof(VgHashNode*) * (n_chunks));
+ s = 0;
+
+ // Copy the mempool chunks and the non-marked malloc chunks into a
+ // combined array of chunks.
+ VG_(HT_ResetIter)(MC_(mempool_list));
+ while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
+ VG_(HT_ResetIter)(mp->chunks);
+ while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
+ tl_assert(s < n_chunks);
+ chunks[s++] = mc;
+ }
+ }
+ for (m = 0; m < n_mallocs; ++m) {
+ if (!malloc_chunk_holds_a_pool_chunk[m]) {
+ tl_assert(s < n_chunks);
+ chunks[s++] = mallocs[m];
+ }
+ }
+ tl_assert(s == n_chunks);
+
+ // Free temporaries.
+ VG_(free)(mallocs);
+ VG_(free)(malloc_chunk_holds_a_pool_chunk);
+
+ *pn_chunks = n_chunks;
+
+ return chunks;
+}
+
+/*------------------------------------------------------------*/
+/*--- The leak detector proper. ---*/
+/*------------------------------------------------------------*/
+
+// Holds extra info about each block during leak checking.
+typedef
+ struct {
+ UInt state:2; // Reachedness.
+ SizeT indirect_szB : (sizeof(SizeT)*8)-2; // If Unreached, how many bytes
+ // are unreachable from here.
+ }
+ LC_Extra;
+
+// An array holding pointers to every chunk we're checking. Sorted by address.
+static MC_Chunk** lc_chunks;
+// How many chunks we're dealing with.
+static Int lc_n_chunks;
+
+// 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
+// pushed onto it (actually I think the maximum is lc_n_chunks-1 but let's
+// be conservative).
+static Int* lc_markstack;
+// The index of the top element of the stack; -1 if the stack is empty, 0 if
+// the stack has one element, 1 if it has two, etc.
+static Int lc_markstack_top;
+
+// Keeps track of how many bytes of memory we've scanned, for printing.
+// (Nb: We don't keep track of how many register bytes we've scanned.)
+static SizeT lc_scanned_szB;
SizeT MC_(bytes_leaked) = 0;
SizeT MC_(blocks_reachable) = 0;
SizeT MC_(blocks_suppressed) = 0;
-static Int lc_compar(void* n1, void* n2)
+
+/* TODO: GIVE THIS A PROPER HOME
+ TODO: MERGE THIS WITH DUPLICATE IN m_main.c and coredump-elf.c.
+ Extract from aspacem a vector of the current segment start
+ addresses. The vector is dynamically allocated and should be freed
+ by the caller when done. REQUIRES m_mallocfree to be running.
+ Writes the number of addresses required into *n_acquired. */
+
+static Addr* get_seg_starts ( /*OUT*/Int* n_acquired )
{
- MC_Chunk* mc1 = *(MC_Chunk**)n1;
- MC_Chunk* mc2 = *(MC_Chunk**)n2;
- if (mc1->data < mc2->data) return -1;
- if (mc1->data > mc2->data) return 1;
- return 0;
+ Addr* starts;
+ Int n_starts, r = 0;
+
+ n_starts = 1;
+ while (True) {
+ starts = VG_(malloc)( "mc.gss.1", n_starts * sizeof(Addr) );
+ if (starts == NULL)
+ break;
+ r = VG_(am_get_segment_starts)( starts, n_starts );
+ if (r >= 0)
+ break;
+ VG_(free)(starts);
+ n_starts *= 2;
+ }
+
+ if (starts == NULL) {
+ *n_acquired = 0;
+ return NULL;
+ }
+
+ *n_acquired = r;
+ return starts;
}
-/* If ptr is pointing to a heap-allocated block which hasn't been seen
- before, push it onto the mark stack. Clique is the index of the
- clique leader; -1 if none. */
-static void lc_markstack_push_WRK(Addr ptr, Int clique)
+
+// Determines if a pointer is to a chunk. Returns the chunk number et al
+// via call-by-reference.
+static Bool
+lc_is_a_chunk_ptr(Addr ptr, Int* pch_no, MC_Chunk** pch, LC_Extra** pex)
{
- Int sh_no;
+ Int ch_no;
+ MC_Chunk* ch;
+ LC_Extra* ex;
- /* quick filter */
- if (!VG_(am_is_valid_for_client)(ptr, 1, VKI_PROT_NONE))
- return;
+ // Quick filter.
+ if (!VG_(am_is_valid_for_client)(ptr, 1, VKI_PROT_READ)) {
+ return False;
+ } else {
+ ch_no = find_chunk_for(ptr, lc_chunks, lc_n_chunks);
+ tl_assert(ch_no >= -1 && ch_no < lc_n_chunks);
- sh_no = find_shadow_for(ptr, lc_shadows, lc_n_shadows);
+ if (ch_no == -1) {
+ return False;
+ } else {
+ // Ok, we've found a pointer to a chunk. Get the MC_Chunk and its
+ // LC_Extra.
+ ch = lc_chunks[ch_no];
+ ex = &(lc_extras[ch_no]);
- if (VG_DEBUG_LEAKCHECK)
- VG_(printf)("ptr=%#lx -> block %d\n", ptr, sh_no);
+ tl_assert(ptr >= ch->data);
+ tl_assert(ptr < ch->data + ch->szB + (ch->szB==0 ? 1 : 0));
- if (sh_no == -1)
- return;
+ if (VG_DEBUG_LEAKCHECK)
+ VG_(printf)("ptr=%#lx -> block %d\n", ptr, ch_no);
- tl_assert(sh_no >= 0 && sh_no < lc_n_shadows);
- tl_assert(ptr >= lc_shadows[sh_no]->data);
- tl_assert(ptr < lc_shadows[sh_no]->data
- + lc_shadows[sh_no]->szB
- + (lc_shadows[sh_no]->szB==0 ? 1 : 0));
+ *pch_no = ch_no;
+ *pch = ch;
+ *pex = ex;
- if (lc_markstack[sh_no].state == Unreached) {
- if (0)
- VG_(printf)("pushing %#lx-%#lx\n", lc_shadows[sh_no]->data,
- lc_shadows[sh_no]->data + lc_shadows[sh_no]->szB);
+ return True;
+ }
+ }
+}
- tl_assert(lc_markstack[sh_no].next == -1);
- lc_markstack[sh_no].next = lc_markstack_top;
- lc_markstack_top = sh_no;
+// Push a chunk (well, just its index) onto the mark stack.
+static void lc_push(Int ch_no, MC_Chunk* ch)
+{
+ if (0) {
+ VG_(printf)("pushing %#lx-%#lx\n", ch->data, ch->data + ch->szB);
}
+ lc_markstack_top++;
+ tl_assert(lc_markstack_top < lc_n_chunks);
+ lc_markstack[lc_markstack_top] = ch_no;
+}
- tl_assert(clique >= -1 && clique < lc_n_shadows);
-
- if (clique != -1) {
- if (0)
- VG_(printf)("mopup: %d: %#lx is %d\n",
- sh_no, lc_shadows[sh_no]->data, lc_markstack[sh_no].state);
-
- /* An unmarked block - add it to the clique. Add its size to
- the clique-leader's indirect size. If the new block was
- itself a clique leader, it isn't any more, so add its
- indirect to the new clique leader.
-
- If this block *is* the clique leader, it means this is a
- cyclic structure, so none of this applies. */
- if (lc_markstack[sh_no].state == Unreached) {
- lc_markstack[sh_no].state = IndirectLeak;
-
- if (sh_no != clique) {
- if (VG_DEBUG_CLIQUE) {
- if (lc_markstack[sh_no].indirect)
- VG_(printf)(" clique %d joining clique %d adding %lu+%lu bytes\n",
- sh_no, clique,
- lc_shadows[sh_no]->szB + 0UL,
- lc_markstack[sh_no].indirect);
- else
- VG_(printf)(" %d joining %d adding %lu\n",
- sh_no, clique,
- lc_shadows[sh_no]->szB + 0UL);
- }
-
- lc_markstack[clique].indirect += lc_shadows[sh_no]->szB;
- lc_markstack[clique].indirect += lc_markstack[sh_no].indirect;
- lc_markstack[sh_no].indirect = 0; /* shouldn't matter */
- }
- }
- } else if (ptr == lc_shadows[sh_no]->data) {
- lc_markstack[sh_no].state = Proper;
+// Return the index of the chunk on the top of the mark stack, or -1 if
+// there isn't one.
+static Bool lc_pop(Int* ret)
+{
+ if (-1 == lc_markstack_top) {
+ return False;
} else {
- if (lc_markstack[sh_no].state == Unreached)
- lc_markstack[sh_no].state = Interior;
+ tl_assert(0 <= lc_markstack_top && lc_markstack_top < lc_n_chunks);
+ *ret = lc_markstack[lc_markstack_top];
+ lc_markstack_top--;
+ return True;
+ }
+}
+
+
+// If 'ptr' is pointing to a heap-allocated block which hasn't been seen
+// before, push it onto the mark stack.
+static void
+lc_push_without_clique_if_a_chunk_ptr(Addr ptr, Bool is_prior_definite)
+{
+ Int ch_no;
+ MC_Chunk* ch;
+ LC_Extra* ex;
+
+ if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
+ return;
+
+ // Only push it if it hasn't been seen previously.
+ if (ex->state == Unreached) {
+ lc_push(ch_no, ch);
+ }
+
+ // Possibly upgrade the state, ie. one of:
+ // - Unreached --> Possible
+ // - Unreached --> Reachable
+ // - Possible --> Reachable
+ if (ptr == ch->data && is_prior_definite) {
+ // 'ptr' points to the start of the block, and the prior node is
+ // definite, which means that this block is definitely reachable.
+ ex->state = Reachable;
+
+ } else if (ex->state == Unreached) {
+ // Either 'ptr' is a interior-pointer, or the prior node isn't definite,
+ // which means that we can only mark this block as possibly reachable.
+ ex->state = Possible;
}
}
-static void lc_markstack_push(Addr ptr)
+static void
+lc_push_if_a_chunk_ptr_register(Addr ptr)
{
- lc_markstack_push_WRK(ptr, -1);
+ lc_push_without_clique_if_a_chunk_ptr(ptr, /*is_prior_definite*/True);
}
-/* Return the top of the mark stack, if any. */
-static Int lc_markstack_pop(void)
+// If ptr is pointing to a heap-allocated block which hasn't been seen
+// 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)
{
- Int ret = lc_markstack_top;
+ Int ch_no;
+ MC_Chunk* ch;
+ LC_Extra* ex;
+
+ tl_assert(0 <= clique && clique < lc_n_chunks);
+
+ if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
+ return;
- if (ret != -1) {
- lc_markstack_top = lc_markstack[ret].next;
- lc_markstack[ret].next = -1;
+ // If it's not Unreached, it's already been handled so ignore it.
+ // If ch_no==clique, it's the clique leader, which means this is a cyclic
+ // structure; again ignore it because it's already been handled.
+ if (ex->state == Unreached && ch_no != 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
+ // clique-leader's indirect size. Also, if the new block was
+ // 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)
+ VG_(printf)(" clique %d joining clique %d adding %lu+%lu\n",
+ ch_no, clique, (SizeT)ch->szB, (SizeT)ex->indirect_szB);
+ else
+ VG_(printf)(" block %d joining clique %d adding %lu\n",
+ ch_no, clique, (SizeT)ch->szB);
+ }
+
+ lc_extras[clique].indirect_szB += ch->szB;
+ lc_extras[clique].indirect_szB += ex->indirect_szB;
+ ex->indirect_szB = 0; // Shouldn't matter.
}
+}
- return ret;
+static void
+lc_push_if_a_chunk_ptr(Addr ptr, Int 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);
}
-/* Scan a block of memory between [start, start+len). This range may
- be bogus, inaccessable, or otherwise strange; we deal with it.
+static jmp_buf memscan_jmpbuf;
- If clique != -1, it means we're gathering leaked memory into
- cliques, and clique is the index of the current clique leader. */
-static void lc_scan_memory_WRK(Addr start, SizeT len, Int clique)
+static
+void scan_all_valid_memory_catcher ( Int sigNo, Addr addr )
+{
+ if (0)
+ VG_(printf)("OUCH! sig=%d addr=%#lx\n", sigNo, addr);
+ if (sigNo == VKI_SIGSEGV || sigNo == VKI_SIGBUS)
+ __builtin_longjmp(memscan_jmpbuf, 1);
+}
+
+// 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.
+static void
+lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique)
{
- Addr ptr = VG_ROUNDUP(start, sizeof(Addr));
+ Addr ptr = VG_ROUNDUP(start, sizeof(Addr));
Addr end = VG_ROUNDDN(start+len, sizeof(Addr));
vki_sigset_t sigmask;
if (VG_DEBUG_LEAKCHECK)
- VG_(printf)("scan %#lx-%#lx\n", start, start+len);
+ VG_(printf)("scan %#lx-%#lx (%lu)\n", start, end, len);
+
VG_(sigprocmask)(VKI_SIG_SETMASK, NULL, &sigmask);
VG_(set_fault_catcher)(scan_all_valid_memory_catcher);
- // lc_scanned += end-ptr;
-
+ // We might be in the middle of a page. Do a cheap check to see if
+ // it's valid; if not, skip onto the next page.
if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ))
- ptr = VG_PGROUNDUP(ptr+1); /* first page bad */
+ ptr = VG_PGROUNDUP(ptr+1); // First page is bad - skip it.
while (ptr < end) {
Addr addr;
- /* Skip invalid chunks */
- if (!(*lc_is_within_valid_secondary)(ptr)) {
- ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
- continue;
+ // Skip invalid chunks.
+ if ( ! MC_(is_within_valid_secondary)(ptr) ) {
+ ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
+ continue;
}
- /* Look to see if this page seems reasonble */
+ // Look to see if this page seems reasonable.
if ((ptr % VKI_PAGE_SIZE) == 0) {
- if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ))
- ptr += VKI_PAGE_SIZE; /* bad page - skip it */
+ if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) {
+ ptr += VKI_PAGE_SIZE; // Bad page - skip it.
+ continue;
+ }
}
if (__builtin_setjmp(memscan_jmpbuf) == 0) {
- if ((*lc_is_valid_aligned_word)(ptr)) {
- lc_scanned += sizeof(Addr);
- addr = *(Addr *)ptr;
- lc_markstack_push_WRK(addr, clique);
- } else if (0 && VG_DEBUG_LEAKCHECK)
- VG_(printf)("%#lx not valid\n", ptr);
- ptr += sizeof(Addr);
+ if ( MC_(is_valid_aligned_word)(ptr) ) {
+ lc_scanned_szB += sizeof(Addr);
+ 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);
+ } else if (0 && VG_DEBUG_LEAKCHECK) {
+ VG_(printf)("%#lx not valid\n", ptr);
+ }
+ ptr += sizeof(Addr);
} else {
- /* We need to restore the signal mask, because we were
- longjmped out of a signal handler. */
- VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL);
+ // We need to restore the signal mask, because we were
+ // longjmped out of a signal handler.
+ VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL);
- ptr = VG_PGROUNDUP(ptr+1); /* bad page - skip it */
+ ptr = VG_PGROUNDUP(ptr+1); // Bad page - skip it.
}
}
}
-static void lc_scan_memory(Addr start, SizeT len)
+// Process the mark stack until empty.
+static void lc_process_markstack(Int clique)
{
- if (VG_(clo_verbosity) > 2) {
- VG_(message)(Vg_DebugMsg, " Scanning segment: %#lx..%#lx (%ld)",
- start, start+len-1, len);
- }
- lc_scan_memory_WRK(start, len, -1);
-}
+ Int top;
+ Bool is_prior_definite;
-/* Process the mark stack until empty. If mopup is true, then we're
- actually gathering leaked blocks, so they should be marked
- IndirectLeak. */
-static void lc_do_leakcheck(Int clique)
-{
- Int top;
+ while (lc_pop(&top)) {
+ tl_assert(top >= 0 && top < lc_n_chunks);
- while((top = lc_markstack_pop()) != -1) {
- tl_assert(top >= 0 && top < lc_n_shadows);
- tl_assert(lc_markstack[top].state != Unreached);
+ // See comment about 'is_prior_definite' at the top to understand this.
+ is_prior_definite = ( Possible != lc_extras[top].state );
- lc_scan_memory_WRK(lc_shadows[top]->data, lc_shadows[top]->szB, clique);
+ lc_scan_memory(lc_chunks[top]->data, lc_chunks[top]->szB,
+ is_prior_definite, clique);
}
}
-static void full_report(ThreadId tid)
+static void print_results(ThreadId tid, Bool is_full_check)
{
- Int i;
- Int n_lossrecords;
+ Int i, n_lossrecords;
LossRecord* errlist;
LossRecord* p;
- Bool is_suppressed;
-
- /* Go through and group lost structures into cliques. For each
- Unreached block, push it onto the mark stack, and find all the
- blocks linked to it. These are marked IndirectLeak, and their
- size is added to the clique leader's indirect size. If one of
- the found blocks was itself a clique leader (from a previous
- pass), then the cliques are merged. */
- for (i = 0; i < lc_n_shadows; i++) {
- if (VG_DEBUG_CLIQUE)
- VG_(printf)("cliques: %d at %#lx -> Loss state %d\n",
- i, lc_shadows[i]->data, lc_markstack[i].state);
- if (lc_markstack[i].state != Unreached)
- continue;
-
- tl_assert(lc_markstack_top == -1);
-
- if (VG_DEBUG_CLIQUE)
- VG_(printf)("%d: gathering clique %#lx\n", i, lc_shadows[i]->data);
-
- lc_markstack_push_WRK(lc_shadows[i]->data, i);
-
- lc_do_leakcheck(i);
+ Bool is_suppressed;
- tl_assert(lc_markstack_top == -1);
- tl_assert(lc_markstack[i].state == IndirectLeak
- /* jrs 20051218: Ashley Pittman supplied a
- custom-allocator test program which causes the ==
- IndirectLeak condition to fail - it causes .state
- to be Unreached. Since I have no idea how this
- clique stuff works and no time to figure it out,
- just allow that condition too. This could well be
- a completely bogus fix. It doesn't seem unsafe
- given that in any case the .state field is
- immediately overwritten by the next statement. */
- || lc_markstack[i].state == Unreached);
-
- lc_markstack[i].state = Unreached; /* Return to unreached state,
- to indicate its a clique
- leader */
- }
-
- /* Common up the lost blocks so we can print sensible error messages. */
+ // Common up the lost blocks so we can print sensible error messages.
n_lossrecords = 0;
errlist = NULL;
- for (i = 0; i < lc_n_shadows; i++) {
- ExeContext* where = lc_shadows[i]->where;
+ for (i = 0; i < lc_n_chunks; i++) {
+ MC_Chunk* ch = lc_chunks[i];
+ LC_Extra* ex = &(lc_extras)[i];
for (p = errlist; p != NULL; p = p->next) {
- if (p->loss_mode == lc_markstack[i].state
+ if (p->loss_mode == ex->state
&& VG_(eq_ExeContext) ( MC_(clo_leak_resolution),
p->allocated_at,
- where) ) {
+ ch->where) ) {
break;
- }
+ }
}
if (p != NULL) {
- p->num_blocks ++;
- p->total_bytes += lc_shadows[i]->szB;
- p->indirect_bytes += lc_markstack[i].indirect;
+ p->num_blocks++;
+ p->total_bytes += ch->szB;
+ p->indirect_szB += ex->indirect_szB;
} else {
- n_lossrecords ++;
+ n_lossrecords++;
p = VG_(malloc)( "mc.fr.1", sizeof(LossRecord));
- p->loss_mode = lc_markstack[i].state;
- p->allocated_at = where;
- p->total_bytes = lc_shadows[i]->szB;
- p->indirect_bytes = lc_markstack[i].indirect;
+ p->loss_mode = ex->state;
+ p->allocated_at = ch->where;
+ p->total_bytes = ch->szB;
+ p->indirect_szB = ex->indirect_szB;
p->num_blocks = 1;
p->next = errlist;
errlist = p;
}
}
- /* Print out the commoned-up blocks and collect summary stats. */
+ MC_(blocks_leaked) = MC_(bytes_leaked) = 0;
+ MC_(blocks_indirect) = MC_(bytes_indirect) = 0;
+ MC_(blocks_dubious) = MC_(bytes_dubious) = 0;
+ MC_(blocks_reachable) = MC_(bytes_reachable) = 0;
+ MC_(blocks_suppressed) = MC_(bytes_suppressed) = 0;
+
+ // Print out the commoned-up blocks and collect summary stats.
for (i = 0; i < n_lossrecords; i++) {
Bool print_record;
LossRecord* p_min = NULL;
SizeT n_min = ~(0x0L);
for (p = errlist; p != NULL; p = p->next) {
if (p->num_blocks > 0 && p->total_bytes < n_min) {
- n_min = p->total_bytes + p->indirect_bytes;
+ n_min = p->total_bytes + p->indirect_szB;
p_min = p;
}
}
tl_assert(p_min != NULL);
- /* Ok to have tst==NULL; it's only used if --gdb-attach=yes, and
- we disallow that when --leak-check=yes.
-
- Prints the error if not suppressed, unless it's reachable (Proper
- or IndirectLeak) and --show-reachable=no */
-
- print_record = ( MC_(clo_show_reachable) ||
- Unreached == p_min->loss_mode ||
- Interior == p_min->loss_mode );
-
- // Nb: because VG_(unique_error) does all the error processing
- // immediately, and doesn't save the error, leakExtra can be
- // stack-allocated.
+ // Rules for printing:
+ // - We don't show suppressed loss records ever (and that's controlled
+ // within the error manager).
+ // - We show non-suppressed loss records that are not "reachable" if
+ // --leak-check=yes.
+ // - We show all non-suppressed loss records if --leak-check=yes and
+ // --show-reachable=yes.
+ //
+ // Nb: here "reachable" means Reachable *or* IndirectLeak; note that
+ // this is different to "still reachable" used elsewhere because it
+ // includes indirectly lost blocks!
+ //
+ print_record = is_full_check &&
+ ( MC_(clo_show_reachable) ||
+ Unreached == p_min->loss_mode ||
+ Possible == p_min->loss_mode );
is_suppressed =
MC_(record_leak_error) ( tid, i+1, n_lossrecords, p_min,
print_record );
MC_(bytes_suppressed) += p_min->total_bytes;
} else if (Unreached == p_min->loss_mode) {
- MC_(blocks_leaked) += p_min->num_blocks;
- MC_(bytes_leaked) += p_min->total_bytes;
+ MC_(blocks_leaked) += p_min->num_blocks;
+ MC_(bytes_leaked) += p_min->total_bytes;
} else if (IndirectLeak == p_min->loss_mode) {
- MC_(blocks_indirect) += p_min->num_blocks;
- MC_(bytes_indirect) += p_min->total_bytes;
+ MC_(blocks_indirect) += p_min->num_blocks;
+ MC_(bytes_indirect) += p_min->total_bytes;
- } else if (Interior == p_min->loss_mode) {
- MC_(blocks_dubious) += p_min->num_blocks;
- MC_(bytes_dubious) += p_min->total_bytes;
+ } else if (Possible == p_min->loss_mode) {
+ MC_(blocks_dubious) += p_min->num_blocks;
+ MC_(bytes_dubious) += p_min->total_bytes;
- } else if (Proper == p_min->loss_mode) {
- MC_(blocks_reachable) += p_min->num_blocks;
- MC_(bytes_reachable) += p_min->total_bytes;
+ } else if (Reachable == p_min->loss_mode) {
+ MC_(blocks_reachable) += p_min->num_blocks;
+ MC_(bytes_reachable) += p_min->total_bytes;
} else {
- VG_(tool_panic)("generic_detect_memory_leaks: unknown loss mode");
+ VG_(tool_panic)("unknown loss mode");
}
p_min->num_blocks = 0;
}
-}
-
-/* Compute a quick summary of the leak check. */
-static void make_summary(void)
-{
- Int i;
-
- for(i = 0; i < lc_n_shadows; i++) {
- SizeT size = lc_shadows[i]->szB;
-
- switch(lc_markstack[i].state) {
- case Unreached:
- MC_(blocks_leaked)++;
- MC_(bytes_leaked) += size;
- break;
-
- case Proper:
- MC_(blocks_reachable)++;
- MC_(bytes_reachable) += size;
- break;
-
- case Interior:
- MC_(blocks_dubious)++;
- MC_(bytes_dubious) += size;
- break;
-
- case IndirectLeak: /* shouldn't happen */
- MC_(blocks_indirect)++;
- MC_(bytes_indirect) += size;
- break;
- }
- }
-}
-
-static MC_Chunk**
-find_active_shadows(UInt* n_shadows)
-{
- /* Our goal is to construct a set of shadows that includes every
- * mempool chunk, and every malloc region that *doesn't* contain a
- * mempool chunk. We do this in several phases.
- *
- * First we collect all the malloc chunks into an array and sort it.
- * We do this because we want to query the chunks by interior
- * pointers, requiring binary search.
- *
- * Second we build an array containing a Bool for each malloc chunk,
- * indicating whether it contains any mempools.
- *
- * Third we loop over the mempool tables. For each chunk in each
- * pool, we set the entry in the Bool array corresponding to the
- * malloc chunk containing the mempool chunk.
- *
- * Finally we copy the mempool chunks and the non-marked malloc
- * chunks into a combined array of shadows, free our temporaries,
- * and return the combined array.
- */
-
- MC_Mempool *mp;
- MC_Chunk **mallocs, **shadows, *mc;
- UInt n_mallocs, m, s;
- Bool *malloc_chunk_holds_a_pool_chunk;
-
- mallocs = (MC_Chunk**) VG_(HT_to_array)( MC_(malloc_list), &n_mallocs );
-
- if (n_mallocs == 0) {
- tl_assert(mallocs == NULL);
- *n_shadows = 0;
- return NULL;
- }
-
- VG_(ssort)((void*)mallocs, n_mallocs,
- sizeof(VgHashNode*), lc_compar);
-
- malloc_chunk_holds_a_pool_chunk = VG_(calloc)( "mc.fas.1",
- n_mallocs, sizeof(Bool) );
-
- *n_shadows = n_mallocs;
-
- VG_(HT_ResetIter)(MC_(mempool_list));
- while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
- VG_(HT_ResetIter)(mp->chunks);
- while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
-
- /* We'll need a shadow for this chunk. */
- ++(*n_shadows);
-
- /* Possibly invalidate the malloc holding the beginning of
- this chunk. */
- m = find_shadow_for(mc->data, mallocs, n_mallocs);
- if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
- tl_assert(*n_shadows > 0);
- --(*n_shadows);
- malloc_chunk_holds_a_pool_chunk[m] = True;
- }
-
- /* Possibly invalidate the malloc holding the end of this chunk. */
- if (mc->szB > 1) {
- m = find_shadow_for(mc->data + (mc->szB - 1), mallocs, n_mallocs);
- if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
- tl_assert(*n_shadows > 0);
- --(*n_shadows);
- malloc_chunk_holds_a_pool_chunk[m] = True;
- }
- }
- }
- }
- tl_assert(*n_shadows > 0);
- shadows = VG_(malloc)("mc.fas.2", sizeof(VgHashNode*) * (*n_shadows));
- s = 0;
-
- /* Copy the mempool chunks into the final array. */
- VG_(HT_ResetIter)(MC_(mempool_list));
- while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
- VG_(HT_ResetIter)(mp->chunks);
- while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
- tl_assert(s < *n_shadows);
- shadows[s++] = mc;
+ if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
+ UMSG("");
+ UMSG("LEAK SUMMARY:");
+ UMSG(" definitely lost: %'lu bytes in %'lu blocks.",
+ MC_(bytes_leaked), MC_(blocks_leaked) );
+ UMSG(" indirectly lost: %'lu bytes in %'lu blocks.",
+ MC_(bytes_indirect), MC_(blocks_indirect) );
+ UMSG(" possibly lost: %'lu bytes in %'lu blocks.",
+ MC_(bytes_dubious), MC_(blocks_dubious) );
+ UMSG(" still reachable: %'lu bytes in %'lu blocks.",
+ MC_(bytes_reachable), MC_(blocks_reachable) );
+ UMSG(" suppressed: %'lu bytes in %'lu blocks.",
+ MC_(bytes_suppressed), MC_(blocks_suppressed) );
+ if (!is_full_check &&
+ (MC_(blocks_leaked) + MC_(blocks_indirect) +
+ MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
+ UMSG("Rerun with --leak-check=full to see details of leaked memory.");
}
- }
-
- /* Copy the malloc chunks into the final array. */
- for (m = 0; m < n_mallocs; ++m) {
- if (!malloc_chunk_holds_a_pool_chunk[m]) {
- tl_assert(s < *n_shadows);
- shadows[s++] = mallocs[m];
+ if (is_full_check &&
+ MC_(blocks_reachable) > 0 && !MC_(clo_show_reachable))
+ {
+ UMSG("Reachable blocks (those to which a pointer was found) are not shown.");
+ UMSG("To see them, rerun with: --leak-check=full --show-reachable=yes");
}
}
-
- tl_assert(s == *n_shadows);
- VG_(free)(mallocs);
- VG_(free)(malloc_chunk_holds_a_pool_chunk);
-
- return shadows;
}
+/*------------------------------------------------------------*/
+/*--- Top-level entry point. ---*/
+/*------------------------------------------------------------*/
-/* Top level entry point to leak detector. Call here, passing in
- suitable address-validating functions (see comment at top of
- scan_all_valid_memory above). These functions used to encapsulate the
- differences between Memcheck and Addrcheck; they no longer do but it
- doesn't hurt to keep them here.
-*/
-void MC_(do_detect_memory_leaks) (
- ThreadId tid, LeakCheckMode mode,
- Bool (*is_within_valid_secondary) ( Addr ),
- Bool (*is_valid_aligned_word) ( Addr )
-)
+void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckMode mode )
{
Int i;
tl_assert(mode != LC_Off);
- lc_shadows = find_active_shadows(&lc_n_shadows);
+ // Get the chunks, stop if there were none.
+ lc_chunks = find_active_chunks(&lc_n_chunks);
+ if (lc_n_chunks == 0) {
+ tl_assert(lc_chunks == NULL);
+ if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
+ UMSG("All heap blocks were freed -- no leaks are possible.");
+ }
+ return;
+ }
- /* Sort the array. */
- VG_(ssort)((void*)lc_shadows, lc_n_shadows, sizeof(VgHashNode*), lc_compar);
+ // Sort the array so blocks are in ascending order in memory.
+ VG_(ssort)(lc_chunks, lc_n_chunks, sizeof(VgHashNode*), compare_MC_Chunks);
- /* Sanity check; assert that the blocks are now in order */
- for (i = 0; i < lc_n_shadows-1; i++) {
- tl_assert( lc_shadows[i]->data <= lc_shadows[i+1]->data);
+ // Sanity check -- make sure they're in order.
+ for (i = 0; i < lc_n_chunks-1; i++) {
+ tl_assert( lc_chunks[i]->data <= lc_chunks[i+1]->data);
}
- /* Sanity check -- make sure they don't overlap. But do allow
- exact duplicates. If this assertion fails, it may mean that the
- application has done something stupid with
- VALGRIND_MALLOCLIKE_BLOCK client requests, specifically, has
- made overlapping requests (which are nonsensical). Another way
- to screw up is to use VALGRIND_MALLOCLIKE_BLOCK for stack
- locations; again nonsensical. */
- for (i = 0; i < lc_n_shadows-1; i++) {
+ // Sanity check -- make sure they don't overlap. But do allow exact
+ // duplicates. If this assertion fails, it may mean that the application
+ // has done something stupid with VALGRIND_MALLOCLIKE_BLOCK client
+ // requests, specifically, has made overlapping requests (which are
+ // nonsensical). Another way to screw up is to use
+ // VALGRIND_MALLOCLIKE_BLOCK for stack locations; again nonsensical.
+ for (i = 0; i < lc_n_chunks-1; i++) {
+ MC_Chunk* ch1 = lc_chunks[i];
+ MC_Chunk* ch2 = lc_chunks[i+1];
Bool nonsense_overlap = ! (
- /* normal case - no overlap */
- (lc_shadows[i]->data + lc_shadows[i]->szB <= lc_shadows[i+1]->data)
- ||
- /* degenerate case: exact duplicates */
- (lc_shadows[i]->data == lc_shadows[i+1]->data
- && lc_shadows[i]->szB == lc_shadows[i+1]->szB)
+ // Normal case - no overlap.
+ (ch1->data + ch1->szB <= ch2->data) ||
+ // Degenerate case: exact duplicates.
+ (ch1->data == ch2->data && ch1->szB == ch2->szB)
);
if (nonsense_overlap) {
- VG_(message)(Vg_UserMsg, "Block [0x%lx, 0x%lx) overlaps with block [0x%lx, 0x%lx)",
- lc_shadows[ i]->data, (lc_shadows[ i]->data + lc_shadows[ i]->szB),
- lc_shadows[1+ i]->data, (lc_shadows[1+ i]->data + lc_shadows[1+ i]->szB) );
+ UMSG("Block [0x%lx, 0x%lx) overlaps with block [0x%lx, 0x%lx)",
+ ch1->data, (ch1->data + ch1->szB),
+ ch2->data, (ch2->data + ch2->szB));
}
tl_assert (!nonsense_overlap);
}
- if (lc_n_shadows == 0) {
- tl_assert(lc_shadows == NULL);
- if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
- VG_(message)(Vg_UserMsg,
- "All heap blocks were freed -- no leaks are possible.");
- }
- return;
+ // Initialise lc_extras.
+ 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].indirect_szB = 0;
}
- if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
- VG_(message)(Vg_UserMsg,
- "searching for pointers to %'d not-freed blocks.",
- lc_n_shadows );
-
- lc_min_mallocd_addr = lc_shadows[0]->data;
- lc_max_mallocd_addr = lc_shadows[lc_n_shadows-1]->data
- + lc_shadows[lc_n_shadows-1]->szB;
-
- lc_markstack = VG_(malloc)( "mc.ddml.1",
- lc_n_shadows * sizeof(*lc_markstack) );
- for (i = 0; i < lc_n_shadows; i++) {
- lc_markstack[i].next = -1;
- lc_markstack[i].state = Unreached;
- lc_markstack[i].indirect = 0;
+ // Initialise lc_markstack.
+ lc_markstack = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(Int) );
+ for (i = 0; i < lc_n_chunks; i++) {
+ lc_markstack[i] = -1;
}
lc_markstack_top = -1;
- lc_is_within_valid_secondary = is_within_valid_secondary;
- lc_is_valid_aligned_word = is_valid_aligned_word;
-
- lc_scanned = 0;
-
- /* Push roots onto the mark stack. Roots are:
- - the integer registers of all threads
- - all mappings belonging to the client, including stacks
- - .. but excluding any client heap segments.
- Client heap segments are excluded because we wish to differentiate
- client heap blocks which are referenced only from inside the heap
- from those outside. This facilitates the indirect vs direct loss
- categorisation, which [if the users ever manage to understand it]
- is really useful for detecting lost cycles.
- */
- { Addr* seg_starts;
- Int n_seg_starts;
- seg_starts = get_seg_starts( &n_seg_starts );
- tl_assert(seg_starts && n_seg_starts > 0);
- /* VG_(am_show_nsegments)( 0,"leakcheck"); */
- for (i = 0; i < n_seg_starts; i++) {
- 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);
- lc_scan_memory(seg->start, seg->end+1 - seg->start);
- }
+ // Verbosity.
+ if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
+ UMSG( "searching for pointers to %'d not-freed blocks.", lc_n_chunks );
+
+ // Scan the memory root-set, pushing onto the mark stack any blocks
+ // pointed to.
+ {
+ Int n_seg_starts;
+ Addr* seg_starts = get_seg_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)",
+ seg->start, seg->end, seg_size);
+ }
+ lc_scan_memory(seg->start, seg_size, /*is_prior_definite*/True, -1);
+ }
}
- /* Push registers onto mark stack */
- VG_(apply_to_GP_regs)(lc_markstack_push);
+ // Scan GP registers for chunk pointers.
+ VG_(apply_to_GP_regs)(lc_push_if_a_chunk_ptr_register);
- /* Keep walking the heap until everything is found */
- lc_do_leakcheck(-1);
+ // Process the pushed blocks. After this, every block that is reachable
+ // from the root-set has been traced.
+ lc_process_markstack(/*clique*/-1);
if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
- VG_(message)(Vg_UserMsg, "checked %'lu bytes.", lc_scanned);
+ UMSG("checked %'lu bytes.", lc_scanned_szB);
+
+ // Trace all the leaked blocks to determine which are directly leaked and
+ // which are indirectly leaked. For each Unreached block, push it onto
+ // the mark stack, and find all the as-yet-Unreached blocks reachable
+ // from it. These form a clique and are marked IndirectLeak, and their
+ // size is added to the clique leader's indirect size. If one of the
+ // found blocks was itself a clique leader (from a previous clique), then
+ // the cliques are merged.
+ for (i = 0; i < lc_n_chunks; i++) {
+ MC_Chunk* ch = lc_chunks[i];
+ LC_Extra* ex = &(lc_extras[i]);
- MC_(blocks_leaked) = MC_(bytes_leaked) = 0;
- MC_(blocks_indirect) = MC_(bytes_indirect) = 0;
- MC_(blocks_dubious) = MC_(bytes_dubious) = 0;
- MC_(blocks_reachable) = MC_(bytes_reachable) = 0;
- MC_(blocks_suppressed) = MC_(bytes_suppressed) = 0;
+ if (VG_DEBUG_CLIQUE)
+ VG_(printf)("cliques: %d at %#lx -> Loss state %d\n",
+ i, ch->data, ex->state);
- if (mode == LC_Full)
- full_report(tid);
- else
- make_summary();
+ tl_assert(lc_markstack_top == -1);
- if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
- VG_(message)(Vg_UserMsg, "");
- VG_(message)(Vg_UserMsg, "LEAK SUMMARY:");
- VG_(message)(Vg_UserMsg, " definitely lost: %'lu bytes in %'lu blocks.",
- MC_(bytes_leaked), MC_(blocks_leaked) );
- if (MC_(blocks_indirect) > 0)
- VG_(message)(Vg_UserMsg, " indirectly lost: %'lu bytes in %'lu blocks.",
- MC_(bytes_indirect), MC_(blocks_indirect) );
- VG_(message)(Vg_UserMsg, " possibly lost: %'lu bytes in %'lu blocks.",
- MC_(bytes_dubious), MC_(blocks_dubious) );
- VG_(message)(Vg_UserMsg, " still reachable: %'lu bytes in %'lu blocks.",
- MC_(bytes_reachable), MC_(blocks_reachable) );
- VG_(message)(Vg_UserMsg, " suppressed: %'lu bytes in %'lu blocks.",
- MC_(bytes_suppressed), MC_(blocks_suppressed) );
- if (mode == LC_Summary
- && (MC_(blocks_leaked) + MC_(blocks_indirect)
- + MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
- VG_(message)(Vg_UserMsg,
- "Rerun with --leak-check=full to see details of leaked memory.");
- }
- if (MC_(blocks_reachable) > 0 && !MC_(clo_show_reachable) && mode == LC_Full) {
- VG_(message)(Vg_UserMsg,
- "Reachable blocks (those to which a pointer was found) are not shown.");
- VG_(message)(Vg_UserMsg,
- "To see them, rerun with: --leak-check=full --show-reachable=yes");
+ if (ex->state == Unreached) {
+ if (VG_DEBUG_CLIQUE)
+ VG_(printf)("%d: gathering clique %#lx\n", i, ch->data);
+
+ // Push this Unreached block onto the stack and process it.
+ lc_push(i, ch);
+ lc_process_markstack(i);
+
+ tl_assert(lc_markstack_top == -1);
+ tl_assert(ex->state == Unreached);
}
}
+
+ print_results( tid, ( mode == LC_Full ? True : False ) );
- VG_(free) ( lc_shadows );
+ VG_(free) ( lc_chunks );
+ VG_(free) ( lc_extras );
VG_(free) ( lc_markstack );
}