bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

vma-iter on OpenBSD


From: Bruno Haible
Subject: vma-iter on OpenBSD
Date: Thu, 27 Jan 2011 11:42:37 +0100
User-agent: KMail/1.9.9

So far, get_rusage_as() did not work on OpenBSD, because it has no /proc
file system that is mounted by default.

But OpenBSD has a unique system call, mquery() [1], whose purpose is to
ask the kernel at which address an mmap() can be placed without risking a
collision with an already existing virtual memory area. This system call
can be abused, to get the VMA list. The result is suboptimal, in that it
cannot tell the protections of a VMA, and also merges adjacent VMAs
(such as the stack area and the kernel area, for example). But it's
good enough for get_rusage_as().

With this patch, the memory leaks tests for dprintf() and fprintf() now
also work on OpenBSD: They succeed, and they fail if I re-enable the
memory leak fixed on 2009-12-15.


[1] http://resin.csoft.net/cgi-bin/man.cgi?section=2&topic=mquery


2011-01-27  Bruno Haible  <address@hidden>

        vma-iter, get-rusage-as: Add OpenBSD support.
        * modules/vma-iter (configure.ac): Test for mquery.
        * lib/vma-iter.h (VMA_ITERATE_SUPPORTED): Define also on OpenBSD.
        * lib/vma-iter.c: Include <sys/mman.h>.
        (vma_iterate): Add an implementation based on mquery().
        * lib/resource-ext.h (get_rusage_as): Update comments.
        * lib/get-rusage-as.c: Likewise.
        * lib/get-rusage-data.c: Likewise.

--- modules/vma-iter.orig       Thu Jan 27 11:29:57 2011
+++ modules/vma-iter    Thu Jan 27 01:20:20 2011
@@ -14,6 +14,7 @@
 
 configure.ac:
 gl_FUNC_MMAP_ANON
+AC_CHECK_FUNCS_ONCE([mquery])
 
 Makefile.am:
 lib_SOURCES += vma-iter.c
--- lib/vma-iter.h.orig Thu Jan 27 11:29:57 2011
+++ lib/vma-iter.h      Thu Jan 27 04:23:03 2011
@@ -51,7 +51,7 @@
    this platform.
    Note that even when this macro is defined, vma_iterate() may still fail to
    find any virtual memory area, for example if /proc is not mounted.  */
-#if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ || defined 
__sgi || defined __osf__ || (defined __APPLE__ && defined __MACH__) || (defined 
_WIN32 || defined __WIN32__) || defined __CYGWIN__ || defined __BEOS__ || 
defined __HAIKU__
+#if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ || defined 
__sgi || defined __osf__ || (defined __APPLE__ && defined __MACH__) || (defined 
_WIN32 || defined __WIN32__) || defined __CYGWIN__ || defined __BEOS__ || 
defined __HAIKU__ || HAVE_MQUERY
 # define VMA_ITERATE_SUPPORTED 1
 #endif
 
--- lib/vma-iter.c.orig Thu Jan 27 11:29:57 2011
+++ lib/vma-iter.c      Thu Jan 27 04:20:14 2011
@@ -44,6 +44,11 @@
 # include <OS.h>
 #endif
 
+#if HAVE_MQUERY /* OpenBSD */
+# include <sys/types.h>
+# include <sys/mman.h> /* mquery */
+#endif
+
 
 /* Support for reading text files in the /proc file system.  */
 
@@ -490,6 +495,99 @@
         break;
     }
 
+#elif HAVE_MQUERY /* OpenBSD */
+
+  uintptr_t pagesize;
+  uintptr_t address;
+  int /*bool*/ address_known_mapped;
+
+  pagesize = getpagesize ();
+  /* Avoid calling mquery with a NULL first argument, because this argument
+     value has a specific meaning.  We know the NULL page is unmapped.  */
+  address = pagesize;
+  address_known_mapped = 0;
+  for (;;)
+    {
+      /* Test whether the page at address is mapped.  */
+      if (address_known_mapped
+          || mquery ((void *) address, pagesize, 0, MAP_FIXED, -1, 0)
+             == (void *) -1)
+        {
+          /* The page at address is mapped.
+             This is the start of an interval.  */
+          uintptr_t start = address;
+          uintptr_t end;
+
+          /* Find the end of the interval.  */
+          end = (uintptr_t) mquery ((void *) address, pagesize, 0, 0, -1, 0);
+          if (end == (uintptr_t) (void *) -1)
+            end = 0; /* wrap around */
+          address = end;
+
+          /* It's too complicated to find out about the flags.  Just pass 0.  
*/
+          if (callback (data, start, end, 0))
+            break;
+
+          if (address < pagesize) /* wrap around? */
+            break;
+        }
+      /* Here we know that the page at address is unmapped.  */
+      {
+        uintptr_t query_size = pagesize;
+
+        address += pagesize;
+
+        /* Query larger and larger blocks, to get through the unmapped address
+           range with few mquery() calls.  */
+        for (;;)
+          {
+            if (2 * query_size > query_size)
+              query_size = 2 * query_size;
+            if (address + query_size - 1 < query_size) /* wrap around? */
+              {
+                address_known_mapped = 0;
+                break;
+              }
+            if (mquery ((void *) address, query_size, 0, MAP_FIXED, -1, 0)
+                == (void *) -1)
+              {
+                /* Not all the interval [address .. address + query_size - 1]
+                   is unmapped.  */
+                address_known_mapped = (query_size == pagesize);
+                break;
+              }
+            /* The interval [address .. address + query_size - 1] is
+               unmapped.  */
+            address += query_size;
+          }
+        /* Reduce the query size again, to determine the precise size of the
+           unmapped interval that starts at address.  */
+        while (query_size > pagesize)
+          {
+            query_size = query_size / 2;
+            if (address + query_size - 1 >= query_size)
+              {
+                if (mquery ((void *) address, query_size, 0, MAP_FIXED, -1, 0)
+                    != (void *) -1)
+                  {
+                    /* The interval [address .. address + query_size - 1] is
+                       unmapped.  */
+                    address += query_size;
+                    address_known_mapped = 0;
+                  }
+                else
+                  address_known_mapped = (query_size == pagesize);
+              }
+          }
+        /* Here again query_size = pagesize, and
+           either address + pagesize - 1 < pagesize, or
+           mquery ((void *) address, pagesize, 0, MAP_FIXED, -1, 0) fails.
+           So, the unmapped area ends at address.  */
+      }
+      if (address + pagesize - 1 < pagesize) /* wrap around? */
+        break;
+    }
+
 #endif
 }
 
--- lib/resource-ext.h.orig     Thu Jan 27 11:29:56 2011
+++ lib/resource-ext.h  Thu Jan 27 04:30:11 2011
@@ -28,7 +28,7 @@
 /* Returns the amount of address space currently in use by the current
    process, or zero if unknown.
    This is the quantity which is limited by setrlimit(RLIMIT_AS,...).
-   Note: This function always returns zero on OpenBSD and AIX.  */
+   Note: This function always returns zero on AIX.  */
 extern uintptr_t get_rusage_as (void);
 
 /* Returns the size of the data segment, or zero if unknown.
--- lib/get-rusage-as.c.orig    Thu Jan 27 11:29:56 2011
+++ lib/get-rusage-as.c Thu Jan 27 04:30:11 2011
@@ -66,7 +66,7 @@
 
    OpenBSD:
      a) setrlimit exists, but RLIMIT_AS is not defined.
-     b) No VMA iteration API exists.
+     b) mquery() can be used to find out about the virtual memory areas.
 
    AIX:
      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
--- lib/get-rusage-data.c.orig  Thu Jan 27 11:29:56 2011
+++ lib/get-rusage-data.c       Thu Jan 27 04:37:04 2011
@@ -69,7 +69,9 @@
 
    OpenBSD:
      a) setrlimit with RLIMIT_DATA works.
-     b) No VMA iteration API exists.
+     b) mquery() can be used to find out about the virtual memory areas.
+     get_rusage_data_via_setrlimit() works much better than
+     get_rusage_data_via_iterator().
      Note that malloc() appears to use mmap() for both large and small
      allocations.
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]