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) /* */ 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) /* */ 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, /* */ 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) /* */ 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) /* */ 366 { 367 return 0; 368 } 369 370 #endif 371 372 373 uintptr_t 374 get_rusage_as (void) /* */ 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 () /* */ 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 */