1 /* Getter for RLIMIT_DATA. 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 /* Specification. */ 21 #include "resource-ext.h" 22 23 /* The "data segment size" is defined as the virtual memory area of the 24 current process that contains malloc()ed memory. 25 26 There are two ways of retrieving the current data segment size: 27 a) by trying setrlimit with various values and observing whether the 28 kernel allows additional sbrk() calls, 29 b) by using system dependent APIs that allow to iterate over the list 30 of virtual memory areas. 31 We define two functions 32 get_rusage_data_via_setrlimit(), 33 get_rusage_data_via_iterator(). 34 35 The variant 36 a') by trying setrlimit with various values and observing whether 37 additional malloc() calls succeed 38 is not as good as a), because a malloc() call can be served by already 39 allocated memory or through mmap(), and because a malloc() of 1 page may 40 require 2 pages. 41 42 Discussion per platform: 43 44 Linux: 45 a) setrlimit with RLIMIT_DATA works. 46 b) The /proc/self/maps file contains a list of the virtual memory areas. 47 get_rusage_data_via_setrlimit() returns the sum of the length of the 48 executable's data segment plus the heap VMA (an anonymous memory area), 49 whereas get_rusage_data_via_iterator() returns only the latter. 50 Note that malloc() falls back on mmap() for large allocations and also 51 for small allocations if there is not enough room in the data segment. 52 53 Mac OS X: 54 a) setrlimit with RLIMIT_DATA succeeds but does not really work: The OS 55 ignores RLIMIT_DATA. Therefore get_rusage_data_via_setrlimit() is 56 always 0. 57 b) The Mach based API works. 58 Note that malloc() falls back on mmap() for large allocations. 59 60 FreeBSD: 61 a) setrlimit with RLIMIT_DATA works. 62 b) The /proc/self/maps file contains a list of the virtual memory areas. 63 64 NetBSD: 65 a) setrlimit with RLIMIT_DATA works. 66 b) The /proc/self/maps file contains a list of the virtual memory areas. 67 Both methods agree. 68 Note that malloc() uses mmap() for large allocations. 69 70 OpenBSD: 71 a) setrlimit with RLIMIT_DATA works. 72 b) mquery() can be used to find out about the virtual memory areas. 73 get_rusage_data_via_setrlimit() works much better than 74 get_rusage_data_via_iterator(). 75 Note that malloc() appears to use mmap() for both large and small 76 allocations. 77 78 AIX: 79 a) setrlimit with RLIMIT_DATA works. 80 b) No VMA iteration API exists. 81 82 HP-UX: 83 a) setrlimit with RLIMIT_DATA works, except on HP-UX 11.00, where it 84 cannot restore the previous limits, and except on HP-UX 11.11, where 85 it sometimes has no effect. 86 b) pstat_getprocvm() can be used to find out about the virtual memory 87 areas. 88 Both methods agree, except that the value of get_rusage_data_via_iterator() 89 is sometimes 4 KB larger than get_rusage_data_via_setrlimit(). 90 91 IRIX: 92 a) setrlimit with RLIMIT_DATA works. 93 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP. 94 get_rusage_data_via_setrlimit() works slightly better than 95 get_rusage_data_via_iterator() before the first malloc() call. 96 97 OSF/1: 98 a) setrlimit with RLIMIT_DATA works. 99 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP. 100 Both methods agree. 101 102 Solaris: 103 a) setrlimit with RLIMIT_DATA works. 104 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the 105 /proc/self/maps file contains a list of the virtual memory areas. 106 get_rusage_data_via_setrlimit() ignores the data segment of the executable, 107 whereas get_rusage_data_via_iterator() includes it. 108 109 Cygwin: 110 a) setrlimit with RLIMIT_DATA always fails. 111 get_rusage_data_via_setrlimit() therefore produces a wrong value. 112 b) The /proc/$pid/maps file lists only the memory areas belonging to 113 the executable and shared libraries, not the anonymous memory. 114 But the native Windows API works. 115 Note that malloc() apparently falls back on mmap() for large allocations. 116 117 mingw: 118 a) There is no setrlimit function. 119 b) There is no sbrk() function. 120 Note that malloc() falls back on VirtualAlloc() for large allocations. 121 122 BeOS, Haiku: 123 a) On BeOS, there is no setrlimit function. 124 On Haiku, setrlimit exists. RLIMIT_DATA is defined but setrlimit fails. 125 b) There is a specific BeOS API: get_next_area_info(). 126 */ 127 128 129 #include <errno.h> /* errno */ 130 #include <stdlib.h> /* size_t, abort, malloc, free, sbrk */ 131 #include <fcntl.h> /* open, O_RDONLY */ 132 #include <unistd.h> /* getpagesize, read, close */ 133 134 135 /* System support for get_rusage_data_via_setrlimit(). */ 136 137 #if HAVE_SETRLIMIT 138 # include <sys/time.h> 139 # include <sys/resource.h> /* getrlimit, setrlimit */ 140 # include <sys/utsname.h> 141 # include <string.h> /* strlen, strcmp */ 142 #endif 143 144 145 /* System support for get_rusage_data_via_iterator(). */ 146 147 #include "vma-iter.h" 148 149 150 #if !(defined __APPLE__ && defined __MACH__) || defined TEST 151 /* Implement get_rusage_data_via_setrlimit(). */ 152 153 # if HAVE_SETRLIMIT && defined RLIMIT_DATA && HAVE_SBRK && !defined __HAIKU__ 154 155 # ifdef _AIX 156 # define errno_expected() (errno == EINVAL || errno == EFAULT) 157 # else 158 # define errno_expected() (errno == EINVAL) 159 # endif 160 161 static uintptr_t 162 get_rusage_data_via_setrlimit (void) /* */ 163 { 164 uintptr_t result; 165 166 struct rlimit orig_limit; 167 168 # ifdef __hpux 169 /* On HP-UX 11.00, setrlimit() of RLIMIT_DATA does not work: It cannot 170 restore the previous limits. 171 On HP-UX 11.11, setrlimit() of RLIMIT_DATA does not work: It sometimes 172 has no effect on the next sbrk() call. */ 173 { 174 struct utsname buf; 175 176 if (uname (&buf) == 0 177 && strlen (buf.release) >= 5 178 && (strcmp (buf.release + strlen (buf.release) - 5, "11.00") == 0 179 || strcmp (buf.release + strlen (buf.release) - 5, "11.11") == 0)) 180 return 0; 181 } 182 # endif 183 184 /* Record the original limit. */ 185 if (getrlimit (RLIMIT_DATA, &orig_limit) < 0) 186 return 0; 187 188 if (orig_limit.rlim_max != RLIM_INFINITY 189 && (orig_limit.rlim_cur == RLIM_INFINITY 190 || orig_limit.rlim_cur > orig_limit.rlim_max)) 191 /* We may not be able to restore the current rlim_cur value. 192 So bail out. */ 193 return 0; 194 195 { 196 /* The granularity is a single page. */ 197 const intptr_t pagesize = getpagesize (); 198 199 uintptr_t low_bound = 0; 200 uintptr_t high_bound; 201 202 for (;;) 203 { 204 /* Here we know that the data segment size is >= low_bound. */ 205 struct rlimit try_limit; 206 uintptr_t try_next = 2 * low_bound + pagesize; 207 208 if (try_next < low_bound) 209 /* Overflow. */ 210 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize; 211 212 /* There's no point in trying a value > orig_limit.rlim_max, as 213 setrlimit would fail anyway. */ 214 if (orig_limit.rlim_max != RLIM_INFINITY 215 && orig_limit.rlim_max < try_next) 216 try_next = orig_limit.rlim_max; 217 218 /* Avoid endless loop. */ 219 if (try_next == low_bound) 220 { 221 /* try_next could not be increased. */ 222 result = low_bound; 223 goto done; 224 } 225 226 try_limit.rlim_max = orig_limit.rlim_max; 227 try_limit.rlim_cur = try_next; 228 if (setrlimit (RLIMIT_DATA, &try_limit) == 0) 229 { 230 /* Allocate a page of memory, to compare the current data segment 231 size with try_limit.rlim_cur. */ 232 void *new_page = sbrk (pagesize); 233 234 if (new_page != (void *)(-1)) 235 { 236 /* The page could be added successfully. Free it. */ 237 sbrk (- pagesize); 238 /* We know that the data segment size is 239 < try_limit.rlim_cur. */ 240 high_bound = try_next; 241 break; 242 } 243 else 244 { 245 /* We know that the data segment size is 246 >= try_limit.rlim_cur. */ 247 low_bound = try_next; 248 } 249 } 250 else 251 { 252 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */ 253 if (! errno_expected ()) 254 abort (); 255 /* We know that the data segment size is 256 >= try_limit.rlim_cur. */ 257 low_bound = try_next; 258 } 259 } 260 261 /* Here we know that the data segment size is 262 >= low_bound and < high_bound. */ 263 while (high_bound - low_bound > pagesize) 264 { 265 struct rlimit try_limit; 266 uintptr_t try_next = 267 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize; 268 269 /* Here low_bound <= try_next < high_bound. */ 270 try_limit.rlim_max = orig_limit.rlim_max; 271 try_limit.rlim_cur = try_next; 272 if (setrlimit (RLIMIT_DATA, &try_limit) == 0) 273 { 274 /* Allocate a page of memory, to compare the current data segment 275 size with try_limit.rlim_cur. */ 276 void *new_page = sbrk (pagesize); 277 278 if (new_page != (void *)(-1)) 279 { 280 /* The page could be added successfully. Free it. */ 281 sbrk (- pagesize); 282 /* We know that the data segment size is 283 < try_limit.rlim_cur. */ 284 high_bound = try_next; 285 } 286 else 287 { 288 /* We know that the data segment size is 289 >= try_limit.rlim_cur. */ 290 low_bound = try_next; 291 } 292 } 293 else 294 { 295 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */ 296 if (! errno_expected ()) 297 abort (); 298 /* We know that the data segment size is 299 >= try_limit.rlim_cur. */ 300 low_bound = try_next; 301 } 302 } 303 304 result = low_bound; 305 } 306 307 done: 308 /* Restore the original rlim_cur value. */ 309 if (setrlimit (RLIMIT_DATA, &orig_limit) < 0) 310 abort (); 311 312 return result; 313 } 314 315 # else 316 317 static uintptr_t 318 get_rusage_data_via_setrlimit (void) /* */ 319 { 320 return 0; 321 } 322 323 # endif 324 325 #endif 326 327 328 #if !(defined __APPLE__ && defined __MACH__) || defined TEST 329 /* Implement get_rusage_data_via_iterator(). */ 330 331 # if VMA_ITERATE_SUPPORTED 332 333 struct locals 334 { 335 uintptr_t brk_value; 336 uintptr_t data_segment_size; 337 }; 338 339 static int 340 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end, /* */ 341 unsigned int flags) 342 { 343 struct locals *lp = (struct locals *) data; 344 345 if (start <= lp->brk_value && lp->brk_value - 1 <= end - 1) 346 { 347 lp->data_segment_size = end - start; 348 return 1; 349 } 350 return 0; 351 } 352 353 static uintptr_t 354 get_rusage_data_via_iterator (void) /* */ 355 { 356 # if (defined _WIN32 && !defined __CYGWIN__) || defined __BEOS__ || defined __HAIKU__ 357 /* On native Windows, there is no sbrk() function. 358 On Haiku, sbrk(0) always returns 0. */ 359 static void *brk_value; 360 361 if (brk_value == NULL) 362 { 363 brk_value = malloc (1); 364 if (brk_value == NULL) 365 return 0; 366 } 367 # else 368 void *brk_value; 369 370 brk_value = sbrk (0); 371 if (brk_value == (void *)-1) 372 return 0; 373 # endif 374 375 { 376 struct locals l; 377 378 l.brk_value = (uintptr_t) brk_value; 379 l.data_segment_size = 0; 380 vma_iterate (vma_iterate_callback, &l); 381 382 return l.data_segment_size; 383 } 384 } 385 386 # else 387 388 static uintptr_t 389 get_rusage_data_via_iterator (void) /* */ 390 { 391 return 0; 392 } 393 394 # endif 395 396 #endif 397 398 399 uintptr_t 400 get_rusage_data (void) /* */ 401 { 402 #if (defined __APPLE__ && defined __MACH__) /* Mac OS X */ 403 /* get_rusage_data_via_setrlimit() does not work: it always returns 0. 404 get_rusage_data_via_iterator() does not work: it always returns 0x400000. 405 And sbrk() is deprecated. */ 406 return 0; 407 #elif defined __minix /* Minix */ 408 /* get_rusage_data_via_setrlimit() does not work: it always returns 0. 409 get_rusage_data_via_iterator() does not work: it shrinks upon malloc. */ 410 return 0; 411 #elif defined __CYGWIN__ /* Cygwin */ 412 /* get_rusage_data_via_setrlimit() does not work. 413 Prefer get_rusage_data_via_iterator(). */ 414 return get_rusage_data_via_iterator (); 415 #elif HAVE_SETRLIMIT && defined RLIMIT_DATA && !defined __HAIKU__ 416 # if defined __linux__ || defined __ANDROID__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined _AIX || defined __hpux || defined __sgi || defined __osf__ || defined __sun /* Linux, FreeBSD, NetBSD, OpenBSD, AIX, HP-UX, IRIX, OSF/1, Solaris */ 417 /* get_rusage_data_via_setrlimit() works. */ 418 return get_rusage_data_via_setrlimit (); 419 # else 420 /* Prefer get_rusage_data_via_setrlimit() if it succeeds, 421 because the caller may want to use the result with setrlimit(). */ 422 uintptr_t result; 423 424 result = get_rusage_data_via_setrlimit (); 425 if (result == 0) 426 result = get_rusage_data_via_iterator (); 427 return result; 428 # endif 429 #else 430 return get_rusage_data_via_iterator (); 431 #endif 432 } 433 434 435 #ifdef TEST 436 437 #include <stdio.h> 438 439 int 440 main () /* */ 441 { 442 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n", 443 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (), 444 get_rusage_data ()); 445 malloc (0x88); 446 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n", 447 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (), 448 get_rusage_data ()); 449 malloc (0x8812); 450 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n", 451 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (), 452 get_rusage_data ()); 453 malloc (0x281237); 454 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n", 455 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (), 456 get_rusage_data ()); 457 return 0; 458 } 459 460 #endif /* TEST */