mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-08 13:01:17 +00:00
961 lines
32 KiB
C
961 lines
32 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Memory-related stuff: segment initialisation and tracking, ---*/
|
|
/*--- stack operations ---*/
|
|
/*--- vg_memory.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Valgrind, an extensible x86 protected-mode
|
|
emulator for monitoring program execution on x86-Unixes.
|
|
|
|
Copyright (C) 2000-2002 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 "vg_include.h"
|
|
|
|
/* Define to debug the memory-leak-detector. */
|
|
/* #define VG_DEBUG_LEAKCHECK */
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
/*--- Initialise program data/text etc on program startup. ---*/
|
|
/*--------------------------------------------------------------*/
|
|
|
|
typedef
|
|
struct _ExeSeg {
|
|
Addr start;
|
|
UInt size;
|
|
struct _ExeSeg* next;
|
|
}
|
|
ExeSeg;
|
|
|
|
/* The list of current executable segments loaded. Required so that when a
|
|
segment is munmap'd, if it's executable we can recognise it as such and
|
|
invalidate translations for it, and drop any basic-block specific
|
|
information being stored. If symbols are being used, this list will have
|
|
the same segments recorded in it as the SegInfo symbols list (but much
|
|
less information about each segment).
|
|
*/
|
|
static ExeSeg* exeSegsHead = NULL;
|
|
|
|
/* Prepend it -- mmaps/munmaps likely to follow a stack pattern(?) so this
|
|
is good.
|
|
Also check no segments overlap, which would be very bad. Check is linear
|
|
for each seg added (quadratic overall) but the total number should be
|
|
small (konqueror has around 50 --njn). */
|
|
static void add_exe_segment_to_list( a, len )
|
|
{
|
|
Addr lo = a;
|
|
Addr hi = a + len - 1;
|
|
ExeSeg* es;
|
|
ExeSeg* es2;
|
|
|
|
/* Prepend it */
|
|
es = (ExeSeg*)VG_(arena_malloc)(VG_AR_CORE, sizeof(ExeSeg));
|
|
es->start = a;
|
|
es->size = len;
|
|
es->next = exeSegsHead;
|
|
exeSegsHead = es;
|
|
|
|
/* Check there's no overlap with the rest of the list */
|
|
for (es2 = es->next; es2 != NULL; es2 = es2->next) {
|
|
Addr lo2 = es2->start;
|
|
Addr hi2 = es2->start + es2->size - 1;
|
|
Bool overlap;
|
|
vg_assert(lo < hi);
|
|
vg_assert(lo2 < hi2);
|
|
/* the main assertion */
|
|
overlap = (lo <= lo2 && lo2 <= hi)
|
|
|| (lo <= hi2 && hi2 <= hi);
|
|
if (overlap) {
|
|
VG_(printf)("\n\nOVERLAPPING EXE SEGMENTS\n"
|
|
" new: start %p, size %d\n"
|
|
" old: start %p, size %d\n\n",
|
|
es->start, es->size, es2->start, es2->size );
|
|
vg_assert(! overlap);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Bool remove_if_exe_segment_from_list( Addr a, UInt len )
|
|
{
|
|
ExeSeg **prev_next_ptr = & exeSegsHead,
|
|
*curr = exeSegsHead;
|
|
|
|
while (True) {
|
|
if (curr == NULL) break;
|
|
if (a == curr->start) break;
|
|
prev_next_ptr = &curr->next;
|
|
curr = curr->next;
|
|
}
|
|
if (curr == NULL)
|
|
return False;
|
|
|
|
vg_assert(*prev_next_ptr == curr);
|
|
|
|
*prev_next_ptr = curr->next;
|
|
|
|
VG_(arena_free)(VG_AR_CORE, curr);
|
|
return True;
|
|
}
|
|
|
|
/* Records the exe segment in the ExeSeg list (checking for overlaps), and
|
|
reads debug info if required. Note the entire /proc/pid/maps file is
|
|
read for the debug info, but it just reads symbols for newly added exe
|
|
segments. This is required to find out their names if they have one. So
|
|
we don't use this at startup because it's overkill and can screw reading
|
|
of /proc/pid/maps.
|
|
*/
|
|
void VG_(new_exe_segment) ( Addr a, UInt len )
|
|
{
|
|
add_exe_segment_to_list( a, len );
|
|
VG_(maybe_read_symbols)();
|
|
}
|
|
|
|
/* Invalidate translations as necessary (also discarding any basic
|
|
block-specific info retained by the skin) and unload any debug
|
|
symbols. */
|
|
// Nb: remove_if_exe_segment_from_list() and VG_(maybe_unload_symbols)()
|
|
// both ignore 'len', but that seems that's ok for most programs... see
|
|
// comment above vg_syscalls.c:mmap_segment() et al for more details.
|
|
void VG_(remove_if_exe_segment) ( Addr a, UInt len )
|
|
{
|
|
if (remove_if_exe_segment_from_list( a, len )) {
|
|
VG_(invalidate_translations) ( a, len );
|
|
VG_(maybe_unload_symbols) ( a, len );
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
void startup_segment_callback ( Addr start, UInt size,
|
|
Char rr, Char ww, Char xx,
|
|
UInt foffset, UChar* filename )
|
|
{
|
|
UInt r_esp;
|
|
Bool is_stack_segment;
|
|
|
|
/* Sanity check ... if this is the executable's text segment,
|
|
ensure it is loaded where we think it ought to be. Any file
|
|
name which doesn't contain ".so" is assumed to be the
|
|
executable. */
|
|
if (filename != NULL
|
|
&& xx == 'x'
|
|
&& VG_(strstr(filename, ".so")) == NULL
|
|
) {
|
|
/* We assume this is the executable. */
|
|
if (start != VG_ASSUMED_EXE_BASE) {
|
|
VG_(message)(Vg_UserMsg,
|
|
"FATAL: executable base addr not as assumed.");
|
|
VG_(message)(Vg_UserMsg, "name %s, actual %p, assumed %p.",
|
|
filename, start, VG_ASSUMED_EXE_BASE);
|
|
VG_(message)(Vg_UserMsg,
|
|
"One reason this could happen is that you have a shared object");
|
|
VG_(message)(Vg_UserMsg,
|
|
" whose name doesn't contain the characters \".so\", so Valgrind ");
|
|
VG_(message)(Vg_UserMsg,
|
|
"naively assumes it is the executable. ");
|
|
VG_(message)(Vg_UserMsg,
|
|
"In that case, rename it appropriately.");
|
|
VG_(core_panic)("VG_ASSUMED_EXE_BASE doesn't match reality");
|
|
}
|
|
}
|
|
|
|
if (0)
|
|
VG_(message)(Vg_DebugMsg,
|
|
"initial map %8x-%8x %c%c%c? %8x (%d) (%s)",
|
|
start,start+size,rr,ww,xx,foffset,
|
|
size, filename?filename:(UChar*)"NULL");
|
|
|
|
if (rr != 'r' && xx != 'x' && ww != 'w') {
|
|
/* Implausible as it seems, R H 6.2 generates such segments:
|
|
40067000-400ac000 r-xp 00000000 08:05 320686 /usr/X11R6/lib/libXt.so.6.0
|
|
400ac000-400ad000 ---p 00045000 08:05 320686 /usr/X11R6/lib/libXt.so.6.0
|
|
400ad000-400b0000 rw-p 00045000 08:05 320686 /usr/X11R6/lib/libXt.so.6.0
|
|
when running xedit. So just ignore them. */
|
|
if (0)
|
|
VG_(printf)("No permissions on a segment mapped from %s\n",
|
|
filename?filename:(UChar*)"NULL");
|
|
return;
|
|
}
|
|
|
|
/* This parallels what happens when we mmap some new memory */
|
|
if (filename != NULL && xx == 'x') {
|
|
VG_(new_exe_segment)( start, size );
|
|
}
|
|
VG_TRACK( new_mem_startup, start, size, rr=='r', ww=='w', xx=='x' );
|
|
|
|
/* If this is the stack segment mark all below %esp as noaccess. */
|
|
r_esp = VG_(baseBlock)[VGOFF_(m_esp)];
|
|
is_stack_segment = start <= r_esp && r_esp < start+size;
|
|
if (is_stack_segment) {
|
|
if (0)
|
|
VG_(message)(Vg_DebugMsg, "invalidating stack area: %x .. %x",
|
|
start,r_esp);
|
|
VG_TRACK( die_mem_stack, start, r_esp-start );
|
|
}
|
|
}
|
|
|
|
|
|
/* 1. Records exe segments from /proc/pid/maps -- always necessary, because
|
|
if they're munmap()ed we need to know if they were executable in order
|
|
to discard translations. Also checks there's no exe segment overlaps.
|
|
|
|
2. Marks global variables that might be accessed from generated code;
|
|
|
|
3. Sets up the end of the data segment so that vg_syscalls.c can make
|
|
sense of calls to brk().
|
|
*/
|
|
void VG_(init_memory) ( void )
|
|
{
|
|
/* 1 and 2 */
|
|
VG_(read_procselfmaps) ( startup_segment_callback );
|
|
|
|
/* 3 */
|
|
VG_TRACK( post_mem_write, (Addr) & VG_(running_on_simd_CPU), 1 );
|
|
VG_TRACK( post_mem_write, (Addr) & VG_(clo_trace_malloc), 1 );
|
|
VG_TRACK( post_mem_write, (Addr) & VG_(clo_sloppy_malloc), 1 );
|
|
|
|
/* 4 */
|
|
VG_(init_dataseg_end_for_brk)();
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- Tracking permissions around %esp changes. ---*/
|
|
/*------------------------------------------------------------*/
|
|
|
|
/*
|
|
The stack
|
|
~~~~~~~~~
|
|
The stack's segment seems to be dynamically extended downwards
|
|
by the kernel as the stack pointer moves down. Initially, a
|
|
1-page (4k) stack is allocated. When %esp moves below that for
|
|
the first time, presumably a page fault occurs. The kernel
|
|
detects that the faulting address is in the range from %esp upwards
|
|
to the current valid stack. It then extends the stack segment
|
|
downwards for enough to cover the faulting address, and resumes
|
|
the process (invisibly). The process is unaware of any of this.
|
|
|
|
That means that Valgrind can't spot when the stack segment is
|
|
being extended. Fortunately, we want to precisely and continuously
|
|
update stack permissions around %esp, so we need to spot all
|
|
writes to %esp anyway.
|
|
|
|
The deal is: when %esp is assigned a lower value, the stack is
|
|
being extended. Create a secondary maps to fill in any holes
|
|
between the old stack ptr and this one, if necessary. Then
|
|
mark all bytes in the area just "uncovered" by this %esp change
|
|
as write-only.
|
|
|
|
When %esp goes back up, mark the area receded over as unreadable
|
|
and unwritable.
|
|
|
|
Just to record the %esp boundary conditions somewhere convenient:
|
|
%esp always points to the lowest live byte in the stack. All
|
|
addresses below %esp are not live; those at and above it are.
|
|
*/
|
|
|
|
/* Does this address look like something in or vaguely near the
|
|
current thread's stack? */
|
|
static __attribute__((unused))
|
|
Bool is_plausible_stack_addr ( ThreadState* tst, Addr aa )
|
|
{
|
|
UInt a = (UInt)aa;
|
|
//PROF_EVENT(100); PPP
|
|
if (a <= tst->stack_highest_word &&
|
|
a > tst->stack_highest_word - VG_PLAUSIBLE_STACK_SIZE)
|
|
return True;
|
|
else
|
|
return False;
|
|
}
|
|
|
|
|
|
/* Kludgey ... how much does %esp have to change before we reckon that
|
|
the application is switching stacks ? */
|
|
#define VG_HUGE_DELTA (VG_PLAUSIBLE_STACK_SIZE / 4)
|
|
|
|
static __attribute__((unused))
|
|
Addr get_page_base ( Addr a )
|
|
{
|
|
return a & ~(VKI_BYTES_PER_PAGE-1);
|
|
}
|
|
|
|
static void vg_handle_esp_assignment_SLOWLY ( Addr old_esp, Addr new_esp );
|
|
|
|
__attribute__ ((regparm (1)))
|
|
void VG_(handle_esp_assignment) ( Addr new_esp )
|
|
{
|
|
UInt old_esp;
|
|
Int delta;
|
|
|
|
VGP_MAYBE_PUSHCC(VgpStack);
|
|
|
|
old_esp = VG_(baseBlock)[VGOFF_(m_esp)];
|
|
delta = ((Int)new_esp) - ((Int)old_esp);
|
|
|
|
/* Update R_ESP */
|
|
VG_(baseBlock)[VGOFF_(m_esp)] = new_esp;
|
|
|
|
//PROF_EVENT(101); PPP
|
|
|
|
# ifndef VG_DEBUG_MEMORY
|
|
|
|
if (IS_ALIGNED4_ADDR(old_esp) && IS_ALIGNED4_ADDR(new_esp)) {
|
|
|
|
/* Deal with the most common cases fast. These are ordered in
|
|
the sequence most common first. */
|
|
|
|
# ifdef VG_PROFILE_MEMORY
|
|
// PPP
|
|
if (delta == - 4) PROF_EVENT(102);
|
|
else if (delta == 4) PROF_EVENT(103);
|
|
else if (delta == -12) PROF_EVENT(104);
|
|
else if (delta == - 8) PROF_EVENT(105);
|
|
else if (delta == 16) PROF_EVENT(106);
|
|
else if (delta == 12) PROF_EVENT(107);
|
|
else if (delta == 0) PROF_EVENT(108);
|
|
else if (delta == 8) PROF_EVENT(109);
|
|
else if (delta == -16) PROF_EVENT(110);
|
|
else if (delta == 20) PROF_EVENT(111);
|
|
else if (delta == -20) PROF_EVENT(112);
|
|
else if (delta == 24) PROF_EVENT(113);
|
|
else if (delta == -24) PROF_EVENT(114);
|
|
else if (delta > 0) PROF_EVENT(115); // PPP: new: aligned_big_pos
|
|
else PROF_EVENT(116); // PPP: new: aligned_big_neg
|
|
# endif
|
|
|
|
if (delta < 0 && delta > -2000) {
|
|
VG_TRACK(new_mem_stack_aligned, new_esp, -delta);
|
|
VGP_MAYBE_POPCC(VgpStack);
|
|
return;
|
|
}
|
|
else
|
|
if (delta > 0 && delta < 2000) {
|
|
VG_TRACK(die_mem_stack_aligned, old_esp, delta);
|
|
VGP_MAYBE_POPCC(VgpStack);
|
|
return;
|
|
}
|
|
if (delta == 0) {
|
|
VGP_MAYBE_POPCC(VgpStack);
|
|
return;
|
|
}
|
|
/* otherwise fall onto the slow-but-general case */
|
|
}
|
|
|
|
# endif
|
|
|
|
/* The above special cases handle 90% to 95% of all the stack
|
|
adjustments. The rest we give to the slow-but-general
|
|
mechanism. */
|
|
/* VG_(printf)("big delta %d\n", delta); */
|
|
vg_handle_esp_assignment_SLOWLY ( old_esp, new_esp );
|
|
VGP_MAYBE_POPCC(VgpStack);
|
|
}
|
|
|
|
|
|
static void vg_handle_esp_assignment_SLOWLY ( Addr old_esp, Addr new_esp )
|
|
{
|
|
Int delta;
|
|
|
|
delta = ((Int)new_esp) - ((Int)old_esp);
|
|
//VG_(printf)("delta %d (%x) %x --> %x\n", delta, delta, old_esp, new_esp);
|
|
//PROF_EVENT(120); PPP
|
|
if (-(VG_HUGE_DELTA) < delta && delta < VG_HUGE_DELTA) {
|
|
/* "Ordinary" stack change. */
|
|
if (new_esp < old_esp) {
|
|
/* Moving down; the stack is growing. */
|
|
//PROF_EVENT(121); PPP
|
|
VG_TRACK( new_mem_stack, new_esp, -delta );
|
|
|
|
} else if (new_esp > old_esp) {
|
|
/* Moving up; the stack is shrinking. */
|
|
//PROF_EVENT(122); PPP
|
|
VG_TRACK( die_mem_stack, old_esp, delta );
|
|
|
|
} else {
|
|
/* when old_esp == new_esp */
|
|
//PROF_EVENT(123); PPP
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* %esp has changed by more than HUGE_DELTA. We take this to mean
|
|
that the application is switching to a new stack, for whatever
|
|
reason, and we attempt to initialise the permissions around the
|
|
new stack in some plausible way. All pretty kludgey; needed to
|
|
make netscape-4.07 run without generating thousands of error
|
|
contexts.
|
|
|
|
If we appear to be switching back to the main stack, don't mess
|
|
with the permissions in the area at and above the stack ptr.
|
|
Otherwise, we're switching to an alternative stack; make the
|
|
area above %esp readable -- this doesn't seem right -- the right
|
|
thing to do would be to make it writable -- but is needed to
|
|
avoid huge numbers of errs in netscape. To be investigated. */
|
|
|
|
{
|
|
# if 0
|
|
Addr invalid_down_to = get_page_base(new_esp)
|
|
- 0 * VKI_BYTES_PER_PAGE;
|
|
Addr valid_up_to = get_page_base(new_esp) + VKI_BYTES_PER_PAGE
|
|
+ 0 * VKI_BYTES_PER_PAGE;
|
|
ThreadState* tst = VG_(get_current_thread_state)();
|
|
# endif
|
|
//PROF_EVENT(124); PPP
|
|
if (VG_(clo_verbosity) > 1)
|
|
VG_(message)(Vg_UserMsg, "Warning: client switching stacks? "
|
|
"%%esp: %p --> %p", old_esp, new_esp);
|
|
/* VG_(printf)("na %p, %%esp %p, wr %p\n",
|
|
invalid_down_to, new_esp, valid_up_to ); */
|
|
# if 0
|
|
/* JRS 20021001: following discussions with John Regehr, just
|
|
remove this. If a stack switch happens, it seems best not to
|
|
mess at all with memory permissions. Seems to work well with
|
|
Netscape 4.X. Really the only remaining difficulty is knowing
|
|
exactly when a stack switch is happening. */
|
|
VG_TRACK( die_mem_stack, invalid_down_to, new_esp - invalid_down_to );
|
|
if (!is_plausible_stack_addr(tst, new_esp)) {
|
|
VG_TRACK( post_mem_write, new_esp, valid_up_to - new_esp );
|
|
}
|
|
# endif
|
|
}
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Support for memory leak detectors ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*------------------------------------------------------------*/
|
|
/*--- 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 the
|
|
memcheck and addrcheck skins.
|
|
*/
|
|
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 Addr pageBase, addr;
|
|
volatile UInt res, numPages, page, primaryMapNo;
|
|
volatile UInt page_first_word, nWordsNotified;
|
|
|
|
vki_ksigaction sigbus_saved;
|
|
vki_ksigaction sigbus_new;
|
|
vki_ksigaction sigsegv_saved;
|
|
vki_ksigaction sigsegv_new;
|
|
vki_ksigset_t blockmask_saved;
|
|
vki_ksigset_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.ksa_flags = VKI_SA_ONSTACK | VKI_SA_RESTART;
|
|
sigbus_new.ksa_restorer = NULL;
|
|
res = VG_(ksigemptyset)( &sigbus_new.ksa_mask );
|
|
sk_assert(res == 0);
|
|
|
|
sigsegv_new.ksa_handler = vg_scan_all_valid_memory_sighandler;
|
|
sigsegv_new.ksa_flags = VKI_SA_ONSTACK | VKI_SA_RESTART;
|
|
sigsegv_new.ksa_restorer = NULL;
|
|
res = VG_(ksigemptyset)( &sigsegv_new.ksa_mask );
|
|
sk_assert(res == 0+0);
|
|
|
|
res = VG_(ksigemptyset)( &unblockmask_new );
|
|
res |= VG_(ksigaddset)( &unblockmask_new, VKI_SIGBUS );
|
|
res |= VG_(ksigaddset)( &unblockmask_new, VKI_SIGSEGV );
|
|
res |= VG_(ksigaddset)( &unblockmask_new, VKI_SIGTERM );
|
|
sk_assert(res == 0+0+0);
|
|
|
|
res = VG_(ksigaction)( VKI_SIGBUS, &sigbus_new, &sigbus_saved );
|
|
sk_assert(res == 0+0+0+0);
|
|
|
|
res = VG_(ksigaction)( VKI_SIGSEGV, &sigsegv_new, &sigsegv_saved );
|
|
sk_assert(res == 0+0+0+0+0);
|
|
|
|
res = VG_(ksigprocmask)( VKI_SIG_UNBLOCK, &unblockmask_new, &blockmask_saved );
|
|
sk_assert(res == 0+0+0+0+0+0);
|
|
|
|
/* The signal handlers are installed. Actually do the memory scan. */
|
|
numPages = 1 << (32-VKI_BYTES_PER_PAGE_BITS);
|
|
sk_assert(numPages == 1048576);
|
|
sk_assert(4096 == (1 << VKI_BYTES_PER_PAGE_BITS));
|
|
|
|
nWordsNotified = 0;
|
|
|
|
for (page = 0; page < numPages; page++) {
|
|
|
|
/* Base address of this 4k page. */
|
|
pageBase = page << VKI_BYTES_PER_PAGE_BITS;
|
|
|
|
/* Skip if this page is in an unused 64k chunk. */
|
|
primaryMapNo = pageBase >> 16;
|
|
if (!is_valid_64k_chunk(primaryMapNo))
|
|
continue;
|
|
|
|
/* 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_BYTES_PER_PAGE; 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_(ksigaction)( VKI_SIGBUS, &sigbus_saved, NULL );
|
|
sk_assert(res == 0 +0);
|
|
|
|
res = VG_(ksigaction)( VKI_SIGSEGV, &sigsegv_saved, NULL );
|
|
sk_assert(res == 0 +0 +0);
|
|
|
|
res = VG_(ksigprocmask)( VKI_SIG_SETMASK, &blockmask_saved, NULL );
|
|
sk_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,
|
|
ShadowChunk** 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,
|
|
ShadowChunk** 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 = ((Addr)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;
|
|
}
|
|
sk_assert(ptr >= a_mid_lo && ptr <= a_mid_hi);
|
|
retVal = mid;
|
|
break;
|
|
}
|
|
|
|
# ifdef VG_DEBUG_LEAKCHECK
|
|
vg_assert(retVal == find_shadow_for_OLD ( ptr, shadows, n_shadows ));
|
|
# endif
|
|
/* VG_(printf)("%d\n", retVal); */
|
|
return retVal;
|
|
}
|
|
|
|
|
|
|
|
static
|
|
void sort_malloc_shadows ( ShadowChunk** shadows, UInt n_shadows )
|
|
{
|
|
Int incs[14] = { 1, 4, 13, 40, 121, 364, 1093, 3280,
|
|
9841, 29524, 88573, 265720,
|
|
797161, 2391484 };
|
|
Int lo = 0;
|
|
Int hi = n_shadows-1;
|
|
Int i, j, h, bigN, hp;
|
|
ShadowChunk* v;
|
|
|
|
bigN = hi - lo + 1; if (bigN < 2) return;
|
|
hp = 0; while (hp < 14 && incs[hp] < bigN) hp++; hp--;
|
|
vg_assert(0 <= hp && hp < 14);
|
|
|
|
for (; hp >= 0; hp--) {
|
|
h = incs[hp];
|
|
i = lo + h;
|
|
while (1) {
|
|
if (i > hi) break;
|
|
v = shadows[i];
|
|
j = i;
|
|
while (shadows[j-h]->data > v->data) {
|
|
shadows[j] = shadows[j-h];
|
|
j = j - h;
|
|
if (j <= (lo + h - 1)) break;
|
|
}
|
|
shadows[j] = v;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Globals, for the callback used by VG_(detect_memory_leaks). */
|
|
|
|
static ShadowChunk** vglc_shadows;
|
|
static Int vglc_n_shadows;
|
|
static Reachedness* vglc_reachedness;
|
|
static Addr vglc_min_mallocd_addr;
|
|
static Addr vglc_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_(within_stack)(a)) return;
|
|
if (VG_(within_m_state_static)(a)) return;
|
|
if (a == (Addr)(&vglc_min_mallocd_addr)) return;
|
|
if (a == (Addr)(&vglc_max_mallocd_addr)) return;
|
|
|
|
/* OK, let's get on and do something Useful for a change. */
|
|
|
|
ptr = (Addr)word_at_a;
|
|
if (ptr >= vglc_min_mallocd_addr && ptr <= vglc_max_mallocd_addr) {
|
|
/* Might be legitimate; we'll have to investigate further. */
|
|
sh_no = find_shadow_for ( ptr, vglc_shadows, vglc_n_shadows );
|
|
if (sh_no != -1) {
|
|
/* Found a block at/into which ptr points. */
|
|
sk_assert(sh_no >= 0 && sh_no < vglc_n_shadows);
|
|
sk_assert(ptr < vglc_shadows[sh_no]->data
|
|
+ vglc_shadows[sh_no]->size);
|
|
/* Decide whether Proper-ly or Interior-ly reached. */
|
|
if (ptr == vglc_shadows[sh_no]->data) {
|
|
if (0) VG_(printf)("pointer at %p to %p\n", a, word_at_a );
|
|
vglc_reachedness[sh_no] = Proper;
|
|
} else {
|
|
if (vglc_reachedness[sh_no] == Unreached)
|
|
vglc_reachedness[sh_no] = Interior;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* 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 the Memcheck and Addrcheck skins.
|
|
Also pass in a skin-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 VG_(generic_detect_memory_leaks) (
|
|
Bool is_valid_64k_chunk ( UInt ),
|
|
Bool is_valid_address ( Addr ),
|
|
ExeContext* get_where ( ShadowChunk* ),
|
|
VgRes leak_resolution,
|
|
Bool show_reachable
|
|
)
|
|
{
|
|
Int i;
|
|
Int blocks_leaked, bytes_leaked;
|
|
Int blocks_dubious, bytes_dubious;
|
|
Int blocks_reachable, bytes_reachable;
|
|
Int n_lossrecords;
|
|
UInt bytes_notified;
|
|
|
|
LossRecord* errlist;
|
|
LossRecord* p;
|
|
|
|
/* VG_(get_malloc_shadows) allocates storage for shadows */
|
|
vglc_shadows = VG_(get_malloc_shadows)( &vglc_n_shadows );
|
|
if (vglc_n_shadows == 0) {
|
|
sk_assert(vglc_shadows == NULL);
|
|
VG_(message)(Vg_UserMsg,
|
|
"No malloc'd blocks -- no leaks are possible.\n");
|
|
return;
|
|
}
|
|
|
|
VG_(message)(Vg_UserMsg,
|
|
"searching for pointers to %d not-freed blocks.",
|
|
vglc_n_shadows );
|
|
sort_malloc_shadows ( vglc_shadows, vglc_n_shadows );
|
|
|
|
/* Sanity check; assert that the blocks are now in order and that
|
|
they don't overlap. */
|
|
for (i = 0; i < vglc_n_shadows-1; i++) {
|
|
sk_assert( ((Addr)vglc_shadows[i]->data)
|
|
< ((Addr)vglc_shadows[i+1]->data) );
|
|
sk_assert( ((Addr)vglc_shadows[i]->data) + vglc_shadows[i]->size
|
|
< ((Addr)vglc_shadows[i+1]->data) );
|
|
}
|
|
|
|
vglc_min_mallocd_addr = ((Addr)vglc_shadows[0]->data);
|
|
vglc_max_mallocd_addr = ((Addr)vglc_shadows[vglc_n_shadows-1]->data)
|
|
+ vglc_shadows[vglc_n_shadows-1]->size - 1;
|
|
|
|
vglc_reachedness
|
|
= VG_(malloc)( vglc_n_shadows * sizeof(Reachedness) );
|
|
for (i = 0; i < vglc_n_shadows; i++)
|
|
vglc_reachedness[i] = Unreached;
|
|
|
|
/* Do the scan of memory. */
|
|
bytes_notified
|
|
= VKI_BYTES_PER_WORD
|
|
* vg_scan_all_valid_memory (
|
|
is_valid_64k_chunk,
|
|
is_valid_address,
|
|
&vg_detect_memory_leaks_notify_addr
|
|
);
|
|
|
|
VG_(message)(Vg_UserMsg, "checked %d bytes.", bytes_notified);
|
|
|
|
blocks_leaked = bytes_leaked = 0;
|
|
blocks_dubious = bytes_dubious = 0;
|
|
blocks_reachable = bytes_reachable = 0;
|
|
|
|
for (i = 0; i < vglc_n_shadows; i++) {
|
|
if (vglc_reachedness[i] == Unreached) {
|
|
blocks_leaked++;
|
|
bytes_leaked += vglc_shadows[i]->size;
|
|
}
|
|
else if (vglc_reachedness[i] == Interior) {
|
|
blocks_dubious++;
|
|
bytes_dubious += vglc_shadows[i]->size;
|
|
}
|
|
else if (vglc_reachedness[i] == Proper) {
|
|
blocks_reachable++;
|
|
bytes_reachable += vglc_shadows[i]->size;
|
|
}
|
|
}
|
|
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(Vg_UserMsg, "definitely lost: %d bytes in %d blocks.",
|
|
bytes_leaked, blocks_leaked );
|
|
VG_(message)(Vg_UserMsg, "possibly lost: %d bytes in %d blocks.",
|
|
bytes_dubious, blocks_dubious );
|
|
VG_(message)(Vg_UserMsg, "still reachable: %d bytes in %d blocks.",
|
|
bytes_reachable, blocks_reachable );
|
|
|
|
|
|
/* Common up the lost blocks so we can print sensible error
|
|
messages. */
|
|
|
|
n_lossrecords = 0;
|
|
errlist = NULL;
|
|
for (i = 0; i < vglc_n_shadows; i++) {
|
|
|
|
/* 'where' stored in 'skin_extra' field; extract using function
|
|
supplied by the calling skin. */
|
|
ExeContext* where = get_where ( vglc_shadows[i] );
|
|
|
|
for (p = errlist; p != NULL; p = p->next) {
|
|
if (p->loss_mode == vglc_reachedness[i]
|
|
&& VG_(eq_ExeContext) ( leak_resolution,
|
|
p->allocated_at,
|
|
where) ) {
|
|
break;
|
|
}
|
|
}
|
|
if (p != NULL) {
|
|
p->num_blocks ++;
|
|
p->total_bytes += vglc_shadows[i]->size;
|
|
} else {
|
|
n_lossrecords ++;
|
|
p = VG_(malloc)(sizeof(LossRecord));
|
|
p->loss_mode = vglc_reachedness[i];
|
|
p->allocated_at = where;
|
|
p->total_bytes = vglc_shadows[i]->size;
|
|
p->num_blocks = 1;
|
|
p->next = errlist;
|
|
errlist = p;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < n_lossrecords; i++) {
|
|
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;
|
|
}
|
|
}
|
|
sk_assert(p_min != NULL);
|
|
|
|
if ( (!show_reachable) && (p_min->loss_mode == Proper)) {
|
|
p_min->num_blocks = 0;
|
|
continue;
|
|
}
|
|
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(
|
|
Vg_UserMsg,
|
|
"%d bytes in %d blocks are %s in loss record %d of %d",
|
|
p_min->total_bytes, p_min->num_blocks,
|
|
p_min->loss_mode==Unreached ? "definitely lost" :
|
|
(p_min->loss_mode==Interior ? "possibly lost"
|
|
: "still reachable"),
|
|
i+1, n_lossrecords
|
|
);
|
|
VG_(pp_ExeContext)(p_min->allocated_at);
|
|
p_min->num_blocks = 0;
|
|
}
|
|
|
|
VG_(message)(Vg_UserMsg, "");
|
|
VG_(message)(Vg_UserMsg, "LEAK SUMMARY:");
|
|
VG_(message)(Vg_UserMsg, " definitely lost: %d bytes in %d blocks.",
|
|
bytes_leaked, blocks_leaked );
|
|
VG_(message)(Vg_UserMsg, " possibly lost: %d bytes in %d blocks.",
|
|
bytes_dubious, blocks_dubious );
|
|
VG_(message)(Vg_UserMsg, " still reachable: %d bytes in %d blocks.",
|
|
bytes_reachable, blocks_reachable );
|
|
if (!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_(message)(Vg_UserMsg, "");
|
|
|
|
VG_(free) ( vglc_shadows );
|
|
VG_(free) ( vglc_reachedness );
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- end vg_memory.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|