bug-gnulib
[Top][All Lists]
Advanced

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

sigsegv: Optimize stackvma implementation for AIX 7


From: Bruno Haible
Subject: sigsegv: Optimize stackvma implementation for AIX 7
Date: Sun, 10 Jul 2022 23:23:52 +0200

This patch integrates an improvement for AIX 7, made in GNU libsigsegv.


2022-07-10  Bruno Haible  <bruno@clisp.org>

        sigsegv: Optimize stackvma implementation for AIX 7.
        Reported by Neha Jain <nehajain29@in.ibm.com> in
        <https://lists.gnu.org/archive/html/bug-m4/2022-06/msg00005.html>
        via Eric Blake.
        * lib/stackvma.c: For AIX, add implementation that uses /proc/$pid/map,
        based on lib/vma-iter.c.
        (sigsegv_get_vma): Use it on AIX 7 or higher.

diff --git a/lib/stackvma.c b/lib/stackvma.c
index 4d0e08616a..aab3575c4c 100644
--- a/lib/stackvma.c
+++ b/lib/stackvma.c
@@ -46,7 +46,7 @@
     || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined 
__DragonFly__ \
     || defined __NetBSD__ \
     || (defined __APPLE__ && defined __MACH__) \
-    || defined __sgi || defined __sun \
+    || defined _AIX || defined __sgi || defined __sun \
     || defined __CYGWIN__ || defined __HAIKU__
 
 /* This file contains the proximity test function for the simple cases, where
@@ -1465,17 +1465,312 @@ sigsegv_get_vma (uintptr_t req_address, struct 
vma_struct *vma)
   return -1;
 }
 
-/* -------------------------------------------------------------------------- 
*/
+/* ----------------------------- stackvma-aix.c ----------------------------- 
*/
 
 #elif defined _AIX /* AIX */
 
+# include <unistd.h> /* getpagesize, getpid, close, read */
+# include <errno.h> /* EINTR */
+# include <fcntl.h> /* open */
+# include <string.h> /* memcpy */
+# include <sys/types.h>
+# include <sys/mman.h> /* mmap, munmap */
+# include <sys/procfs.h> /* prmap_t */
+# include <sys/utsname.h> /* uname */
+
+struct callback_locals
+{
+  uintptr_t address;
+  struct vma_struct *vma;
+# if STACK_DIRECTION < 0
+  uintptr_t prev;
+# else
+  int stop_at_next_vma;
+# endif
+  int retval;
+};
+
+static int
+callback (struct callback_locals *locals, uintptr_t start, uintptr_t end)
+{
+# if STACK_DIRECTION < 0
+  if (locals->address >= start && locals->address <= end - 1)
+    {
+      locals->vma->start = start;
+      locals->vma->end = end;
+      locals->vma->prev_end = locals->prev;
+      locals->retval = 0;
+      return 1;
+    }
+  locals->prev = end;
+# else
+  if (locals->stop_at_next_vma)
+    {
+      locals->vma->next_start = start;
+      locals->stop_at_next_vma = 0;
+      return 1;
+    }
+  if (locals->address >= start && locals->address <= end - 1)
+    {
+      locals->vma->start = start;
+      locals->vma->end = end;
+      locals->retval = 0;
+      locals->stop_at_next_vma = 1;
+      return 0;
+    }
+# endif
+  return 0;
+}
+
+/* Iterate over the virtual memory areas of the current process.
+   If such iteration is supported, the callback is called once for every
+   virtual memory area, in ascending order, with the following arguments:
+     - LOCALS is the same argument as passed to vma_iterate.
+     - START is the address of the first byte in the area, page-aligned.
+     - END is the address of the last byte in the area plus 1, page-aligned.
+       Note that it may be 0 for the last area in the address space.
+   If the callback returns 0, the iteration continues.  If it returns 1,
+   the iteration terminates prematurely.
+   This function may open file descriptors, but does not call malloc().
+   Return 0 if all went well, or -1 in case of error.  */
+/* This code is a simplified copy (no handling of protection flags) of the
+   code in gnulib's lib/vma-iter.c.  */
+static int
+vma_iterate (struct callback_locals *locals)
+{
+  /* On AIX, there is a /proc/$pic/map file, that contains records of type
+     prmap_t, defined in <sys/procfs.h>.  In older versions of AIX, it lists
+     only the virtual memory areas that are connected to a file, not the
+     anonymous ones.  But at least since AIX 7.1, it is well usable.  */
+
+  char fnamebuf[6+10+4+1];
+  char *fname;
+  int fd;
+  size_t memneed;
+
+  if (pagesize == 0)
+    init_pagesize ();
+
+  /* Construct fname = sprintf (fnamebuf+i, "/proc/%u/map", getpid ()).  */
+  fname = fnamebuf + sizeof (fnamebuf) - (4+1);
+  memcpy (fname, "/map", 4+1);
+  {
+    unsigned int value = getpid ();
+    do
+      *--fname = (value % 10) + '0';
+    while ((value = value / 10) > 0);
+  }
+  fname -= 6;
+  memcpy (fname, "/proc/", 6);
+
+  fd = open (fname, O_RDONLY | O_CLOEXEC);
+  if (fd < 0)
+    return -1;
+
+  /* The contents of /proc/<pid>/map contains a number of prmap_t entries,
+     then an entirely null prmap_t entry, then a heap of NUL terminated
+     strings.
+     Documentation: https://www.ibm.com/docs/en/aix/7.1?topic=files-proc-file
+     We read the entire contents, but look only at the prmap_t entries and
+     ignore the tail part.  */
+
+  for (memneed = 2 * pagesize; ; memneed = 2 * memneed)
+    {
+      /* Allocate memneed bytes of memory.
+         We cannot use alloca here, because not much stack space is guaranteed.
+         We also cannot use malloc here, because a malloc() call may call 
mmap()
+         and thus pre-allocate available memory.
+         So use mmap(), and ignore the resulting VMA if it occurs among the
+         resulting VMAs.  (Normally it doesn't, because it was allocated after
+         the open() call.)  */
+      void *auxmap;
+      unsigned long auxmap_start;
+      unsigned long auxmap_end;
+      ssize_t nbytes;
+
+      auxmap = (void *) mmap ((void *) 0, memneed, PROT_READ | PROT_WRITE,
+                              MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+      if (auxmap == (void *) -1)
+        {
+          close (fd);
+          return -1;
+        }
+      auxmap_start = (unsigned long) auxmap;
+      auxmap_end = auxmap_start + memneed;
+
+      /* Read the contents of /proc/<pid>/map in a single system call.
+         This guarantees a consistent result (no duplicated or omitted
+         entries).  */
+     retry:
+      do
+        nbytes = read (fd, auxmap, memneed);
+      while (nbytes < 0 && errno == EINTR);
+      if (nbytes <= 0)
+        {
+          munmap (auxmap, memneed);
+          close (fd);
+          return -1;
+        }
+      if (nbytes == memneed)
+        {
+          /* Need more memory.  */
+          munmap (auxmap, memneed);
+          if (lseek (fd, 0, SEEK_SET) < 0)
+            {
+              close (fd);
+              return -1;
+            }
+        }
+      else
+        {
+          if (read (fd, (char *) auxmap + nbytes, 1) > 0)
+            {
+              /* Oops, we had a short read.  Retry.  */
+              if (lseek (fd, 0, SEEK_SET) < 0)
+                {
+                  munmap (auxmap, memneed);
+                  close (fd);
+                  return -1;
+                }
+              goto retry;
+            }
+
+          /* We now have the entire contents of /proc/<pid>/map in memory.  */
+          prmap_t* maps = (prmap_t *) auxmap;
+
+          /* The entries are not sorted by address.  Therefore
+             1. Extract the relevant information into an array.
+             2. Sort the array in ascending order.
+             3. Invoke the callback.  */
+          typedef struct
+            {
+              uintptr_t start;
+              uintptr_t end;
+            }
+          vma_t;
+          /* Since 2 * sizeof (vma_t) <= sizeof (prmap_t), we can reuse the
+             same memory.  */
+          vma_t *vmas = (vma_t *) auxmap;
+
+          vma_t *vp = vmas;
+          {
+            prmap_t* mp;
+            for (mp = maps;;)
+              {
+                unsigned long start, end;
+
+                start = (unsigned long) mp->pr_vaddr;
+                end = start + mp->pr_size;
+                if (start == 0 && end == 0 && mp->pr_mflags == 0)
+                  break;
+                /* Discard empty VMAs and kernel VMAs.  */
+                if (start < end && (mp->pr_mflags & MA_KERNTEXT) == 0)
+                  {
+                    if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
+                      {
+                        /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
+                           = [start,auxmap_start-1] u [auxmap_end,end-1].  */
+                        if (start < auxmap_start)
+                          {
+                            vp->start = start;
+                            vp->end = auxmap_start;
+                            vp++;
+                          }
+                        if (auxmap_end - 1 < end - 1)
+                          {
+                            vp->start = auxmap_end;
+                            vp->end = end;
+                            vp++;
+                          }
+                      }
+                    else
+                      {
+                        vp->start = start;
+                        vp->end = end;
+                        vp++;
+                      }
+                  }
+                mp++;
+              }
+          }
+
+          size_t nvmas = vp - vmas;
+          /* Sort the array in ascending order.
+             Better not call qsort(), since it may call malloc().
+             Insertion-sort is OK in this case, despite its worst-case running
+             time of O(N²), since the number of VMAs will rarely be larger than
+             1000.  */
+          {
+            size_t i;
+            for (i = 1; i < nvmas; i++)
+              {
+                /* Invariant: Here vmas[0..i-1] is sorted.  */
+                size_t j;
+                for (j = i; j > 0 && vmas[j - 1].start > vmas[j].start; j--)
+                  {
+                    vma_t tmp = vmas[j - 1];
+                    vmas[j - 1] = vmas[j];
+                    vmas[j] = tmp;
+                  }
+                /* Invariant: Here vmas[0..i] is sorted.  */
+              }
+          }
+
+          /* Invoke the callback.  */
+          {
+            size_t i;
+            for (i = 0; i < nvmas; i++)
+              {
+                vma_t *vpi = &vmas[i];
+                if (callback (locals, vpi->start, vpi->end))
+                  break;
+              }
+          }
+
+          munmap (auxmap, memneed);
+          break;
+        }
+    }
+
+  close (fd);
+  return 0;
+}
+
 int
 sigsegv_get_vma (uintptr_t address, struct vma_struct *vma)
 {
+  struct utsname u;
+  if (uname (&u) >= 0
+      /* && strcmp (u.sysname, "AIX") == 0 */
+      && !(u.version[0] >= '1' && u.version[0] <= '6' && u.version[1] == '\0'))
+    {
+      /* AIX 7 or higher.  */
+      struct callback_locals locals;
+      locals.address = address;
+      locals.vma = vma;
+#if STACK_DIRECTION < 0
+      locals.prev = 0;
+#else
+      locals.stop_at_next_vma = 0;
+#endif
+      locals.retval = -1;
+
+      vma_iterate (&locals);
+      if (locals.retval == 0)
+        {
+#if !(STACK_DIRECTION < 0)
+          if (locals.stop_at_next_vma)
+            vma->next_start = 0;
+#endif
+          vma->is_near_this = simple_is_near_this;
+          return 0;
+        }
+    }
+
   return mincore_get_vma (address, vma);
 }
 
-/* --------------------------- stackvma-procfs.h --------------------------- */
+/* --------------------------- stackvma-procfs.c --------------------------- */
 
 #elif defined __sgi || defined __sun /* IRIX, Solaris */
 






reply via email to

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