]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Bug 514297 - Track madvise MADV_GUARD_INSTALL in address space manager
authorMartin Cermak <mcermak@redhat.com>
Tue, 31 Mar 2026 15:50:48 +0000 (17:50 +0200)
committerMartin Cermak <mcermak@redhat.com>
Tue, 31 Mar 2026 16:10:05 +0000 (18:10 +0200)
Linux 6.13+ and Glibc 2.42+ introduce lightweight stack guard
pages based on madvise() syscall.

The purpose of a guard page is to prevent buggy (or malicious)
code from overrunning a memory region. An inaccessible page
placed at the end of a region will cause a segmentation fault
should the running process try to read or write to it;
well-placed guard pages can trap a number of common buffer
overruns and similar problems. Prior to 6.13, though, the only
way to put a guard page into a process's address space was to set
the protections on one or more pages with mprotect(); that works,
but at the cost of creating a new virtual memory area (VMA) to
contain the affected page(s). Placing a lot of guard pages will
create a lot of VMAs, which can slow down many memory-management
functions.

The new guard-page feature addresses this problem by working at
the page-table level rather than creating a new VMA. A process
can create guard pages with a call to madvise(), requesting the
MADV_GUARD_INSTALL operation. The indicated range of memory will
be rendered inaccessible; any data that might have been stored
there prior to the operation will be deleted. There is an
operation (MADV_GUARD_REMOVE) to remove guard pages as well.

https://lwn.net/Articles/1011366/

With glibc commit a6fbe36b7f31 and others, a guard page is
installed for each new thread.  In the future, guard pages might
be used also for DSOs supporting multiple kernel page sizes.
Except for madvise, a guard page may also be removed via
munmap().  This update introduces the support for this new type
of linux guard pages into Valgrind.

Add new --max-guard-pages command line switch to allow
customizing the maximal count of guard pages Valgrind can handle.
Add new testcase memcheck/tests/linux/madv_guard.

https://bugs.kde.org/show_bug.cgi?id=514297

33 files changed:
.gitignore
NEWS
configure.ac
coregrind/m_addrinfo.c
coregrind/m_aspacemgr/aspacemgr-common.c
coregrind/m_aspacemgr/aspacemgr-linux.c
coregrind/m_aspacemgr/priv_aspacemgr.h
coregrind/m_main.c
coregrind/m_options.c
coregrind/m_syswrap/priv_syswrap-generic.h
coregrind/m_syswrap/syswrap-amd64-linux.c
coregrind/m_syswrap/syswrap-arm-linux.c
coregrind/m_syswrap/syswrap-arm64-linux.c
coregrind/m_syswrap/syswrap-generic.c
coregrind/m_syswrap/syswrap-mips32-linux.c
coregrind/m_syswrap/syswrap-mips64-linux.c
coregrind/m_syswrap/syswrap-nanomips-linux.c
coregrind/m_syswrap/syswrap-ppc32-linux.c
coregrind/m_syswrap/syswrap-ppc64-linux.c
coregrind/m_syswrap/syswrap-riscv64-linux.c
coregrind/m_syswrap/syswrap-s390x-linux.c
coregrind/m_syswrap/syswrap-x86-linux.c
coregrind/pub_core_aspacemgr.h
coregrind/pub_core_options.h
docs/xml/manual-core.xml
include/pub_tool_aspacemgr.h
memcheck/tests/linux/Makefile.am
memcheck/tests/linux/filter_madv_guard [new file with mode: 0755]
memcheck/tests/linux/madv_guard.c [new file with mode: 0644]
memcheck/tests/linux/madv_guard.stdout.exp [new file with mode: 0644]
memcheck/tests/linux/madv_guard.vgtest [new file with mode: 0644]
none/tests/cmdline1.stdout.exp
none/tests/cmdline2.stdout.exp

index 7b3b1a6267c714e5cbf267c37b8b44c2f39e682d..a185e7dab6f7d46f205b12a1b3d09f0f88c75250 100644 (file)
 /memcheck/tests/lsframe2
 /memcheck/tests/Makefile
 /memcheck/tests/Makefile.in
+/memcheck/tests/linux/madv_guard
 /memcheck/tests/mallinfo
 /memcheck/tests/mallinfo2
 /memcheck/tests/malloc1
diff --git a/NEWS b/NEWS
index 1e1f58a9eeecae4fec7e37dca8ffafc7de6be659..2b746247b0f14bebc83ab99e4fa4b991221de406 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -94,6 +94,7 @@ are not entered into bugzilla tend to get forgotten about or ignored.
 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
index cacb522eb6d59296f6adc236a824fa66b84f72d7..15152fdbb4cd151ed8c7b638ce7d35cf160b9eb4 100644 (file)
@@ -1569,6 +1569,25 @@ AC_MSG_RESULT([no])
 
 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
index 32d2fd4c901b27901db6f7c325ccdb424062aae9..1bce6aa8e046f6debb772f618256206d3d458d73 100644 (file)
@@ -228,7 +228,14 @@ void VG_(describe_addr) ( DiEpoch ep, Addr a, /*OUT*/AddrInfo* ai )
       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. */
index 379ad773db412e6fff67a66e114ec66ffad7c063..95473ff6a6cc654e72bc6405d6ddd30eb32c1903 100644 (file)
@@ -324,6 +324,12 @@ Int ML_(am_fcntl) ( Int fd, Int cmd, Addr arg )
    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, 
index ae9846e54f61007b223da54926811fa7d3234d82..c4d66bb7a6c9070c2972aeca59044074938b7408 100644 (file)
 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))
 
@@ -467,27 +474,32 @@ static void show_nsegment ( Int logLevel, Int segNo, const NSegment* seg )
 {
    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;
 
@@ -495,27 +507,28 @@ static void show_nsegment ( Int logLevel, Int segNo, const NSegment* seg )
          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;
 
@@ -675,6 +688,9 @@ static Bool maybe_merge_nsegments ( NSegment* s1, const NSegment* s2 )
 
       case SkFree:
          s1->end = s2->end;
+#if defined(VGO_linux)
+         s1->hasGuardPages |= s2->hasGuardPages;
+#endif
          return True;
 
       case SkAnonC: case SkAnonV:
@@ -682,6 +698,9 @@ static Bool maybe_merge_nsegments ( NSegment* s1, const NSegment* s2 )
              && 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;
@@ -694,6 +713,9 @@ static Bool maybe_merge_nsegments ( NSegment* s1, const NSegment* s2 )
                               + ((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;
          }
@@ -705,6 +727,9 @@ static Bool maybe_merge_nsegments ( NSegment* s1, const NSegment* s2 )
       case SkResvn:
          if (s1->smode == SmFixed && s2->smode == SmFixed) {
             s1->end = s2->end;
+#if defined(VGO_linux)
+            s1->hasGuardPages |= s2->hasGuardPages;
+#endif
             return True;
          }
 
@@ -1061,6 +1086,264 @@ void ML_(am_do_sanity_check)( void )
    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
 
 /*-----------------------------------------------------------------*/
 /*---                                                           ---*/
@@ -1237,6 +1520,9 @@ Bool is_valid_for( UInt kinds, Addr start, SizeT len, UInt prot )
 {
    Int  i, iLo, iHi;
    Bool needR, needW, needX;
+#if defined(VGO_linux)
+   Bool needGuardPageCheck = False;
+#endif
 
    if (len == 0)
       return True; /* somewhat dubious case */
@@ -1267,11 +1553,23 @@ Bool is_valid_for( UInt kinds, Addr start, SizeT len, UInt prot )
            && (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;
 }
 
@@ -1421,6 +1719,11 @@ static void split_nsegment_at ( Addr a )
 
    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]));
 }
@@ -1540,6 +1843,9 @@ static void init_nsegment ( /*OUT*/NSegment* seg )
    seg->isFF     = False;
    seg->ignore_offset = False;
 #endif
+#if defined(VGO_linux)
+   seg->hasGuardPages = False;
+#endif
 
 }
 
@@ -1969,6 +2275,18 @@ Addr VG_(am_startup) ( Addr sp_at_startup )
 
    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;
 }
@@ -2434,6 +2752,36 @@ Bool VG_(am_notify_mprotect)( Addr start, SizeT len, UInt prot )
    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
@@ -2480,6 +2828,11 @@ Bool VG_(am_notify_munmap)( Addr start, SizeT len )
 #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;
@@ -3747,6 +4100,35 @@ static void parse_procselfmaps (
 
    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----------------------------*/
index 338b3545b595c4833c650251b8e41446fe66aaf5..e64ae5eea345800de7412c4bc8a3f29ce7219af0 100644 (file)
@@ -51,7 +51,7 @@
                                  // 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 ---------------*/
 
@@ -119,6 +123,7 @@ extern void   ML_(am_close) ( Int fd );
 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. */
index 5cf629f7794db10028484c6a5fa54334ee970d92..b707fd9876d6789f0606252bdb738386ebc1044d 100644 (file)
@@ -252,8 +252,10 @@ static void usage_NORETURN ( int need_help )
 "                  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[] =
@@ -379,7 +381,8 @@ static void usage_NORETURN ( int need_help )
                   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);
@@ -512,7 +515,15 @@ static void process_option (Clo_Mode mode,
    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
@@ -1333,8 +1344,8 @@ Int valgrind_main ( Int argc, HChar **argv, HChar **envp )
    /* 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;
@@ -1355,6 +1366,14 @@ Int valgrind_main ( Int argc, HChar **argv, HChar **envp )
                                                    &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
index 612ef8d8beb77bb840246df98652beec53b38265..faf095b2183967241203b1308a98aeaa97b00d76 100644 (file)
@@ -188,6 +188,7 @@ Bool   VG_(clo_keep_debuginfo) = False;
 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;
index ed5c1dba3711665f13d7095849a1e9af34d4c873..7bbb3e2a3c6335f30d62a6450d34566f02b0dcd0 100644 (file)
@@ -101,6 +101,9 @@ ML_(notify_core_and_tool_of_munmap) ( Addr a, SizeT len );
 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 );
index 70e3327bb045877a188f429a007ac361e5ccdc1d..1c9b0d77047641ac21df72bcf20341b65817ab28 100644 (file)
@@ -503,7 +503,7 @@ static SyscallTableEntry syscall_table[] = {
    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 
index faeb61238d97e6dc64a2eb12b6f106f75b38e55e..4233ea92148fd707d4670ea762197aff3ca0a809 100644 (file)
@@ -813,7 +813,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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
index ad704a462076acc88c2151a313a4a3c29b758dfd..4debcf8ab2b385c2044f082e8b4f1b27e63c23c6 100644 (file)
@@ -781,7 +781,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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
index 5699c78abc52b33a4366332903c8077f0d7b2100..e3f4bf55fe468bdb3edee84c5fd9442fbd85f406 100644 (file)
@@ -270,7 +270,20 @@ ML_(notify_core_and_tool_of_mprotect) ( Addr a, SizeT len, Int prot )
                                  "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
@@ -3112,18 +3125,24 @@ PRE(sys_madvise)
                         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)
 {
index da2f70f8cbc785cb98ebf86a5f8646a48687dd49..fde0f614e4c177fa0ed6e97517032f0ef0c61dd4 100644 (file)
@@ -980,7 +980,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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
    //..
index 8eaa9e263f608cbd284f5b98d44a22b76284bfb1..b08dc3b9bd404ef0e7477a2aacb63da18c191cd1 100644 (file)
@@ -495,7 +495,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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),
index 589bc6de45b72a6903f354582aef85fde3f33849..3e99320c7622256e3ae8095c76136418178338ae 100644 (file)
@@ -760,7 +760,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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),
index 0f6a9b234c517ceeec63febcb005d557ac110dcc..f56a2de7dbe203ae5465d83852dd8ec65572b06d 100644 (file)
@@ -861,7 +861,7 @@ static SyscallTableEntry syscall_table[] = {
    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
index 9e774a916c421f04842efdaf9c3b877320fd8f84..c8ba94da8e0d11e5d60d4d08936ed6bb3a4a2a30 100644 (file)
@@ -846,7 +846,7 @@ static SyscallTableEntry syscall_table[] = {
    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
index 4502a86adae82ef6aaadb7ec4fe931bf5ad528b2..d119482453f68a220489722b9a46ed52893e115a 100644 (file)
@@ -539,7 +539,7 @@ static SyscallTableEntry syscall_main_table[] = {
    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 */
index 2c403b6594559d7feced29d09015567f92fb3fdf..bda61440b7e96972810cf69f6d6ce7c687a97c9b 100644 (file)
@@ -673,7 +673,7 @@ static SyscallTableEntry syscall_table[] = {
    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
index 58944db755a76a3d3fc1159ac553dca6601d06b5..6f727dc58c8194bd5cb310fea4a1af5327062397 100644 (file)
@@ -1422,7 +1422,7 @@ static SyscallTableEntry syscall_table[] = {
    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
index 070fa549516e8858e741f224b12f16a50a5dd39b..ee6598886fe3f8164a303a9b33878bb0a29433cf 100644 (file)
@@ -119,6 +119,11 @@ extern void VG_(am_show_nsegments) ( Int logLevel, const HChar* who );
 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
@@ -187,6 +192,9 @@ extern Bool VG_(am_notify_client_shmat)( Addr a, SizeT len, UInt prot );
    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
index 6462ae90c884451d5fe414e38c20b96edcd761ec..f6b84ea8c4cfb2332f93ea76d1958e915afd5860 100644 (file)
@@ -326,6 +326,10 @@ extern Word VG_(clo_main_stacksize);
 #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.
index f132de9a6f22087ee75530394a6407312f0176af..c86d86678c441ae90dc98e2e32ec770b0e89cea6 100644 (file)
@@ -1826,6 +1826,23 @@ that can report errors, e.g. Memcheck, but not Cachegrind.</para>
     </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>
index 2f4775d6eb894f1afe558de5fb26bce12e63ee60..cefd595afcf3dd835021445e28b10793d7a8e632 100644 (file)
@@ -113,6 +113,9 @@ typedef
 #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;
index 4b26d747b625e4803ba00e21f4de76f0cb9c3a8c..17dc4c314a59c74e9d384c8364b66d8c18f69ab0 100644 (file)
@@ -2,6 +2,7 @@
 include $(top_srcdir)/Makefile.tool-tests.am
 
 dist_noinst_SCRIPTS = filter_stderr \
+       filter_madv_guard \
        filter_ioctl_procmap_query
 
 EXTRA_DIST = \
@@ -22,6 +23,7 @@ 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 \
@@ -114,6 +116,10 @@ if HAVE_NR_IO_PGETEVENTS
 endif
 endif
 
+if HAVE_MADV_GUARD_INSTALL
+        check_PROGRAMS += madv_guard
+endif
+
 AM_CFLAGS   += $(AM_FLAG_M3264_PRI)
 AM_CXXFLAGS += $(AM_FLAG_M3264_PRI)
 
diff --git a/memcheck/tests/linux/filter_madv_guard b/memcheck/tests/linux/filter_madv_guard
new file mode 100755 (executable)
index 0000000..5b89af7
--- /dev/null
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+sed 's/0x[0-f][0-f]*/0x.../g'
diff --git a/memcheck/tests/linux/madv_guard.c b/memcheck/tests/linux/madv_guard.c
new file mode 100644 (file)
index 0000000..3c8d519
--- /dev/null
@@ -0,0 +1,89 @@
+#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;
+}
diff --git a/memcheck/tests/linux/madv_guard.stdout.exp b/memcheck/tests/linux/madv_guard.stdout.exp
new file mode 100644 (file)
index 0000000..00b1c71
--- /dev/null
@@ -0,0 +1,10 @@
+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
diff --git a/memcheck/tests/linux/madv_guard.vgtest b/memcheck/tests/linux/madv_guard.vgtest
new file mode 100644 (file)
index 0000000..d86de07
--- /dev/null
@@ -0,0 +1,4 @@
+prog: madv_guard
+vgopts: -q --sanity-level=3
+stdout_filter: filter_madv_guard
+prereq: test -e ./madv_guard
index 5d3ea056977e9ffc388ada53590c288893d9bec8..0541412fae38b45e549f50e7502c0f12659e57cd 100644 (file)
@@ -163,8 +163,10 @@ usage: valgrind [options] prog-and-args
                   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)
index 2ed4fc02e85f55a9d3c88430ecd571f08d29edda..7490a573f6096036ac65c5d9090c06466b351655 100644 (file)
@@ -163,8 +163,10 @@ usage: valgrind [options] prog-and-args
                   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)