mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-08 21:09:49 +00:00
It compiles, but aborts immediately if you try to run it.
I didn't include ldt.c; I'm not sure how the LDT is used on AMD64. It can be
added later if necessary.
While doing this, did some 64-bit cleanness fixes:
- Added necessary intermediate casts to ULong to avoid warnings when converting
ThreadId to void* and vice versa, in vg_scheduler.c.
- Fixed VALGRIND_NON_SIMD_CALL[0123] to use 'long' as the return type.
- Fixed VALGRIND_PRINTF{,BACKTRACE} to use unsigned longs instead of unsigned
ints, as needed.
- Converted some offsets in vg_symtab2.h from "Int" to "OffT".
- Made strlen, strncat, etc, use SizeT instead of 'unsigned int' for the length
parameter.
- Couple of other minor things.
I had to insert some "#ifdef __amd64__" and "#ifndef __amd64__" guards in
places. In particular, in vg_mylibc.c, some of our syscall wrappers aren't
appropriate for AMD64 because the syscall numbering is a bit different in
places. This difference will have to be abstracted out somehow.
Also rewrote the sys_fcntl and sys_fcntl64 wrappers, as required for AMD64.
Also moved the ipc wrapper into x86, since it's not applicable for
AMD64. However, it is applicable (I think) for ARM, so it would be nice
to work out a way to share syscall wrappers between some, but not all,
archs. Hmm. Also now using the real IPC constants rather than magic
numbers in the wrapper.
Other non-AMD64-related fixes:
- ARM: fixed syscall table by accounting for the fact that syscall
numbers don't start at 0, but rather at 0x900000.
- Converted a few places to use ThreadId instead of 'int' or 'Int' for
thread IDs.
- Added both AMD64 and ARM (which I'd forgotten) entries to valgrind.spec.in.
- Tweaked comments in various places.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@3136
557 lines
19 KiB
C
557 lines
19 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- The leak checker, shared between Memcheck and Addrcheck. ---*/
|
|
/*--- mac_leakcheck.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of MemCheck, a heavyweight Valgrind tool for
|
|
detecting memory errors, and AddrCheck, a lightweight Valgrind tool
|
|
for detecting memory errors.
|
|
|
|
Copyright (C) 2000-2004 Julian Seward
|
|
jseward@acm.org
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
|
|
The GNU General Public License is contained in the file COPYING.
|
|
*/
|
|
|
|
#include "mac_shared.h"
|
|
|
|
/* Define to debug the memory-leak-detector. */
|
|
/* #define VG_DEBUG_LEAKCHECK */
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Low-level address-space scanning, for the leak ---*/
|
|
/*--- detector. ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
static
|
|
jmp_buf memscan_jmpbuf;
|
|
|
|
|
|
static
|
|
void vg_scan_all_valid_memory_sighandler ( Int sigNo )
|
|
{
|
|
__builtin_longjmp(memscan_jmpbuf, 1);
|
|
}
|
|
|
|
|
|
/* Safely (avoiding SIGSEGV / SIGBUS) scan the entire valid address
|
|
space and pass the addresses and values of all addressible,
|
|
defined, aligned words to notify_word. This is the basis for the
|
|
leak detector. Returns the number of calls made to notify_word.
|
|
|
|
Addresses are validated 3 ways. First we enquire whether (addr >>
|
|
16) denotes a 64k chunk in use, by asking is_valid_64k_chunk(). If
|
|
so, we decide for ourselves whether each x86-level (4 K) page in
|
|
the chunk is safe to inspect. If yes, we enquire with
|
|
is_valid_address() whether or not each of the 1024 word-locations
|
|
on the page is valid. Only if so are that address and its contents
|
|
passed to notify_word.
|
|
|
|
This is all to avoid duplication of this machinery between
|
|
Memcheck and Addrcheck.
|
|
*/
|
|
static
|
|
UInt vg_scan_all_valid_memory ( Bool is_valid_64k_chunk ( UInt ),
|
|
Bool is_valid_address ( Addr ),
|
|
void (*notify_word)( Addr, UInt ) )
|
|
{
|
|
/* All volatile, because some gccs seem paranoid about longjmp(). */
|
|
volatile Bool anyValid;
|
|
volatile Addr pageBase, addr;
|
|
volatile UInt res, numPages, page, primaryMapNo;
|
|
volatile UInt page_first_word, nWordsNotified;
|
|
|
|
struct vki_sigaction sigbus_saved;
|
|
struct vki_sigaction sigbus_new;
|
|
struct vki_sigaction sigsegv_saved;
|
|
struct vki_sigaction sigsegv_new;
|
|
vki_sigset_t blockmask_saved;
|
|
vki_sigset_t unblockmask_new;
|
|
|
|
/* Temporarily install a new sigsegv and sigbus handler, and make
|
|
sure SIGBUS, SIGSEGV and SIGTERM are unblocked. (Perhaps the
|
|
first two can never be blocked anyway?) */
|
|
|
|
sigbus_new.ksa_handler = vg_scan_all_valid_memory_sighandler;
|
|
sigbus_new.sa_flags = VKI_SA_ONSTACK | VKI_SA_RESTART;
|
|
sigbus_new.sa_restorer = NULL;
|
|
res = VG_(sigemptyset)( &sigbus_new.sa_mask );
|
|
tl_assert(res == 0);
|
|
|
|
sigsegv_new.ksa_handler = vg_scan_all_valid_memory_sighandler;
|
|
sigsegv_new.sa_flags = VKI_SA_ONSTACK | VKI_SA_RESTART;
|
|
sigsegv_new.sa_restorer = NULL;
|
|
res = VG_(sigemptyset)( &sigsegv_new.sa_mask );
|
|
tl_assert(res == 0+0);
|
|
|
|
res = VG_(sigemptyset)( &unblockmask_new );
|
|
res |= VG_(sigaddset)( &unblockmask_new, VKI_SIGBUS );
|
|
res |= VG_(sigaddset)( &unblockmask_new, VKI_SIGSEGV );
|
|
res |= VG_(sigaddset)( &unblockmask_new, VKI_SIGTERM );
|
|
tl_assert(res == 0+0+0);
|
|
|
|
res = VG_(sigaction)( VKI_SIGBUS, &sigbus_new, &sigbus_saved );
|
|
tl_assert(res == 0+0+0+0);
|
|
|
|
res = VG_(sigaction)( VKI_SIGSEGV, &sigsegv_new, &sigsegv_saved );
|
|
tl_assert(res == 0+0+0+0+0);
|
|
|
|
res = VG_(sigprocmask)( VKI_SIG_UNBLOCK, &unblockmask_new, &blockmask_saved );
|
|
tl_assert(res == 0+0+0+0+0+0);
|
|
|
|
/* The signal handlers are installed. Actually do the memory scan. */
|
|
numPages = 1 << (32-VKI_PAGE_SHIFT);
|
|
tl_assert(numPages == 1048576);
|
|
tl_assert(4096 == (1 << VKI_PAGE_SHIFT));
|
|
|
|
nWordsNotified = 0;
|
|
|
|
for (page = 0; page < numPages; page++) {
|
|
|
|
/* Base address of this 4k page. */
|
|
pageBase = page << VKI_PAGE_SHIFT;
|
|
|
|
/* Skip if this page is in an unused 64k chunk. */
|
|
primaryMapNo = pageBase >> 16;
|
|
if (!is_valid_64k_chunk(primaryMapNo))
|
|
continue;
|
|
|
|
/* Next, establish whether or not we want to consider any
|
|
locations on this page. We need to do so before actually
|
|
prodding it, because prodding it when in fact it is not
|
|
needed can cause a page fault which under some rare
|
|
circumstances can cause the kernel to extend the stack
|
|
segment all the way down to here, which is seriously bad.
|
|
Hence: */
|
|
anyValid = False;
|
|
for (addr = pageBase; addr < pageBase+VKI_PAGE_SIZE; addr += 4) {
|
|
if (is_valid_address(addr)) {
|
|
anyValid = True;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!anyValid)
|
|
continue; /* nothing interesting here .. move to the next page */
|
|
|
|
/* Ok, we have to prod cautiously at the page and see if it
|
|
explodes or not. */
|
|
if (__builtin_setjmp(memscan_jmpbuf) == 0) {
|
|
/* try this ... */
|
|
page_first_word = * (volatile UInt*)pageBase;
|
|
/* we get here if we didn't get a fault */
|
|
/* Scan the page */
|
|
for (addr = pageBase; addr < pageBase+VKI_PAGE_SIZE; addr += 4) {
|
|
if (is_valid_address(addr)) {
|
|
nWordsNotified++;
|
|
notify_word ( addr, *(UInt*)addr );
|
|
}
|
|
}
|
|
} else {
|
|
/* We get here if reading the first word of the page caused a
|
|
fault, which in turn caused the signal handler to longjmp.
|
|
Ignore this page. */
|
|
if (0)
|
|
VG_(printf)(
|
|
"vg_scan_all_valid_memory_sighandler: ignoring page at %p\n",
|
|
(void*)pageBase
|
|
);
|
|
}
|
|
}
|
|
|
|
/* Restore signal state to whatever it was before. */
|
|
res = VG_(sigaction)( VKI_SIGBUS, &sigbus_saved, NULL );
|
|
tl_assert(res == 0 +0);
|
|
|
|
res = VG_(sigaction)( VKI_SIGSEGV, &sigsegv_saved, NULL );
|
|
tl_assert(res == 0 +0 +0);
|
|
|
|
res = VG_(sigprocmask)( VKI_SIG_SETMASK, &blockmask_saved, NULL );
|
|
tl_assert(res == 0 +0 +0 +0);
|
|
|
|
return nWordsNotified;
|
|
}
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Detecting leaked (unreachable) malloc'd blocks. ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/* A block is either
|
|
-- Proper-ly reached; a pointer to its start has been found
|
|
-- Interior-ly reached; only an interior pointer to it has been found
|
|
-- Unreached; so far, no pointers to any part of it have been found.
|
|
*/
|
|
typedef
|
|
enum { Unreached, Interior, Proper }
|
|
Reachedness;
|
|
|
|
/* A block record, used for generating err msgs. */
|
|
typedef
|
|
struct _LossRecord {
|
|
struct _LossRecord* next;
|
|
/* Where these lost blocks were allocated. */
|
|
ExeContext* allocated_at;
|
|
/* Their reachability. */
|
|
Reachedness loss_mode;
|
|
/* Number of blocks and total # bytes involved. */
|
|
UInt total_bytes;
|
|
UInt num_blocks;
|
|
}
|
|
LossRecord;
|
|
|
|
|
|
/* 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. */
|
|
|
|
#ifdef VG_DEBUG_LEAKCHECK
|
|
/* Used to sanity-check the fast binary-search mechanism. */
|
|
static
|
|
Int find_shadow_for_OLD ( Addr ptr,
|
|
MAC_Chunk** shadows,
|
|
Int n_shadows )
|
|
|
|
{
|
|
Int i;
|
|
Addr a_lo, a_hi;
|
|
PROF_EVENT(70);
|
|
for (i = 0; i < n_shadows; i++) {
|
|
PROF_EVENT(71);
|
|
a_lo = shadows[i]->data;
|
|
a_hi = ((Addr)shadows[i]->data) + shadows[i]->size - 1;
|
|
if (a_lo <= ptr && ptr <= a_hi)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
static
|
|
Int find_shadow_for ( Addr ptr,
|
|
MAC_Chunk** shadows,
|
|
Int n_shadows )
|
|
{
|
|
Addr a_mid_lo, a_mid_hi;
|
|
Int lo, mid, hi, retVal;
|
|
/* VG_(printf)("find shadow for %p = ", ptr); */
|
|
retVal = -1;
|
|
lo = 0;
|
|
hi = n_shadows-1;
|
|
while (True) {
|
|
/* 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]->size - 1;
|
|
|
|
if (ptr < a_mid_lo) {
|
|
hi = mid-1;
|
|
continue;
|
|
}
|
|
if (ptr > a_mid_hi) {
|
|
lo = mid+1;
|
|
continue;
|
|
}
|
|
tl_assert(ptr >= a_mid_lo && ptr <= a_mid_hi);
|
|
retVal = mid;
|
|
break;
|
|
}
|
|
|
|
# ifdef VG_DEBUG_LEAKCHECK
|
|
tl_assert(retVal == find_shadow_for_OLD ( ptr, shadows, n_shadows ));
|
|
# endif
|
|
/* VG_(printf)("%d\n", retVal); */
|
|
return retVal;
|
|
}
|
|
|
|
/* Globals, for the following callback used by VG_(detect_memory_leaks). */
|
|
static MAC_Chunk** lc_shadows;
|
|
static Int lc_n_shadows;
|
|
static Reachedness* lc_reachedness;
|
|
static Addr lc_min_mallocd_addr;
|
|
static Addr lc_max_mallocd_addr;
|
|
|
|
static
|
|
void vg_detect_memory_leaks_notify_addr ( Addr a, UInt word_at_a )
|
|
{
|
|
Int sh_no;
|
|
Addr ptr;
|
|
|
|
/* Rule out some known causes of bogus pointers. Mostly these do
|
|
not cause much trouble because only a few false pointers can
|
|
ever lurk in these places. This mainly stops it reporting that
|
|
blocks are still reachable in stupid test programs like this
|
|
|
|
int main (void) { char* a = malloc(100); return 0; }
|
|
|
|
which people seem inordinately fond of writing, for some reason.
|
|
|
|
Note that this is a complete kludge. It would be better to
|
|
ignore any addresses corresponding to valgrind.so's .bss and
|
|
.data segments, but I cannot think of a reliable way to identify
|
|
where the .bss segment has been put. If you can, drop me a
|
|
line.
|
|
*/
|
|
if (!VG_(is_client_addr)(a)) return;
|
|
|
|
/* OK, let's get on and do something Useful for a change. */
|
|
|
|
ptr = (Addr)word_at_a;
|
|
if (ptr >= lc_min_mallocd_addr && ptr <= lc_max_mallocd_addr) {
|
|
/* Might be legitimate; we'll have to investigate further. */
|
|
sh_no = find_shadow_for ( ptr, lc_shadows, lc_n_shadows );
|
|
if (sh_no != -1) {
|
|
/* Found a block at/into which ptr points. */
|
|
tl_assert(sh_no >= 0 && sh_no < lc_n_shadows);
|
|
tl_assert(ptr < lc_shadows[sh_no]->data + lc_shadows[sh_no]->size);
|
|
/* Decide whether Proper-ly or Interior-ly reached. */
|
|
if (ptr == lc_shadows[sh_no]->data) {
|
|
if (0) VG_(printf)("pointer at %p to %p\n", a, word_at_a );
|
|
lc_reachedness[sh_no] = Proper;
|
|
} else {
|
|
if (lc_reachedness[sh_no] == Unreached)
|
|
lc_reachedness[sh_no] = Interior;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Used for printing leak errors, avoids exposing the LossRecord type (which
|
|
comes in as void*, requiring a cast. */
|
|
void MAC_(pp_LeakError)(void* vl, UInt n_this_record, UInt n_total_records)
|
|
{
|
|
LossRecord* l = (LossRecord*)vl;
|
|
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(Vg_UserMsg,
|
|
"%d bytes in %d blocks are %s in loss record %d of %d",
|
|
l->total_bytes, l->num_blocks,
|
|
l->loss_mode==Unreached ? "definitely lost"
|
|
: (l->loss_mode==Interior ? "possibly lost"
|
|
: "still reachable"),
|
|
n_this_record, n_total_records
|
|
);
|
|
VG_(pp_ExeContext)(l->allocated_at);
|
|
}
|
|
|
|
Int MAC_(bytes_leaked) = 0;
|
|
Int MAC_(bytes_dubious) = 0;
|
|
Int MAC_(bytes_reachable) = 0;
|
|
Int MAC_(bytes_suppressed) = 0;
|
|
|
|
static Int lc_compar(void* n1, void* n2)
|
|
{
|
|
MAC_Chunk* mc1 = *(MAC_Chunk**)n1;
|
|
MAC_Chunk* mc2 = *(MAC_Chunk**)n2;
|
|
return (mc1->data < mc2->data ? -1 : 1);
|
|
}
|
|
|
|
/* Top level entry point to leak detector. Call here, passing in
|
|
suitable address-validating functions (see comment at top of
|
|
vg_scan_all_valid_memory above). All this is to avoid duplication
|
|
of the leak-detection code for Memcheck and Addrcheck.
|
|
Also pass in a tool-specific function to extract the .where field
|
|
for allocated blocks, an indication of the resolution wanted for
|
|
distinguishing different allocation points, and whether or not
|
|
reachable blocks should be shown.
|
|
*/
|
|
void MAC_(do_detect_memory_leaks) (
|
|
ThreadId tid,
|
|
Bool is_valid_64k_chunk ( UInt ),
|
|
Bool is_valid_address ( Addr )
|
|
)
|
|
{
|
|
Int i;
|
|
Int blocks_leaked;
|
|
Int blocks_dubious;
|
|
Int blocks_reachable;
|
|
Int blocks_suppressed;
|
|
Int n_lossrecords;
|
|
UInt bytes_notified;
|
|
Bool is_suppressed;
|
|
|
|
LossRecord* errlist;
|
|
LossRecord* p;
|
|
|
|
/* VG_(HT_to_array) allocates storage for shadows */
|
|
lc_shadows = (MAC_Chunk**)VG_(HT_to_array)( MAC_(malloc_list),
|
|
&lc_n_shadows );
|
|
|
|
/* Sort the array. */
|
|
VG_(ssort)((void*)lc_shadows, lc_n_shadows, sizeof(VgHashNode*), lc_compar);
|
|
|
|
/* 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 don't overlap */
|
|
for (i = 0; i < lc_n_shadows-1; i++) {
|
|
tl_assert( lc_shadows[i]->data + lc_shadows[i]->size
|
|
< lc_shadows[i+1]->data );
|
|
}
|
|
|
|
if (lc_n_shadows == 0) {
|
|
tl_assert(lc_shadows == NULL);
|
|
if (VG_(clo_verbosity) >= 1) {
|
|
VG_(message)(Vg_UserMsg,
|
|
"No malloc'd blocks -- no leaks are possible.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (VG_(clo_verbosity) > 0)
|
|
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]->size - 1;
|
|
|
|
lc_reachedness = VG_(malloc)( lc_n_shadows * sizeof(Reachedness) );
|
|
for (i = 0; i < lc_n_shadows; i++)
|
|
lc_reachedness[i] = Unreached;
|
|
|
|
/* Do the scan of memory. */
|
|
bytes_notified
|
|
= sizeof(UWord)
|
|
* vg_scan_all_valid_memory (
|
|
is_valid_64k_chunk,
|
|
is_valid_address,
|
|
&vg_detect_memory_leaks_notify_addr
|
|
);
|
|
|
|
if (VG_(clo_verbosity) > 0)
|
|
VG_(message)(Vg_UserMsg, "checked %d bytes.", bytes_notified);
|
|
|
|
/* 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 (p = errlist; p != NULL; p = p->next) {
|
|
if (p->loss_mode == lc_reachedness[i]
|
|
&& VG_(eq_ExeContext) ( MAC_(clo_leak_resolution),
|
|
p->allocated_at,
|
|
where) ) {
|
|
break;
|
|
}
|
|
}
|
|
if (p != NULL) {
|
|
p->num_blocks ++;
|
|
p->total_bytes += lc_shadows[i]->size;
|
|
} else {
|
|
n_lossrecords ++;
|
|
p = VG_(malloc)(sizeof(LossRecord));
|
|
p->loss_mode = lc_reachedness[i];
|
|
p->allocated_at = where;
|
|
p->total_bytes = lc_shadows[i]->size;
|
|
p->num_blocks = 1;
|
|
p->next = errlist;
|
|
errlist = p;
|
|
}
|
|
}
|
|
|
|
/* Print out the commoned-up blocks and collect summary stats. */
|
|
blocks_leaked = MAC_(bytes_leaked) = 0;
|
|
blocks_dubious = MAC_(bytes_dubious) = 0;
|
|
blocks_reachable = MAC_(bytes_reachable) = 0;
|
|
blocks_suppressed = MAC_(bytes_suppressed) = 0;
|
|
|
|
for (i = 0; i < n_lossrecords; i++) {
|
|
Bool print_record;
|
|
LossRecord* p_min = NULL;
|
|
UInt n_min = 0xFFFFFFFF;
|
|
for (p = errlist; p != NULL; p = p->next) {
|
|
if (p->num_blocks > 0 && p->total_bytes < n_min) {
|
|
n_min = p->total_bytes;
|
|
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)
|
|
and --show-reachable=no */
|
|
|
|
print_record = ( MAC_(clo_show_reachable) || Proper != p_min->loss_mode );
|
|
is_suppressed =
|
|
VG_(unique_error) ( tid, LeakErr, (UInt)i+1,
|
|
(Char*)(UWord)n_lossrecords, (void*) p_min,
|
|
p_min->allocated_at, print_record,
|
|
/*allow_GDB_attach*/False, /*count_error*/False );
|
|
|
|
if (is_suppressed) {
|
|
blocks_suppressed += p_min->num_blocks;
|
|
MAC_(bytes_suppressed) += p_min->total_bytes;
|
|
|
|
} else if (Unreached == p_min->loss_mode) {
|
|
blocks_leaked += p_min->num_blocks;
|
|
MAC_(bytes_leaked) += p_min->total_bytes;
|
|
|
|
} else if (Interior == p_min->loss_mode) {
|
|
blocks_dubious += p_min->num_blocks;
|
|
MAC_(bytes_dubious) += p_min->total_bytes;
|
|
|
|
} else if (Proper == p_min->loss_mode) {
|
|
blocks_reachable += p_min->num_blocks;
|
|
MAC_(bytes_reachable) += p_min->total_bytes;
|
|
|
|
} else {
|
|
VG_(tool_panic)("generic_detect_memory_leaks: unknown loss mode");
|
|
}
|
|
p_min->num_blocks = 0;
|
|
}
|
|
|
|
if (VG_(clo_verbosity) > 0) {
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(Vg_UserMsg, "LEAK SUMMARY:");
|
|
VG_(message)(Vg_UserMsg, " definitely lost: %d bytes in %d blocks.",
|
|
MAC_(bytes_leaked), blocks_leaked );
|
|
VG_(message)(Vg_UserMsg, " possibly lost: %d bytes in %d blocks.",
|
|
MAC_(bytes_dubious), blocks_dubious );
|
|
VG_(message)(Vg_UserMsg, " still reachable: %d bytes in %d blocks.",
|
|
MAC_(bytes_reachable), blocks_reachable );
|
|
VG_(message)(Vg_UserMsg, " suppressed: %d bytes in %d blocks.",
|
|
MAC_(bytes_suppressed), blocks_suppressed );
|
|
if (!MAC_(clo_show_reachable)) {
|
|
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: --show-reachable=yes");
|
|
}
|
|
}
|
|
|
|
VG_(free) ( lc_shadows );
|
|
VG_(free) ( lc_reachedness );
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- end mac_leakcheck.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|