root/maint/gnulib/lib/get-rusage-as.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_rusage_as_via_setrlimit
  2. get_rusage_as_via_setrlimit
  3. vma_iterate_callback
  4. get_rusage_as_via_iterator
  5. get_rusage_as_via_iterator
  6. get_rusage_as
  7. main

   1 /* Getter for RLIMIT_AS.
   2    Copyright (C) 2011-2021 Free Software Foundation, Inc.
   3    Written by Bruno Haible <bruno@clisp.org>, 2011.
   4 
   5    This program is free software: you can redistribute it and/or modify
   6    it under the terms of the GNU General Public License as published by
   7    the Free Software Foundation; either version 3 of the License, or
   8    (at your option) any later version.
   9 
  10    This program is distributed in the hope that it will be useful,
  11    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13    GNU General Public License for more details.
  14 
  15    You should have received a copy of the GNU General Public License
  16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
  17 
  18 #include <config.h>
  19 
  20 /* On Android, when targeting Android 4.4 or older with a GCC toolchain,
  21    prevent a compilation error
  22      "error: call to 'mmap' declared with attribute error: mmap is not
  23       available with _FILE_OFFSET_BITS=64 when using GCC until android-21.
  24       Either raise your minSdkVersion, disable _FILE_OFFSET_BITS=64, or
  25       switch to Clang."
  26    The files that we access in this compilation unit are less than 2 GB
  27    large.  */
  28 #if defined __ANDROID__
  29 # undef _FILE_OFFSET_BITS
  30 #endif
  31 
  32 /* Specification.  */
  33 #include "resource-ext.h"
  34 
  35 /* The "address space size" is defined as the total size of the virtual memory
  36    areas of the current process.  This includes
  37      - areas belonging to the executable and shared libraries,
  38      - areas allocated by malloc() or mmap(),
  39      - the stack and environment areas,
  40      - gaps and guard pages (mappings with PROT_NONE),
  41      - other system dependent areas, such as vsyscall or vdso on Linux.
  42 
  43    There are two ways of retrieving the current address space size:
  44      a) by trying setrlimit with various values and observing whether the
  45         kernel allows additional mmap calls,
  46      b) by using system dependent APIs that allow to iterate over the list
  47         of virtual memory areas.
  48    We don't use the mincore() based approach here, because it would be very
  49    slow when applied to an entire address space, especially on 64-bit
  50    platforms.
  51    We define two functions
  52      get_rusage_as_via_setrlimit(),
  53      get_rusage_as_via_iterator().
  54 
  55    Discussion per platform:
  56 
  57    Linux:
  58      a) setrlimit with RLIMIT_AS works.
  59      b) The /proc/self/maps file contains a list of the virtual memory areas.
  60      Both methods agree, except that on x86_64 systems, the value of
  61      get_rusage_as_via_iterator() is 4 KB higher than
  62      get_rusage_as_via_setrlimit().
  63 
  64    Mac OS X:
  65      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
  66         ignores RLIMIT_AS. mmap() of a page always succeeds, therefore
  67         get_rusage_as_via_setrlimit() is always 0.
  68      b) The Mach based API works.
  69 
  70    FreeBSD:
  71      a) setrlimit with RLIMIT_AS works.
  72      b) The /proc/self/maps file contains a list of the virtual memory areas.
  73 
  74    NetBSD:
  75      a) setrlimit with RLIMIT_AS works.
  76      b) The /proc/self/maps file contains a list of the virtual memory areas.
  77      Both methods agree,
  78 
  79    OpenBSD:
  80      a) setrlimit exists, but RLIMIT_AS is not defined.
  81      b) mquery() can be used to find out about the virtual memory areas.
  82 
  83    AIX:
  84      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
  85         apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
  86         therefore get_rusage_as_via_setrlimit() is always 0.
  87      b) No VMA iteration API exists.
  88 
  89    HP-UX:
  90      a) setrlimit with RLIMIT_AS works.
  91      b) pstat_getprocvm() can be used to find out about the virtual memory
  92         areas.
  93      Both methods agree, except that the value of get_rusage_as_via_iterator()
  94      is slightly larger higher than get_rusage_as_via_setrlimit(), by 4 KB in
  95      32-bit mode and by 40 KB in 64-bit mode.
  96 
  97    IRIX:
  98      a) setrlimit with RLIMIT_AS works.
  99      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
 100      Both methods agree,
 101 
 102    OSF/1:
 103      a) setrlimit with RLIMIT_AS works.
 104      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
 105      The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
 106      get_rusage_as_via_iterator().  It's not clear why.
 107 
 108    Solaris:
 109      a) setrlimit with RLIMIT_AS works.
 110      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the
 111         /proc/self/maps file contains a list of the virtual memory areas.
 112      Both methods agree,
 113 
 114    Cygwin:
 115      a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
 116         get_rusage_as_via_setrlimit() therefore produces a wrong value.
 117      b) The /proc/$pid/maps file lists only the memory areas belonging to
 118         the executable and shared libraries, not the anonymous memory.
 119         But the native Windows API works.
 120 
 121    mingw:
 122      a) There is no setrlimit function.
 123      b) The native Windows API works.
 124 
 125    BeOS, Haiku:
 126      a) On BeOS, there is no setrlimit function.
 127         On Haiku, setrlimit exists. RLIMIT_AS is defined but setrlimit fails.
 128      b) There is a specific BeOS API: get_next_area_info().
 129  */
 130 
 131 
 132 #include <errno.h> /* errno */
 133 #include <stdlib.h> /* size_t, abort */
 134 #include <fcntl.h> /* open, O_RDONLY */
 135 #include <unistd.h> /* getpagesize, read, close */
 136 
 137 
 138 /* System support for get_rusage_as_via_setrlimit().  */
 139 
 140 #if HAVE_SETRLIMIT
 141 # include <sys/time.h>
 142 # include <sys/resource.h> /* getrlimit, setrlimit */
 143 #endif
 144 
 145 /* Test whether mmap() and mprotect() are available.
 146    We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
 147    HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
 148    mprotect() function in libgcc.a.  */
 149 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
 150 # include <fcntl.h>
 151 # include <sys/types.h>
 152 # include <sys/mman.h> /* mmap, munmap */
 153 /* Define MAP_FILE when it isn't otherwise.  */
 154 # ifndef MAP_FILE
 155 #  define MAP_FILE 0
 156 # endif
 157 #endif
 158 
 159 
 160 /* System support for get_rusage_as_via_iterator().  */
 161 
 162 #include "vma-iter.h"
 163 
 164 
 165 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
 166 
 167 static uintptr_t
 168 get_rusage_as_via_setrlimit (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 169 {
 170   uintptr_t result;
 171 
 172   struct rlimit orig_limit;
 173 
 174 # if HAVE_MAP_ANONYMOUS
 175   const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
 176   const int fd = -1;
 177 # else /* !HAVE_MAP_ANONYMOUS */
 178   const int flags = MAP_FILE | MAP_PRIVATE;
 179   int fd = open ("/dev/zero", O_RDONLY | O_CLOEXEC, 0666);
 180   if (fd < 0)
 181     return 0;
 182 # endif
 183 
 184   /* Record the original limit.  */
 185   if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
 186     {
 187       result = 0;
 188       goto done2;
 189     }
 190 
 191   if (orig_limit.rlim_max != RLIM_INFINITY
 192       && (orig_limit.rlim_cur == RLIM_INFINITY
 193           || orig_limit.rlim_cur > orig_limit.rlim_max))
 194     /* We may not be able to restore the current rlim_cur value.
 195        So bail out.  */
 196     {
 197       result = 0;
 198       goto done2;
 199     }
 200 
 201   {
 202     /* The granularity is a single page.  */
 203     const size_t pagesize = getpagesize ();
 204 
 205     uintptr_t low_bound = 0;
 206     uintptr_t high_bound;
 207 
 208     for (;;)
 209       {
 210         /* Here we know that the address space size is >= low_bound.  */
 211         struct rlimit try_limit;
 212         uintptr_t try_next = 2 * low_bound + pagesize;
 213 
 214         if (try_next < low_bound)
 215           /* Overflow.  */
 216           try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
 217 
 218         /* There's no point in trying a value > orig_limit.rlim_max, as
 219            setrlimit would fail anyway.  */
 220         if (orig_limit.rlim_max != RLIM_INFINITY
 221             && orig_limit.rlim_max < try_next)
 222           try_next = orig_limit.rlim_max;
 223 
 224         /* Avoid endless loop.  */
 225         if (try_next == low_bound)
 226           {
 227             /* try_next could not be increased.  */
 228             result = low_bound;
 229             goto done1;
 230           }
 231 
 232         try_limit.rlim_max = orig_limit.rlim_max;
 233         try_limit.rlim_cur = try_next;
 234         if (setrlimit (RLIMIT_AS, &try_limit) == 0)
 235           {
 236             /* Allocate a page of memory, to compare the current address space
 237                size with try_limit.rlim_cur.  */
 238             void *new_page =
 239               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
 240 
 241             if (new_page != (void *)(-1))
 242               {
 243                 /* The page could be added successfully.  Free it.  */
 244                 if (munmap (new_page, pagesize) < 0)
 245                   abort ();
 246                 /* We know that the address space size is
 247                    < try_limit.rlim_cur.  */
 248                 high_bound = try_next;
 249                 break;
 250               }
 251             else
 252               {
 253                 /* We know that the address space size is
 254                    >= try_limit.rlim_cur.  */
 255                 low_bound = try_next;
 256               }
 257           }
 258         else
 259           {
 260             /* Here we expect only EINVAL, not EPERM.  */
 261             if (errno != EINVAL)
 262               abort ();
 263             /* We know that the address space size is
 264                >= try_limit.rlim_cur.  */
 265             low_bound = try_next;
 266           }
 267       }
 268 
 269     /* Here we know that the address space size is
 270        >= low_bound and < high_bound.  */
 271     while (high_bound - low_bound > pagesize)
 272       {
 273         struct rlimit try_limit;
 274         uintptr_t try_next =
 275           low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
 276 
 277         /* Here low_bound <= try_next < high_bound.  */
 278         try_limit.rlim_max = orig_limit.rlim_max;
 279         try_limit.rlim_cur = try_next;
 280         if (setrlimit (RLIMIT_AS, &try_limit) == 0)
 281           {
 282             /* Allocate a page of memory, to compare the current address space
 283                size with try_limit.rlim_cur.  */
 284             void *new_page =
 285               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
 286 
 287             if (new_page != (void *)(-1))
 288               {
 289                 /* The page could be added successfully.  Free it.  */
 290                 if (munmap (new_page, pagesize) < 0)
 291                   abort ();
 292                 /* We know that the address space size is
 293                    < try_limit.rlim_cur.  */
 294                 high_bound = try_next;
 295               }
 296             else
 297               {
 298                 /* We know that the address space size is
 299                    >= try_limit.rlim_cur.  */
 300                 low_bound = try_next;
 301               }
 302           }
 303         else
 304           {
 305             /* Here we expect only EINVAL, not EPERM.  */
 306             if (errno != EINVAL)
 307               abort ();
 308             /* We know that the address space size is
 309                >= try_limit.rlim_cur.  */
 310             low_bound = try_next;
 311           }
 312       }
 313 
 314     result = low_bound;
 315   }
 316 
 317  done1:
 318   /* Restore the original rlim_cur value.  */
 319   if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
 320     abort ();
 321 
 322  done2:
 323 # if !HAVE_MAP_ANONYMOUS
 324   close (fd);
 325 # endif
 326   return result;
 327 }
 328 
 329 #else
 330 
 331 static uintptr_t
 332 get_rusage_as_via_setrlimit (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 333 {
 334   return 0;
 335 }
 336 
 337 #endif
 338 
 339 
 340 #if VMA_ITERATE_SUPPORTED
 341 
 342 static int
 343 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
     /* [previous][next][first][last][top][bottom][index][help] */
 344                       unsigned int flags)
 345 {
 346   uintptr_t *totalp = (uintptr_t *) data;
 347 
 348   *totalp += end - start;
 349   return 0;
 350 }
 351 
 352 static uintptr_t
 353 get_rusage_as_via_iterator (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 354 {
 355   uintptr_t total = 0;
 356 
 357   vma_iterate (vma_iterate_callback, &total);
 358 
 359   return total;
 360 }
 361 
 362 #else
 363 
 364 static uintptr_t
 365 get_rusage_as_via_iterator (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 366 {
 367   return 0;
 368 }
 369 
 370 #endif
 371 
 372 
 373 uintptr_t
 374 get_rusage_as (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 375 {
 376 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ || defined __MVS__ || defined __SANITIZE_THREAD__ /* Mac OS X, AIX, Cygwin, z/OS, gcc -fsanitize=thread */
 377   /* get_rusage_as_via_setrlimit() does not work.
 378      Prefer get_rusage_as_via_iterator().  */
 379   return get_rusage_as_via_iterator ();
 380 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
 381   /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
 382      because the caller may want to use the result with setrlimit().  */
 383   uintptr_t result;
 384 
 385   result = get_rusage_as_via_setrlimit ();
 386   if (result == 0)
 387     result = get_rusage_as_via_iterator ();
 388   return result;
 389 #else
 390   return get_rusage_as_via_iterator ();
 391 #endif
 392 }
 393 
 394 
 395 #ifdef TEST
 396 
 397 #include <stdio.h>
 398 
 399 int
 400 main ()
     /* [previous][next][first][last][top][bottom][index][help] */
 401 {
 402   printf ("Initially:           0x%08lX 0x%08lX 0x%08lX\n",
 403           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
 404           get_rusage_as ());
 405   malloc (0x88);
 406   printf ("After small malloc:  0x%08lX 0x%08lX 0x%08lX\n",
 407           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
 408           get_rusage_as ());
 409   malloc (0x8812);
 410   printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
 411           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
 412           get_rusage_as ());
 413   malloc (0x281237);
 414   printf ("After large malloc:  0x%08lX 0x%08lX 0x%08lX\n",
 415           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
 416           get_rusage_as ());
 417   return 0;
 418 }
 419 
 420 #endif /* TEST */

/* [previous][next][first][last][top][bottom][index][help] */