Skip to content
os_dep.c 167 KiB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
/*
 * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
 * Copyright (c) 1991-1995 by Xerox Corporation.  All rights reserved.
 * Copyright (c) 1996-1999 by Silicon Graphics.  All rights reserved.
 * Copyright (c) 1999 by Hewlett-Packard Company.  All rights reserved.
 *
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
 * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 * Permission is hereby granted to use or copy this program
 * for any purpose,  provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 */

#include "private/gc_priv.h"

#if defined(LINUX) && !defined(POWERPC)
# include <linux/version.h>
# if (LINUX_VERSION_CODE <= 0x10400)
    /* Ugly hack to get struct sigcontext_struct definition.  Required  */
    /* for some early 1.3.X releases.  Will hopefully go away soon.     */
    /* in some later Linux releases, asm/sigcontext.h may have to       */
    /* be included instead.                                             */
#   define __KERNEL__
#   include <asm/signal.h>
#   undef __KERNEL__
# else
    /* Kernels prior to 2.1.1 defined struct sigcontext_struct instead of */
    /* struct sigcontext.  libc6 (glibc2) uses "struct sigcontext" in     */
    /* prototypes, so we have to include the top-level sigcontext.h to    */
    /* make sure the former gets defined to be the latter if appropriate. */
#   include <features.h>
#   if 2 <= __GLIBC__
#     if 2 == __GLIBC__ && 0 == __GLIBC_MINOR__
        /* glibc 2.1 no longer has sigcontext.h.  But signal.h          */
        /* has the right declaration for glibc 2.1.                     */
#       include <sigcontext.h>
#     endif /* 0 == __GLIBC_MINOR__ */
#   else /* not 2 <= __GLIBC__ */
      /* libc5 doesn't have <sigcontext.h>: go directly with the kernel   */
      /* one.  Check LINUX_VERSION_CODE to see which we should reference. */
#     include <asm/sigcontext.h>
#   endif /* 2 <= __GLIBC__ */
# endif
#endif

#if !defined(OS2) && !defined(PCR) && !defined(AMIGA) && !defined(MACOS) \
    && !defined(MSWINCE) && !defined(__CC_ARM)
# include <sys/types.h>
# if !defined(MSWIN32)
#   include <unistd.h>
# endif
#endif

#include <stdio.h>
#if defined(MSWINCE) || defined(SN_TARGET_PS3)
# define SIGSEGV 0 /* value is irrelevant */
#else
# include <signal.h>
#endif

#if defined(UNIX_LIKE) || defined(CYGWIN32) || defined(NACL)
# include <fcntl.h>
#endif

#if defined(LINUX) || defined(LINUX_STACKBOTTOM)
# include <ctype.h>
#endif

/* Blatantly OS dependent routines, except for those that are related   */
/* to dynamic loading.                                                  */

#ifdef AMIGA
# define MK_GC_AMIGA_DEF
# include "extra/AmigaOS.c"
# undef MK_GC_AMIGA_DEF
#endif

#if defined(MSWIN32) || defined(MSWINCE) || defined(CYGWIN32)
# ifndef WIN32_LEAN_AND_MEAN
#   define WIN32_LEAN_AND_MEAN 1
# endif
# define NOSERVICE
# include <windows.h>
  /* It's not clear this is completely kosher under Cygwin.  But it     */
  /* allows us to get a working MK_GC_get_stack_base.                      */
#endif

#ifdef MACOS
# include <Processes.h>
#endif

#ifdef IRIX5
# include <sys/uio.h>
# include <malloc.h>   /* for locking */
#endif

#if defined(LINUX) || defined(FREEBSD) || defined(SOLARIS) || defined(IRIX5) \
        || ((defined(USE_MMAP) || defined(USE_MUNMAP)) \
        && !defined(MSWIN32) && !defined(MSWINCE))
# define MMAP_SUPPORTED
#endif

#if defined(MMAP_SUPPORTED) || defined(ADD_HEAP_GUARD_PAGES)
# if defined(USE_MUNMAP) && !defined(USE_MMAP)
#   error "invalid config - USE_MUNMAP requires USE_MMAP"
# endif
# include <sys/types.h>
# include <sys/mman.h>
# include <sys/stat.h>
# include <errno.h>
#endif

#ifdef DARWIN
  /* for get_etext and friends */
# include <mach-o/getsect.h>
#endif

#ifdef DJGPP
  /* Apparently necessary for djgpp 2.01.  May cause problems with      */
  /* other versions.                                                    */
  typedef long unsigned int caddr_t;
#endif

#ifdef PCR
# include "il/PCR_IL.h"
# include "th/PCR_ThCtl.h"
# include "mm/PCR_MM.h"
#endif

#if !defined(NO_EXECUTE_PERMISSION)
  STATIC MK_GC_bool MK_GC_pages_executable = TRUE;
#else
  STATIC MK_GC_bool MK_GC_pages_executable = FALSE;
#endif
#define IGNORE_PAGES_EXECUTABLE 1
                        /* Undefined on MK_GC_pages_executable real use.   */

#ifdef NEED_PROC_MAPS
/* We need to parse /proc/self/maps, either to find dynamic libraries,  */
/* and/or to find the register backing store base (IA64).  Do it once   */
/* here.                                                                */

#define READ read

/* Repeatedly perform a read call until the buffer is filled or */
/* we encounter EOF.                                            */
STATIC ssize_t MK_GC_repeat_read(int fd, char *buf, size_t count)
{
    size_t num_read = 0;
    ssize_t result;

    ASSERT_CANCEL_DISABLED();
    while (num_read < count) {
        result = READ(fd, buf + num_read, count - num_read);
        if (result < 0) return result;
        if (result == 0) break;
        num_read += result;
    }
    return num_read;
}

#ifdef THREADS
  /* Determine the length of a file by incrementally reading it into a  */
  /* This would be silly to use on a file supporting lseek, but Linux   */
  /* /proc files usually do not.                                        */
  STATIC size_t MK_GC_get_file_len(int f)
  {
    size_t total = 0;
    ssize_t result;
#   define GET_FILE_LEN_BUF_SZ 500
    char buf[GET_FILE_LEN_BUF_SZ];

    do {
        result = read(f, buf, GET_FILE_LEN_BUF_SZ);
        if (result == -1) return 0;
        total += result;
    } while (result > 0);
    return total;
  }

  STATIC size_t MK_GC_get_maps_len(void)
  {
    int f = open("/proc/self/maps", O_RDONLY);
    size_t result;
    if (f < 0) return 0; /* treat missing file as empty */
    result = MK_GC_get_file_len(f);
    close(f);
    return result;
  }
#endif /* THREADS */

/* Copy the contents of /proc/self/maps to a buffer in our address      */
/* space.  Return the address of the buffer, or zero on failure.        */
/* This code could be simplified if we could determine its size ahead   */
/* of time.                                                             */
MK_GC_INNER char * MK_GC_get_maps(void)
{
    int f;
    ssize_t result;
    static char *maps_buf = NULL;
    static size_t maps_buf_sz = 1;
    size_t maps_size, old_maps_size = 0;

    /* The buffer is essentially static, so there must be a single client. */
    MK_GC_ASSERT(I_HOLD_LOCK());

    /* Note that in the presence of threads, the maps file can  */
    /* essentially shrink asynchronously and unexpectedly as    */
    /* threads that we already think of as dead release their   */
    /* stacks.  And there is no easy way to read the entire     */
    /* file atomically.  This is arguably a misfeature of the   */
    /* /proc/.../maps interface.                                */

    /* Since we don't believe the file can grow                 */
    /* asynchronously, it should suffice to first determine     */
    /* the size (using lseek or read), and then to reread the   */
    /* file.  If the size is inconsistent we have to retry.     */
    /* This only matters with threads enabled, and if we use    */
    /* this to locate roots (not the default).                  */

#   ifdef THREADS
        /* Determine the initial size of /proc/self/maps.       */
        /* Note that lseek doesn't work, at least as of 2.6.15. */
        maps_size = MK_GC_get_maps_len();
        if (0 == maps_size) return 0;
#   else
        maps_size = 4000;       /* Guess */
#   endif

    /* Read /proc/self/maps, growing maps_buf as necessary.     */
    /* Note that we may not allocate conventionally, and        */
    /* thus can't use stdio.                                    */
        do {
            while (maps_size >= maps_buf_sz) {
              /* Grow only by powers of 2, since we leak "too small" buffers.*/
              while (maps_size >= maps_buf_sz) maps_buf_sz *= 2;
              maps_buf = MK_GC_scratch_alloc(maps_buf_sz);
#             ifdef THREADS
                /* Recompute initial length, since we allocated.        */
                /* This can only happen a few times per program         */
                /* execution.                                           */
                maps_size = MK_GC_get_maps_len();
                if (0 == maps_size) return 0;
#             endif
              if (maps_buf == 0) return 0;
            }
            MK_GC_ASSERT(maps_buf_sz >= maps_size + 1);
            f = open("/proc/self/maps", O_RDONLY);
            if (-1 == f) return 0;
#           ifdef THREADS
              old_maps_size = maps_size;
#           endif
            maps_size = 0;
            do {
                result = MK_GC_repeat_read(f, maps_buf, maps_buf_sz-1);
                if (result <= 0)
                  break;
                maps_size += result;
            } while ((size_t)result == maps_buf_sz-1);
            close(f);
            if (result <= 0)
              return 0;
#           ifdef THREADS
              if (maps_size > old_maps_size) {
                if (MK_GC_print_stats)
                  MK_GC_log_printf(
                        "Unexpected maps size growth from %lu to %lu\n",
                        (unsigned long)old_maps_size,
                        (unsigned long)maps_size);
                ABORT("Unexpected asynchronous /proc/self/maps growth: "
                      "unregistered thread?");
              }
#           endif
        } while (maps_size >= maps_buf_sz || maps_size < old_maps_size);
                /* In the single-threaded case, the second clause is false. */
        maps_buf[maps_size] = '\0';

        /* Apply fn to result.  */
        return maps_buf;
}

/*
 *  MK_GC_parse_map_entry parses an entry from /proc/self/maps so we can
 *  locate all writable data segments that belong to shared libraries.
 *  The format of one of these entries and the fields we care about
 *  is as follows:
 *  XXXXXXXX-XXXXXXXX r-xp 00000000 30:05 260537     name of mapping...\n
 *  ^^^^^^^^ ^^^^^^^^ ^^^^          ^^
 *  start    end      prot          maj_dev
 *
 *  Note that since about august 2003 kernels, the columns no longer have
 *  fixed offsets on 64-bit kernels.  Hence we no longer rely on fixed offsets
 *  anywhere, which is safer anyway.
 */

/* Assign various fields of the first line in buf_ptr to (*start),      */
/* (*end), (*prot), (*maj_dev) and (*mapping_name).  mapping_name may   */
/* be NULL. (*prot) and (*mapping_name) are assigned pointers into the  */
/* original buffer.                                                     */
MK_GC_INNER char *MK_GC_parse_map_entry(char *buf_ptr, ptr_t *start, ptr_t *end,
                                  char **prot, unsigned int *maj_dev,
                                  char **mapping_name)
{
    char *start_start, *end_start, *maj_dev_start;
    char *p;
    char *endp;

    if (buf_ptr == NULL || *buf_ptr == '\0') {
        return NULL;
    }

    p = buf_ptr;
    while (isspace(*p)) ++p;
    start_start = p;
    MK_GC_ASSERT(isxdigit(*start_start));
    *start = (ptr_t)strtoul(start_start, &endp, 16); p = endp;
    MK_GC_ASSERT(*p=='-');

    ++p;
    end_start = p;
    MK_GC_ASSERT(isxdigit(*end_start));
    *end = (ptr_t)strtoul(end_start, &endp, 16); p = endp;
    MK_GC_ASSERT(isspace(*p));

    while (isspace(*p)) ++p;
    MK_GC_ASSERT(*p == 'r' || *p == '-');
    *prot = p;
    /* Skip past protection field to offset field */
       while (!isspace(*p)) ++p; while (isspace(*p)) ++p;
    MK_GC_ASSERT(isxdigit(*p));
    /* Skip past offset field, which we ignore */
          while (!isspace(*p)) ++p; while (isspace(*p)) ++p;
    maj_dev_start = p;
    MK_GC_ASSERT(isxdigit(*maj_dev_start));
    *maj_dev = strtoul(maj_dev_start, NULL, 16);

    if (mapping_name == 0) {
      while (*p && *p++ != '\n');
    } else {
      while (*p && *p != '\n' && *p != '/' && *p != '[') p++;
      *mapping_name = p;
      while (*p && *p++ != '\n');
    }

    return p;
}

#if defined(IA64) || defined(INCLUDE_LINUX_THREAD_DESCR)
  /* Try to read the backing store base from /proc/self/maps.           */
  /* Return the bounds of the writable mapping with a 0 major device,   */
  /* which includes the address passed as data.                         */
  /* Return FALSE if there is no such mapping.                          */
  MK_GC_INNER MK_GC_bool MK_GC_enclosing_mapping(ptr_t addr, ptr_t *startp,
                                        ptr_t *endp)
  {
    char *prot;
    ptr_t my_start, my_end;
    unsigned int maj_dev;
    char *maps = MK_GC_get_maps();
    char *buf_ptr = maps;

    if (0 == maps) return(FALSE);
    for (;;) {
      buf_ptr = MK_GC_parse_map_entry(buf_ptr, &my_start, &my_end,
                                   &prot, &maj_dev, 0);

      if (buf_ptr == NULL) return FALSE;
      if (prot[1] == 'w' && maj_dev == 0) {
          if (my_end > addr && my_start <= addr) {
            *startp = my_start;
            *endp = my_end;
            return TRUE;
          }
      }
    }
    return FALSE;
  }
#endif /* IA64 || INCLUDE_LINUX_THREAD_DESCR */

#if defined(REDIRECT_MALLOC)
  /* Find the text(code) mapping for the library whose name, after      */
  /* stripping the directory part, starts with nm.                      */
  MK_GC_INNER MK_GC_bool MK_GC_text_mapping(char *nm, ptr_t *startp, ptr_t *endp)
  {
    size_t nm_len = strlen(nm);
    char *prot;
    char *map_path;
    ptr_t my_start, my_end;
    unsigned int maj_dev;
    char *maps = MK_GC_get_maps();
    char *buf_ptr = maps;

    if (0 == maps) return(FALSE);
    for (;;) {
      buf_ptr = MK_GC_parse_map_entry(buf_ptr, &my_start, &my_end,
                                   &prot, &maj_dev, &map_path);

      if (buf_ptr == NULL) return FALSE;
      if (prot[0] == 'r' && prot[1] == '-' && prot[2] == 'x') {
          char *p = map_path;
          /* Set p to point just past last slash, if any. */
            while (*p != '\0' && *p != '\n' && *p != ' ' && *p != '\t') ++p;
            while (*p != '/' && p >= map_path) --p;
            ++p;
          if (strncmp(nm, p, nm_len) == 0) {
            *startp = my_start;
            *endp = my_end;
            return TRUE;
          }
      }
    }
    return FALSE;
  }
#endif /* REDIRECT_MALLOC */

#ifdef IA64
  static ptr_t backing_store_base_from_proc(void)
  {
    ptr_t my_start, my_end;
    if (!MK_GC_enclosing_mapping(MK_GC_save_regs_in_stack(), &my_start, &my_end)) {
        if (MK_GC_print_stats) {
          MK_GC_log_printf("Failed to find backing store base from /proc\n");
        }
        return 0;
    }
    return my_start;
  }
#endif

#endif /* NEED_PROC_MAPS */

#if defined(SEARCH_FOR_DATA_START)
  /* The I386 case can be handled without a search.  The Alpha case     */
  /* used to be handled differently as well, but the rules changed      */
  /* for recent Linux versions.  This seems to be the easiest way to    */
  /* cover all versions.                                                */

# if defined(LINUX) || defined(HURD)
    /* Some Linux distributions arrange to define __data_start.  Some   */
    /* define data_start as a weak symbol.  The latter is technically   */
    /* broken, since the user program may define data_start, in which   */
    /* case we lose.  Nonetheless, we try both, preferring __data_start.*/
    /* We assume gcc-compatible pragmas.        */
#   pragma weak __data_start
    extern int __data_start[];
#   pragma weak data_start
    extern int data_start[];
# endif /* LINUX */
  extern int _end[];

  ptr_t MK_GC_data_start = NULL;

  ptr_t MK_GC_find_limit(ptr_t, MK_GC_bool);

  MK_GC_INNER void MK_GC_init_linux_data_start(void)
  {

#   if defined(LINUX) || defined(HURD)
      /* Try the easy approaches first: */
      if ((ptr_t)__data_start != 0) {
          MK_GC_data_start = (ptr_t)(__data_start);
          return;
      }
      if ((ptr_t)data_start != 0) {
          MK_GC_data_start = (ptr_t)(data_start);
          return;
      }
#   endif /* LINUX */
    MK_GC_data_start = MK_GC_find_limit((ptr_t)(_end), FALSE);
  }
#endif /* SEARCH_FOR_DATA_START */

#ifdef ECOS

# ifndef ECOS_MK_GC_MEMORY_SIZE
#   define ECOS_MK_GC_MEMORY_SIZE (448 * 1024)
# endif /* ECOS_MK_GC_MEMORY_SIZE */

  /* FIXME: This is a simple way of allocating memory which is          */
  /* compatible with ECOS early releases.  Later releases use a more    */
  /* sophisticated means of allocating memory than this simple static   */
  /* allocator, but this method is at least bound to work.              */
  static char ecos_gc_memory[ECOS_MK_GC_MEMORY_SIZE];
  static char *ecos_gc_brk = ecos_gc_memory;

  static void *tiny_sbrk(ptrdiff_t increment)
  {
    void *p = ecos_gc_brk;
    ecos_gc_brk += increment;
    if (ecos_gc_brk > ecos_gc_memory + sizeof(ecos_gc_memory)) {
      ecos_gc_brk -= increment;
      return NULL;
    }
    return p;
  }
# define sbrk tiny_sbrk
#endif /* ECOS */

#if defined(NETBSD) && defined(__ELF__)
  ptr_t MK_GC_data_start = NULL;
  ptr_t MK_GC_find_limit(ptr_t, MK_GC_bool);

  extern char **environ;

  MK_GC_INNER void MK_GC_init_netbsd_elf(void)
  {
        /* This may need to be environ, without the underscore, for     */
        /* some versions.                                               */
    MK_GC_data_start = MK_GC_find_limit((ptr_t)&environ, FALSE);
  }
#endif /* NETBSD */

#ifdef OPENBSD
  static struct sigaction old_segv_act;
  STATIC sigjmp_buf MK_GC_jmp_buf_openbsd;

# ifdef THREADS
#   include <sys/syscall.h>
    extern sigset_t __syscall(quad_t, ...);
# endif

  /* Don't use MK_GC_find_limit() because siglongjmp() outside of the      */
  /* signal handler by-passes our userland pthreads lib, leaving        */
  /* SIGSEGV and SIGPROF masked.  Instead, use this custom one that     */
  /* works-around the issues.                                           */

  /*ARGSUSED*/
  STATIC void MK_GC_fault_handler_openbsd(int sig)
  {
     siglongjmp(MK_GC_jmp_buf_openbsd, 1);
  }

  /* Return the first non-addressible location > p or bound.    */
  /* Requires the allocation lock.                              */
  STATIC ptr_t MK_GC_find_limit_openbsd(ptr_t p, ptr_t bound)
  {
    static volatile ptr_t result;
             /* Safer if static, since otherwise it may not be  */
             /* preserved across the longjmp.  Can safely be    */
             /* static since it's only called with the          */
             /* allocation lock held.                           */

    struct sigaction act;
    size_t pgsz = (size_t)sysconf(_SC_PAGESIZE);
    MK_GC_ASSERT(I_HOLD_LOCK());

    act.sa_handler = MK_GC_fault_handler_openbsd;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_NODEFER | SA_RESTART;
    sigaction(SIGSEGV, &act, &old_segv_act);

    if (sigsetjmp(MK_GC_jmp_buf_openbsd, 1) == 0) {
      result = (ptr_t)((word)p & ~(pgsz-1));
      for (;;) {
        result += pgsz;
        if (result >= bound) {
          result = bound;
          break;
        }
        MK_GC_noop1((word)(*result));
      }
    }

#   ifdef THREADS
      /* Due to the siglongjump we need to manually unmask SIGPROF.     */
      __syscall(SYS_sigprocmask, SIG_UNBLOCK, sigmask(SIGPROF));
#   endif

    sigaction(SIGSEGV, &old_segv_act, 0);
    return(result);
  }

  /* Return first addressable location > p or bound.    */
  /* Requires the allocation lock.                      */
  STATIC ptr_t MK_GC_skip_hole_openbsd(ptr_t p, ptr_t bound)
  {
    static volatile ptr_t result;
    static volatile int firstpass;

    struct sigaction act;
    size_t pgsz = (size_t)sysconf(_SC_PAGESIZE);
    MK_GC_ASSERT(I_HOLD_LOCK());

    act.sa_handler = MK_GC_fault_handler_openbsd;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_NODEFER | SA_RESTART;
    sigaction(SIGSEGV, &act, &old_segv_act);

    firstpass = 1;
    result = (ptr_t)((word)p & ~(pgsz-1));
    if (sigsetjmp(MK_GC_jmp_buf_openbsd, 1) != 0 || firstpass) {
      firstpass = 0;
      result += pgsz;
      if (result >= bound) {
        result = bound;
      } else {
        MK_GC_noop1((word)(*result));
      }
    }

    sigaction(SIGSEGV, &old_segv_act, 0);
    return(result);
  }
#endif /* OPENBSD */

# ifdef OS2

# include <stddef.h>

# if !defined(__IBMC__) && !defined(__WATCOMC__) /* e.g. EMX */

struct exe_hdr {
    unsigned short      magic_number;
    unsigned short      padding[29];
    long                new_exe_offset;
};

#define E_MAGIC(x)      (x).magic_number
#define EMAGIC          0x5A4D
#define E_LFANEW(x)     (x).new_exe_offset

struct e32_exe {
    unsigned char       magic_number[2];
    unsigned char       byte_order;
    unsigned char       word_order;
    unsigned long       exe_format_level;
    unsigned short      cpu;
    unsigned short      os;
    unsigned long       padding1[13];
    unsigned long       object_table_offset;
    unsigned long       object_count;
    unsigned long       padding2[31];
};

#define E32_MAGIC1(x)   (x).magic_number[0]
#define E32MAGIC1       'L'
#define E32_MAGIC2(x)   (x).magic_number[1]
#define E32MAGIC2       'X'
#define E32_BORDER(x)   (x).byte_order
#define E32LEBO         0
#define E32_WORDER(x)   (x).word_order
#define E32LEWO         0
#define E32_CPU(x)      (x).cpu
#define E32CPU286       1
#define E32_OBJTAB(x)   (x).object_table_offset
#define E32_OBJCNT(x)   (x).object_count

struct o32_obj {
    unsigned long       size;
    unsigned long       base;
    unsigned long       flags;
    unsigned long       pagemap;
    unsigned long       mapsize;
    unsigned long       reserved;
};

#define O32_FLAGS(x)    (x).flags
#define OBJREAD         0x0001L
#define OBJWRITE        0x0002L
#define OBJINVALID      0x0080L
#define O32_SIZE(x)     (x).size
#define O32_BASE(x)     (x).base

# else  /* IBM's compiler */

/* A kludge to get around what appears to be a header file bug */
# ifndef WORD
#   define WORD unsigned short
# endif
# ifndef DWORD
#   define DWORD unsigned long
# endif

# define EXE386 1
# include <newexe.h>
# include <exe386.h>

# endif  /* __IBMC__ */

# define INCL_DOSEXCEPTIONS
# define INCL_DOSPROCESS
# define INCL_DOSERRORS
# define INCL_DOSMODULEMGR
# define INCL_DOSMEMMGR
# include <os2.h>

# endif /* OS/2 */

/* Find the page size */
MK_GC_INNER word MK_GC_page_size = 0;

#if defined(MSWIN32) || defined(MSWINCE) || defined(CYGWIN32)
# ifndef VER_PLATFORM_WIN32_CE
#   define VER_PLATFORM_WIN32_CE 3
# endif

# if defined(MSWINCE) && defined(THREADS)
    MK_GC_INNER MK_GC_bool MK_GC_dont_query_stack_min = FALSE;
# endif

  MK_GC_INNER void MK_GC_setpagesize(void)
  {
    GetSystemInfo(&MK_GC_sysinfo);
    MK_GC_page_size = MK_GC_sysinfo.dwPageSize;
#   if defined(MSWINCE) && !defined(_WIN32_WCE_EMULATION)
      {
        OSVERSIONINFO verInfo;
        /* Check the current WinCE version.     */
        verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        if (!GetVersionEx(&verInfo))
          ABORT("GetVersionEx failed");
        if (verInfo.dwPlatformId == VER_PLATFORM_WIN32_CE &&
            verInfo.dwMajorVersion < 6) {
          /* Only the first 32 MB of address space belongs to the       */
          /* current process (unless WinCE 6.0+ or emulation).          */
          MK_GC_sysinfo.lpMaximumApplicationAddress = (LPVOID)((word)32 << 20);
#         ifdef THREADS
            /* On some old WinCE versions, it's observed that           */
            /* VirtualQuery calls don't work properly when used to      */
            /* get thread current stack committed minimum.              */
            if (verInfo.dwMajorVersion < 5)
              MK_GC_dont_query_stack_min = TRUE;
#         endif
        }
      }
#   endif
  }

# ifndef CYGWIN32
#   define is_writable(prot) ((prot) == PAGE_READWRITE \
                            || (prot) == PAGE_WRITECOPY \
                            || (prot) == PAGE_EXECUTE_READWRITE \
                            || (prot) == PAGE_EXECUTE_WRITECOPY)
#  if 0  /* "#if 0" for MKCL 1.1.3 and later, "#if 1" before. */
    /* Return the number of bytes that are writable starting at p.      */
    /* The pointer p is assumed to be page aligned.                     */
    /* If base is not 0, *base becomes the beginning of the             */
    /* allocation region containing p.                                  */
    STATIC word MK_GC_get_writable_length(ptr_t p, ptr_t *base)
    {
      MEMORY_BASIC_INFORMATION buf;
      word result;
      word protect;

      result = VirtualQuery(p, &buf, sizeof(buf));
      if (result != sizeof(buf)) ABORT("Weird VirtualQuery result");
      if (base != 0) *base = (ptr_t)(buf.AllocationBase);
      protect = (buf.Protect & ~(PAGE_GUARD | PAGE_NOCACHE));
      if (!is_writable(protect)) {
        return(0);
      }
      if (buf.State != MEM_COMMIT) return(0);
      return(buf.RegionSize);
    }

    MK_GC_API int MK_GC_CALL MK_GC_get_stack_base(struct MK_GC_stack_base *sb)
    {
      ptr_t trunc_sp = (ptr_t)((word)MK_GC_approx_sp() & ~(MK_GC_page_size - 1));
      /* FIXME: This won't work if called from a deeply recursive       */
      /* client code (and the committed stack space has grown).         */
      word size = MK_GC_get_writable_length(trunc_sp, 0);
      MK_GC_ASSERT(size != 0);
      sb -> mem_base = trunc_sp + size;
      return MK_GC_SUCCESS;
    }
#  else
    MK_GC_API int MK_GC_CALL MK_GC_get_stack_base(struct MK_GC_stack_base *sb)
    { /* JCB */
      word a_var = 0;
      MEMORY_BASIC_INFORMATION mbi;

      SIZE_T mbi_size = VirtualQuery(&a_var, &mbi, sizeof(mbi));

      ptr_t stack_top = mbi.BaseAddress + mbi.RegionSize;
      ptr_t stack_base = mbi.AllocationBase;
      word stack_size = stack_top - stack_base;

      if (mbi_size != sizeof(mbi)) ABORT("Weird VirtualQuery result");

      sb -> mem_base = stack_top;
      return MK_GC_SUCCESS;
    }
#  endif
# else /* CYGWIN32 */
    /* An alternate version for Cygwin (adapted from Dave Korn's        */
    /* gcc version of boehm-gc).                                        */
    MK_GC_API int MK_GC_CALL MK_GC_get_stack_base(struct MK_GC_stack_base *sb)
    {
      extern void * _tlsbase __asm__ ("%fs:4");
      sb -> mem_base = _tlsbase;
      return MK_GC_SUCCESS;
    }
# endif /* CYGWIN32 */
# define HAVE_GET_STACK_BASE

#else /* !MSWIN32 */
  MK_GC_INNER void MK_GC_setpagesize(void)
  {
#   if defined(MPROTECT_VDB) || defined(PROC_VDB) || defined(USE_MMAP)
      MK_GC_page_size = GETPAGESIZE();
      if (!MK_GC_page_size) ABORT("getpagesize() failed");
#   else
      /* It's acceptable to fake it.    */
      MK_GC_page_size = HBLKSIZE;
#   endif
  }
#endif /* !MSWIN32 */

#ifdef BEOS
# include <kernel/OS.h>

  MK_GC_API int MK_GC_CALL MK_GC_get_stack_base(struct MK_GC_stack_base *sb)
  {
    thread_info th;
    get_thread_info(find_thread(NULL),&th);
    sb->mem_base = th.stack_end;
    return MK_GC_SUCCESS;
  }
# define HAVE_GET_STACK_BASE
#endif /* BEOS */

#ifdef OS2
  MK_GC_API int MK_GC_CALL MK_GC_get_stack_base(struct MK_GC_stack_base *sb)
  {
    PTIB ptib; /* thread information block */
    PPIB ppib;
    if (DosGetInfoBlocks(&ptib, &ppib) != NO_ERROR) {
      ABORT("DosGetInfoBlocks failed");
    }
    sb->mem_base = ptib->tib_pstacklimit;
    return MK_GC_SUCCESS;
  }
# define HAVE_GET_STACK_BASE
#endif /* OS2 */

# ifdef AMIGA
#   define MK_GC_AMIGA_SB
#   include "extra/AmigaOS.c"
#   undef MK_GC_AMIGA_SB
# endif /* AMIGA */

# if defined(NEED_FIND_LIMIT) || defined(UNIX_LIKE)

    typedef void (*MK_GC_fault_handler_t)(int);

#   if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1) \
       || defined(HURD) || defined(NETBSD)
        static struct sigaction old_segv_act;
#       if defined(_sigargs) /* !Irix6.x */ || defined(HPUX) \
           || defined(HURD) || defined(NETBSD) || defined(FREEBSD)
            static struct sigaction old_bus_act;
#       endif
#   else
        static MK_GC_fault_handler_t old_segv_handler, old_bus_handler;
#   endif

    MK_GC_INNER void MK_GC_set_and_save_fault_handler(MK_GC_fault_handler_t h)
    {
#       if defined(SUNOS5SIGS) || defined(IRIX5) \
           || defined(OSF1) || defined(HURD) || defined(NETBSD)
          struct sigaction act;

          act.sa_handler = h;
#         ifdef SIGACTION_FLAGS_NODEFER_HACK
            /* Was necessary for Solaris 2.3 and very temporary */
            /* NetBSD bugs.                                     */
            act.sa_flags = SA_RESTART | SA_NODEFER;
#         else
            act.sa_flags = SA_RESTART;
#         endif

          (void) sigemptyset(&act.sa_mask);
#         ifdef MK_GC_IRIX_THREADS
            /* Older versions have a bug related to retrieving and      */
            /* and setting a handler at the same time.                  */
            (void) sigaction(SIGSEGV, 0, &old_segv_act);
            (void) sigaction(SIGSEGV, &act, 0);
#         else
            (void) sigaction(SIGSEGV, &act, &old_segv_act);
#           if defined(IRIX5) && defined(_sigargs) /* Irix 5.x, not 6.x */ \
               || defined(HPUX) || defined(HURD) || defined(NETBSD) \
               || defined(FREEBSD)
              /* Under Irix 5.x or HP/UX, we may get SIGBUS.    */
              /* Pthreads doesn't exist under Irix 5.x, so we   */
              /* don't have to worry in the threads case.       */
              (void) sigaction(SIGBUS, &act, &old_bus_act);
#           endif
#         endif /* MK_GC_IRIX_THREADS */
#       else
          old_segv_handler = signal(SIGSEGV, h);
#         ifdef SIGBUS
            old_bus_handler = signal(SIGBUS, h);
#         endif
#       endif
    }
# endif /* NEED_FIND_LIMIT || UNIX_LIKE */

# if defined(NEED_FIND_LIMIT) \
     || (defined(USE_PROC_FOR_LIBRARIES) && defined(THREADS))
  /* Some tools to implement HEURISTIC2 */
#   define MIN_PAGE_SIZE 256    /* Smallest conceivable page size, bytes */

    /*ARGSUSED*/
    STATIC void MK_GC_fault_handler(int sig)
    {
        LONGJMP(MK_GC_jmp_buf, 1);
    }

    MK_GC_INNER void MK_GC_setup_temporary_fault_handler(void)
    {
        /* Handler is process-wide, so this should only happen in       */
        /* one thread at a time.                                        */
        MK_GC_ASSERT(I_HOLD_LOCK());
        MK_GC_set_and_save_fault_handler(MK_GC_fault_handler);
    }

    MK_GC_INNER void MK_GC_reset_fault_handler(void)
    {
#       if defined(SUNOS5SIGS) || defined(IRIX5) \
           || defined(OSF1) || defined(HURD) || defined(NETBSD)
          (void) sigaction(SIGSEGV, &old_segv_act, 0);
#         if defined(IRIX5) && defined(_sigargs) /* Irix 5.x, not 6.x */ \
             || defined(HPUX) || defined(HURD) || defined(NETBSD) \
             || defined(FREEBSD)
              (void) sigaction(SIGBUS, &old_bus_act, 0);
#         endif
#       else
          (void) signal(SIGSEGV, old_segv_handler);
#         ifdef SIGBUS
            (void) signal(SIGBUS, old_bus_handler);
#         endif
#       endif
    }

    /* Return the first non-addressable location > p (up) or    */
    /* the smallest location q s.t. [q,p) is addressable (!up). */
    /* We assume that p (up) or p-1 (!up) is addressable.       */
    /* Requires allocation lock.                                */
    STATIC ptr_t MK_GC_find_limit_with_bound(ptr_t p, MK_GC_bool up, ptr_t bound)
    {
        static volatile ptr_t result;
                /* Safer if static, since otherwise it may not be       */
                /* preserved across the longjmp.  Can safely be         */
                /* static since it's only called with the               */
                /* allocation lock held.                                */

        MK_GC_ASSERT(I_HOLD_LOCK());
        MK_GC_setup_temporary_fault_handler();
        if (SETJMP(MK_GC_jmp_buf) == 0) {
            result = (ptr_t)(((word)(p))
                              & ~(MIN_PAGE_SIZE-1));
            for (;;) {
                if (up) {
                    result += MIN_PAGE_SIZE;
                    if (result >= bound) {
                      result = bound;
                      break;
                    }
                } else {
                    result -= MIN_PAGE_SIZE;
                    if (result <= bound) {
                      result = bound - MIN_PAGE_SIZE;
                                        /* This is to compensate        */
                                        /* further result increment (we */
                                        /* do not modify "up" variable  */
                                        /* since it might be clobbered  */
                                        /* by setjmp otherwise).        */
                      break;
                    }
                }
                MK_GC_noop1((word)(*result));
            }
        }
        MK_GC_reset_fault_handler();
        if (!up) {
            result += MIN_PAGE_SIZE;
        }
        return(result);
    }

    ptr_t MK_GC_find_limit(ptr_t p, MK_GC_bool up)
    {
        return MK_GC_find_limit_with_bound(p, up, up ? (ptr_t)(word)(-1) : 0);
    }
# endif /* NEED_FIND_LIMIT || USE_PROC_FOR_LIBRARIES */

#ifdef HPUX_STACKBOTTOM

#include <sys/param.h>
#include <sys/pstat.h>

  MK_GC_INNER ptr_t MK_GC_get_register_stack_base(void)
  {
    struct pst_vm_status vm_status;

    int i = 0;