]> Savannah Git Hosting - gnulib.git/commitdiff
vma-iter: Add support for AIX 7.
authorBruno Haible <bruno@clisp.org>
Sun, 10 Jul 2022 19:52:11 +0000 (21:52 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 31 Aug 2022 23:18:29 +0000 (01:18 +0200)
* lib/vma-iter.h (VMA_ITERATE_SUPPORTED): Define also on AIX.
* lib/vma-iter.c (vma_iterate): Add code for AIX, known to work on
AIX 7.
* lib/get-rusage-as.c: Update comments.
* lib/get-rusage-data.c: Likewise.
* tests/test-get-rusage-as.c (main): Take into account the special
address space organization on AIX in 32-bit mode.

ChangeLog
lib/get-rusage-as.c
lib/get-rusage-data.c
lib/vma-iter.c
lib/vma-iter.h
tests/test-get-rusage-as.c

index 5715888f64991684e73229e26df1c54064a74c18..c9d1deb5dd98c1c7ffd083ec6154b08f8c9025c3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2022-07-10  Bruno Haible  <bruno@clisp.org>
+
+       vma-iter: Add support for AIX 7.
+       * lib/vma-iter.h (VMA_ITERATE_SUPPORTED): Define also on AIX.
+       * lib/vma-iter.c (vma_iterate): Add code for AIX, known to work on
+       AIX 7.
+       * lib/get-rusage-as.c: Update comments.
+       * lib/get-rusage-data.c: Likewise.
+       * tests/test-get-rusage-as.c (main): Take into account the special
+       address space organization on AIX in 32-bit mode.
+
 2022-07-04  Bruno Haible  <bruno@clisp.org>
 
        havelib: Fix 'configure --help' output.
index 2e18ed93d4c641a74ad3cf37f4a5fbcb28bc7ab1..12a55c8b0531fb166efc27268876b519b35336a1 100644 (file)
@@ -84,7 +84,7 @@
      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
         apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
         therefore get_rusage_as_via_setrlimit() is always 0.
-     b) No VMA iteration API exists.
+     b) The /proc/$pid/map file contains a list of the virtual memory areas.
 
    HP-UX:
      a) setrlimit with RLIMIT_AS works.
index 0d6f7f929a0ef63c9936098960fcfdb7bf33e856..f273ad5b1d200f53d330f2ade1659f91143265ee 100644 (file)
@@ -77,7 +77,7 @@
 
    AIX:
      a) setrlimit with RLIMIT_DATA works.
-     b) No VMA iteration API exists.
+     b) The /proc/$pid/map file contains a list of the virtual memory areas.
 
    HP-UX:
      a) setrlimit with RLIMIT_DATA works, except on HP-UX 11.00, where it
index c6d6d46ac7e095b2573d2b935bb494c046341bfe..fca4626ee341328708528fba03b395fcd41f6bb7 100644 (file)
 # include <sys/sysctl.h> /* sysctl, struct kinfo_vmentry */
 #endif
 
+#if defined _AIX /* AIX */
+# include <string.h> /* memcpy */
+# include <sys/types.h>
+# include <sys/mman.h> /* mmap, munmap */
+# include <sys/procfs.h> /* prmap_t */
+#endif
+
 #if defined __sgi || defined __osf__ /* IRIX, OSF/1 */
 # include <string.h> /* memcpy */
 # include <sys/types.h>
 # include <sys/mman.h> /* mquery */
 #endif
 
-/* Note: On AIX, there is a /proc/$pic/map file, that contains records of type
-   prmap_t, defined in <sys/procfs.h>.  But it lists only the virtual memory
-   areas that are connected to a file, not the anonymous ones.  */
-
 
 /* Support for reading text files in the /proc file system.  */
 
@@ -889,6 +892,218 @@ vma_iterate (vma_iterate_callback_fn callback, void *data)
   return vma_iterate_bsd (callback, data);
 # endif
 
+#elif defined _AIX /* AIX */
+
+/* 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.  */
+
+  size_t pagesize;
+  char fnamebuf[6+10+4+1];
+  char *fname;
+  int fd;
+  size_t memneed;
+
+  pagesize = getpagesize ();
+
+  /* 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;
+              unsigned int flags;
+            }
+          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)
+                  {
+                    unsigned int flags;
+                    flags = 0;
+                    if (mp->pr_mflags & MA_READ)
+                      flags |= VMA_PROT_READ;
+                    if (mp->pr_mflags & MA_WRITE)
+                      flags |= VMA_PROT_WRITE;
+                    if (mp->pr_mflags & MA_EXEC)
+                      flags |= VMA_PROT_EXECUTE;
+
+                    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->flags = flags;
+                            vp++;
+                          }
+                        if (auxmap_end - 1 < end - 1)
+                          {
+                            vp->start = auxmap_end;
+                            vp->end = end;
+                            vp->flags = flags;
+                            vp++;
+                          }
+                      }
+                    else
+                      {
+                        vp->start = start;
+                        vp->end = end;
+                        vp->flags = flags;
+                        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 (data, vpi->start, vpi->end, vpi->flags))
+                  break;
+              }
+          }
+
+          munmap (auxmap, memneed);
+          break;
+        }
+    }
+
+  close (fd);
+  return 0;
+
 #elif defined __sgi || defined __osf__ /* IRIX, OSF/1 */
 
   size_t pagesize;
index ac67da15e51e3adc5fa7af417800860b2b180ced..dd8549b1e1af96d88f4937d38238aab3a3923c99 100644 (file)
@@ -52,7 +52,7 @@ extern int vma_iterate (vma_iterate_callback_fn callback, void *data);
    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 __ANDROID__ || defined __GNU__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__ || defined __sgi || defined __osf__ || defined __sun || HAVE_PSTAT_GETPROCVM || (defined __APPLE__ && defined __MACH__) || defined _WIN32 || defined __CYGWIN__ || defined __BEOS__ || defined __HAIKU__ || defined __minix || HAVE_MQUERY
+#if defined __linux__ || defined __ANDROID__ || defined __GNU__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__ || defined _AIX || defined __sgi || defined __osf__ || defined __sun || HAVE_PSTAT_GETPROCVM || (defined __APPLE__ && defined __MACH__) || defined _WIN32 || defined __CYGWIN__ || defined __BEOS__ || defined __HAIKU__ || defined __minix || HAVE_MQUERY
 # define VMA_ITERATE_SUPPORTED 1
 #endif
 
index d146d1928f14eb76726443970b25365a03f3410c..3725b821d8f6d4c0ed20900a585366e0b64a6649 100644 (file)
@@ -62,7 +62,13 @@ main ()
       ASSERT (value3 >= value2);
 
       /* Allocating 2.5 MB of memory should increase the address space size.  */
-      ASSERT (value3 > value1);
+      #ifdef _AIX
+      /* Except on AIX in 32-bit mode, where a single interval is used for
+         malloc and for the stack.  malloc() blocks are taken from the bottom
+         of this interval.  The stack sits at its top.  */
+      if (sizeof (void *) > 4)
+      #endif
+        ASSERT (value3 > value1);
 
       return 0;
     }