/memcheck/tests/lsframe2
/memcheck/tests/Makefile
/memcheck/tests/Makefile.in
+/memcheck/tests/linux/madv_guard
/memcheck/tests/mallinfo
/memcheck/tests/mallinfo2
/memcheck/tests/malloc1
514094 readlink("/proc/self/exe") overwrites buffer beyond its return value
514206 Assertion '!sr_isError(sr)' failed - mmap fd points to an open
descriptor to a PCI device
+514297 Track madvise MADV_GUARD_INSTALL in address space manager
514343 Add a valgrind.h macro VALGRIND_REPLACES_MALLOC
514596 Add SSE4.1 BLENDPD instruction for x86 32 bit
514613 Unclosed leak_summary/still_reachable tag in xml output
AM_CONDITIONAL([HAVE_AT_FDCWD], [test x$ac_have_at_fdcwd = xyes])
+
+# Check for MADV_GUARD_INSTALL
+
+AC_MSG_CHECKING([for MADV_GUARD_INSTALL])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/mman.h>
+]], [[
+ int a = MADV_GUARD_INSTALL;
+]])], [
+ac_have_madv_guard_install=yes
+AC_MSG_RESULT([yes])
+], [
+ac_have_madv_guard_install=no
+AC_MSG_RESULT([no])
+])
+
+AM_CONDITIONAL([HAVE_MADV_GUARD_INSTALL], [test x$ac_have_madv_guard_install = xyes])
+
+
# Check for stpncpy function definition in string.h
# This explicitly checks with _GNU_SOURCE defined since that is also
# used in the test case (some systems might define it without anyway
if (tid != VG_INVALID_THREADID) {
/* Should be below stack pointer, as if it is >= SP, it
will have been described as StackPos_stacked above. */
- stackPos = StackPos_below_stack_ptr;
+ const NSegment *seg = VG_(am_find_nsegment) (a);
+#if defined(VGO_linux)
+ /* On linux we might be hitting madvise guard pages, bug 514297 */
+ if (seg->hasGuardPages && VG_(is_guarded)(a) ) {
+ stackPos = StackPos_guard_page;
+ } else
+#endif
+ stackPos = StackPos_below_stack_ptr;
} else {
/* Try to find a stack with guard page containing a.
For this, check if a is in a page mapped without r, w and x. */
return sr_isError(res) ? -1 : sr_Res(res);
}
+Int ML_(am_lseek) ( Int fd, vki_off_t off, Int whence )
+{
+ SysRes res = VG_(do_syscall3)(__NR_lseek, fd, off, whence);
+ return sr_isError(res) ? -1 : sr_Res(res);
+}
+
/* Get the dev, inode and mode info for a file descriptor, if
possible. Returns True on success. */
Bool ML_(am_get_fd_d_i_m)( Int fd,
static NSegment nsegments[VG_N_SEGMENTS];
static Int nsegments_used = 0;
+/* bookkeeping for madvise() guard pages, bug 514297 */
+#if defined(VGO_linux)
+static UInt VG_N_GUARDS;
+static Addr *guardpages;
+static Int nguardpages_used = 0;
+#endif
+
#define Addr_MIN ((Addr)0)
#define Addr_MAX ((Addr)(-1ULL))
{
HChar len_buf[20];
show_len_concisely(len_buf, seg->start, seg->end);
+ const char *tail = "";
+
+#if defined(VGO_linux)
+ tail = seg->hasGuardPages ? " (G)" : " (g)";
+#endif
switch (seg->kind) {
case SkFree:
VG_(debugLog)(
logLevel, "aspacem",
- "%3d: %s %010lx-%010lx %s\n",
+ "%3d: %s %010lx-%010lx %s%s\n",
segNo, show_SegKind(seg->kind),
- seg->start, seg->end, len_buf
+ seg->start, seg->end, len_buf, tail
);
break;
case SkAnonC: case SkAnonV: case SkShmC:
VG_(debugLog)(
logLevel, "aspacem",
- "%3d: %s %010lx-%010lx %s %c%c%c%c%c\n",
+ "%3d: %s %010lx-%010lx %s %c%c%c%c%c%s\n",
segNo, show_SegKind(seg->kind),
seg->start, seg->end, len_buf,
seg->hasR ? 'r' : '-', seg->hasW ? 'w' : '-',
seg->hasX ? 'x' : '-', seg->hasT ? 'T' : '-',
- seg->isCH ? 'H' : '-'
+ seg->isCH ? 'H' : '-', tail
);
break;
VG_(debugLog)(
logLevel, "aspacem",
"%3d: %s %010lx-%010lx %s %c%c%c%c%c d=0x%03llx "
- "i=%-7llu o=%-7lld (%d,%d)\n",
+ "i=%-7llu o=%-7lld (%d,%d)%s\n",
segNo, show_SegKind(seg->kind),
seg->start, seg->end, len_buf,
seg->hasR ? 'r' : '-', seg->hasW ? 'w' : '-',
seg->hasX ? 'x' : '-', seg->hasT ? 'T' : '-',
seg->isCH ? 'H' : '-',
seg->dev, seg->ino, seg->offset,
- ML_(am_segname_get_seqnr)(seg->fnIdx), seg->fnIdx
+ ML_(am_segname_get_seqnr)(seg->fnIdx), seg->fnIdx,
+ tail
);
break;
case SkResvn:
VG_(debugLog)(
logLevel, "aspacem",
- "%3d: %s %010lx-%010lx %s %c%c%c%c%c %s\n",
+ "%3d: %s %010lx-%010lx %s %c%c%c%c%c %s%s\n",
segNo, show_SegKind(seg->kind),
seg->start, seg->end, len_buf,
seg->hasR ? 'r' : '-', seg->hasW ? 'w' : '-',
seg->hasX ? 'x' : '-', seg->hasT ? 'T' : '-',
seg->isCH ? 'H' : '-',
- show_ShrinkMode(seg->smode)
+ show_ShrinkMode(seg->smode), tail
);
break;
case SkFree:
s1->end = s2->end;
+#if defined(VGO_linux)
+ s1->hasGuardPages |= s2->hasGuardPages;
+#endif
return True;
case SkAnonC: case SkAnonV:
&& s1->hasX == s2->hasX && s1->isCH == s2->isCH) {
s1->end = s2->end;
s1->hasT |= s2->hasT;
+#if defined(VGO_linux)
+ s1->hasGuardPages |= s2->hasGuardPages;
+#endif
return True;
}
break;
+ ((ULong)s2->start) - ((ULong)s1->start) ) {
s1->end = s2->end;
s1->hasT |= s2->hasT;
+#if defined(VGO_linux)
+ s1->hasGuardPages |= s2->hasGuardPages;
+#endif
ML_(am_dec_refcount)(s1->fnIdx);
return True;
}
case SkResvn:
if (s1->smode == SmFixed && s2->smode == SmFixed) {
s1->end = s2->end;
+#if defined(VGO_linux)
+ s1->hasGuardPages |= s2->hasGuardPages;
+#endif
return True;
}
AM_SANITY_CHECK;
}
+/*-----------------------------------------------------------------*/
+/*--- ---*/
+/*--- Low level access / modification of the guardpages array. ---*/
+/*--- ---*/
+/*-----------------------------------------------------------------*/
+
+/* This bug 514297 related section is linux specific.
+ Guard whole the section with defined(VGO_linux) */
+
+Bool is_guarded_segment( Int );
+Bool is_guarded_interval( Addr, Addr );
+
+#if defined(VGO_linux)
+static void guard_page_install ( Addr addr ) {
+ /* Note that this only installs guard pages into the
+ guardpages array. But it doesn't flag hasGuardPages
+ for segments having guard pages.
+ That's handled in guard_pages_install() below. */
+ Addr addr_aligned = addr & ~(VKI_PAGE_SIZE - 1);
+ if (nguardpages_used >= VG_N_GUARDS) {
+ VG_(printf)("Use --max-guard-pages=INT to specify a larger number of\n"
+ "guard pages and rerun valgrind\n");
+ VG_(core_panic)("Max number of guard pages is too low");
+ }
+ // bisect
+ Int mid = 0,
+ lo = 0,
+ hi = nguardpages_used - 1;
+ while (True) {
+ if (lo > hi) {
+ break;
+ } else {
+ mid = (lo + hi) / 2;
+ if (addr_aligned < guardpages[mid]) { hi = mid - 1; continue; }
+ if (addr_aligned > guardpages[mid]) { lo = mid + 1; continue; }
+ if (addr_aligned == guardpages[mid]) {
+ VG_(debugLog)(0,"aspacem",
+ "Attempt to reinstall already existing guard page\n");
+ return;
+ }
+ }
+ }
+ // merge in
+ for (Int i=nguardpages_used; i > lo; i--)
+ guardpages[i] = guardpages[i-1];
+ guardpages[lo] = addr_aligned;
+ nguardpages_used++;
+}
+
+
+inline static void guard_pages_install ( Addr addr, SizeT len ) {
+ Int iLo = find_nsegment_idx(addr);
+ Int iHi = find_nsegment_idx(addr + len - 1);
+
+ // Record the new guard pages in the guardpages array
+ Int pages = (len - 1) / VKI_PAGE_SIZE + 1;
+ for (Int i=0; i < pages; i++)
+ guard_page_install(addr + i * VKI_PAGE_SIZE);
+
+ // These 5 should be guaranteed by find_nsegment_idx.
+ aspacem_assert(0 <= iLo && iLo < nsegments_used);
+ aspacem_assert(0 <= iHi && iHi < nsegments_used);
+ aspacem_assert(iLo <= iHi);
+ aspacem_assert(nsegments[iLo].start <= addr );
+ aspacem_assert(nsegments[iHi].end >= addr + len - 1 );
+
+ // Flag the new guardpages in the nsegments array
+ for (Int i = iLo; i <= iHi; i++)
+ nsegments[i].hasGuardPages = True;
+}
+
+static void guard_page_remove ( Addr addr, Bool check ) {
+ /* Note that this only removes guard pages from the
+ guardpages array. But it doesn't unflag hasGuardPages
+ for segments not having any guard pages any more.
+ That's handled in guard_pages_remove() below. */
+ Addr addr_aligned = addr & ~(VKI_PAGE_SIZE - 1);
+ // search
+ Int mid = 0,
+ lo = 0,
+ hi = nguardpages_used - 1;
+ while (True) {
+ if (lo > hi) {
+ if (check == False) {
+ // Here we just return. The address wasn't found, and
+ // thus can't be removed from the evidence. This may
+ // happen when munmap() is called. Unmapping memory
+ // removes also guard pages. In this case we remove
+ // guard page from V's evidence if there is one, but
+ // if there is none, we don't complain and go ahead.
+ return;
+ }
+ }
+ aspacem_assert(lo <= hi);
+ mid = (lo + hi) / 2;
+ if (addr_aligned < guardpages[mid]) { hi = mid - 1; continue; }
+ if (addr_aligned > guardpages[mid]) { lo = mid + 1; continue; }
+ if (addr_aligned == guardpages[mid]) break;
+ }
+ // remove
+ for(Int i=mid; i<nguardpages_used; i++)
+ guardpages[i] = guardpages[i+1];
+ nguardpages_used--;
+}
+
+inline static void guard_pages_remove ( Addr addr, SizeT len, Bool check ) {
+ Int iLo = find_nsegment_idx(addr);
+ Int iHi = find_nsegment_idx(addr + len - 1);
+ Bool guardPageSeen;
+
+ // Reflect the guard pages removal in the guardpages array
+ Int pages = (len - 1) / VKI_PAGE_SIZE + 1;
+ for (Int i=0; i < pages; i++)
+ guard_page_remove (addr + i * VKI_PAGE_SIZE, check);
+
+ // These 5 should be guaranteed by find_nsegment_idx.
+ aspacem_assert(0 <= iLo && iLo < nsegments_used);
+ aspacem_assert(0 <= iHi && iHi < nsegments_used);
+ aspacem_assert(iLo <= iHi);
+ aspacem_assert(nsegments[iLo].start <= addr );
+ aspacem_assert(nsegments[iHi].end >= addr + len - 1 );
+
+ // Unflag segments not having any guard pages any more
+ for (Int i = iLo; i <= iHi; i++) {
+ Addr aLo = nsegments[i].start;
+ Addr aHi = nsegments[i].end;
+ guardPageSeen = False;
+ for (Int j = 0; j < nguardpages_used; j++) {
+ if ((guardpages[j] >= aLo) && (guardpages[j] <= aHi))
+ guardPageSeen = True;
+ }
+ if (guardPageSeen == False)
+ nsegments[i].hasGuardPages = False;
+
+ }
+}
+
+static void is_guarded_sanity ( Addr addr, Bool expected )
+{
+ static Int VG_(cl_pagemap_fd) = -1;
+ static Bool pagemap_io_err = False;
+ // Don't repeatedly complain about io /proc/self/pagemap IO errors
+ if (pagemap_io_err == True)
+ return;
+ if (VG_(cl_pagemap_fd) == -1) {
+ VG_(cl_pagemap_fd) = sr_Res(ML_(am_open)("/proc/self/pagemap", VKI_O_RDONLY, 0 ));
+ if(VG_(cl_pagemap_fd) == -1) {
+ pagemap_io_err = True;
+ VG_(debugLog)(0, "aspacem", "I/O error on /proc/self/pagemap");
+ }
+ VG_(cl_pagemap_fd) = VG_(safe_fd)(VG_(cl_pagemap_fd));
+ }
+ Addr addr_page_aligned = addr & ~(VKI_PAGE_SIZE - 1);
+ vki_off_t offset = ((vki_uint64_t)addr_page_aligned / VKI_PAGE_SIZE) * sizeof(vki_uint64_t);
+ Int ret = ML_(am_lseek) (VG_(cl_pagemap_fd), offset, VKI_SEEK_SET);
+ if (ret == -1) {
+ VG_(debugLog)(0, "aspacem", "failed lseek in pagemap\n");
+ pagemap_io_err = True;
+ }
+ // https://docs.kernel.org/admin-guide/mm/pagemap.html
+ vki_uint64_t entry; // one 64-bit value for each virtual page
+ ret = ML_(am_read) (VG_(cl_pagemap_fd), &entry, sizeof(vki_uint64_t));
+ if (ret == -1) {
+ VG_(debugLog)(0, "aspacem", "failed reading pagemap\n");
+ pagemap_io_err = True;
+ }
+ if (((entry >> 58) & 1) == 1) {
+ VG_(debugLog)(1, "aspacem",
+ "madvise guard page hit at addr 0x%lx\n", addr);
+ if (expected == True) {
+ return;
+ } else {
+ ML_(am_barf)("FATAL: failed guard page sanity check\n");
+ ML_(am_exit)(1);
+ }
+ }
+ if (expected == False) {
+ return;
+ } else {
+ VG_(debugLog)(0, "Valgrind:",
+ "FATAL: failed guard page sanity check\n");
+ ML_(am_exit)(1);
+ }
+}
+
+Bool VG_(is_guarded) ( Addr addr ) {
+ Addr addr_aligned = addr & ~(VKI_PAGE_SIZE - 1);
+ Int mid,
+ lo = 0,
+ hi = nguardpages_used - 1;
+ while (True) {
+ if (lo > hi) {
+ if (LIKELY(VG_(clo_sanity_level) < 3)) {
+ /* do nothing */
+ } else {
+ is_guarded_sanity ( addr, False );
+ }
+ return False;
+ }
+ mid = (lo + hi) / 2;
+ if (addr_aligned < guardpages[mid]) { hi = mid - 1; continue; }
+ if (addr_aligned > guardpages[mid]) { lo = mid + 1; continue; }
+ if (LIKELY(VG_(clo_sanity_level) < 3)) {
+ /* do nothing */
+ } else {
+ is_guarded_sanity ( addr, True );
+ }
+ return True;
+ }
+}
+
+/* Check if segment with given id has at least one guard page */
+Bool is_guarded_segment( Int seg ) {
+ return is_guarded_interval (nsegments[seg].start,
+ nsegments[seg].end);
+}
+
+/* Check if there is a guard page in the guardpages array
+ evidence in given interval of addresses */
+Bool is_guarded_interval ( Addr aStart, Addr aEnd ) {
+ if (nguardpages_used < 1)
+ return False;
+ /* Quickly find the beginning of interesting interval
+ of the guardpages array by bisecting it */
+ Int mid = 0,
+ lo = 0,
+ hi = nguardpages_used - 1;
+ while (True) {
+ if (lo > hi) {
+ break;
+ } else {
+ mid = (lo + hi) / 2;
+ if (aStart < guardpages[mid]) { hi = mid - 1; continue; }
+ if (aStart > guardpages[mid]) { lo = mid + 1; continue; }
+ if (aStart == guardpages[mid]) {
+ /* Lucky enough to step on the guard page early */
+ return True;
+ }
+ }
+ }
+ if (lo >= nguardpages_used) {
+ /* Out of range, no guard page for this segment for sure */
+ return False;
+ }
+ /* Scan the interesting interval of guardpages array one by one */
+ for (Int i = lo; i<= nguardpages_used; i++) {
+ if (guardpages[i] > aEnd)
+ return False;
+ return True;
+ }
+ return False;
+}
+#else
+/* Provide stub VG_(is_guarded)() for non-linux targets */
+Bool VG_(is_guarded) ( Addr addr ) {
+ return False;
+}
+#endif
/*-----------------------------------------------------------------*/
/*--- ---*/
{
Int i, iLo, iHi;
Bool needR, needW, needX;
+#if defined(VGO_linux)
+ Bool needGuardPageCheck = False;
+#endif
if (len == 0)
return True; /* somewhat dubious case */
&& (needW ? nsegments[i].hasW : True)
&& (needX ? nsegments[i].hasX : True) ) {
/* ok */
+#if defined(VGO_linux)
+ if ( ( nsegments[i].hasGuardPages )
+ && (prot != VKI_PROT_NONE) ) {
+ needGuardPageCheck = True;
+ }
+#endif
} else {
return False;
}
}
+#if defined(VGO_linux)
+ if (needGuardPageCheck && VG_(is_guarded)(start)) {
+ return False;
+ }
+#endif
+
return True;
}
ML_(am_inc_refcount)(nsegments[i].fnIdx);
+#if defined(VGO_linux)
+ nsegments[i].hasGuardPages = is_guarded_segment(i);
+ nsegments[i+1].hasGuardPages = is_guarded_segment(i+1);
+#endif
+
aspacem_assert(sane_NSegment(&nsegments[i]));
aspacem_assert(sane_NSegment(&nsegments[i+1]));
}
seg->isFF = False;
seg->ignore_offset = False;
#endif
+#if defined(VGO_linux)
+ seg->hasGuardPages = False;
+#endif
}
VG_(am_show_nsegments)(2, "With contents of /proc/self/maps");
+#if defined(VGO_linux)
+ /* With glibc upstream commit a6fbe36b7f31 and others, on x86_64,
+ a new madvise(MADV_GUARD_INSTALL ... ) guard page is installed for
+ each new thread. In the future, MADV_GUARD_INSTALL is likely to
+ be used with DSOs supporting multiple kernel page sizes. A rough
+ estimation of max madvise guard page count is Nthreads + 3 * DSOcnt.
+ Madvise guard pages are tracked in the guardpages array below. The
+ array size is set via --max-guard-pages or --max-threads: */
+ VG_N_GUARDS = VG_(clo_max_guard_pages);
+ guardpages = VG_(calloc)("aspacem.guardpages", VG_N_GUARDS, sizeof(Addr));
+#endif
+
AM_SANITY_CHECK;
return suggested_clstack_end;
}
return needDiscard;
}
+/* Notifiy aspacem about madvise(MADV_GUARD_INSTALL), bug 514297 */
+#if defined(VGO_linux)
+Bool VG_(am_notify_madv_guard)( Addr start, SizeT len, Bool install )
+{
+ aspacem_assert(VG_IS_PAGE_ALIGNED(start));
+ aspacem_assert(VG_IS_PAGE_ALIGNED(len));
+
+ if (len == 0)
+ return False;
+
+ if (install) {
+ VG_(debugLog)(1, "aspacem",
+ "installing guard pages (addr=0x%lx, len=0x%lx)\n",
+ start, len);
+ guard_pages_install(start, len);
+ } else {
+ VG_(debugLog)(1, "aspacem",
+ "removing guard pages (addr=0x%lx, len=0x%lx)\n",
+ start, len);
+ guard_pages_remove(start, len, True);
+ }
+
+ AM_SANITY_CHECK;
+
+ // The return val determines whether translations will be discarded.
+ // That is supposed to happen when guard page is installed, but not
+ // otherwise.
+ return install;
+}
+#endif
/* Notifies aspacem that an munmap completed successfully. The
segment array is updated accordingly. As with
#endif
add_segment( &seg );
+ /* Unmapping drops guard pages (if present) */
+#if defined(VGO_linux)
+ guard_pages_remove( start, len, False );
+#endif
+
/* Unmapping could create two adjacent free segments, so a preen is
needed. add_segment() will do that, so no need to here. */
AM_SANITY_CHECK;
if (record_gap && gapStart < Addr_MAX)
(*record_gap) ( gapStart, Addr_MAX - gapStart + 1 );
+
+#if defined(VGO_linux)
+ // Iterate over guard pages
+ for (i = 0; i<nguardpages_used; i++) {
+ // Check if every guard page in V's evidence has respective
+ // record in kernel's evidence.
+ is_guarded_sanity(guardpages[i], True);
+ // Make sure that every guard page belongs to a segment
+ // flagged with hasGuardPages.
+ if(nsegments[find_nsegment_idx(guardpages[i])].hasGuardPages == False) {
+ VG_(debugLog)(0, "Valgrind:",
+ "FATAL: failed guard page sanity check2 at address 0x%lx.\n", guardpages[i]);
+ ML_(am_exit)(1);
+ }
+ }
+ // Iterate over segments. For each segment flagged with hasGuardPages
+ // make sure that it actually contains at least one guard page.
+ for (i = 0; i < nsegments_used; i++) {
+ if (is_guarded_segment(i) != nsegments[i].hasGuardPages) {
+ for (Int k=0; k<nguardpages_used; k++)
+ VG_(debugLog)(0,"aspacem","guard page: seg=%d id=%d addr=0x%lx\n",
+ find_nsegment_idx(guardpages[k]), k, guardpages[k]);
+ VG_(am_show_nsegments)(0, "aspacem");
+ VG_(debugLog)(0, "Valgrind:",
+ "FATAL: segment %d: inconsistent guard page evidence\n", i);
+ ML_(am_exit)(1);
+ }
+ }
+#endif
}
/*------END-procmaps-parser-for-Linux----------------------------*/
// VG_(mk_SysRes_Error)
// VG_(mk_SysRes_Success)
-#include "pub_core_options.h" // VG_(clo_sanity_level)
+#include "pub_core_options.h" // VG_(clo_sanity_level), VG_(clo_max_guard_pages)
#if defined(VGO_freebsd)
#include "pub_core_libcproc.h" // VG_(sysctlbyname)
#include "pub_core_mach.h" // macos support
#endif
+#if defined(VGO_linux)
+#include "pub_core_libcfile.h" // VG_(safe_fd)
+#include "pub_core_mallocfree.h" // VG_(calloc)
+#endif
/* --------------- Implemented in aspacemgr-common.c ---------------*/
extern Int ML_(am_read) ( Int fd, void* buf, Int count);
extern Int ML_(am_readlink) ( const HChar* path, HChar* buf, UInt bufsiz );
extern Int ML_(am_fcntl) ( Int fd, Int cmd, Addr arg );
+extern Int ML_(am_lseek) ( Int fd, vki_off_t off, Int whence );
/* Get the dev, inode and mode info for a file descriptor, if
possible. Returns True on success. */
" recovered by stack scanning [5]\n"
" --resync-filter=no|yes|verbose [yes on MacOS, no on other OSes]\n"
" attempt to avoid expensive address-space-resync operations\n"
-" --max-threads=<number> maximum number of threads that valgrind can\n"
-" handle [%d]\n"
+" --max-threads=<number> maximum number of threads that valgrind can\n"
+" handle [%d]\n"
+" --max-guard-pages=<number> maximum number of madvise guard pages that\n"
+" valgrind can handle [%d]\n"
"\n";
const HChar usage2[] =
VG_(clo_vgdb_poll) /* int */,
VG_(vgdb_prefix_default)() /* char* */,
N_SECTORS_DEFAULT /* int */,
- MAX_THREADS_DEFAULT /* int */
+ MAX_THREADS_DEFAULT /* int */,
+ MAX_GUARDS_DEFAULT /* int */
);
if (need_help > 1 && VG_(details).name) {
VG_(printf)(" user options for %s:\n", VG_(details).name);
else if VG_INT_CLOM(cloE, arg, "--main-stacksize", VG_(clo_main_stacksize)) {}
// Set up VG_(clo_max_threads); needed for VG_(tl_pre_clo_init)
- else if VG_INT_CLOM(cloE, arg, "--max-threads", VG_(clo_max_threads)) {}
+ else if VG_INT_CLOM(cloE, arg, "--max-threads", VG_(clo_max_threads)) {
+ // VG_(clo_max_guard_pages) defaults to VG_(clo_max_threads)
+ // unless explititly set otherwise - below. With glibc upstream
+ // commit a6fbe36b7f31 and others, a guard page is installed for
+ // each new thread.
+ VG_(clo_max_guard_pages) = VG_(clo_max_threads);
+ }
+ // Set up VG_(clo_max_guard_pages); needed for aspacemgr init
+ else if VG_INT_CLOM(cloE, arg, "--max-guard-pages", VG_(clo_max_guard_pages)) {}
// Set up VG_(clo_sim_hints). This is needed a.o. for an inner
// running in an outer, to have "no-inner-prefix" enabled
/* Start the debugging-log system ASAP. First find out how many
"-d"s were specified. This is a pre-scan of the command line. Also
get --profile-heap=yes, --core-redzone-size, --redzone-size
- --aspace-minaddr which are needed by the time we start up dynamic
- memory management. */
+ --aspace-minaddr, --max-guard-pages which are needed by the time
+ we start up dynamic memory management. */
loglevel = 0;
for (i = 1; i < argc; i++) {
const HChar* tmp_str;
&errmsg))
VG_(fmsg_bad_option)(argv[i], "%s\n", errmsg);
}
+ if VG_INT_CLOM(cloE, argv[i], "--max-threads", VG_(clo_max_threads)) {
+ // VG_(clo_max_guard_pages) defaults to VG_(clo_max_threads)
+ // unless explititly set otherwise - below. With glibc upstream
+ // commit a6fbe36b7f31 and others, a guard page is installed for
+ // each new thread.
+ VG_(clo_max_guard_pages) = VG_(clo_max_threads);
+ }
+ if VG_INT_CLOM(cloE, argv[i], "--max-guard-pages", VG_(clo_max_guard_pages)) {}
}
/* ... and start the debug logger. Now we can safely emit logging
Bool VG_(clo_show_emwarns) = False;
Word VG_(clo_max_stackframe) = 2000000;
UInt VG_(clo_max_threads) = MAX_THREADS_DEFAULT;
+UInt VG_(clo_max_guard_pages) = MAX_GUARDS_DEFAULT;
Word VG_(clo_main_stacksize) = 0; /* use client's rlimit.stack */
Word VG_(clo_valgrind_stacksize) = VG_DEFAULT_STACK_ACTIVE_SZB;
Bool VG_(clo_wait_for_gdb) = False;
extern void
ML_(notify_core_and_tool_of_mprotect) ( Addr a, SizeT len, Int prot );
+extern void
+ML_(notify_core_and_tool_of_madv_guard) ( Addr a, SizeT len, Bool install );
+
extern void
ML_(pre_mem_read_sockaddr) ( ThreadId tid, const HChar *description,
struct vki_sockaddr *sa, UInt salen );
GENX_(__NR_mremap, sys_mremap), // 25
GENX_(__NR_msync, sys_msync), // 26
GENXY(__NR_mincore, sys_mincore), // 27
- GENX_(__NR_madvise, sys_madvise), // 28
+ GENXY(__NR_madvise, sys_madvise), // 28
LINX_(__NR_shmget, sys_shmget), // 29
LINXY(__NR_shmat, sys_shmat), // 30
LINX_(__NR_setfsgid32, sys_setfsgid), // 216
LINX_(__NR_pivot_root, sys_pivot_root), // 217
GENXY(__NR_mincore, sys_mincore), // 218
- GENX_(__NR_madvise, sys_madvise), // 219
+ GENXY(__NR_madvise, sys_madvise), // 219
GENXY(__NR_getdents64, sys_getdents64), // 220
LINXY(__NR_fcntl64, sys_fcntl64), // 221
GENX_(__NR_mlockall, sys_mlockall), // 230
LINX_(__NR_munlockall, sys_munlockall), // 231
GENXY(__NR_mincore, sys_mincore), // 232
- GENX_(__NR_madvise, sys_madvise), // 233
+ GENXY(__NR_madvise, sys_madvise), // 233
LINX_(__NR_remap_file_pages, sys_remap_file_pages), // 234
LINX_(__NR_mbind, sys_mbind), // 235
LINXY(__NR_get_mempolicy, sys_get_mempolicy), // 236
"ML_(notify_core_and_tool_of_mprotect)" );
}
-
+#if defined(VGO_linux)
+void
+ML_(notify_core_and_tool_of_madv_guard) ( Addr a, SizeT len, Bool install )
+{
+ page_align_addr_and_len(&a, &len);
+ if (install) {
+ if (VG_(am_notify_madv_guard)( a, len, True ))
+ VG_(discard_translations)( a, (ULong)len,
+ "ML_(notify_core_and_tool_of_madv_guard)" );
+ } else {
+ VG_(am_notify_madv_guard)(a, len, False);
+ }
+}
+#endif
#if HAVE_MREMAP
/* Expand (or shrink) an existing mapping, potentially moving it at
ARG1, ARG2, SARG3);
PRE_REG_READ3(long, "madvise",
unsigned long, start, vki_size_t, length, int, advice);
- /* Ugly hack to try to bypass the problem of guard pages not being
- understood by valgrind aspace manager.
- By making the syscall fail, we expect glibc to fallback
- on implementing guard pages with mprotect PROT_NONE to ensure
- the valgrind address space manager is not confused wrongly
- believing the guard page is rw. */
-#ifdef VKI_MADV_GUARD_INSTALL
- if (ARG3 == VKI_MADV_GUARD_INSTALL)
- SET_STATUS_Failure( VKI_EINVAL );
-#endif
}
+#if defined(VGO_linux)
+POST(sys_madvise)
+{
+ if (ARG3 == VKI_MADV_GUARD_INSTALL) {
+ Addr a = ARG1;
+ SizeT len = ARG2;
+ ML_(notify_core_and_tool_of_madv_guard)(a, len, True);
+ }
+ if (ARG3 == VKI_MADV_GUARD_REMOVE) {
+ Addr a = ARG1;
+ SizeT len = ARG2;
+ ML_(notify_core_and_tool_of_madv_guard)(a, len, False);
+ }
+}
+#endif
+
#if HAVE_MREMAP
PRE(sys_mremap)
{
PLAXY (__NR_fstat64, sys_fstat64), // 215
LINX_ (__NR_pivot_root, sys_pivot_root), // 216
GENXY (__NR_mincore, sys_mincore), // 217
- GENX_ (__NR_madvise, sys_madvise), // 218
+ GENXY (__NR_madvise, sys_madvise), // 218
GENXY (__NR_getdents64, sys_getdents64), // 219
LINXY (__NR_fcntl64, sys_fcntl64), // 220
//..
GENX_ (__NR_mremap, sys_mremap),
GENX_ (__NR_msync, sys_msync),
GENXY (__NR_mincore, sys_mincore),
- GENX_ (__NR_madvise, sys_madvise),
+ GENXY (__NR_madvise, sys_madvise),
LINX_ (__NR_shmget, sys_shmget),
LINXY (__NR_shmat, sys_shmat),
LINXY (__NR_shmctl, sys_shmctl),
GENX_ (__NR_mlockall, sys_mlockall),
LINX_ (__NR_munlockall, sys_munlockall),
GENXY (__NR_mincore, sys_mincore),
- GENX_ (__NR_madvise, sys_madvise),
+ GENXY (__NR_madvise, sys_madvise),
LINX_ (__NR_mbind, sys_mbind),
LINXY (__NR_get_mempolicy, sys_get_mempolicy),
LINX_ (__NR_set_mempolicy, sys_set_mempolicy),
GENXY(__NR_getdents64, sys_getdents64), // 202
LINX_(__NR_pivot_root, sys_pivot_root), // 203
LINXY(__NR_fcntl64, sys_fcntl64), // 204
- GENX_(__NR_madvise, sys_madvise), // 205
+ GENXY(__NR_madvise, sys_madvise), // 205
GENXY(__NR_mincore, sys_mincore), // 206
LINX_(__NR_gettid, sys_gettid), // 207
LINXY(__NR_tkill, sys_tkill), // 208 */Linux
LINX_(__NR_pivot_root, sys_pivot_root), // 203
LINXY(__NR_fcntl64, sys_fcntl64), // 204 !!!!?? 32bit only */
- GENX_(__NR_madvise, sys_madvise), // 205
+ GENXY(__NR_madvise, sys_madvise), // 205
GENXY(__NR_mincore, sys_mincore), // 206
LINX_(__NR_gettid, sys_gettid), // 207
LINXY(__NR_tkill, sys_tkill), // 208
GENX_(__NR_mlockall, sys_mlockall), /* 230 */
LINX_(__NR_munlockall, sys_munlockall), /* 231 */
GENXY(__NR_mincore, sys_mincore), /* 232 */
- GENX_(__NR_madvise, sys_madvise), /* 233 */
+ GENXY(__NR_madvise, sys_madvise), /* 233 */
LINX_(__NR_remap_file_pages, sys_remap_file_pages), /* 234 */
LINX_(__NR_mbind, sys_mbind), /* 235 */
LINXY(__NR_get_mempolicy, sys_get_mempolicy), /* 236 */
LINX_(__NR_setfsgid, sys_setfsgid), // 216
LINX_(__NR_pivot_root, sys_pivot_root), // 217
GENXY(__NR_mincore, sys_mincore), // 218
- GENX_(__NR_madvise, sys_madvise), // 219
+ GENXY(__NR_madvise, sys_madvise), // 219
GENXY(__NR_getdents64, sys_getdents64), // 220
GENX_(221, sys_ni_syscall), /* unimplemented (by the kernel) */ // 221
LINX_(__NR_setfsgid32, sys_setfsgid), // 216
LINX_(__NR_pivot_root, sys_pivot_root), // 217
GENXY(__NR_mincore, sys_mincore), // 218
- GENX_(__NR_madvise, sys_madvise), // 219
+ GENXY(__NR_madvise, sys_madvise), // 219
GENXY(__NR_getdents64, sys_getdents64), // 220
LINXY(__NR_fcntl64, sys_fcntl64), // 221
extern Bool VG_(am_do_sync_check) ( const HChar* fn,
const HChar* file, Int line );
+/* VG_(is_guarded) checks if address is part of a madvise
+ MADV_GUARD_INSTALL guard page */
+extern Bool VG_(is_guarded) ( Addr addr );
+
+
//--------------------------------------------------------------
// Functions pertaining to the central query-notify mechanism
// used to handle mmap/munmap/mprotect resulting from client
range. */
extern Bool VG_(am_notify_mprotect)( Addr start, SizeT len, UInt prot );
+/* Bug 514297: Notifies aspacem about madvise(MADV_GUARD_*) */
+extern Bool VG_(am_notify_madv_guard) ( Addr start, SizeT len, Bool install );
+
/* Notifies aspacem that an munmap completed successfully. The
segment array is updated accordingly. As with
VG_(am_notify_mprotect), we merely record the given info, and don't
#define MAX_THREADS_DEFAULT 500
extern UInt VG_(clo_max_threads);
+/* The maximum number of madvise guard pages we support. */
+#define MAX_GUARDS_DEFAULT 500
+extern UInt VG_(clo_max_guard_pages);
+
/* If the same IP is found twice in a backtrace in a sequence of max
VG_(clo_merge_recursive_frames) frames, then the recursive call
is merged in the backtrace.
</listitem>
</varlistentry>
+ <varlistentry id="opt.max-guard-pages" xreflabel="--max-guard-pages">
+ <term>
+ <option><![CDATA[--max-guard-pages=<number> [default: 500] ]]></option>
+ </term>
+ <listitem>
+ <para>By default, Valgrind can handle to up to 500 madvise
+ guard pages. With thread heavy workloads the default value
+ might not be sufficient. Use this option to provide a different
+ limit. E.g.
+ <computeroutput>--max-guard-pages=3000</computeroutput>.
+ If <computeroutput>--max-guard-pages</computeroutput> is not
+ set explicitly, then it will default to the
+ <computeroutput>--max-threads</computeroutput> setting.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="opt.realloc-zero-bytes-frees" xreflabel="--realloc-zero-bytes-frees">
<term>
<option><![CDATA[--realloc-zero-bytes-frees=yes|no [default: yes for glibc no otherwise] ]]></option>
#if defined(VGO_freebsd)
Bool isFF; // True --> is a fixed file mapping
Bool ignore_offset; // True --> we can't work out segment offset
+#endif
+#if defined(VGO_linux)
+ Bool hasGuardPages; // True --> contains guard page (bug 514297)
#endif
}
NSegment;
include $(top_srcdir)/Makefile.tool-tests.am
dist_noinst_SCRIPTS = filter_stderr \
+ filter_madv_guard \
filter_ioctl_procmap_query
EXTRA_DIST = \
ioctl-tiocsig.vgtest \
lsframe1.vgtest lsframe1.stderr.exp \
lsframe2.vgtest lsframe2.stderr.exp \
+ madv_guard.stdout.exp madv_guard.vgtest \
memfd_create.vgtest memfd_create.stderr.exp \
memfd_create.stderr.exp-fcntl64 \
rfcomm.vgtest rfcomm.stderr.exp \
endif
endif
+if HAVE_MADV_GUARD_INSTALL
+ check_PROGRAMS += madv_guard
+endif
+
AM_CFLAGS += $(AM_FLAG_M3264_PRI)
AM_CXXFLAGS += $(AM_FLAG_M3264_PRI)
--- /dev/null
+#! /bin/sh
+
+sed 's/0x[0-f][0-f]*/0x.../g'
--- /dev/null
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+int fd = -1; // for /proc/self/pagemap
+size_t ps = 0; // page size
+
+void check_addr(char *p)
+{
+ // check page aligned-ness
+ if ((uintptr_t)p % ps != 0)
+ perror("page not aligned!\n");
+
+ // https://docs.kernel.org/admin-guide/mm/pagemap.html
+ off_t offset = ((uintptr_t)p / ps) * sizeof(uint64_t);
+ uint64_t entry; // one 64-bit value for each virtual page
+ if (lseek(fd, offset, SEEK_SET) == (off_t)-1)
+ perror("lseek /proc/self/pagemap failed\n");
+
+ read(fd, &entry, sizeof(entry));
+ // Bit 58 pte is a guard region (since 6.15) (see madvise (2) man page)
+ printf("page 0x%lx, is guardpage: %lu, present: %lu\n",
+ (unsigned long)p, ((entry >> 58) & 1), ((entry >> 63) & 1));
+
+ // attempt accessing the address
+ // if ((open(p, O_RDONLY) == -1) && errno == EFAULT)
+ // // EFAULT means that we've hit guarded region
+ // printf("accessing address 0x%lx failed\n", (unsigned long)p);
+ // else if ((open(p, O_RDONLY) == -1) && errno == ENOENT)
+ // // ENOENT means no such file or directory, and that's OK here
+ // // we didn't hit a guarded region, we consider this a pass
+ // printf("accessing address 0x%lx passed\n", (unsigned long)p);
+ // else
+ // // We never open a valid file here, so this can't happen!
+ // printf("the impossible happened, open() succeeded!\n");
+
+}
+
+void install_guardpage(char *p)
+{
+ printf("Installing guard page, addr=0x%lx\n", (unsigned long)p);
+ int rv = madvise(p, ps, MADV_GUARD_INSTALL);
+ if (rv != 0)
+ perror("madvise failed\n");
+}
+
+void remove_guardpage(char *p)
+{
+ printf("Removing guard page, addr=0x%lx\n", (unsigned long)p);
+ int rv = madvise(p, ps, MADV_GUARD_REMOVE);
+ if (rv != 0)
+ perror("madvise failed\n");
+}
+
+int main(void)
+{
+ fd = open("/proc/self/pagemap", O_RDONLY);
+ if (fd < 0)
+ perror("open /proc/self/pagemap failed\n");
+
+ ps = sysconf(_SC_PAGESIZE);
+
+ char *p = mmap(NULL, ps, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ printf("force page-in by printing this: %d\n", p[0]);
+
+ check_addr(p);
+ install_guardpage(p);
+ check_addr(p);
+ remove_guardpage(p);
+ // The page-in below isn't needed from the persp of checking whether we've
+ // hit a guard page or not. It affects whether the page is present though,
+ // so it does impact the expected stdout.log so to speak.
+ printf("force page-in by printing this: %d\n", p[0]);
+ check_addr(p);
+ install_guardpage(p);
+ check_addr(p);
+ // A valid way to nuke a guard page is to unmap it (per man 2 madvise)
+ munmap(p, ps);
+ check_addr(p);
+
+ close(fd);
+ return 0;
+}
--- /dev/null
+force page-in by printing this: 0
+page 0x..., is guardpage: 0, present: 1
+Installing guard page, addr=0x...
+page 0x..., is guardpage: 1, present: 0
+Removing guard page, addr=0x...
+force page-in by printing this: 0
+page 0x..., is guardpage: 0, present: 1
+Installing guard page, addr=0x...
+page 0x..., is guardpage: 1, present: 0
+page 0x..., is guardpage: 0, present: 0
--- /dev/null
+prog: madv_guard
+vgopts: -q --sanity-level=3
+stdout_filter: filter_madv_guard
+prereq: test -e ./madv_guard
recovered by stack scanning [5]
--resync-filter=no|yes|verbose [yes on MacOS, no on other OSes]
attempt to avoid expensive address-space-resync operations
- --max-threads=<number> maximum number of threads that valgrind can
- handle [500]
+ --max-threads=<number> maximum number of threads that valgrind can
+ handle [500]
+ --max-guard-pages=<number> maximum number of madvise guard pages that
+ valgrind can handle [500]
user options for Nulgrind:
(none)
recovered by stack scanning [5]
--resync-filter=no|yes|verbose [yes on MacOS, no on other OSes]
attempt to avoid expensive address-space-resync operations
- --max-threads=<number> maximum number of threads that valgrind can
- handle [500]
+ --max-threads=<number> maximum number of threads that valgrind can
+ handle [500]
+ --max-guard-pages=<number> maximum number of madvise guard pages that
+ valgrind can handle [500]
user options for Nulgrind:
(none)