OSDN Git Service

2007-01-16 Petr Salinger <Petr.Salinger@seznam.cz>
[pf3gnuchains/gcc-fork.git] / boehm-gc / os_dep.c
index 362bd93..540dcba 100644 (file)
@@ -14,7 +14,7 @@
  * modified is included with the above copyright notice.
  */
 
-# include "gc_priv.h"
+# include "private/gc_priv.h"
 
 # if defined(LINUX) && !defined(POWERPC)
 #   include <linux/version.h>
@@ -45,7 +45,8 @@
 #     endif /* 2 <= __GLIBC__ */
 #   endif
 # endif
-# if !defined(OS2) && !defined(PCR) && !defined(AMIGA) && !defined(MACOS)
+# if !defined(OS2) && !defined(PCR) && !defined(AMIGA) && !defined(MACOS) \
+    && !defined(MSWINCE)
 #   include <sys/types.h>
 #   if !defined(MSWIN32) && !defined(SUNOS4)
 #      include <unistd.h>
 # endif
 
 # include <stdio.h>
-# include <signal.h>
+# if defined(MSWINCE)
+#   define SIGSEGV 0 /* value is irrelevant */
+# else
+#   include <signal.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.                                                 */
 
-# if !defined(THREADS) && !defined(STACKBOTTOM) && defined(HEURISTIC2)
+# if defined(HEURISTIC2) || defined(SEARCH_FOR_DATA_START)
 #   define NEED_FIND_LIMIT
 # endif
 
-# if defined(IRIX_THREADS) || defined(HPUX_THREADS)
+# if !defined(STACKBOTTOM) && defined(HEURISTIC2)
 #   define NEED_FIND_LIMIT
 # endif
 
-# if (defined(SUNOS4) & defined(DYNAMIC_LOADING)) && !defined(PCR)
+# if (defined(SUNOS4) && defined(DYNAMIC_LOADING)) && !defined(PCR)
 #   define NEED_FIND_LIMIT
 # endif
 
-# if (defined(SVR4) || defined(AUX) || defined(DGUX)) && !defined(PCR)
+# if (defined(SVR4) || defined(AUX) || defined(DGUX) \
+      || (defined(LINUX) && defined(SPARC))) && !defined(PCR)
 #   define NEED_FIND_LIMIT
 # endif
 
-# if defined(LINUX) && \
-     (defined(SPARC) || defined(ALPHA) || defined(IA64))
-#   define NEED_FIND_LIMIT
-# endif
+#if defined(FREEBSD) && (defined(I386) || defined(X86_64) || defined(powerpc) || defined(__powerpc__))
+#  include <machine/trap.h>
+#  if !defined(PCR)
+#    define NEED_FIND_LIMIT
+#  endif
+#endif
 
-#ifdef NEED_FIND_LIMIT
-#   include <setjmp.h>
+#if (defined(NETBSD) || defined(OPENBSD)) && defined(__ELF__) \
+    && !defined(NEED_FIND_LIMIT)
+   /* Used by GC_init_netbsd_elf() below.      */
+#  define NEED_FIND_LIMIT
 #endif
 
-#ifdef FREEBSD
-#  include <machine/trap.h>
+#ifdef NEED_FIND_LIMIT
+#   include <setjmp.h>
 #endif
 
 #ifdef AMIGA
-# include <proto/exec.h>
-# include <proto/dos.h>
-# include <dos/dosextens.h>
-# include <workbench/startup.h>
+# define GC_AMIGA_DEF
+# include "AmigaOS.c"
+# undef GC_AMIGA_DEF
 #endif
 
-#ifdef MSWIN32
+#if defined(MSWIN32) || defined(MSWINCE)
 # define WIN32_LEAN_AND_MEAN
 # define NOSERVICE
 # include <windows.h>
 # include <sys/uio.h>
 # include <malloc.h>   /* for locking */
 #endif
-#ifdef USE_MMAP
+#if defined(USE_MMAP) || defined(USE_MUNMAP)
+# ifndef USE_MMAP
+    --> USE_MUNMAP requires USE_MMAP
+# endif
 # include <sys/types.h>
 # include <sys/mman.h>
 # include <sys/stat.h>
+# include <errno.h>
+#endif
+
+#ifdef UNIX_LIKE
 # include <fcntl.h>
+# if defined(SUNOS5SIGS) && !defined(FREEBSD)
+#  include <sys/siginfo.h>
+# endif
+  /* Define SETJMP and friends to be the version that restores */
+  /* the signal mask.                                          */
+# define SETJMP(env) sigsetjmp(env, 1)
+# define LONGJMP(env, val) siglongjmp(env, val)
+# define JMP_BUF sigjmp_buf
+#else
+# define SETJMP(env) setjmp(env)
+# define LONGJMP(env, val) longjmp(env, val)
+# define JMP_BUF jmp_buf
 #endif
 
-#ifdef SUNOS5SIGS
-# include <sys/siginfo.h>
-# undef setjmp
-# undef longjmp
-# define setjmp(env) sigsetjmp(env, 1)
-# define longjmp(env, val) siglongjmp(env, val)
-# define jmp_buf sigjmp_buf
+#ifdef DARWIN
+/* for get_etext and friends */
+#include <mach-o/getsect.h>
 #endif
 
 #ifdef DJGPP
-  /* Apparently necessary for djgpp 2.01.  May casuse problems with    */
+  /* Apparently necessary for djgpp 2.01.  May cause problems with     */
   /* other versions.                                                   */
   typedef long unsigned int caddr_t;
 #endif
 # define OPT_PROT_EXEC 0
 #endif
 
-#if defined(LINUX) && (defined(SPARC) || defined(ALPHA) || defined(IA64))
+#if defined(LINUX) && \
+    (defined(USE_PROC_FOR_LIBRARIES) || defined(IA64) || !defined(SMALL_CONFIG))
+
+/* 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.                                           */
+ssize_t GC_repeat_read(int fd, char *buf, size_t count)
+{
+    ssize_t num_read = 0;
+    ssize_t result;
+    
+    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;
+}
+
+/*
+ * Apply fn to a buffer containing the contents of /proc/self/maps.
+ * Return the result of fn or, if we failed, 0.
+ * We currently do nothing to /proc/self/maps other than simply read
+ * it.  This code could be simplified if we could determine its size
+ * ahead of time.
+ */
+
+word GC_apply_to_maps(word (*fn)(char *))
+{
+    int f;
+    int result;
+    size_t maps_size = 4000;  /* Initial guess.        */
+    static char init_buf[1];
+    static char *maps_buf = init_buf;
+    static size_t maps_buf_sz = 1;
+
+    /* Read /proc/self/maps, growing maps_buf as necessary.    */
+        /* Note that we may not allocate conventionally, and   */
+        /* thus can't use stdio.                               */
+       do {
+           if (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 = GC_scratch_alloc(maps_buf_sz);
+             if (maps_buf == 0) return 0;
+           }
+           f = open("/proc/self/maps", O_RDONLY);
+           if (-1 == f) return 0;
+           maps_size = 0;
+           do {
+               result = GC_repeat_read(f, maps_buf, maps_buf_sz-1);
+               if (result <= 0) return 0;
+               maps_size += result;
+           } while (result == maps_buf_sz-1);
+           close(f);
+       } while (maps_size >= maps_buf_sz);
+        maps_buf[maps_size] = '\0';
+       
+    /* Apply fn to result. */
+       return fn(maps_buf);
+}
+
+#endif /* Need GC_apply_to_maps */
+
+#if defined(LINUX) && (defined(USE_PROC_FOR_LIBRARIES) || defined(IA64))
+//
+//  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 auguat 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_buf and *maj_dev.  Only *prot_buf may be set for unwritable maps.
+ */
+char *GC_parse_map_entry(char *buf_ptr, word *start, word *end,
+                                char *prot_buf, unsigned int *maj_dev)
+{
+    char *start_start, *end_start, *prot_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;
+    GC_ASSERT(isxdigit(*start_start));
+    *start = strtoul(start_start, &endp, 16); p = endp;
+    GC_ASSERT(*p=='-');
+
+    ++p;
+    end_start = p;
+    GC_ASSERT(isxdigit(*end_start));
+    *end = strtoul(end_start, &endp, 16); p = endp;
+    GC_ASSERT(isspace(*p));
+
+    while (isspace(*p)) ++p;
+    prot_start = p;
+    GC_ASSERT(*prot_start == 'r' || *prot_start == '-');
+    memcpy(prot_buf, prot_start, 4);
+    prot_buf[4] = '\0';
+    if (prot_buf[1] == 'w') {/* we can skip the rest if it's not writable. */
+       /* Skip past protection field to offset field */
+          while (!isspace(*p)) ++p; while (isspace(*p)) ++p;
+          GC_ASSERT(isxdigit(*p));
+       /* Skip past offset field, which we ignore */
+          while (!isspace(*p)) ++p; while (isspace(*p)) ++p;
+       maj_dev_start = p;
+        GC_ASSERT(isxdigit(*maj_dev_start));
+        *maj_dev = strtoul(maj_dev_start, NULL, 16);
+    }
+
+    while (*p && *p++ != '\n');
+
+    return p;
+}
+
+#endif /* Need to parse /proc/self/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.                                               */
-  ptr_t GC_data_start;
 
-  extern char * GC_copyright[];  /* Any data symbol would do. */
+# ifdef LINUX
+    /* 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, prefering __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 GC_data_start;
 
   void GC_init_linux_data_start()
   {
     extern ptr_t GC_find_limit();
 
-    GC_data_start = GC_find_limit((ptr_t)GC_copyright, FALSE);
+#   ifdef LINUX
+      /* Try the easy approaches first:        */
+      if ((ptr_t)__data_start != 0) {
+         GC_data_start = (ptr_t)(__data_start);
+         return;
+      }
+      if ((ptr_t)data_start != 0) {
+         GC_data_start = (ptr_t)(data_start);
+         return;
+      }
+#   endif /* LINUX */
+    GC_data_start = GC_find_limit((ptr_t)(_end), FALSE);
   }
 #endif
 
 # endif /* ECOS_GC_MEMORY_SIZE */
 
 // setjmp() function, as described in ANSI para 7.6.1.1
-#define setjmp( __env__ )  hal_setjmp( __env__ )
+#undef SETJMP
+#define SETJMP( __env__ )  hal_setjmp( __env__ )
 
 // FIXME: This is a simple way of allocating memory which is
 // compatible with ECOS early releases.  Later releases use a more
@@ -192,6 +380,19 @@ static void *tiny_sbrk(ptrdiff_t increment)
 #define sbrk tiny_sbrk
 # endif /* ECOS */
 
+#if (defined(NETBSD) || defined(OPENBSD)) && defined(__ELF__)
+  ptr_t GC_data_start;
+
+  void GC_init_netbsd_elf()
+  {
+    extern ptr_t GC_find_limit();
+    extern char **environ;
+       /* This may need to be environ, without the underscore, for     */
+       /* some versions.                                               */
+    GC_data_start = GC_find_limit((ptr_t)&environ, FALSE);
+  }
+#endif
+
 # ifdef OS2
 
 # include <stddef.h>
@@ -296,10 +497,11 @@ void GC_enable_signals(void)
 # else
 
 #  if !defined(PCR) && !defined(AMIGA) && !defined(MSWIN32) \
+      && !defined(MSWINCE) \
       && !defined(MACOS) && !defined(DJGPP) && !defined(DOS4GW) \
-      && !defined(NO_SIGSET)
+      && !defined(NOSYS) && !defined(ECOS)
 
-#   if defined(sigmask) && !defined(UTS4)
+#   if defined(sigmask) && !defined(UTS4) && !defined(HURD)
        /* Use the traditional BSD interface */
 #      define SIGSET_T int
 #      define SIG_DEL(set, signal) (set) &= ~(sigmask(signal))
@@ -372,7 +574,7 @@ void GC_enable_signals()
 # endif /*!OS/2 */
 
 /* Ivan Demakov: simplest way (to me) */
-#if defined (DOS4GW) || defined (NO_SIGSET)
+#if defined (DOS4GW)
   void GC_disable_signals() { }
   void GC_enable_signals() { }
 #endif
@@ -380,13 +582,11 @@ void GC_enable_signals()
 /* Find the page size */
 word GC_page_size;
 
-# ifdef MSWIN32
+# if defined(MSWIN32) || defined(MSWINCE)
   void GC_setpagesize()
   {
-    SYSTEM_INFO sysinfo;
-    
-    GetSystemInfo(&sysinfo);
-    GC_page_size = sysinfo.dwPageSize;
+    GetSystemInfo(&GC_sysinfo);
+    GC_page_size = GC_sysinfo.dwPageSize;
   }
 
 # else
@@ -411,7 +611,7 @@ word GC_page_size;
  * With threads, GC_mark_roots needs to know how to do this.
  * Called with allocator lock held.
  */
-# ifdef MSWIN32 
+# if defined(MSWIN32) || defined(MSWINCE)
 # define is_writable(prot) ((prot) == PAGE_READWRITE \
                            || (prot) == PAGE_WRITECOPY \
                            || (prot) == PAGE_EXECUTE_READWRITE \
@@ -448,7 +648,17 @@ ptr_t GC_get_stack_base()
 }
 
 
-# else
+# endif /* MS Windows */
+
+# ifdef BEOS
+# include <kernel/OS.h>
+ptr_t GC_get_stack_base(){
+       thread_info th;
+       get_thread_info(find_thread(NULL),&th);
+       return th.stack_end;
+}
+# endif /* BEOS */
+
 
 # ifdef OS2
 
@@ -464,67 +674,15 @@ ptr_t GC_get_stack_base()
     return((ptr_t)(ptib -> tib_pstacklimit));
 }
 
-# else
+# endif /* OS2 */
 
 # ifdef AMIGA
+#   define GC_AMIGA_SB
+#   include "AmigaOS.c"
+#   undef GC_AMIGA_SB
+# endif /* AMIGA */
 
-ptr_t GC_get_stack_base()
-{
-    struct Process *proc = (struct Process*)SysBase->ThisTask;
-    /* Reference: Amiga Guru Book Pages: 42,567,574 */
-    if (proc->pr_Task.tc_Node.ln_Type==NT_PROCESS
-        && proc->pr_CLI != NULL) {
-       /* first ULONG is StackSize */
-       /*longPtr = proc->pr_ReturnAddr;
-       size = longPtr[0];*/
-
-       return (char *)proc->pr_ReturnAddr + sizeof(ULONG);
-    } else {
-       return (char *)proc->pr_Task.tc_SPUpper;
-    }
-}
-
-#if 0 /* old version */
-ptr_t GC_get_stack_base()
-{
-    extern struct WBStartup *_WBenchMsg;
-    extern long __base;
-    extern long __stack;
-    struct Task *task;
-    struct Process *proc;
-    struct CommandLineInterface *cli;
-    long size;
-
-    if ((task = FindTask(0)) == 0) {
-       GC_err_puts("Cannot find own task structure\n");
-       ABORT("task missing");
-    }
-    proc = (struct Process *)task;
-    cli = BADDR(proc->pr_CLI);
-
-    if (_WBenchMsg != 0 || cli == 0) {
-       size = (char *)task->tc_SPUpper - (char *)task->tc_SPLower;
-    } else {
-       size = cli->cli_DefaultStack * 4;
-    }
-    return (ptr_t)(__base + GC_max(size, __stack));
-}
-#endif /* 0 */
-
-# else /* !AMIGA, !OS2, ... */
-
-# ifdef NEED_FIND_LIMIT
-  /* Some tools to implement HEURISTIC2        */
-#   define MIN_PAGE_SIZE 256   /* Smallest conceivable page size, bytes */
-    /* static */ jmp_buf GC_jmp_buf;
-    
-    /*ARGSUSED*/
-    void GC_fault_handler(sig)
-    int sig;
-    {
-        longjmp(GC_jmp_buf, 1);
-    }
+# if defined(NEED_FIND_LIMIT) || defined(UNIX_LIKE)
 
 #   ifdef __STDC__
        typedef void (*handler)(int);
@@ -532,61 +690,87 @@ ptr_t GC_get_stack_base()
        typedef void (*handler)();
 #   endif
 
-#   if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1)
+#   if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1) \
+    || defined(HURD) || defined(NETBSD)
        static struct sigaction old_segv_act;
-#      if defined(_sigargs) || defined(HPUX) /* !Irix6.x */
+#      if defined(IRIX5) || defined(HPUX) \
+       || defined(HURD) || defined(NETBSD)
            static struct sigaction old_bus_act;
 #      endif
 #   else
         static handler old_segv_handler, old_bus_handler;
 #   endif
     
-    void GC_setup_temporary_fault_handler()
+#   ifdef __STDC__
+      void GC_set_and_save_fault_handler(handler h)
+#   else
+      void GC_set_and_save_fault_handler(h)
+      handler h;
+#   endif
     {
-# ifndef ECOS
-#      if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1)
+#      if defined(SUNOS5SIGS) || defined(IRIX5)  \
+        || defined(OSF1) || defined(HURD) || defined(NETBSD)
          struct sigaction      act;
 
-         act.sa_handler        = GC_fault_handler;
-          act.sa_flags          = SA_RESTART | SA_NODEFER;
-          /* The presence of SA_NODEFER represents yet another gross    */
-          /* hack.  Under Solaris 2.3, siglongjmp doesn't appear to     */
-          /* interact correctly with -lthread.  We hide the confusion   */
-          /* by making sure that signal handling doesn't affect the     */
-          /* signal mask.                                               */
+         act.sa_handler        = h;
+#        if 0 /* 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 IRIX_THREADS
+#        ifdef 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);
+               (void) sigaction(SIGBUS, 0, &old_bus_act);
+               (void) sigaction(SIGBUS, &act, 0);
 #        else
                (void) sigaction(SIGSEGV, &act, &old_segv_act);
-#              if defined(IRIX5) && defined(_sigargs) /* Irix 5.x, not 6.x */ \
-                  || defined(HPUX)
+#              if defined(IRIX5) \
+                  || defined(HPUX) || defined(HURD) || defined(NETBSD)
                    /* 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 /* IRIX_THREADS */
+#        endif /* GC_IRIX_THREADS */
 #      else
-         old_segv_handler = signal(SIGSEGV, GC_fault_handler);
+         old_segv_handler = signal(SIGSEGV, h);
 #        ifdef SIGBUS
-           old_bus_handler = signal(SIGBUS, GC_fault_handler);
+           old_bus_handler = signal(SIGBUS, h);
 #        endif
 #      endif
-# endif /* ECOS */
+    }
+# endif /* NEED_FIND_LIMIT || UNIX_LIKE */
+
+# ifdef NEED_FIND_LIMIT
+  /* Some tools to implement HEURISTIC2        */
+#   define MIN_PAGE_SIZE 256   /* Smallest conceivable page size, bytes */
+    /* static */ JMP_BUF GC_jmp_buf;
+    
+    /*ARGSUSED*/
+    void GC_fault_handler(sig)
+    int sig;
+    {
+        LONGJMP(GC_jmp_buf, 1);
+    }
+
+    void GC_setup_temporary_fault_handler()
+    {
+       GC_set_and_save_fault_handler(GC_fault_handler);
     }
     
     void GC_reset_fault_handler()
     {
-# ifndef ECOS
-#       if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1)
+#       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)
+#        if defined(IRIX5) \
+            || defined(HPUX) || defined(HURD) || defined(NETBSD)
              (void) sigaction(SIGBUS, &old_bus_act, 0);
 #        endif
 #       else
@@ -595,16 +779,15 @@ ptr_t GC_get_stack_base()
            (void) signal(SIGBUS, old_bus_handler);
 #        endif
 #       endif
-# endif /* ECOS */
     }
 
     /* Return the first nonaddressible location > p (up) or    */
-    /* the smallest location q s.t. [q,p] is addressible (!up).        */
+    /* the smallest location q s.t. [q,p) is addressable (!up).        */
+    /* We assume that p (up) or p-1 (!up) is addressable.      */
     ptr_t GC_find_limit(p, up)
     ptr_t p;
     GC_bool up;
     {
-# ifndef ECOS
         static VOLATILE ptr_t result;
                /* Needs to be static, since otherwise it may not be    */
                /* preserved across the longjmp.  Can safely be         */
@@ -613,7 +796,7 @@ ptr_t GC_get_stack_base()
 
 
        GC_setup_temporary_fault_handler();
-       if (setjmp(GC_jmp_buf) == 0) {
+       if (SETJMP(GC_jmp_buf) == 0) {
            result = (ptr_t)(((word)(p))
                              & ~(MIN_PAGE_SIZE-1));
            for (;;) {
@@ -630,60 +813,212 @@ ptr_t GC_get_stack_base()
            result += MIN_PAGE_SIZE;
        }
        return(result);
-# else /* ECOS */
-       abort();
-# endif /* ECOS */
     }
 # endif
 
-# ifndef ECOS
+#if defined(ECOS) || defined(NOSYS)
+  ptr_t GC_get_stack_base()
+  {
+    return STACKBOTTOM;
+  }
+#endif
+
+#ifdef HPUX_STACKBOTTOM
+
+#include <sys/param.h>
+#include <sys/pstat.h>
+
+  ptr_t GC_get_register_stack_base(void)
+  {
+    struct pst_vm_status vm_status;
+
+    int i = 0;
+    while (pstat_getprocvm(&vm_status, sizeof(vm_status), 0, i++) == 1) {
+      if (vm_status.pst_type == PS_RSESTACK) {
+        return (ptr_t) vm_status.pst_vaddr;
+      }
+    }
+
+    /* old way to get the register stackbottom */
+    return (ptr_t)(((word)GC_stackbottom - BACKING_STORE_DISPLACEMENT - 1)
+                   & ~(BACKING_STORE_ALIGNMENT - 1));
+  }
+
+#endif /* HPUX_STACK_BOTTOM */
 
 #ifdef LINUX_STACKBOTTOM
 
+#include <sys/types.h>
+#include <sys/stat.h>
+
 # define STAT_SKIP 27   /* Number of fields preceding startstack       */
-                       /* field in /proc/<pid>/stat                    */
+                       /* field in /proc/self/stat                     */
+
+#ifdef USE_LIBC_PRIVATES
+# pragma weak __libc_stack_end
+  extern ptr_t __libc_stack_end;
+#endif
+
+# ifdef IA64
+    /* Try to read the backing store base from /proc/self/maps.        */
+    /* We look for the writable mapping with a 0 major device,  */
+    /* which is        as close to our frame as possible, but below it.*/
+    static word backing_store_base_from_maps(char *maps)
+    {
+      char prot_buf[5];
+      char *buf_ptr = maps;
+      word start, end;
+      unsigned int maj_dev;
+      word current_best = 0;
+      word dummy;
+  
+      for (;;) {
+        buf_ptr = GC_parse_map_entry(buf_ptr, &start, &end, prot_buf, &maj_dev);
+       if (buf_ptr == NULL) return current_best;
+       if (prot_buf[1] == 'w' && maj_dev == 0) {
+           if (end < (word)(&dummy) && start > current_best) current_best = start;
+       }
+      }
+      return current_best;
+    }
+
+    static word backing_store_base_from_proc(void)
+    {
+        return GC_apply_to_maps(backing_store_base_from_maps);
+    }
+
+#   ifdef USE_LIBC_PRIVATES
+#     pragma weak __libc_ia64_register_backing_store_base
+      extern ptr_t __libc_ia64_register_backing_store_base;
+#   endif
+
+    ptr_t GC_get_register_stack_base(void)
+    {
+#     ifdef USE_LIBC_PRIVATES
+        if (0 != &__libc_ia64_register_backing_store_base
+           && 0 != __libc_ia64_register_backing_store_base) {
+         /* Glibc 2.2.4 has a bug such that for dynamically linked     */
+         /* executables __libc_ia64_register_backing_store_base is     */
+         /* defined but uninitialized during constructor calls.        */
+         /* Hence we check for both nonzero address and value.         */
+         return __libc_ia64_register_backing_store_base;
+        }
+#     endif
+      word result = backing_store_base_from_proc();
+      if (0 == result) {
+         /* Use dumb heuristics.  Works only for default configuration. */
+         result = (word)GC_stackbottom - BACKING_STORE_DISPLACEMENT;
+         result += BACKING_STORE_ALIGNMENT - 1;
+         result &= ~(BACKING_STORE_ALIGNMENT - 1);
+         /* Verify that it's at least readable.  If not, we goofed. */
+         GC_noop1(*(word *)result); 
+      }
+      return (ptr_t)result;
+    }
+# endif
 
   ptr_t GC_linux_stack_base(void)
   {
-    char buf[50];
-    FILE *f;
+    /* We read the stack base value from /proc/self/stat.  We do this  */
+    /* using direct I/O system calls in order to avoid calling malloc   */
+    /* in case REDIRECT_MALLOC is defined.                             */ 
+#   define STAT_BUF_SIZE 4096
+#   define STAT_READ read
+         /* Should probably call the real read, if read is wrapped.    */
+    char stat_buf[STAT_BUF_SIZE];
+    int f;
     char c;
     word result = 0;
-    int i;
-
-    sprintf(buf, "/proc/%d/stat", getpid());
-    f = fopen(buf, "r");
-    if (NULL == f) ABORT("Couldn't open /proc/<pid>/stat");
-    c = getc(f);
+    size_t i, buf_offset = 0;
+
+    /* First try the easy way.  This should work for glibc 2.2 */
+    /* This fails in a prelinked ("prelink" command) executable */
+    /* since the correct value of __libc_stack_end never       */
+    /* becomes visible to us.  The second test works around    */
+    /* this.                                                   */  
+#   ifdef USE_LIBC_PRIVATES
+      if (0 != &__libc_stack_end && 0 != __libc_stack_end ) {
+#       ifdef IA64
+         /* Some versions of glibc set the address 16 bytes too        */
+         /* low while the initialization code is running.              */
+         if (((word)__libc_stack_end & 0xfff) + 0x10 < 0x1000) {
+           return __libc_stack_end + 0x10;
+         } /* Otherwise it's not safe to add 16 bytes and we fall      */
+           /* back to using /proc.                                     */
+#      else 
+#      ifdef SPARC
+         /* Older versions of glibc for 64-bit Sparc do not set
+          * this variable correctly, it gets set to either zero
+          * or one.
+          */
+         if (__libc_stack_end != (ptr_t) (unsigned long)0x1)
+           return __libc_stack_end;
+#      else
+         return __libc_stack_end;
+#      endif
+#      endif
+      }
+#   endif
+    f = open("/proc/self/stat", O_RDONLY);
+    if (f < 0 || STAT_READ(f, stat_buf, STAT_BUF_SIZE) < 2 * STAT_SKIP) {
+       ABORT("Couldn't read /proc/self/stat");
+    }
+    c = stat_buf[buf_offset++];
     /* Skip the required number of fields.  This number is hopefully   */
     /* constant across all Linux implementations.                      */
       for (i = 0; i < STAT_SKIP; ++i) {
-       while (isspace(c)) c = getc(f);
-       while (!isspace(c)) c = getc(f);
+       while (isspace(c)) c = stat_buf[buf_offset++];
+       while (!isspace(c)) c = stat_buf[buf_offset++];
       }
-    while (isspace(c)) c = getc(f);
+    while (isspace(c)) c = stat_buf[buf_offset++];
     while (isdigit(c)) {
       result *= 10;
       result += c - '0';
-      c = getc(f);
+      c = stat_buf[buf_offset++];
     }
+    close(f);
     if (result < 0x10000000) ABORT("Absurd stack bottom value");
     return (ptr_t)result;
   }
 
 #endif /* LINUX_STACKBOTTOM */
 
+#ifdef FREEBSD_STACKBOTTOM
+
+/* This uses an undocumented sysctl call, but at least one expert      */
+/* believes it will stay.                                              */
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+  ptr_t GC_freebsd_stack_base(void)
+  {
+    int nm[2] = {CTL_KERN, KERN_USRSTACK};
+    ptr_t base;
+    size_t len = sizeof(ptr_t);
+    int r = sysctl(nm, 2, &base, &len, NULL, 0);
+    
+    if (r) ABORT("Error getting stack base");
+
+    return base;
+  }
+
+#endif /* FREEBSD_STACKBOTTOM */
+
+#if !defined(BEOS) && !defined(AMIGA) && !defined(MSWIN32) \
+    && !defined(MSWINCE) && !defined(OS2) && !defined(NOSYS) && !defined(ECOS)
+
 ptr_t GC_get_stack_base()
 {
+#   if defined(HEURISTIC1) || defined(HEURISTIC2) || \
+       defined(LINUX_STACKBOTTOM) || defined(FREEBSD_STACKBOTTOM)
     word dummy;
     ptr_t result;
+#   endif
 
 #   define STACKBOTTOM_ALIGNMENT_M1 ((word)STACK_GRAN - 1)
 
-#  if defined(STACKBASE)
-    extern ptr_t STACKBASE;
-    return(STACKBASE);
-#   else
 #   ifdef STACKBOTTOM
        return(STACKBOTTOM);
 #   else
@@ -700,6 +1035,9 @@ ptr_t GC_get_stack_base()
 #      ifdef LINUX_STACKBOTTOM
           result = GC_linux_stack_base();
 #      endif
+#      ifdef FREEBSD_STACKBOTTOM
+          result = GC_freebsd_stack_base();
+#      endif
 #      ifdef HEURISTIC2
 #          ifdef STACK_GROWS_DOWN
                result = GC_find_limit((ptr_t)(&dummy), TRUE);
@@ -725,13 +1063,9 @@ ptr_t GC_get_stack_base()
 #      endif
        return(result);
 #   endif /* STACKBOTTOM */
-#   endif /* STACKBASE */
 }
-# endif /* ECOS */
 
-# endif /* ! AMIGA */
-# endif /* ! OS2 */
-# endif /* ! MSWIN32 */
+# endif /* ! AMIGA, !OS 2, ! MS Windows, !BEOS, !NOSYS, !ECOS */
 
 /*
  * Register static data segment(s) as roots.
@@ -831,45 +1165,45 @@ void GC_register_data_segments()
     }
 }
 
-# else
+# else /* !OS2 */
+
+# if defined(MSWIN32) || defined(MSWINCE)
 
 # ifdef MSWIN32
   /* Unfortunately, we have to handle win32s very differently from NT,         */
   /* Since VirtualQuery has very different semantics.  In particular,  */
   /* under win32s a VirtualQuery call on an unmapped page returns an   */
-  /* invalid result.  Under GC_register_data_segments is a noop and    */
+  /* invalid result.  Under NT, GC_register_data_segments is a noop and        */
   /* all real work is done by GC_register_dynamic_libraries.  Under    */
   /* win32s, we cannot find the data segments associated with dll's.   */
-  /* We rgister the main data segment here.                            */
-  GC_bool GC_win32s = FALSE;   /* We're running under win32s.  */
-  
-  GC_bool GC_is_win32s()
-  {
-      DWORD v = GetVersion();
-      
-      /* Check that this is not NT, and Windows major version <= 3     */
-      return ((v & 0x80000000) && (v & 0xff) <= 3);
-  }
+  /* We register the main data segment here.                           */
+  GC_bool GC_no_win32_dlls = FALSE;     
+       /* This used to be set for gcc, to avoid dealing with           */
+       /* the structured exception handling issues.  But we now have   */
+       /* assembly code to do that right.                              */
+  GC_bool GC_wnt = FALSE;
+        /* This is a Windows NT derivative, i.e. NT, W2K, XP or later.  */
   
   void GC_init_win32()
   {
-      GC_win32s = GC_is_win32s();
+    /* if we're running under win32s, assume that no DLLs will be loaded */
+    DWORD v = GetVersion();
+    GC_wnt = !(v & 0x80000000);
+    GC_no_win32_dlls |= ((!GC_wnt) && (v & 0xff) <= 3);
   }
-  
+
   /* Return the smallest address a such that VirtualQuery              */
   /* returns correct results for all addresses between a and start.    */
   /* Assumes VirtualQuery returns correct information for start.       */
   ptr_t GC_least_described_address(ptr_t start)
   {  
     MEMORY_BASIC_INFORMATION buf;
-    SYSTEM_INFO sysinfo;
     DWORD result;
     LPVOID limit;
     ptr_t p;
     LPVOID q;
     
-    GetSystemInfo(&sysinfo);
-    limit = sysinfo.lpMinimumApplicationAddress;
+    limit = GC_sysinfo.lpMinimumApplicationAddress;
     p = (ptr_t)((word)start & ~(GC_page_size - 1));
     for (;;) {
        q = (LPVOID)(p - GC_page_size);
@@ -880,48 +1214,118 @@ void GC_register_data_segments()
     }
     return(p);
   }
+# endif
+
+# ifndef REDIRECT_MALLOC
+  /* We maintain a linked list of AllocationBase values that we know   */
+  /* correspond to malloc heap sections.  Currently this is only called */
+  /* during a GC.  But there is some hope that for long running                */
+  /* programs we will eventually see most heap sections.               */
+
+  /* In the long run, it would be more reliable to occasionally walk   */
+  /* the malloc heap with HeapWalk on the default heap.  But that      */
+  /* apparently works only for NT-based Windows.                       */ 
+
+  /* In the long run, a better data structure would also be nice ...   */
+  struct GC_malloc_heap_list {
+    void * allocation_base;
+    struct GC_malloc_heap_list *next;
+  } *GC_malloc_heap_l = 0;
+
+  /* Is p the base of one of the malloc heap sections we already know  */
+  /* about?                                                            */
+  GC_bool GC_is_malloc_heap_base(ptr_t p)
+  {
+    struct GC_malloc_heap_list *q = GC_malloc_heap_l;
+
+    while (0 != q) {
+      if (q -> allocation_base == p) return TRUE;
+      q = q -> next;
+    }
+    return FALSE;
+  }
+
+  void *GC_get_allocation_base(void *p)
+  {
+    MEMORY_BASIC_INFORMATION buf;
+    DWORD result = VirtualQuery(p, &buf, sizeof(buf));
+    if (result != sizeof(buf)) {
+      ABORT("Weird VirtualQuery result");
+    }
+    return buf.AllocationBase;
+  }
+
+  size_t GC_max_root_size = 100000;    /* Appr. largest root size.     */
+
+  void GC_add_current_malloc_heap()
+  {
+    struct GC_malloc_heap_list *new_l =
+                 malloc(sizeof(struct GC_malloc_heap_list));
+    void * candidate = GC_get_allocation_base(new_l);
+
+    if (new_l == 0) return;
+    if (GC_is_malloc_heap_base(candidate)) {
+      /* Try a little harder to find malloc heap.                      */
+       size_t req_size = 10000;
+       do {
+         void *p = malloc(req_size);
+         if (0 == p) { free(new_l); return; }
+         candidate = GC_get_allocation_base(p);
+         free(p);
+         req_size *= 2;
+       } while (GC_is_malloc_heap_base(candidate)
+                && req_size < GC_max_root_size/10 && req_size < 500000);
+       if (GC_is_malloc_heap_base(candidate)) {
+         free(new_l); return;
+       }
+    }
+#   ifdef CONDPRINT
+      if (GC_print_stats)
+         GC_printf1("Found new system malloc AllocationBase at 0x%lx\n",
+                     candidate);
+#   endif
+    new_l -> allocation_base = candidate;
+    new_l -> next = GC_malloc_heap_l;
+    GC_malloc_heap_l = new_l;
+  }
+# endif /* REDIRECT_MALLOC */
   
   /* Is p the start of either the malloc heap, or of one of our */
   /* heap sections?                                            */
   GC_bool GC_is_heap_base (ptr_t p)
   {
      
-     register unsigned i;
+     unsigned i;
      
 #    ifndef REDIRECT_MALLOC
-       static ptr_t malloc_heap_pointer = 0;
+       static word last_gc_no = -1;
      
-       if (0 == malloc_heap_pointer) {
-         MEMORY_BASIC_INFORMATION buf;
-         register DWORD result = VirtualQuery(malloc(1), &buf, sizeof(buf));
-         
-         if (result != sizeof(buf)) {
-             ABORT("Weird VirtualQuery result");
-         }
-         malloc_heap_pointer = (ptr_t)(buf.AllocationBase);
+       if (last_gc_no != GC_gc_no) {
+        GC_add_current_malloc_heap();
+        last_gc_no = GC_gc_no;
        }
-       if (p == malloc_heap_pointer) return(TRUE);
+       if (GC_root_size > GC_max_root_size) GC_max_root_size = GC_root_size;
+       if (GC_is_malloc_heap_base(p)) return TRUE;
 #    endif
      for (i = 0; i < GC_n_heap_bases; i++) {
-         if (GC_heap_bases[i] == p) return(TRUE);
+         if (GC_heap_bases[i] == p) return TRUE;
      }
-     return(FALSE);
+     return FALSE ;
   }
-  
+
+# ifdef MSWIN32
   void GC_register_root_section(ptr_t static_root)
   {
       MEMORY_BASIC_INFORMATION buf;
-      SYSTEM_INFO sysinfo;
       DWORD result;
       DWORD protect;
       LPVOID p;
       char * base;
       char * limit, * new_limit;
     
-      if (!GC_win32s) return;
+      if (!GC_no_win32_dlls) return;
       p = base = limit = GC_least_described_address(static_root);
-      GetSystemInfo(&sysinfo);
-      while (p < sysinfo.lpMaximumApplicationAddress) {
+      while (p < GC_sysinfo.lpMaximumApplicationAddress) {
         result = VirtualQuery(p, &buf, sizeof(buf));
         if (result != sizeof(buf) || buf.AllocationBase == 0
             || GC_is_heap_base(buf.AllocationBase)) break;
@@ -942,130 +1346,21 @@ void GC_register_data_segments()
       }
       if (base != limit) GC_add_roots_inner(base, limit, FALSE);
   }
+#endif
   
   void GC_register_data_segments()
   {
+#     ifdef MSWIN32
       static char dummy;
-      
       GC_register_root_section((ptr_t)(&dummy));
-  }
-# else
-# ifdef AMIGA
-
-   void GC_register_data_segments()
-   {
-     struct Process    *proc;
-     struct CommandLineInterface *cli;
-     BPTR myseglist;
-     ULONG *data;
-     int       num;
-
-
-#    ifdef __GNUC__
-        ULONG dataSegSize;
-        GC_bool found_segment = FALSE;
-       extern char __data_size[];
-
-       dataSegSize=__data_size+8;
-       /* Can`t find the Location of __data_size, because
-           it`s possible that is it, inside the segment. */
-
 #     endif
-
-       proc= (struct Process*)SysBase->ThisTask;
-
-       /* Reference: Amiga Guru Book Pages: 538ff,565,573
-                    and XOper.asm */
-       if (proc->pr_Task.tc_Node.ln_Type==NT_PROCESS) {
-         if (proc->pr_CLI == NULL) {
-           myseglist = proc->pr_SegList;
-         } else {
-           /* ProcLoaded       'Loaded as a command: '*/
-           cli = BADDR(proc->pr_CLI);
-           myseglist = cli->cli_Module;
-         }
-       } else {
-         ABORT("Not a Process.");
-       }
-
-       if (myseglist == NULL) {
-           ABORT("Arrrgh.. can't find segments, aborting");
-       }
-
-       /* xoper hunks Shell Process */
-
-       num=0;
-        for (data = (ULONG *)BADDR(myseglist); data != NULL;
-             data = (ULONG *)BADDR(data[0])) {
-         if (((ULONG) GC_register_data_segments < (ULONG) &data[1]) ||
-             ((ULONG) GC_register_data_segments > (ULONG) &data[1] + data[-1])) {
-#             ifdef __GNUC__
-               if (dataSegSize == data[-1]) {
-                 found_segment = TRUE;
-               }
-#            endif
-             GC_add_roots_inner((char *)&data[1],
-                                ((char *)&data[1]) + data[-1], FALSE);
-          }
-          ++num;
-        } /* for */
-#      ifdef __GNUC__
-          if (!found_segment) {
-            ABORT("Can`t find correct Segments.\nSolution: Use an newer version of ixemul.library");
-          }
-#      endif
-  }
-
-#if 0 /* old version */
-  void GC_register_data_segments()
-  {
-    extern struct WBStartup *_WBenchMsg;
-    struct Process     *proc;
-    struct CommandLineInterface *cli;
-    BPTR myseglist;
-    ULONG *data;
-
-    if ( _WBenchMsg != 0 ) {
-       if ((myseglist = _WBenchMsg->sm_Segment) == 0) {
-           GC_err_puts("No seglist from workbench\n");
-           return;
-       }
-    } else {
-       if ((proc = (struct Process *)FindTask(0)) == 0) {
-           GC_err_puts("Cannot find process structure\n");
-           return;
-       }
-       if ((cli = BADDR(proc->pr_CLI)) == 0) {
-           GC_err_puts("No CLI\n");
-           return;
-       }
-       if ((myseglist = cli->cli_Module) == 0) {
-           GC_err_puts("No seglist from CLI\n");
-           return;
-       }
-    }
-
-    for (data = (ULONG *)BADDR(myseglist); data != 0;
-         data = (ULONG *)BADDR(data[0])) {
-#        ifdef AMIGA_SKIP_SEG
-           if (((ULONG) GC_register_data_segments < (ULONG) &data[1]) ||
-           ((ULONG) GC_register_data_segments > (ULONG) &data[1] + data[-1])) {
-#       else
-          {
-#       endif /* AMIGA_SKIP_SEG */
-          GC_add_roots_inner((char *)&data[1],
-                            ((char *)&data[1]) + data[-1], FALSE);
-         }
-    }
   }
-#endif /* old version */
 
+# else /* !OS2 && !Windows */
 
-# else
-
-# if (defined(SVR4) || defined(AUX) || defined(DGUX)) && !defined(PCR)
-char * GC_SysVGetDataStart(max_page_size, etext_addr)
+# if (defined(SVR4) || defined(AUX) || defined(DGUX) \
+      || (defined(LINUX) && defined(SPARC))) && !defined(PCR)
+ptr_t GC_SysVGetDataStart(max_page_size, etext_addr)
 int max_page_size;
 int * etext_addr;
 {
@@ -1080,7 +1375,7 @@ int * etext_addr;
     /* max_page_size to &etext if &etext is at a page boundary */
     
     GC_setup_temporary_fault_handler();
-    if (setjmp(GC_jmp_buf) == 0) {
+    if (SETJMP(GC_jmp_buf) == 0) {
        /* Try writing to the address.  */
        *result = *result;
         GC_reset_fault_handler();
@@ -1091,21 +1386,61 @@ int * etext_addr;
        /* string constants in the text segment, but after etext.       */
        /* Use plan B.  Note that we now know there is a gap between    */
        /* text and data segments, so plan A bought us something.       */
-       result = (char *)GC_find_limit((ptr_t)(DATAEND) - MIN_PAGE_SIZE, FALSE);
+       result = (char *)GC_find_limit((ptr_t)(DATAEND), FALSE);
     }
-    return((char *)result);
+    return((ptr_t)result);
 }
 # endif
 
-
-void GC_register_data_segments()
+# if defined(FREEBSD) && (defined(I386) || defined(X86_64) || defined(powerpc) || defined(__powerpc__)) && !defined(PCR)
+/* Its unclear whether this should be identical to the above, or       */
+/* whether it should apply to non-X86 architectures.                   */
+/* For now we don't assume that there is always an empty page after    */
+/* etext.  But in some cases there actually seems to be slightly more.  */
+/* This also deals with holes between read-only data and writable data.        */
+ptr_t GC_FreeBSDGetDataStart(max_page_size, etext_addr)
+int max_page_size;
+int * etext_addr;
 {
-#   if !defined(PCR) && !defined(SRC_M3) && !defined(NEXT) && !defined(MACOS) \
-       && !defined(MACOSX)
-#     if defined(REDIRECT_MALLOC) && defined(SOLARIS_THREADS)
-       /* As of Solaris 2.3, the Solaris threads implementation        */
-       /* allocates the data structure for the initial thread with     */
-       /* sbrk at process startup.  It needs to be scanned, so that    */
+    word text_end = ((word)(etext_addr) + sizeof(word) - 1)
+                    & ~(sizeof(word) - 1);
+       /* etext rounded to word boundary       */
+    VOLATILE word next_page = (text_end + (word)max_page_size - 1)
+                             & ~((word)max_page_size - 1);
+    VOLATILE ptr_t result = (ptr_t)text_end;
+    GC_setup_temporary_fault_handler();
+    if (SETJMP(GC_jmp_buf) == 0) {
+       /* Try reading at the address.                          */
+       /* This should happen before there is another thread.   */
+       for (; next_page < (word)(DATAEND); next_page += (word)max_page_size)
+           *(VOLATILE char *)next_page;
+       GC_reset_fault_handler();
+    } else {
+       GC_reset_fault_handler();
+       /* As above, we go to plan B    */
+       result = GC_find_limit((ptr_t)(DATAEND), FALSE);
+    }
+    return(result);
+}
+
+# endif
+
+
+#ifdef AMIGA
+
+#  define GC_AMIGA_DS
+#  include "AmigaOS.c"
+#  undef GC_AMIGA_DS
+
+#else /* !OS2 && !Windows && !AMIGA */
+
+void GC_register_data_segments()
+{
+#   if !defined(PCR) && !defined(SRC_M3) && !defined(MACOS)
+#     if defined(REDIRECT_MALLOC) && defined(GC_SOLARIS_THREADS)
+       /* As of Solaris 2.3, the Solaris threads implementation        */
+       /* allocates the data structure for the initial thread with     */
+       /* sbrk at process startup.  It needs to be scanned, so that    */
        /* we don't lose some malloc allocated data structures          */
        /* hanging from it.  We're on thin ice here ...                 */
         extern caddr_t sbrk();
@@ -1113,11 +1448,11 @@ void GC_register_data_segments()
        GC_add_roots_inner(DATASTART, (char *)sbrk(0), FALSE);
 #     else
        GC_add_roots_inner(DATASTART, (char *)(DATAEND), FALSE);
+#       if defined(DATASTART2)
+         GC_add_roots_inner(DATASTART2, (char *)(DATAEND2), FALSE);
+#       endif
 #     endif
 #   endif
-#   if !defined(PCR) && (defined(NEXT) || defined(MACOSX))
-      GC_add_roots_inner(DATASTART, (char *) get_end(), FALSE);
-#   endif
 #   if defined(MACOS)
     {
 #   if defined(THINK_C)
@@ -1157,15 +1492,16 @@ void GC_register_data_segments()
 }
 
 # endif  /* ! AMIGA */
-# endif  /* ! MSWIN32 */
+# endif  /* ! MSWIN32 && ! MSWINCE*/
 # endif  /* ! OS2 */
 
 /*
  * Auxiliary routines for obtaining memory from OS.
  */
+
 # if !defined(OS2) && !defined(PCR) && !defined(AMIGA) \
-       && !defined(MSWIN32) && !defined(MACOS) && !defined(DOS4GW)
+       && !defined(MSWIN32) && !defined(MSWINCE) \
+       && !defined(MACOS) && !defined(DOS4GW)
 
 # ifdef SUNOS4
     extern caddr_t sbrk();
@@ -1176,7 +1512,8 @@ void GC_register_data_segments()
 #   define SBRK_ARG_T int
 # endif
 
-# ifdef RS6000
+
+# if 0 && defined(RS6000)  /* We now use mmap */
 /* The compiler seems to generate speculative reads one past the end of        */
 /* an allocated object.  Hence we need to make sure that the page      */
 /* following the last heap page is also mapped.                                */
@@ -1207,8 +1544,7 @@ word bytes;
 
 #else  /* Not RS6000 */
 
-#if defined(USE_MMAP)
-/* Tested only under IRIX5 and Solaris 2 */
+#if defined(USE_MMAP) || defined(USE_MUNMAP)
 
 #ifdef USE_MMAP_FIXED
 #   define GC_MMAP_FLAGS MAP_FIXED | MAP_PRIVATE
@@ -1218,24 +1554,61 @@ word bytes;
 #   define GC_MMAP_FLAGS MAP_PRIVATE
 #endif
 
+#ifdef USE_MMAP_ANON
+# define zero_fd -1
+# if defined(MAP_ANONYMOUS)
+#   define OPT_MAP_ANON MAP_ANONYMOUS
+# else
+#   define OPT_MAP_ANON MAP_ANON
+# endif
+#else
+  static int zero_fd;
+# define OPT_MAP_ANON 0
+#endif 
+
+#endif /* defined(USE_MMAP) || defined(USE_MUNMAP) */
+
+#if defined(USE_MMAP)
+/* Tested only under Linux, IRIX5 and Solaris 2 */
+
+#ifndef HEAP_START
+#   define HEAP_START 0
+#endif
+
 ptr_t GC_unix_get_mem(bytes)
 word bytes;
 {
-    static GC_bool initialized = FALSE;
-    static int fd;
     void *result;
     static ptr_t last_addr = HEAP_START;
 
-    if (!initialized) {
-       fd = open("/dev/zero", O_RDONLY);
-       initialized = TRUE;
-    }
+#   ifndef USE_MMAP_ANON
+      static GC_bool initialized = FALSE;
+
+      if (!initialized) {
+         zero_fd = open("/dev/zero", O_RDONLY);
+         fcntl(zero_fd, F_SETFD, FD_CLOEXEC);
+         initialized = TRUE;
+      }
+#   endif
+
     if (bytes & (GC_page_size -1)) ABORT("Bad GET_MEM arg");
     result = mmap(last_addr, bytes, PROT_READ | PROT_WRITE | OPT_PROT_EXEC,
-                 GC_MMAP_FLAGSfd, 0/* offset */);
+                 GC_MMAP_FLAGS | OPT_MAP_ANON, zero_fd, 0/* offset */);
     if (result == MAP_FAILED) return(0);
     last_addr = (ptr_t)result + bytes + GC_page_size - 1;
     last_addr = (ptr_t)((word)last_addr & ~(GC_page_size - 1));
+#   if !defined(LINUX)
+      if (last_addr == 0) {
+        /* Oops.  We got the end of the address space.  This isn't     */
+       /* usable by arbitrary C code, since one-past-end pointers      */
+       /* don't work, so we discard it and try again.                  */
+       munmap(result, (size_t)(-GC_page_size) - (size_t)result);
+                       /* Leave last page mapped, so we can't repeat. */
+       return GC_unix_get_mem(bytes);
+      }
+#   else
+      GC_ASSERT(last_addr != 0);
+#   endif
     return((ptr_t)result);
 }
 
@@ -1289,22 +1662,41 @@ void * os2_alloc(size_t bytes)
 # endif /* OS2 */
 
 
+# if defined(MSWIN32) || defined(MSWINCE)
+SYSTEM_INFO GC_sysinfo;
+# endif
+
 # ifdef MSWIN32
+
+# ifdef USE_GLOBAL_ALLOC
+#   define GLOBAL_ALLOC_TEST 1
+# else
+#   define GLOBAL_ALLOC_TEST GC_no_win32_dlls
+# endif
+
 word GC_n_heap_bases = 0;
 
 ptr_t GC_win32_get_mem(bytes)
 word bytes;
 {
     ptr_t result;
-    
-    if (GC_win32s) {
+
+    if (GLOBAL_ALLOC_TEST) {
        /* VirtualAlloc doesn't like PAGE_EXECUTE_READWRITE.    */
        /* There are also unconfirmed rumors of other           */
        /* problems, so we dodge the issue.                     */
         result = (ptr_t) GlobalAlloc(0, bytes + HBLKSIZE);
         result = (ptr_t)(((word)result + HBLKSIZE) & ~(HBLKSIZE-1));
     } else {
-        result = (ptr_t) VirtualAlloc(NULL, bytes,
+       /* VirtualProtect only works on regions returned by a   */
+       /* single VirtualAlloc call.  Thus we allocate one      */
+       /* extra page, which will prevent merging of blocks     */
+       /* in separate regions, and eliminate any temptation    */
+       /* to call VirtualProtect on a range spanning regions.  */
+       /* This wastes a small amount of memory, and risks      */
+       /* increased fragmentation.  But better alternatives    */
+       /* would require effort.                                */
+        result = (ptr_t) VirtualAlloc(NULL, bytes + 1,
                                      MEM_COMMIT | MEM_RESERVE,
                                      PAGE_EXECUTE_READWRITE);
     }
@@ -1318,27 +1710,92 @@ word bytes;
 
 void GC_win32_free_heap ()
 {
-    if (GC_win32s) {
+    if (GC_no_win32_dlls) {
        while (GC_n_heap_bases > 0) {
            GlobalFree (GC_heap_bases[--GC_n_heap_bases]);
            GC_heap_bases[GC_n_heap_bases] = 0;
        }
     }
 }
+# endif
+
+#ifdef AMIGA
+# define GC_AMIGA_AM
+# include "AmigaOS.c"
+# undef GC_AMIGA_AM
+#endif
 
 
+# ifdef MSWINCE
+word GC_n_heap_bases = 0;
+
+ptr_t GC_wince_get_mem(bytes)
+word bytes;
+{
+    ptr_t result;
+    word i;
+
+    /* Round up allocation size to multiple of page size */
+    bytes = (bytes + GC_page_size-1) & ~(GC_page_size-1);
+
+    /* Try to find reserved, uncommitted pages */
+    for (i = 0; i < GC_n_heap_bases; i++) {
+       if (((word)(-(signed_word)GC_heap_lengths[i])
+            & (GC_sysinfo.dwAllocationGranularity-1))
+           >= bytes) {
+           result = GC_heap_bases[i] + GC_heap_lengths[i];
+           break;
+       }
+    }
+
+    if (i == GC_n_heap_bases) {
+       /* Reserve more pages */
+       word res_bytes = (bytes + GC_sysinfo.dwAllocationGranularity-1)
+                        & ~(GC_sysinfo.dwAllocationGranularity-1);
+       /* If we ever support MPROTECT_VDB here, we will probably need to       */
+       /* ensure that res_bytes is strictly > bytes, so that VirtualProtect    */
+       /* never spans regions.  It seems to be OK for a VirtualFree argument   */
+       /* to span regions, so we should be OK for now.                         */
+       result = (ptr_t) VirtualAlloc(NULL, res_bytes,
+                                     MEM_RESERVE | MEM_TOP_DOWN,
+                                     PAGE_EXECUTE_READWRITE);
+       if (HBLKDISPL(result) != 0) ABORT("Bad VirtualAlloc result");
+           /* If I read the documentation correctly, this can  */
+           /* only happen if HBLKSIZE > 64k or not a power of 2.       */
+       if (GC_n_heap_bases >= MAX_HEAP_SECTS) ABORT("Too many heap sections");
+       GC_heap_bases[GC_n_heap_bases] = result;
+       GC_heap_lengths[GC_n_heap_bases] = 0;
+       GC_n_heap_bases++;
+    }
+
+    /* Commit pages */
+    result = (ptr_t) VirtualAlloc(result, bytes,
+                                 MEM_COMMIT,
+                                 PAGE_EXECUTE_READWRITE);
+    if (result != NULL) {
+       if (HBLKDISPL(result) != 0) ABORT("Bad VirtualAlloc result");
+       GC_heap_lengths[i] += bytes;
+    }
+
+    return(result);                      
+}
 # endif
 
 #ifdef USE_MUNMAP
 
-/* For now, this only works on some Unix-like systems.  If you         */
-/* have something else, don't define USE_MUNMAP.               */
+/* For now, this only works on Win32/WinCE and some Unix-like  */
+/* systems.  If you have something else, don't define          */
+/* USE_MUNMAP.                                                 */
 /* We assume ANSI C to support this feature.                   */
+
+#if !defined(MSWIN32) && !defined(MSWINCE)
+
 #include <unistd.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <fcntl.h>
+
+#endif
 
 /* Compute a page aligned starting address for the unmap       */
 /* operation on a block of size bytes starting at start.       */
@@ -1362,6 +1819,14 @@ ptr_t GC_unmap_end(ptr_t start, word bytes)
     return end_addr;
 }
 
+/* Under Win32/WinCE we commit (map) and decommit (unmap)      */
+/* memory using        VirtualAlloc and VirtualFree.  These functions  */
+/* work on individual allocations of virtual memory, made      */
+/* previously using VirtualAlloc with the MEM_RESERVE flag.    */
+/* The ranges we need to (de)commit may span several of these  */
+/* allocations; therefore we use VirtualQuery to check         */
+/* allocation lengths, and split up the range as necessary.    */
+
 /* We assume that GC_remap is called on exactly the same range */
 /* as a previous call to GC_unmap.  It is safe to consistently */
 /* round the endpoints in both places.                         */
@@ -1371,27 +1836,77 @@ void GC_unmap(ptr_t start, word bytes)
     ptr_t end_addr = GC_unmap_end(start, bytes);
     word len = end_addr - start_addr;
     if (0 == start_addr) return;
-    if (munmap(start_addr, len) != 0) ABORT("munmap failed");
-    GC_unmapped_bytes += len;
+#   if defined(MSWIN32) || defined(MSWINCE)
+      while (len != 0) {
+          MEMORY_BASIC_INFORMATION mem_info;
+         GC_word free_len;
+         if (VirtualQuery(start_addr, &mem_info, sizeof(mem_info))
+             != sizeof(mem_info))
+             ABORT("Weird VirtualQuery result");
+         free_len = (len < mem_info.RegionSize) ? len : mem_info.RegionSize;
+         if (!VirtualFree(start_addr, free_len, MEM_DECOMMIT))
+             ABORT("VirtualFree failed");
+         GC_unmapped_bytes += free_len;
+         start_addr += free_len;
+         len -= free_len;
+      }
+#   else
+      /* We immediately remap it to prevent an intervening mmap from   */
+      /* accidentally grabbing the same address space.                 */
+      {
+       void * result;
+        result = mmap(start_addr, len, PROT_NONE,
+                     MAP_PRIVATE | MAP_FIXED | OPT_MAP_ANON,
+                     zero_fd, 0/* offset */);
+        if (result != (void *)start_addr) ABORT("mmap(...PROT_NONE...) failed");
+      }
+      GC_unmapped_bytes += len;
+#   endif
 }
 
 
 void GC_remap(ptr_t start, word bytes)
 {
-    static int zero_descr = -1;
     ptr_t start_addr = GC_unmap_start(start, bytes);
     ptr_t end_addr = GC_unmap_end(start, bytes);
     word len = end_addr - start_addr;
-    ptr_t result;
 
-    if (-1 == zero_descr) zero_descr = open("/dev/zero", O_RDWR);
-    if (0 == start_addr) return;
-    result = mmap(start_addr, len, PROT_READ | PROT_WRITE | OPT_PROT_EXEC,
-                 MAP_FIXED | MAP_PRIVATE, zero_descr, 0);
-    if (result != start_addr) {
-       ABORT("mmap remapping failed");
-    }
-    GC_unmapped_bytes -= len;
+#   if defined(MSWIN32) || defined(MSWINCE)
+      ptr_t result;
+
+      if (0 == start_addr) return;
+      while (len != 0) {
+          MEMORY_BASIC_INFORMATION mem_info;
+         GC_word alloc_len;
+         if (VirtualQuery(start_addr, &mem_info, sizeof(mem_info))
+             != sizeof(mem_info))
+             ABORT("Weird VirtualQuery result");
+         alloc_len = (len < mem_info.RegionSize) ? len : mem_info.RegionSize;
+         result = VirtualAlloc(start_addr, alloc_len,
+                               MEM_COMMIT,
+                               PAGE_EXECUTE_READWRITE);
+         if (result != start_addr) {
+             ABORT("VirtualAlloc remapping failed");
+         }
+         GC_unmapped_bytes -= alloc_len;
+         start_addr += alloc_len;
+         len -= alloc_len;
+      }
+#   else
+      /* It was already remapped with PROT_NONE. */
+      int result; 
+
+      if (0 == start_addr) return;
+      result = mprotect(start_addr, len,
+                       PROT_READ | PROT_WRITE | OPT_PROT_EXEC);
+      if (result != 0) {
+         GC_err_printf3(
+               "Mprotect failed at 0x%lx (length %ld) with errno %ld\n",
+               start_addr, len, errno);
+         ABORT("Mprotect remapping failed");
+      }
+      GC_unmapped_bytes -= len;
+#   endif
 }
 
 /* Two adjacent blocks have already been unmapped and are about to     */
@@ -1412,16 +1927,31 @@ void GC_unmap_gap(ptr_t start1, word bytes1, ptr_t start2, word bytes2)
     if (0 == start2_addr) end_addr = GC_unmap_end(start1, bytes1 + bytes2);
     if (0 == start_addr) return;
     len = end_addr - start_addr;
-    if (len != 0 && munmap(start_addr, len) != 0) ABORT("munmap failed");
-    GC_unmapped_bytes += len;
+#   if defined(MSWIN32) || defined(MSWINCE)
+      while (len != 0) {
+          MEMORY_BASIC_INFORMATION mem_info;
+         GC_word free_len;
+         if (VirtualQuery(start_addr, &mem_info, sizeof(mem_info))
+             != sizeof(mem_info))
+             ABORT("Weird VirtualQuery result");
+         free_len = (len < mem_info.RegionSize) ? len : mem_info.RegionSize;
+         if (!VirtualFree(start_addr, free_len, MEM_DECOMMIT))
+             ABORT("VirtualFree failed");
+         GC_unmapped_bytes += free_len;
+         start_addr += free_len;
+         len -= free_len;
+      }
+#   else
+      if (len != 0 && munmap(start_addr, len) != 0) ABORT("munmap failed");
+      GC_unmapped_bytes += len;
+#   endif
 }
 
 #endif /* USE_MUNMAP */
 
 /* Routine for pushing any additional roots.  In THREADS       */
 /* environment, this is also responsible for marking from      */
-/* thread stacks.  In the SRC_M3 case, it also handles         */
-/* global variables.                                           */
+/* thread stacks.                                              */
 #ifndef THREADS
 void (*GC_push_other_roots)() = 0;
 #else /* THREADS */
@@ -1448,7 +1978,7 @@ PCR_ERes GC_push_old_obj(void *p, size_t size, PCR_Any data)
 }
 
 
-void GC_default_push_other_roots()
+void GC_default_push_other_roots GC_PROTO((void))
 {
     /* Traverse data allocated by previous memory managers.            */
        {
@@ -1476,6 +2006,10 @@ void GC_default_push_other_roots()
     --> misconfigured
 # endif
 
+void GC_push_thread_structures GC_PROTO((void))
+{
+    /* Not our responsibibility. */
+}
 
 extern void ThreadF__ProcessStacks();
 
@@ -1493,37 +2027,19 @@ int dummy3;
 {
     word q = *p;
     
-    if ((ptr_t)(q) >= GC_least_plausible_heap_addr
-        && (ptr_t)(q) < GC_greatest_plausible_heap_addr) {
-        GC_push_one_checked(q,FALSE);
-    }
+    GC_PUSH_ONE_STACK(q, p);
 }
 
 /* M3 set equivalent to RTHeap.TracedRefTypes */
 typedef struct { int elts[1]; }  RefTypeSet;
 RefTypeSet GC_TracedRefTypes = {{0x1}};
 
-/* From finalize.c */
-extern void GC_push_finalizer_structures();
-
-/* From stubborn.c: */
-# ifdef STUBBORN_ALLOC
-    extern GC_PTR * GC_changing_list_start;
-# endif
-
-
-void GC_default_push_other_roots()
+void GC_default_push_other_roots GC_PROTO((void))
 {
-    /* Use the M3 provided routine for finding static roots.   */
-    /* This is a bit dubious, since it presumes no C roots.    */
-    /* We handle the collector roots explicitly.               */
-       {
-#       ifdef STUBBORN_ALLOC
-           GC_push_one(GC_changing_list_start);
-#       endif
-        GC_push_finalizer_structures();
-        RTMain__GlobalMapProc(GC_m3_push_root, 0, GC_TracedRefTypes);
-       }
+    /* Use the M3 provided routine for finding static roots.    */
+    /* This is a bit dubious, since it presumes no C roots.     */
+    /* We handle the collector roots explicitly in GC_push_roots */
+       RTMain__GlobalMapProc(GC_m3_push_root, 0, GC_TracedRefTypes);
        if (GC_words_allocd > 0) {
            ThreadF__ProcessStacks(GC_push_thread_stack);
        }
@@ -1533,26 +2049,25 @@ void GC_default_push_other_roots()
 
 # endif /* SRC_M3 */
 
-# if defined(SOLARIS_THREADS) || defined(WIN32_THREADS) \
-     || defined(IRIX_THREADS) || defined(LINUX_THREADS) \
-     || defined(IRIX_JDK_THREADS) || defined(HPUX_THREADS)
+# if defined(GC_SOLARIS_THREADS) || defined(GC_PTHREADS) || \
+     defined(GC_WIN32_THREADS)
 
 extern void GC_push_all_stacks();
 
-void GC_default_push_other_roots()
+void GC_default_push_other_roots GC_PROTO((void))
 {
     GC_push_all_stacks();
 }
 
-# endif /* SOLARIS_THREADS || ... */
+# endif /* GC_SOLARIS_THREADS || GC_PTHREADS */
 
-void (*GC_push_other_roots)() = GC_default_push_other_roots;
+void (*GC_push_other_roots) GC_PROTO((void)) = GC_default_push_other_roots;
 
-#endif
+#endif /* THREADS */
 
 /*
  * Routines for accessing dirty  bits on virtual pages.
- * We plan to eventaually implement four strategies for doing so:
+ * We plan to eventually implement four strategies for doing so:
  * DEFAULT_VDB:        A simple dummy implementation that treats every page
  *             as possibly dirty.  This makes incremental collection
  *             useless, but the implementation is still correct.
@@ -1570,7 +2085,6 @@ void (*GC_push_other_roots)() = GC_default_push_other_roots;
  *             make sure that other system calls are similarly protected
  *             or write only to the stack.
  */
 GC_bool GC_dirty_maintained = FALSE;
 
 # ifdef DEFAULT_VDB
@@ -1584,6 +2098,9 @@ GC_bool GC_dirty_maintained = FALSE;
 /* Initialize virtual dirty bit implementation.                        */
 void GC_dirty_init()
 {
+#   ifdef PRINTSTATS
+      GC_printf0("Initializing DEFAULT_VDB...\n");
+#   endif
     GC_dirty_maintained = TRUE;
 }
 
@@ -1625,11 +2142,18 @@ word n;
 {
 }
 
-/* A call hints that h is about to be written. */
-/* May speed up some dirty bit implementations.        */
+/* A call that:                                                */
+/* I) hints that [h, h+nblocks) is about to be written.        */
+/* II) guarantees that protection is removed.          */
+/* (I) may speed up some dirty bit implementations.    */
+/* (II) may be essential if we need to ensure that     */
+/* pointer-free system call buffers in the heap are    */
+/* not protected.                                      */
 /*ARGSUSED*/
-void GC_write_hint(h)
+void GC_remove_protection(h, nblocks, is_ptrfree)
 struct hblk *h;
+word nblocks;
+GC_bool is_ptrfree;
 {
 }
 
@@ -1645,17 +2169,21 @@ struct hblk *h;
 /*
  * This implementation maintains dirty bits itself by catching write
  * faults and keeping track of them.  We assume nobody else catches
- * SIGBUS or SIGSEGV.  We assume no write faults occur in system calls
- * except as a result of a read system call.  This means clients must
- * either ensure that system calls do not touch the heap, or must
- * provide their own wrappers analogous to the one for read.
+ * SIGBUS or SIGSEGV.  We assume no write faults occur in system calls.
+ * This means that clients must ensure that system calls don't write
+ * to the write-protected heap.  Probably the best way to do this is to
+ * ensure that system calls write at most to POINTERFREE objects in the
+ * heap, and do even that only if we are on a platform on which those
+ * are not protected.  Another alternative is to wrap system calls
+ * (see example for read below), but the current implementation holds
+ * a lock across blocking calls, making it problematic for multithreaded
+ * applications. 
  * We assume the page size is a multiple of HBLKSIZE.
- * This implementation is currently SunOS 4.X and IRIX 5.X specific, though we
- * tried to use portable code where easily possible.  It is known
- * not to work under a number of other systems.
+ * We prefer them to be the same.  We avoid protecting POINTERFREE
+ * objects only if they are the same.
  */
 
-# ifndef MSWIN32
+# if !defined(MSWIN32) && !defined(MSWINCE) && !defined(DARWIN)
 
 #   include <sys/mman.h>
 #   include <signal.h>
@@ -1674,7 +2202,26 @@ struct hblk *h;
          
 # else
 
-#   include <signal.h>
+# ifdef DARWIN
+    /* Using vm_protect (mach syscall) over mprotect (BSD syscall) seems to
+       decrease the likelihood of some of the problems described below. */
+    #include <mach/vm_map.h>
+    static mach_port_t GC_task_self;
+    #define PROTECT(addr,len) \
+        if(vm_protect(GC_task_self,(vm_address_t)(addr),(vm_size_t)(len), \
+                FALSE,VM_PROT_READ) != KERN_SUCCESS) { \
+            ABORT("vm_portect failed"); \
+        }
+    #define UNPROTECT(addr,len) \
+        if(vm_protect(GC_task_self,(vm_address_t)(addr),(vm_size_t)(len), \
+                FALSE,VM_PROT_READ|VM_PROT_WRITE) != KERN_SUCCESS) { \
+            ABORT("vm_portect failed"); \
+        }
+# else
+    
+#   ifndef MSWINCE
+#     include <signal.h>
+#   endif
 
     static DWORD protect_junk;
 #   define PROTECT(addr, len) \
@@ -1689,52 +2236,69 @@ struct hblk *h;
                              &protect_junk)) { \
            ABORT("un-VirtualProtect failed"); \
          }
-         
-# endif
+# endif /* !DARWIN */
+# endif /* MSWIN32 || MSWINCE || DARWIN */
 
-#if defined(SUNOS4) || defined(FREEBSD)
+#if defined(SUNOS4) || (defined(FREEBSD) && !defined(SUNOS5SIGS))
     typedef void (* SIG_PF)();
-#endif
-#if defined(SUNOS5SIGS) || defined(OSF1) || defined(LINUX)
+#endif /* SUNOS4 || (FREEBSD && !SUNOS5SIGS) */
+
+#if defined(SUNOS5SIGS) || defined(OSF1) || defined(LINUX) \
+    || defined(HURD)
 # ifdef __STDC__
     typedef void (* SIG_PF)(int);
 # else
     typedef void (* SIG_PF)();
 # endif
-#endif
+#endif /* SUNOS5SIGS || OSF1 || LINUX || HURD */
+
 #if defined(MSWIN32)
     typedef LPTOP_LEVEL_EXCEPTION_FILTER SIG_PF;
 #   undef SIG_DFL
 #   define SIG_DFL (LPTOP_LEVEL_EXCEPTION_FILTER) (-1)
 #endif
+#if defined(MSWINCE)
+    typedef LONG (WINAPI *SIG_PF)(struct _EXCEPTION_POINTERS *);
+#   undef SIG_DFL
+#   define SIG_DFL (SIG_PF) (-1)
+#endif
 
-#if defined(IRIX5) || defined(OSF1)
+#if defined(IRIX5) || defined(OSF1) || defined(HURD)
     typedef void (* REAL_SIG_PF)(int, int, struct sigcontext *);
-#endif
+#endif /* IRIX5 || OSF1 || HURD */
+
 #if defined(SUNOS5SIGS)
-# ifdef HPUX
-#   define SIGINFO __siginfo
+# if defined(HPUX) || defined(FREEBSD)
+#   define SIGINFO_T siginfo_t
 # else
-#   define SIGINFO siginfo
+#   define SIGINFO_T struct siginfo
 # endif
 # ifdef __STDC__
-    typedef void (* REAL_SIG_PF)(int, struct SIGINFO *, void *);
+    typedef void (* REAL_SIG_PF)(int, SIGINFO_T *, void *);
 # else
     typedef void (* REAL_SIG_PF)();
 # endif
-#endif
+#endif /* SUNOS5SIGS */
+
 #if defined(LINUX)
-#   include <linux/version.h>
-#   if (LINUX_VERSION_CODE >= 0x20100) && !defined(M68K) || defined(ALPHA) || defined(IA64)
+#   if __GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 2
       typedef struct sigcontext s_c;
-#   else
-      typedef struct sigcontext_struct s_c;
-#   endif
+#   else  /* glibc < 2.2 */
+#     include <linux/version.h>
+#     if (LINUX_VERSION_CODE >= 0x20100) && !defined(M68K) || defined(ALPHA) || defined(ARM32)
+        typedef struct sigcontext s_c;
+#     else
+        typedef struct sigcontext_struct s_c;
+#     endif
+#   endif  /* glibc < 2.2 */
 #   if defined(ALPHA) || defined(M68K)
       typedef void (* REAL_SIG_PF)(int, int, s_c *);
 #   else
-#     if defined(IA64)
+#     if defined(IA64) || defined(HP_PA) || defined(X86_64)
         typedef void (* REAL_SIG_PF)(int, siginfo_t *, s_c *);
+       /* FIXME:                                                 */
+       /* According to SUSV3, the last argument should have type */
+       /* void * or ucontext_t *                                 */
 #     else
         typedef void (* REAL_SIG_PF)(int, s_c);
 #     endif
@@ -1752,13 +2316,64 @@ struct hblk *h;
        return (char *)faultaddr;
     }
 #   endif /* !ALPHA */
-# endif
+# endif /* LINUX */
 
+#ifndef DARWIN
 SIG_PF GC_old_bus_handler;
 SIG_PF GC_old_segv_handler;    /* Also old MSWIN32 ACCESS_VIOLATION filter */
+#endif /* !DARWIN */
+
+#if defined(THREADS)
+/* We need to lock around the bitmap update in the write fault handler */
+/* in order to avoid the risk of losing a bit.  We do this with a      */
+/* test-and-set spin lock if we know how to do that.  Otherwise we     */
+/* check whether we are already in the handler and use the dumb but    */
+/* safe fallback algorithm of setting all bits in the word.            */
+/* Contention should be very rare, so we do the minimum to handle it   */
+/* correctly.                                                          */
+#ifdef GC_TEST_AND_SET_DEFINED
+  static VOLATILE unsigned int fault_handler_lock = 0;
+  void async_set_pht_entry_from_index(VOLATILE page_hash_table db, int index) {
+    while (GC_test_and_set(&fault_handler_lock)) {}
+    /* Could also revert to set_pht_entry_from_index_safe if initial   */
+    /* GC_test_and_set fails.                                          */
+    set_pht_entry_from_index(db, index);
+    GC_clear(&fault_handler_lock);
+  }
+#else /* !GC_TEST_AND_SET_DEFINED */
+  /* THIS IS INCORRECT! The dirty bit vector may be temporarily wrong, */
+  /* just before we notice the conflict and correct it. We may end up   */
+  /* looking at it while it's wrong.  But this requires contention     */
+  /* exactly when a GC is triggered, which seems far less likely to    */
+  /* fail than the old code, which had no reported failures.  Thus we  */
+  /* leave it this way while we think of something better, or support  */
+  /* GC_test_and_set on the remaining platforms.                       */
+  static VOLATILE word currently_updating = 0;
+  void async_set_pht_entry_from_index(VOLATILE page_hash_table db, int index) {
+    unsigned int update_dummy;
+    currently_updating = (word)(&update_dummy);
+    set_pht_entry_from_index(db, index);
+    /* If we get contention in the 10 or so instruction window here,   */
+    /* and we get stopped by a GC between the two updates, we lose!    */
+    if (currently_updating != (word)(&update_dummy)) {
+       set_pht_entry_from_index_safe(db, index);
+       /* We claim that if two threads concurrently try to update the  */
+       /* dirty bit vector, the first one to execute UPDATE_START      */
+       /* will see it changed when UPDATE_END is executed.  (Note that */
+       /* &update_dummy must differ in two distinct threads.)  It      */
+       /* will then execute set_pht_entry_from_index_safe, thus        */
+       /* returning us to a safe state, though not soon enough.        */
+    }
+  }
+#endif /* !GC_TEST_AND_SET_DEFINED */
+#else /* !THREADS */
+# define async_set_pht_entry_from_index(db, index) \
+       set_pht_entry_from_index(db, index)
+#endif /* !THREADS */
 
 /*ARGSUSED*/
-# if defined (SUNOS4) || defined(FREEBSD)
+#if !defined(DARWIN)
+# if defined (SUNOS4) || (defined(FREEBSD) && !defined(SUNOS5SIGS))
     void GC_write_fault_handler(sig, code, scp, addr)
     int sig, code;
     struct sigcontext *scp;
@@ -1771,28 +2386,39 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
 #   endif
 #   ifdef FREEBSD
 #     define SIG_OK (sig == SIGBUS)
-#     define CODE_OK (code == BUS_PAGE_FAULT)
+#     define CODE_OK TRUE
 #   endif
-# endif
-# if defined(IRIX5) || defined(OSF1)
+# endif /* SUNOS4 || (FREEBSD && !SUNOS5SIGS) */
+
+# if defined(IRIX5) || defined(OSF1) || defined(HURD)
 #   include <errno.h>
     void GC_write_fault_handler(int sig, int code, struct sigcontext *scp)
-#   define SIG_OK (sig == SIGSEGV)
 #   ifdef OSF1
+#     define SIG_OK (sig == SIGSEGV)
 #     define CODE_OK (code == 2 /* experimentally determined */)
 #   endif
 #   ifdef IRIX5
+#     define SIG_OK (sig == SIGSEGV)
 #     define CODE_OK (code == EACCES)
 #   endif
-# endif
+#   ifdef HURD
+#     define SIG_OK (sig == SIGBUS || sig == SIGSEGV)  
+#     define CODE_OK  TRUE
+#   endif
+# endif /* IRIX5 || OSF1 || HURD */
+
 # if defined(LINUX)
 #   if defined(ALPHA) || defined(M68K)
       void GC_write_fault_handler(int sig, int code, s_c * sc)
 #   else
-#     if defined(IA64)
+#     if defined(IA64) || defined(HP_PA) || defined(X86_64)
         void GC_write_fault_handler(int sig, siginfo_t * si, s_c * scp)
 #     else
-        void GC_write_fault_handler(int sig, s_c sc)
+#       if defined(ARM32)
+          void GC_write_fault_handler(int sig, int a2, int a3, int a4, s_c sc)
+#       else
+          void GC_write_fault_handler(int sig, s_c sc)
+#       endif
 #     endif
 #   endif
 #   define SIG_OK (sig == SIGSEGV)
@@ -1800,14 +2426,15 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
        /* Empirically c.trapno == 14, on IA32, but is that useful?     */
        /* Should probably consider alignment issues on other           */
        /* architectures.                                               */
-# endif
+# endif /* LINUX */
+
 # if defined(SUNOS5SIGS)
 #  ifdef __STDC__
-    void GC_write_fault_handler(int sig, struct SIGINFO *scp, void * context)
+    void GC_write_fault_handler(int sig, SIGINFO_T *scp, void * context)
 #  else
     void GC_write_fault_handler(sig, scp, context)
     int sig;
-    struct SIGINFO *scp;
+    SIGINFO_T *scp;
     void * context;
 #  endif
 #   ifdef HPUX
@@ -1818,19 +2445,28 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
                     || (scp -> si_code == SEGV_UNKNOWN) \
                     || (scp -> si_code == BUS_OBJERR)
 #   else
-#     define SIG_OK (sig == SIGSEGV)
-#     define CODE_OK (scp -> si_code == SEGV_ACCERR)
-#   endif
-# endif
-# if defined(MSWIN32)
+#     ifdef FREEBSD
+#       define SIG_OK (sig == SIGBUS)
+#       define CODE_OK (scp -> si_code == BUS_PAGE_FAULT)
+#     else
+#       define SIG_OK (sig == SIGSEGV)
+#       define CODE_OK (scp -> si_code == SEGV_ACCERR)
+#     endif
+#   endif    
+# endif /* SUNOS5SIGS */
+
+# if defined(MSWIN32) || defined(MSWINCE)
     LONG WINAPI GC_write_fault_handler(struct _EXCEPTION_POINTERS *exc_info)
 #   define SIG_OK (exc_info -> ExceptionRecord -> ExceptionCode == \
-                       EXCEPTION_ACCESS_VIOLATION)
+                       STATUS_ACCESS_VIOLATION)
 #   define CODE_OK (exc_info -> ExceptionRecord -> ExceptionInformation[0] == 1)
                        /* Write fault */
-# endif
+# endif /* MSWIN32 || MSWINCE */
 {
     register unsigned i;
+#   if defined(HURD) 
+       char *addr = (char *) code;
+#   endif
 #   ifdef IRIX5
        char * addr = (char *) (size_t) (scp -> sc_badvaddr);
 #   endif
@@ -1841,13 +2477,13 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
        char * addr = (char *) (scp -> si_addr);
 #   endif
 #   ifdef LINUX
-#     ifdef I386
+#     if defined(I386)
        char * addr = (char *) (sc.cr2);
 #     else
 #      if defined(M68K)
           char * addr = NULL;
 
-         struct sigcontext *scp = (struct sigcontext *)(&sc);
+         struct sigcontext *scp = (struct sigcontext *)(sc);
 
          int format = (scp->sc_formatvec >> 12) & 0xf;
          unsigned long *framedata = (unsigned long *)(scp + 1); 
@@ -1859,6 +2495,10 @@ SIG_PF GC_old_segv_handler;      /* Also old MSWIN32 ACCESS_VIOLATION filter */
          } else if (format == 7) {
                /* 68040 */
                ea = framedata[3];
+               if (framedata[1] & 0x08000000) {
+                       /* correct addr on misaligned access */
+                       ea = (ea+4095)&(~4095);
+               }
          } else if (format == 4) {
                /* 68060 */
                ea = framedata[0];
@@ -1872,20 +2512,31 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
 #        ifdef ALPHA
             char * addr = get_fault_addr(sc);
 #        else
-#          ifdef IA64
+#          if defined(IA64) || defined(HP_PA) || defined(X86_64)
              char * addr = si -> si_addr;
+             /* I believe this is claimed to work on all platforms for */
+             /* Linux 2.3.47 and later.  Hopefully we don't have to    */
+             /* worry about earlier kernels on IA64.                   */
 #          else
 #             if defined(POWERPC)
                 char * addr = (char *) (sc.regs->dar);
 #            else
-               --> architecture not supported
+#               if defined(ARM32)
+                  char * addr = (char *)sc.fault_address;
+#               else
+#                if defined(CRIS)
+                   char * addr = (char *)sc.regs.csraddr;
+#                else
+                   --> architecture not supported
+#                endif
+#               endif
 #            endif
 #          endif
 #        endif
 #      endif
 #     endif
 #   endif
-#   if defined(MSWIN32)
+#   if defined(MSWIN32) || defined(MSWINCE)
        char * addr = (char *) (exc_info -> ExceptionRecord
                                -> ExceptionInformation[1]);
 #      define sig SIGSEGV
@@ -1908,6 +2559,10 @@ SIG_PF GC_old_segv_handler;      /* Also old MSWIN32 ACCESS_VIOLATION filter */
            in_allocd_block = (HDR(addr) != 0);
 #      endif
         if (!in_allocd_block) {
+           /* FIXME - We should make sure that we invoke the   */
+           /* old handler with the appropriate calling         */
+           /* sequence, which often depends on SA_SIGINFO.     */
+
            /* Heap blocks now begin and end on page boundaries */
             SIG_PF old_handler;
             
@@ -1917,18 +2572,24 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
                 old_handler = GC_old_bus_handler;
             }
             if (old_handler == SIG_DFL) {
-#              ifndef MSWIN32
+#              if !defined(MSWIN32) && !defined(MSWINCE)
                    GC_err_printf1("Segfault at 0x%lx\n", addr);
                     ABORT("Unexpected bus error or segmentation fault");
 #              else
                    return(EXCEPTION_CONTINUE_SEARCH);
 #              endif
             } else {
-#              if defined (SUNOS4) || defined(FREEBSD)
+#              if defined (SUNOS4) \
+                    || (defined(FREEBSD) && !defined(SUNOS5SIGS))
                    (*old_handler) (sig, code, scp, addr);
                    return;
 #              endif
 #              if defined (SUNOS5SIGS)
+                    /*
+                     * FIXME: For FreeBSD, this code should check if the 
+                     * old signal handler used the traditional BSD style and
+                     * if so call it using that style.
+                     */
                    (*(REAL_SIG_PF)old_handler) (sig, scp, context);
                    return;
 #              endif
@@ -1936,7 +2597,7 @@ SIG_PF GC_old_segv_handler;       /* Also old MSWIN32 ACCESS_VIOLATION filter */
 #                  if defined(ALPHA) || defined(M68K)
                        (*(REAL_SIG_PF)old_handler) (sig, code, sc);
 #                  else 
-#                    if defined(IA64)
+#                    if defined(IA64) || defined(HP_PA) || defined(X86_64)
                        (*(REAL_SIG_PF)old_handler) (sig, si, scp);
 #                    else
                        (*(REAL_SIG_PF)old_handler) (sig, sc);
@@ -1944,7 +2605,7 @@ SIG_PF GC_old_segv_handler;       /* Also old MSWIN32 ACCESS_VIOLATION filter */
 #                  endif
                    return;
 #              endif
-#              if defined (IRIX5) || defined(OSF1)
+#              if defined (IRIX5) || defined(OSF1) || defined(HURD)
                    (*(REAL_SIG_PF)old_handler) (sig, code, scp);
                    return;
 #              endif
@@ -1953,72 +2614,99 @@ SIG_PF GC_old_segv_handler;     /* Also old MSWIN32 ACCESS_VIOLATION filter */
 #              endif
             }
         }
+        UNPROTECT(h, GC_page_size);
+       /* We need to make sure that no collection occurs between       */
+       /* the UNPROTECT and the setting of the dirty bit.  Otherwise   */
+       /* a write by a third thread might go unnoticed.  Reversing     */
+       /* the order is just as bad, since we would end up unprotecting */
+       /* a page in a GC cycle during which it's not marked.           */
+       /* Currently we do this by disabling the thread stopping        */
+       /* signals while this handler is running.  An alternative might */
+       /* be to record the fact that we're about to unprotect, or      */
+       /* have just unprotected a page in the GC's thread structure,   */
+       /* and then to have the thread stopping code set the dirty      */
+       /* flag, if necessary.                                          */
         for (i = 0; i < divHBLKSZ(GC_page_size); i++) {
             register int index = PHT_HASH(h+i);
             
-            set_pht_entry_from_index(GC_dirty_pages, index);
+            async_set_pht_entry_from_index(GC_dirty_pages, index);
         }
-        UNPROTECT(h, GC_page_size);
-#      if defined(OSF1) || defined(LINUX)
+#      if defined(OSF1)
            /* These reset the signal handler each time by default. */
            signal(SIGSEGV, (SIG_PF) GC_write_fault_handler);
 #      endif
        /* The write may not take place before dirty bits are read.     */
        /* But then we'll fault again ...                               */
-#      ifdef MSWIN32
+#      if defined(MSWIN32) || defined(MSWINCE)
            return(EXCEPTION_CONTINUE_EXECUTION);
 #      else
            return;
 #      endif
     }
-#ifdef MSWIN32
+#if defined(MSWIN32) || defined(MSWINCE)
     return EXCEPTION_CONTINUE_SEARCH;
 #else
     GC_err_printf1("Segfault at 0x%lx\n", addr);
     ABORT("Unexpected bus error or segmentation fault");
 #endif
 }
+#endif /* !DARWIN */
 
 /*
  * We hold the allocation lock.  We expect block h to be written
- * shortly.
+ * shortly.  Ensure that all pages containing any part of the n hblks
+ * starting at h are no longer protected.  If is_ptrfree is false,
+ * also ensure that they will subsequently appear to be dirty.
  */
-void GC_write_hint(h)
+void GC_remove_protection(h, nblocks, is_ptrfree)
 struct hblk *h;
+word nblocks;
+GC_bool is_ptrfree;
 {
-    register struct hblk * h_trunc;
-    register unsigned i;
-    register GC_bool found_clean;
+    struct hblk * h_trunc;  /* Truncated to page boundary */
+    struct hblk * h_end;    /* Page boundary following block end */
+    struct hblk * current;
+    GC_bool found_clean;
     
     if (!GC_dirty_maintained) return;
     h_trunc = (struct hblk *)((word)h & ~(GC_page_size-1));
+    h_end = (struct hblk *)(((word)(h + nblocks) + GC_page_size-1)
+                           & ~(GC_page_size-1));
     found_clean = FALSE;
-    for (i = 0; i < divHBLKSZ(GC_page_size); i++) {
-        register int index = PHT_HASH(h_trunc+i);
+    for (current = h_trunc; current < h_end; ++current) {
+        int index = PHT_HASH(current);
             
-        if (!get_pht_entry_from_index(GC_dirty_pages, index)) {
-            found_clean = TRUE;
-            set_pht_entry_from_index(GC_dirty_pages, index);
+        if (!is_ptrfree || current < h || current >= h + nblocks) {
+            async_set_pht_entry_from_index(GC_dirty_pages, index);
         }
     }
-    if (found_clean) {
-       UNPROTECT(h_trunc, GC_page_size);
-    }
+    UNPROTECT(h_trunc, (ptr_t)h_end - (ptr_t)h_trunc);
 }
 
+#if !defined(DARWIN)
 void GC_dirty_init()
 {
-#if defined(SUNOS5SIGS) || defined(IRIX5) /* || defined(OSF1) */
-    struct sigaction   act, oldact;
-#   ifdef IRIX5
+#   if defined(SUNOS5SIGS) || defined(IRIX5) || defined(LINUX) || \
+       defined(OSF1) || defined(HURD)
+      struct sigaction act, oldact;
+      /* We should probably specify SA_SIGINFO for Linux, and handle   */
+      /* the different architectures more uniformly.                   */
+#     if defined(IRIX5) || defined(LINUX) && !defined(X86_64) \
+        || defined(OSF1) || defined(HURD)
        act.sa_flags    = SA_RESTART;
-        act.sa_handler  = GC_write_fault_handler;
-#   else
+        act.sa_handler  = (SIG_PF)GC_write_fault_handler;
+#     else
        act.sa_flags    = SA_RESTART | SA_SIGINFO;
         act.sa_sigaction = GC_write_fault_handler;
-#   endif
-    (void)sigemptyset(&act.sa_mask); 
-#endif
+#     endif
+      (void)sigemptyset(&act.sa_mask);
+#     ifdef SIG_SUSPEND
+        /* Arrange to postpone SIG_SUSPEND while we're in a write fault        */
+        /* handler.  This effectively makes the handler atomic w.r.t.  */
+        /* stopping the world for GC.                                  */
+        (void)sigaddset(&act.sa_mask, SIG_SUSPEND);
+#     endif /* SIG_SUSPEND */
+#    endif
 #   ifdef PRINTSTATS
        GC_printf0("Inititalizing mprotect virtual dirty bit implementation\n");
 #   endif
@@ -2027,7 +2715,7 @@ void GC_dirty_init()
         GC_err_printf0("Page size not multiple of HBLKSIZE\n");
         ABORT("Page size not multiple of HBLKSIZE");
     }
-#   if defined(SUNOS4) || defined(FREEBSD)
+#   if defined(SUNOS4) || (defined(FREEBSD) && !defined(SUNOS5SIGS))
       GC_old_bus_handler = signal(SIGBUS, GC_write_fault_handler);
       if (GC_old_bus_handler == SIG_IGN) {
         GC_err_printf0("Previously ignored bus error!?");
@@ -2039,7 +2727,7 @@ void GC_dirty_init()
 #      endif
       }
 #   endif
-#   if defined(OSF1) || defined(SUNOS4) || defined(LINUX)
+#   if defined(SUNOS4)
       GC_old_segv_handler = signal(SIGSEGV, (SIG_PF)GC_write_fault_handler);
       if (GC_old_segv_handler == SIG_IGN) {
         GC_err_printf0("Previously ignored segmentation violation!?");
@@ -2051,18 +2739,23 @@ void GC_dirty_init()
 #      endif
       }
 #   endif
-#   if defined(SUNOS5SIGS) || defined(IRIX5)
-#     if defined(IRIX_THREADS) || defined(IRIX_JDK_THREADS)
+#   if (defined(SUNOS5SIGS) && !defined(FREEBSD)) || defined(IRIX5) \
+       || defined(LINUX) || defined(OSF1) || defined(HURD)
+      /* SUNOS5SIGS includes HPUX */
+#     if defined(GC_IRIX_THREADS)
        sigaction(SIGSEGV, 0, &oldact);
        sigaction(SIGSEGV, &act, 0);
-#     else
-       sigaction(SIGSEGV, &act, &oldact);
+#     else 
+       {
+         int res = sigaction(SIGSEGV, &act, &oldact);
+         if (res != 0) ABORT("Sigaction failed");
+       }
 #     endif
-#     if defined(_sigargs)
+#     if defined(_sigargs) || defined(HURD) || !defined(SA_SIGINFO)
        /* This is Irix 5.x, not 6.x.  Irix 5.x does not have   */
        /* sa_sigaction.                                        */
        GC_old_segv_handler = oldact.sa_handler;
-#     else /* Irix 6.x or SUNOS5SIGS */
+#     else /* Irix 6.x or SUNOS5SIGS or LINUX */
         if (oldact.sa_flags & SA_SIGINFO) {
           GC_old_segv_handler = (SIG_PF)(oldact.sa_sigaction);
         } else {
@@ -2078,16 +2771,21 @@ void GC_dirty_init()
          GC_err_printf0("Replaced other SIGSEGV handler\n");
 #       endif
       }
-#     ifdef HPUX
-         sigaction(SIGBUS, &act, &oldact);
-          GC_old_bus_handler = oldact.sa_handler;
-          if (GC_old_segv_handler != SIG_DFL) {
-#           ifdef PRINTSTATS
-             GC_err_printf0("Replaced other SIGBUS handler\n");
-#           endif
-          }
-#     endif
-#    endif
+#   endif /* (SUNOS5SIGS && !FREEBSD) || IRIX5 || LINUX || OSF1 || HURD */
+#   if defined(HPUX) || defined(LINUX) || defined(HURD) \
+      || (defined(FREEBSD) && defined(SUNOS5SIGS))
+      sigaction(SIGBUS, &act, &oldact);
+      GC_old_bus_handler = oldact.sa_handler;
+      if (GC_old_bus_handler == SIG_IGN) {
+            GC_err_printf0("Previously ignored bus error!?");
+            GC_old_bus_handler = SIG_DFL;
+      }
+      if (GC_old_bus_handler != SIG_DFL) {
+#       ifdef PRINTSTATS
+         GC_err_printf0("Replaced other SIGBUS handler\n");
+#       endif
+      }
+#   endif /* HPUX || LINUX || HURD || (FREEBSD && SUNOS5SIGS) */
 #   if defined(MSWIN32)
       GC_old_segv_handler = SetUnhandledExceptionFilter(GC_write_fault_handler);
       if (GC_old_segv_handler != NULL) {
@@ -2099,19 +2797,79 @@ void GC_dirty_init()
       }
 #   endif
 }
+#endif /* !DARWIN */
 
+int GC_incremental_protection_needs()
+{
+    if (GC_page_size == HBLKSIZE) {
+       return GC_PROTECTS_POINTER_HEAP;
+    } else {
+       return GC_PROTECTS_POINTER_HEAP | GC_PROTECTS_PTRFREE_HEAP;
+    }
+}
+
+#define HAVE_INCREMENTAL_PROTECTION_NEEDS
 
+#define IS_PTRFREE(hhdr) ((hhdr)->hb_descr == 0)
 
+#define PAGE_ALIGNED(x) !((word)(x) & (GC_page_size - 1))
 void GC_protect_heap()
 {
     ptr_t start;
     word len;
+    struct hblk * current;
+    struct hblk * current_start;  /* Start of block to be protected. */
+    struct hblk * limit;
     unsigned i;
-    
+    GC_bool protect_all = 
+         (0 != (GC_incremental_protection_needs() & GC_PROTECTS_PTRFREE_HEAP));
     for (i = 0; i < GC_n_heap_sects; i++) {
         start = GC_heap_sects[i].hs_start;
         len = GC_heap_sects[i].hs_bytes;
-        PROTECT(start, len);
+       if (protect_all) {
+          PROTECT(start, len);
+       } else {
+         GC_ASSERT(PAGE_ALIGNED(len))
+         GC_ASSERT(PAGE_ALIGNED(start))
+         current_start = current = (struct hblk *)start;
+         limit = (struct hblk *)(start + len);
+         while (current < limit) {
+            hdr * hhdr;
+           word nhblks;
+           GC_bool is_ptrfree;
+
+           GC_ASSERT(PAGE_ALIGNED(current));
+           GET_HDR(current, hhdr);
+           if (IS_FORWARDING_ADDR_OR_NIL(hhdr)) {
+             /* This can happen only if we're at the beginning of a    */
+             /* heap segment, and a block spans heap segments.         */
+             /* We will handle that block as part of the preceding     */
+             /* segment.                                               */
+             GC_ASSERT(current_start == current);
+             current_start = ++current;
+             continue;
+           }
+           if (HBLK_IS_FREE(hhdr)) {
+             GC_ASSERT(PAGE_ALIGNED(hhdr -> hb_sz));
+             nhblks = divHBLKSZ(hhdr -> hb_sz);
+             is_ptrfree = TRUE;        /* dirty on alloc */
+           } else {
+             nhblks = OBJ_SZ_TO_BLOCKS(hhdr -> hb_sz);
+             is_ptrfree = IS_PTRFREE(hhdr);
+           }
+           if (is_ptrfree) {
+             if (current_start < current) {
+               PROTECT(current_start, (ptr_t)current - (ptr_t)current_start);
+             }
+             current_start = (current += nhblks);
+           } else {
+             current += nhblks;
+           }
+         } 
+         if (current_start < current) {
+           PROTECT(current_start, (ptr_t)current - (ptr_t)current_start);
+         }
+       }
     }
 }
 
@@ -2140,15 +2898,23 @@ struct hblk * h;
  * happens to work.
  * On other systems, SET_LOCK_HOLDER and friends must be suitably defined.
  */
+
+static GC_bool syscall_acquired_lock = FALSE;  /* Protected by GC lock. */
  
 void GC_begin_syscall()
 {
-    if (!I_HOLD_LOCK()) LOCK();
+    if (!I_HOLD_LOCK()) {
+       LOCK();
+       syscall_acquired_lock = TRUE;
+    }
 }
 
 void GC_end_syscall()
 {
-    if (!I_HOLD_LOCK()) UNLOCK();
+    if (syscall_acquired_lock) {
+       syscall_acquired_lock = FALSE;
+       UNLOCK();
+    }
 }
 
 void GC_unprotect_range(addr, len)
@@ -2160,7 +2926,7 @@ word len;
     register struct hblk *h;
     ptr_t obj_start;
     
-    if (!GC_incremental) return;
+    if (!GC_dirty_maintained) return;
     obj_start = GC_base(addr);
     if (obj_start == 0) return;
     if (GC_base(addr + len - 1) != obj_start) {
@@ -2172,18 +2938,32 @@ word len;
     for (h = start_block; h <= end_block; h++) {
         register word index = PHT_HASH(h);
         
-        set_pht_entry_from_index(GC_dirty_pages, index);
+        async_set_pht_entry_from_index(GC_dirty_pages, index);
     }
     UNPROTECT(start_block,
              ((ptr_t)end_block - (ptr_t)start_block) + HBLKSIZE);
 }
 
-#ifndef MSWIN32
-/* Replacement for UNIX system call.    */
-/* Other calls that write to the heap   */
-/* should be handled similarly.                 */
+#if 0
+
+/* We no longer wrap read by default, since that was causing too many  */
+/* problems.  It is preferred that the client instead avoids writing   */
+/* to the write-protected heap with a system call.                     */
+/* This still serves as sample code if you do want to wrap system calls.*/
+
+#if !defined(MSWIN32) && !defined(MSWINCE) && !defined(GC_USE_LD_WRAP)
+/* Replacement for UNIX system call.                                     */
+/* Other calls that write to the heap should be handled similarly.       */
+/* Note that this doesn't work well for blocking reads:  It will hold    */
+/* the allocation lock for the entire duration of the call. Multithreaded */
+/* clients should really ensure that it won't block, either by setting           */
+/* the descriptor nonblocking, or by calling select or poll first, to    */
+/* make sure that input is available.                                    */
+/* Another, preferred alternative is to ensure that system calls never           */
+/* write to the protected heap (see above).                              */
 # if defined(__STDC__) && !defined(SUNOS4)
 #   include <unistd.h>
+#   include <sys/uio.h>
     ssize_t read(int fd, void *buf, size_t nbyte)
 # else
 #   ifndef LINT
@@ -2200,10 +2980,12 @@ word len;
     
     GC_begin_syscall();
     GC_unprotect_range(buf, (word)nbyte);
-#   ifdef IRIX5
+#   if defined(IRIX5) || defined(GC_LINUX_THREADS)
        /* Indirect system call may not always be easily available.     */
        /* We could call _read, but that would interfere with the       */
        /* libpthread interception of read.                             */
+       /* On Linux, we have to be careful with the linuxthreads        */
+       /* read interception.                                           */
        {
            struct iovec iov;
 
@@ -2212,12 +2994,43 @@ word len;
            result = readv(fd, &iov, 1);
        }
 #   else
-       result = syscall(SYS_read, fd, buf, nbyte);
+#     if defined(HURD) 
+       result = __read(fd, buf, nbyte);
+#     else
+       /* The two zero args at the end of this list are because one
+          IA-64 syscall() implementation actually requires six args
+          to be passed, even though they aren't always used. */
+       result = syscall(SYS_read, fd, buf, nbyte, 0, 0);
+#     endif /* !HURD */
 #   endif
     GC_end_syscall();
     return(result);
 }
-#endif /* !MSWIN32 */
+#endif /* !MSWIN32 && !MSWINCE && !GC_LINUX_THREADS */
+
+#if defined(GC_USE_LD_WRAP) && !defined(THREADS)
+    /* We use the GNU ld call wrapping facility.                       */
+    /* This requires that the linker be invoked with "--wrap read".    */
+    /* This can be done by passing -Wl,"--wrap read" to gcc.           */
+    /* I'm not sure that this actually wraps whatever version of read  */
+    /* is called by stdio.  That code also mentions __read.            */
+#   include <unistd.h>
+    ssize_t __wrap_read(int fd, void *buf, size_t nbyte)
+    {
+       int result;
+
+       GC_begin_syscall();
+       GC_unprotect_range(buf, (word)nbyte);
+       result = __real_read(fd, buf, nbyte);
+       GC_end_syscall();
+       return(result);
+    }
+
+    /* We should probably also do this for __read, or whatever stdio   */
+    /* actually calls.                                                 */
+#endif
+
+#endif /* 0 */
 
 /*ARGSUSED*/
 GC_bool GC_page_was_ever_dirty(h)
@@ -2256,13 +3069,12 @@ word n;
 #include <sys/syscall.h>
 #include <sys/procfs.h>
 #include <sys/stat.h>
-#include <fcntl.h>
 
-#define INITIAL_BUF_SZ 4096
+#define INITIAL_BUF_SZ 16384
 word GC_proc_buf_size = INITIAL_BUF_SZ;
 char *GC_proc_buf;
 
-#ifdef SOLARIS_THREADS
+#ifdef GC_SOLARIS_THREADS
 /* We don't have exact sp values for threads.  So we count on  */
 /* occasionally declaring stack pages to be fresh.  Thus we    */
 /* need a real implementation of GC_is_fresh.  We can't clear  */
@@ -2313,11 +3125,12 @@ void GC_dirty_init()
     }
     GC_proc_fd = syscall(SYS_ioctl, fd, PIOCOPENPD, 0);
     close(fd);
+    syscall(SYS_fcntl, GC_proc_fd, F_SETFD, FD_CLOEXEC);
     if (GC_proc_fd < 0) {
        ABORT("/proc ioctl failed");
     }
     GC_proc_buf = GC_scratch_alloc(GC_proc_buf_size);
-#   ifdef SOLARIS_THREADS
+#   ifdef GC_SOLARIS_THREADS
        GC_fresh_pages = (struct hblk **)
          GC_scratch_alloc(MAX_FRESH_PAGES * sizeof (struct hblk *));
        if (GC_fresh_pages == 0) {
@@ -2330,12 +3143,14 @@ void GC_dirty_init()
 
 /* Ignore write hints. They don't help us here.        */
 /*ARGSUSED*/
-void GC_write_hint(h)
+void GC_remove_protection(h, nblocks, is_ptrfree)
 struct hblk *h;
+word nblocks;
+GC_bool is_ptrfree;
 {
 }
 
-#ifdef SOLARIS_THREADS
+#ifdef GC_SOLARIS_THREADS
 #   define READ(fd,buf,nbytes) syscall(SYS_read, fd, buf, nbytes)
 #else
 #   define READ(fd,buf,nbytes) read(fd, buf, nbytes)
@@ -2369,12 +3184,12 @@ int dummy;
                 GC_proc_buf = bufp = new_buf;
                 GC_proc_buf_size = new_size;
             }
-            if (syscall(SYS_read, GC_proc_fd, bufp, GC_proc_buf_size) <= 0) {
+            if (READ(GC_proc_fd, bufp, GC_proc_buf_size) <= 0) {
                 WARN("Insufficient space for /proc read\n", 0);
                 /* Punt:       */
                memset(GC_grungy_pages, 0xff, sizeof (page_hash_table));
                memset(GC_written_pages, 0xff, sizeof(page_hash_table));
-#              ifdef SOLARIS_THREADS
+#              ifdef GC_SOLARIS_THREADS
                    BZERO(GC_fresh_pages,
                          MAX_FRESH_PAGES * sizeof (struct hblk *)); 
 #              endif
@@ -2404,7 +3219,7 @@ int dummy;
                        register word index = PHT_HASH(h);
                        
                        set_pht_entry_from_index(GC_grungy_pages, index);
-#                      ifdef SOLARIS_THREADS
+#                      ifdef GC_SOLARIS_THREADS
                          {
                            register int slot = FRESH_PAGE_SLOT(h);
                            
@@ -2422,7 +3237,7 @@ int dummy;
        }
     /* Update GC_written_pages. */
         GC_or_pages(GC_written_pages, GC_grungy_pages);
-#   ifdef SOLARIS_THREADS
+#   ifdef GC_SOLARIS_THREADS
       /* Make sure that old stacks are considered completely clean     */
       /* unless written again.                                         */
        GC_old_stacks_are_fresh();
@@ -2438,7 +3253,7 @@ struct hblk *h;
     register GC_bool result;
     
     result = get_pht_entry_from_index(GC_grungy_pages, index);
-#   ifdef SOLARIS_THREADS
+#   ifdef GC_SOLARIS_THREADS
        if (result && PAGE_IS_FRESH(h)) result = FALSE;
        /* This happens only if page was declared fresh since   */
        /* the read_dirty call, e.g. because it's in an unused  */
@@ -2456,7 +3271,7 @@ struct hblk *h;
     register GC_bool result;
     
     result = get_pht_entry_from_index(GC_written_pages, index);
-#   ifdef SOLARIS_THREADS
+#   ifdef GC_SOLARIS_THREADS
        if (result && PAGE_IS_FRESH(h)) result = FALSE;
 #   endif
     return(result);
@@ -2470,7 +3285,7 @@ word n;
 
     register word index;
     
-#   ifdef SOLARIS_THREADS
+#   ifdef GC_SOLARIS_THREADS
       register word i;
       
       if (GC_fresh_pages != 0) {
@@ -2539,42 +3354,707 @@ struct hblk *h;
 }
 
 /*ARGSUSED*/
-void GC_write_hint(h)
+void GC_remove_protection(h, nblocks, is_ptrfree)
 struct hblk *h;
+word nblocks;
+GC_bool is_ptrfree;
 {
-    PCR_VD_WriteProtectDisable(h, HBLKSIZE);
-    PCR_VD_WriteProtectEnable(h, HBLKSIZE);
+    PCR_VD_WriteProtectDisable(h, nblocks*HBLKSIZE);
+    PCR_VD_WriteProtectEnable(h, nblocks*HBLKSIZE);
 }
 
 # endif /* PCR_VDB */
 
+#if defined(MPROTECT_VDB) && defined(DARWIN)
+/* The following sources were used as a *reference* for this exception handling
+   code:
+      1. Apple's mach/xnu documentation
+      2. Timothy J. Wood's "Mach Exception Handlers 101" post to the
+         omnigroup's macosx-dev list. 
+         www.omnigroup.com/mailman/archive/macosx-dev/2000-June/014178.html
+      3. macosx-nat.c from Apple's GDB source code.
+*/
+   
+/* The bug that caused all this trouble should now be fixed. This should
+   eventually be removed if all goes well. */
+/* define BROKEN_EXCEPTION_HANDLING */
+    
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <mach/thread_status.h>
+#include <mach/exception.h>
+#include <mach/task.h>
+#include <pthread.h>
+
+/* These are not defined in any header, although they are documented */
+extern boolean_t exc_server(mach_msg_header_t *,mach_msg_header_t *);
+extern kern_return_t exception_raise(
+    mach_port_t,mach_port_t,mach_port_t,
+    exception_type_t,exception_data_t,mach_msg_type_number_t);
+extern kern_return_t exception_raise_state(
+    mach_port_t,mach_port_t,mach_port_t,
+    exception_type_t,exception_data_t,mach_msg_type_number_t,
+    thread_state_flavor_t*,thread_state_t,mach_msg_type_number_t,
+    thread_state_t,mach_msg_type_number_t*);
+extern kern_return_t exception_raise_state_identity(
+    mach_port_t,mach_port_t,mach_port_t,
+    exception_type_t,exception_data_t,mach_msg_type_number_t,
+    thread_state_flavor_t*,thread_state_t,mach_msg_type_number_t,
+    thread_state_t,mach_msg_type_number_t*);
+
+
+#define MAX_EXCEPTION_PORTS 16
+
+static struct {
+    mach_msg_type_number_t count;
+    exception_mask_t      masks[MAX_EXCEPTION_PORTS];
+    exception_handler_t   ports[MAX_EXCEPTION_PORTS];
+    exception_behavior_t  behaviors[MAX_EXCEPTION_PORTS];
+    thread_state_flavor_t flavors[MAX_EXCEPTION_PORTS];
+} GC_old_exc_ports;
+
+static struct {
+    mach_port_t exception;
+#if defined(THREADS)
+    mach_port_t reply;
+#endif
+} GC_ports;
+
+typedef struct {
+    mach_msg_header_t head;
+} GC_msg_t;
+
+typedef enum {
+    GC_MP_NORMAL, GC_MP_DISCARDING, GC_MP_STOPPED
+} GC_mprotect_state_t;
+
+/* FIXME: 1 and 2 seem to be safe to use in the msgh_id field,
+   but it isn't  documented. Use the source and see if they
+   should be ok. */
+#define ID_STOP 1
+#define ID_RESUME 2
+
+/* These values are only used on the reply port */
+#define ID_ACK 3
+
+#if defined(THREADS)
+
+GC_mprotect_state_t GC_mprotect_state;
+
+/* The following should ONLY be called when the world is stopped  */
+static void GC_mprotect_thread_notify(mach_msg_id_t id) {
+    struct {
+        GC_msg_t msg;
+        mach_msg_trailer_t trailer;
+    } buf;
+    mach_msg_return_t r;
+    /* remote, local */
+    buf.msg.head.msgh_bits = 
+        MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND,0);
+    buf.msg.head.msgh_size = sizeof(buf.msg);
+    buf.msg.head.msgh_remote_port = GC_ports.exception;
+    buf.msg.head.msgh_local_port = MACH_PORT_NULL;
+    buf.msg.head.msgh_id = id;
+            
+    r = mach_msg(
+        &buf.msg.head,
+        MACH_SEND_MSG|MACH_RCV_MSG|MACH_RCV_LARGE,
+        sizeof(buf.msg),
+        sizeof(buf),
+        GC_ports.reply,
+        MACH_MSG_TIMEOUT_NONE,
+        MACH_PORT_NULL);
+    if(r != MACH_MSG_SUCCESS)
+       ABORT("mach_msg failed in GC_mprotect_thread_notify");
+    if(buf.msg.head.msgh_id != ID_ACK)
+        ABORT("invalid ack in GC_mprotect_thread_notify");
+}
+
+/* Should only be called by the mprotect thread */
+static void GC_mprotect_thread_reply() {
+    GC_msg_t msg;
+    mach_msg_return_t r;
+    /* remote, local */
+    msg.head.msgh_bits = 
+        MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND,0);
+    msg.head.msgh_size = sizeof(msg);
+    msg.head.msgh_remote_port = GC_ports.reply;
+    msg.head.msgh_local_port = MACH_PORT_NULL;
+    msg.head.msgh_id = ID_ACK;
+            
+    r = mach_msg(
+        &msg.head,
+        MACH_SEND_MSG,
+        sizeof(msg),
+        0,
+        MACH_PORT_NULL,
+        MACH_MSG_TIMEOUT_NONE,
+        MACH_PORT_NULL);
+    if(r != MACH_MSG_SUCCESS)
+       ABORT("mach_msg failed in GC_mprotect_thread_reply");
+}
+
+void GC_mprotect_stop() {
+    GC_mprotect_thread_notify(ID_STOP);
+}
+void GC_mprotect_resume() {
+    GC_mprotect_thread_notify(ID_RESUME);
+}
+
+#else /* !THREADS */
+/* The compiler should optimize away any GC_mprotect_state computations */
+#define GC_mprotect_state GC_MP_NORMAL
+#endif
+
+static void *GC_mprotect_thread(void *arg) {
+    mach_msg_return_t r;
+    /* These two structures contain some private kernel data. We don't need to
+       access any of it so we don't bother defining a proper struct. The
+       correct definitions are in the xnu source code. */
+    struct {
+        mach_msg_header_t head;
+        char data[256];
+    } reply;
+    struct {
+        mach_msg_header_t head;
+        mach_msg_body_t msgh_body;
+        char data[1024];
+    } msg;
+
+    mach_msg_id_t id;
+
+    GC_darwin_register_mach_handler_thread(mach_thread_self());
+    
+    for(;;) {
+        r = mach_msg(
+            &msg.head,
+            MACH_RCV_MSG|MACH_RCV_LARGE|
+                (GC_mprotect_state == GC_MP_DISCARDING ? MACH_RCV_TIMEOUT : 0),
+            0,
+            sizeof(msg),
+            GC_ports.exception,
+            GC_mprotect_state == GC_MP_DISCARDING ? 0 : MACH_MSG_TIMEOUT_NONE,
+            MACH_PORT_NULL);
+        
+        id = r == MACH_MSG_SUCCESS ? msg.head.msgh_id : -1;
+        
+#if defined(THREADS)
+        if(GC_mprotect_state == GC_MP_DISCARDING) {
+            if(r == MACH_RCV_TIMED_OUT) {
+                GC_mprotect_state = GC_MP_STOPPED;
+                GC_mprotect_thread_reply();
+                continue;
+            }
+            if(r == MACH_MSG_SUCCESS && (id == ID_STOP || id == ID_RESUME))
+                ABORT("out of order mprotect thread request");
+        }
+#endif
+        
+        if(r != MACH_MSG_SUCCESS) {
+            GC_err_printf2("mach_msg failed with %d %s\n", 
+                (int)r,mach_error_string(r));
+            ABORT("mach_msg failed");
+        }
+        
+        switch(id) {
+#if defined(THREADS)
+            case ID_STOP:
+                if(GC_mprotect_state != GC_MP_NORMAL)
+                    ABORT("Called mprotect_stop when state wasn't normal");
+                GC_mprotect_state = GC_MP_DISCARDING;
+                break;
+            case ID_RESUME:
+                if(GC_mprotect_state != GC_MP_STOPPED)
+                    ABORT("Called mprotect_resume when state wasn't stopped");
+                GC_mprotect_state = GC_MP_NORMAL;
+                GC_mprotect_thread_reply();
+                break;
+#endif /* THREADS */
+            default:
+                   /* Handle the message (calls catch_exception_raise) */
+               if(!exc_server(&msg.head,&reply.head))
+                    ABORT("exc_server failed");
+                /* Send the reply */
+                r = mach_msg(
+                    &reply.head,
+                    MACH_SEND_MSG,
+                    reply.head.msgh_size,
+                    0,
+                    MACH_PORT_NULL,
+                    MACH_MSG_TIMEOUT_NONE,
+                    MACH_PORT_NULL);
+               if(r != MACH_MSG_SUCCESS) {
+                       /* This will fail if the thread dies, but the thread shouldn't
+                          die... */
+                       #ifdef BROKEN_EXCEPTION_HANDLING
+                       GC_err_printf2(
+                        "mach_msg failed with %d %s while sending exc reply\n",
+                        (int)r,mach_error_string(r));
+               #else
+                       ABORT("mach_msg failed while sending exception reply");
+               #endif
+               }
+        } /* switch */
+    } /* for(;;) */
+    /* NOT REACHED */
+    return NULL;
+}
+
+/* All this SIGBUS code shouldn't be necessary. All protection faults should
+   be going throught the mach exception handler. However, it seems a SIGBUS is
+   occasionally sent for some unknown reason. Even more odd, it seems to be
+   meaningless and safe to ignore. */
+#ifdef BROKEN_EXCEPTION_HANDLING
+
+typedef void (* SIG_PF)();
+static SIG_PF GC_old_bus_handler;
+
+/* Updates to this aren't atomic, but the SIGBUSs seem pretty rare.
+   Even if this doesn't get updated property, it isn't really a problem */
+static int GC_sigbus_count;
+
+static void GC_darwin_sigbus(int num,siginfo_t *sip,void *context) {
+    if(num != SIGBUS) ABORT("Got a non-sigbus signal in the sigbus handler");
+    
+    /* Ugh... some seem safe to ignore, but too many in a row probably means
+       trouble. GC_sigbus_count is reset for each mach exception that is
+       handled */
+    if(GC_sigbus_count >= 8) {
+        ABORT("Got more than 8 SIGBUSs in a row!");
+    } else {
+        GC_sigbus_count++;
+        GC_err_printf0("GC: WARNING: Ignoring SIGBUS.\n");
+    }
+}
+#endif /* BROKEN_EXCEPTION_HANDLING */
+
+void GC_dirty_init() {
+    kern_return_t r;
+    mach_port_t me;
+    pthread_t thread;
+    pthread_attr_t attr;
+    exception_mask_t mask;
+    
+#   ifdef PRINTSTATS
+        GC_printf0("Inititalizing mach/darwin mprotect virtual dirty bit "
+            "implementation\n");
+#   endif  
+#      ifdef BROKEN_EXCEPTION_HANDLING
+        GC_err_printf0("GC: WARNING: Enabling workarounds for various darwin "
+            "exception handling bugs.\n");
+#      endif
+    GC_dirty_maintained = TRUE;
+    if (GC_page_size % HBLKSIZE != 0) {
+        GC_err_printf0("Page size not multiple of HBLKSIZE\n");
+        ABORT("Page size not multiple of HBLKSIZE");
+    }
+    
+    GC_task_self = me = mach_task_self();
+    
+    r = mach_port_allocate(me,MACH_PORT_RIGHT_RECEIVE,&GC_ports.exception);
+    if(r != KERN_SUCCESS) ABORT("mach_port_allocate failed (exception port)");
+    
+    r = mach_port_insert_right(me,GC_ports.exception,GC_ports.exception,
+       MACH_MSG_TYPE_MAKE_SEND);
+    if(r != KERN_SUCCESS)
+       ABORT("mach_port_insert_right failed (exception port)");
+
+    #if defined(THREADS)
+        r = mach_port_allocate(me,MACH_PORT_RIGHT_RECEIVE,&GC_ports.reply);
+        if(r != KERN_SUCCESS) ABORT("mach_port_allocate failed (reply port)");
+    #endif
+
+    /* The exceptions we want to catch */  
+    mask = EXC_MASK_BAD_ACCESS;
+
+    r = task_get_exception_ports(
+        me,
+        mask,
+        GC_old_exc_ports.masks,
+        &GC_old_exc_ports.count,
+        GC_old_exc_ports.ports,
+        GC_old_exc_ports.behaviors,
+        GC_old_exc_ports.flavors
+    );
+    if(r != KERN_SUCCESS) ABORT("task_get_exception_ports failed");
+        
+    r = task_set_exception_ports(
+        me,
+        mask,
+        GC_ports.exception,
+        EXCEPTION_DEFAULT,
+        GC_MACH_THREAD_STATE
+    );
+    if(r != KERN_SUCCESS) ABORT("task_set_exception_ports failed");
+
+    if(pthread_attr_init(&attr) != 0) ABORT("pthread_attr_init failed");
+    if(pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED) != 0) 
+        ABORT("pthread_attr_setdetachedstate failed");
+
+#      undef pthread_create
+    /* This will call the real pthread function, not our wrapper */
+    if(pthread_create(&thread,&attr,GC_mprotect_thread,NULL) != 0)
+        ABORT("pthread_create failed");
+    pthread_attr_destroy(&attr);
+    
+    /* Setup the sigbus handler for ignoring the meaningless SIGBUSs */
+    #ifdef BROKEN_EXCEPTION_HANDLING 
+    {
+        struct sigaction sa, oldsa;
+        sa.sa_handler = (SIG_PF)GC_darwin_sigbus;
+        sigemptyset(&sa.sa_mask);
+        sa.sa_flags = SA_RESTART|SA_SIGINFO;
+        if(sigaction(SIGBUS,&sa,&oldsa) < 0) ABORT("sigaction");
+        GC_old_bus_handler = (SIG_PF)oldsa.sa_handler;
+        if (GC_old_bus_handler != SIG_DFL) {
+#              ifdef PRINTSTATS
+                GC_err_printf0("Replaced other SIGBUS handler\n");
+#              endif
+        }
+    }
+    #endif /* BROKEN_EXCEPTION_HANDLING  */
+}
+/* The source code for Apple's GDB was used as a reference for the exception
+   forwarding code. This code is similar to be GDB code only because there is 
+   only one way to do it. */
+static kern_return_t GC_forward_exception(
+        mach_port_t thread,
+        mach_port_t task,
+        exception_type_t exception,
+        exception_data_t data,
+        mach_msg_type_number_t data_count
+) {
+    int i;
+    kern_return_t r;
+    mach_port_t port;
+    exception_behavior_t behavior;
+    thread_state_flavor_t flavor;
+    
+    thread_state_t thread_state;
+    mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX;
+        
+    for(i=0;i<GC_old_exc_ports.count;i++)
+        if(GC_old_exc_ports.masks[i] & (1 << exception))
+            break;
+    if(i==GC_old_exc_ports.count) ABORT("No handler for exception!");
+    
+    port = GC_old_exc_ports.ports[i];
+    behavior = GC_old_exc_ports.behaviors[i];
+    flavor = GC_old_exc_ports.flavors[i];
+
+    if(behavior != EXCEPTION_DEFAULT) {
+        r = thread_get_state(thread,flavor,thread_state,&thread_state_count);
+        if(r != KERN_SUCCESS)
+            ABORT("thread_get_state failed in forward_exception");
+    }
+    
+    switch(behavior) {
+        case EXCEPTION_DEFAULT:
+            r = exception_raise(port,thread,task,exception,data,data_count);
+            break;
+        case EXCEPTION_STATE:
+            r = exception_raise_state(port,thread,task,exception,data,
+                data_count,&flavor,thread_state,thread_state_count,
+                thread_state,&thread_state_count);
+            break;
+        case EXCEPTION_STATE_IDENTITY:
+            r = exception_raise_state_identity(port,thread,task,exception,data,
+                data_count,&flavor,thread_state,thread_state_count,
+                thread_state,&thread_state_count);
+            break;
+        default:
+            r = KERN_FAILURE; /* make gcc happy */
+            ABORT("forward_exception: unknown behavior");
+            break;
+    }
+    
+    if(behavior != EXCEPTION_DEFAULT) {
+        r = thread_set_state(thread,flavor,thread_state,thread_state_count);
+        if(r != KERN_SUCCESS)
+            ABORT("thread_set_state failed in forward_exception");
+    }
+    
+    return r;
+}
+
+#define FWD() GC_forward_exception(thread,task,exception,code,code_count)
+
+/* This violates the namespace rules but there isn't anything that can be done
+   about it. The exception handling stuff is hard coded to call this */
+kern_return_t
+catch_exception_raise(
+   mach_port_t exception_port,mach_port_t thread,mach_port_t task,
+   exception_type_t exception,exception_data_t code,
+   mach_msg_type_number_t code_count
+) {
+    kern_return_t r;
+    char *addr;
+    struct hblk *h;
+    int i;
+#   if defined(POWERPC)
+#     if CPP_WORDSZ == 32
+        thread_state_flavor_t flavor = PPC_EXCEPTION_STATE;
+        mach_msg_type_number_t exc_state_count = PPC_EXCEPTION_STATE_COUNT;
+        ppc_exception_state_t exc_state;
+#     else
+        thread_state_flavor_t flavor = PPC_EXCEPTION_STATE64;
+        mach_msg_type_number_t exc_state_count = PPC_EXCEPTION_STATE64_COUNT;
+        ppc_exception_state64_t exc_state;
+#     endif
+#   elif defined(I386) || defined(X86_64)
+#     if CPP_WORDSZ == 32
+       thread_state_flavor_t flavor = x86_EXCEPTION_STATE32;
+       mach_msg_type_number_t exc_state_count = x86_EXCEPTION_STATE32_COUNT;
+       x86_exception_state32_t exc_state;
+#     else
+       thread_state_flavor_t flavor = x86_EXCEPTION_STATE64;
+       mach_msg_type_number_t exc_state_count = x86_EXCEPTION_STATE64_COUNT;
+       x86_exception_state64_t exc_state;
+#     endif
+#   else
+#      error FIXME for non-ppc darwin
+#   endif
+
+    
+    if(exception != EXC_BAD_ACCESS || code[0] != KERN_PROTECTION_FAILURE) {
+        #ifdef DEBUG_EXCEPTION_HANDLING
+        /* We aren't interested, pass it on to the old handler */
+        GC_printf3("Exception: 0x%x Code: 0x%x 0x%x in catch....\n",
+            exception,
+            code_count > 0 ? code[0] : -1,
+            code_count > 1 ? code[1] : -1); 
+        #endif
+        return FWD();
+    }
+
+    r = thread_get_state(thread,flavor,
+        (natural_t*)&exc_state,&exc_state_count);
+    if(r != KERN_SUCCESS) {
+        /* The thread is supposed to be suspended while the exception handler
+           is called. This shouldn't fail. */
+        #ifdef BROKEN_EXCEPTION_HANDLING
+            GC_err_printf0("thread_get_state failed in "
+                "catch_exception_raise\n");
+            return KERN_SUCCESS;
+        #else
+            ABORT("thread_get_state failed in catch_exception_raise");
+        #endif
+    }
+    
+    /* This is the address that caused the fault */
+#if defined(POWERPC)
+    addr = (char*) exc_state.dar;
+#elif defined (I386) || defined (X86_64)
+    addr = (char*) exc_state.faultvaddr;
+#else
+#   error FIXME for non POWERPC/I386
+#endif
+        
+    if((HDR(addr)) == 0) {
+        /* Ugh... just like the SIGBUS problem above, it seems we get a bogus 
+           KERN_PROTECTION_FAILURE every once and a while. We wait till we get
+           a bunch in a row before doing anything about it. If a "real" fault 
+           ever occurres it'll just keep faulting over and over and we'll hit
+           the limit pretty quickly. */
+        #ifdef BROKEN_EXCEPTION_HANDLING
+            static char *last_fault;
+            static int last_fault_count;
+            
+            if(addr != last_fault) {
+                last_fault = addr;
+                last_fault_count = 0;
+            }
+            if(++last_fault_count < 32) {
+                if(last_fault_count == 1)
+                    GC_err_printf1(
+                        "GC: WARNING: Ignoring KERN_PROTECTION_FAILURE at %p\n",
+                        addr);
+                return KERN_SUCCESS;
+            }
+            
+            GC_err_printf1("Unexpected KERN_PROTECTION_FAILURE at %p\n",addr);
+            /* Can't pass it along to the signal handler because that is
+               ignoring SIGBUS signals. We also shouldn't call ABORT here as
+               signals don't always work too well from the exception handler. */
+            GC_err_printf0("Aborting\n");
+            exit(EXIT_FAILURE);
+        #else /* BROKEN_EXCEPTION_HANDLING */
+            /* Pass it along to the next exception handler 
+               (which should call SIGBUS/SIGSEGV) */
+            return FWD();
+        #endif /* !BROKEN_EXCEPTION_HANDLING */
+    }
+
+    #ifdef BROKEN_EXCEPTION_HANDLING
+        /* Reset the number of consecutive SIGBUSs */
+        GC_sigbus_count = 0;
+    #endif
+    
+    if(GC_mprotect_state == GC_MP_NORMAL) { /* common case */
+        h = (struct hblk*)((word)addr & ~(GC_page_size-1));
+        UNPROTECT(h, GC_page_size);    
+        for (i = 0; i < divHBLKSZ(GC_page_size); i++) {
+            register int index = PHT_HASH(h+i);
+            async_set_pht_entry_from_index(GC_dirty_pages, index);
+        }
+    } else if(GC_mprotect_state == GC_MP_DISCARDING) {
+        /* Lie to the thread for now. No sense UNPROTECT()ing the memory
+           when we're just going to PROTECT() it again later. The thread
+           will just fault again once it resumes */
+    } else {
+        /* Shouldn't happen, i don't think */
+        GC_printf0("KERN_PROTECTION_FAILURE while world is stopped\n");
+        return FWD();
+    }
+    return KERN_SUCCESS;
+}
+#undef FWD
+
+/* These should never be called, but just in case...  */
+kern_return_t catch_exception_raise_state(mach_port_name_t exception_port,
+    int exception, exception_data_t code, mach_msg_type_number_t codeCnt,
+    int flavor, thread_state_t old_state, int old_stateCnt,
+    thread_state_t new_state, int new_stateCnt)
+{
+    ABORT("catch_exception_raise_state");
+    return(KERN_INVALID_ARGUMENT);
+}
+kern_return_t catch_exception_raise_state_identity(
+    mach_port_name_t exception_port, mach_port_t thread, mach_port_t task,
+    int exception, exception_data_t code, mach_msg_type_number_t codeCnt,
+    int flavor, thread_state_t old_state, int old_stateCnt, 
+    thread_state_t new_state, int new_stateCnt)
+{
+    ABORT("catch_exception_raise_state_identity");
+    return(KERN_INVALID_ARGUMENT);
+}
+
+
+#endif /* DARWIN && MPROTECT_VDB */
+
+# ifndef HAVE_INCREMENTAL_PROTECTION_NEEDS
+  int GC_incremental_protection_needs()
+  {
+    return GC_PROTECTS_NONE;
+  }
+# endif /* !HAVE_INCREMENTAL_PROTECTION_NEEDS */
+
 /*
  * Call stack save code for debugging.
  * Should probably be in mach_dep.c, but that requires reorganization.
  */
-#if defined(SPARC) && !defined(LINUX)
-#   if defined(SUNOS4)
-#     include <machine/frame.h>
-#   else
-#     if defined (DRSNX)
-#      include <sys/sparc/frame.h>
-#     else
-#        if defined(OPENBSD)
-#          include <frame.h>
-#        else
-#          include <sys/frame.h>
-#        endif
-#     endif
-#   endif
-#   if NARGS > 6
+
+/* I suspect the following works for most X86 *nix variants, so        */
+/* long as the frame pointer is explicitly stored.  In the case of gcc,        */
+/* compiler flags (e.g. -fomit-frame-pointer) determine whether it is. */
+#if defined(I386) && defined(LINUX) && defined(SAVE_CALL_CHAIN)
+#   include <features.h>
+
+    struct frame {
+       struct frame *fr_savfp;
+       long    fr_savpc;
+        long   fr_arg[NARGS];  /* All the arguments go here.   */
+    };
+#endif
+
+#if defined(SPARC)
+#  if defined(LINUX)
+#    include <features.h>
+
+     struct frame {
+       long    fr_local[8];
+       long    fr_arg[6];
+       struct frame *fr_savfp;
+       long    fr_savpc;
+#       ifndef __arch64__
+         char  *fr_stret;
+#       endif
+       long    fr_argd[6];
+       long    fr_argx[0];
+     };
+#  else
+#    if defined(SUNOS4)
+#      include <machine/frame.h>
+#    else
+#      if defined (DRSNX)
+#       include <sys/sparc/frame.h>
+#      else
+#       if defined(OPENBSD)
+#         include <frame.h>
+#       else
+#         if defined(FREEBSD) || defined(NETBSD)
+#           include <machine/frame.h>
+#         else
+#           include <sys/frame.h>
+#         endif
+#       endif
+#      endif
+#    endif
+#  endif
+#  if NARGS > 6
        --> We only know how to to get the first 6 arguments
-#   endif
+#  endif
+#endif /* SPARC */
 
-#ifdef SAVE_CALL_CHAIN
+#ifdef  NEED_CALLINFO
 /* Fill in the pc and argument information for up to NFRAMES of my     */
 /* callers.  Ignore my frame and my callers frame.                     */
 
-#ifdef OPENBSD
+#ifdef LINUX
+#   include <unistd.h>
+#endif
+
+#endif /* NEED_CALLINFO */
+
+#if defined(GC_HAVE_BUILTIN_BACKTRACE)
+# include <execinfo.h>
+#endif
+
+#ifdef SAVE_CALL_CHAIN
+
+#if NARGS == 0 && NFRAMES % 2 == 0 /* No padding */ \
+    && defined(GC_HAVE_BUILTIN_BACKTRACE)
+
+#ifdef REDIRECT_MALLOC
+  /* Deal with possible malloc calls in backtrace by omitting  */
+  /* the infinitely recursing backtrace.                       */
+# ifdef THREADS
+    __thread   /* If your compiler doesn't understand this */
+               /* you could use something like pthread_getspecific.    */
+# endif
+  GC_in_save_callers = FALSE;
+#endif
+
+void GC_save_callers (info) 
+struct callinfo info[NFRAMES];
+{
+  void * tmp_info[NFRAMES + 1];
+  int npcs, i;
+# define IGNORE_FRAMES 1
+  
+  /* We retrieve NFRAMES+1 pc values, but discard the first, since it  */
+  /* points to our own frame.                                          */
+# ifdef REDIRECT_MALLOC
+    if (GC_in_save_callers) {
+      info[0].ci_pc = (word)(&GC_save_callers);
+      for (i = 1; i < NFRAMES; ++i) info[i].ci_pc = 0;
+      return;
+    }
+    GC_in_save_callers = TRUE;
+# endif
+  GC_ASSERT(sizeof(struct callinfo) == sizeof(void *));
+  npcs = backtrace((void **)tmp_info, NFRAMES + IGNORE_FRAMES);
+  BCOPY(tmp_info+IGNORE_FRAMES, info, (npcs - IGNORE_FRAMES) * sizeof(void *));
+  for (i = npcs - IGNORE_FRAMES; i < NFRAMES; ++i) info[i].ci_pc = 0;
+# ifdef REDIRECT_MALLOC
+    GC_in_save_callers = FALSE;
+# endif
+}
+
+#else /* No builtin backtrace; do it ourselves */
+
+#if (defined(OPENBSD) || defined(NETBSD) || defined(FREEBSD)) && defined(SPARC)
 #  define FR_SAVFP fr_fp
 #  define FR_SAVPC fr_pc
 #else
@@ -2582,30 +4062,219 @@ struct hblk *h;
 #  define FR_SAVPC fr_savpc
 #endif
 
+#if defined(SPARC) && (defined(__arch64__) || defined(__sparcv9))
+#   define BIAS 2047
+#else
+#   define BIAS 0
+#endif
+
 void GC_save_callers (info) 
 struct callinfo info[NFRAMES];
 {
   struct frame *frame;
   struct frame *fp;
   int nframes = 0;
-  word GC_save_regs_in_stack();
-
-  frame = (struct frame *) GC_save_regs_in_stack ();
+# ifdef I386
+    /* We assume this is turned on only with gcc as the compiler. */
+    asm("movl %%ebp,%0" : "=r"(frame));
+    fp = frame;
+# else
+    frame = (struct frame *) GC_save_regs_in_stack ();
+    fp = (struct frame *)((long) frame -> FR_SAVFP + BIAS);
+#endif
   
-  for (fp = frame -> FR_SAVFP; fp != 0 && nframes < NFRAMES;
-       fp = fp -> FR_SAVFP, nframes++) {
+   for (; (!(fp HOTTER_THAN frame) && !(GC_stackbottom HOTTER_THAN (ptr_t)fp)
+          && (nframes < NFRAMES));
+       fp = (struct frame *)((long) fp -> FR_SAVFP + BIAS), nframes++) {
       register int i;
       
       info[nframes].ci_pc = fp->FR_SAVPC;
-      for (i = 0; i < NARGS; i++) {
-       info[nframes].ci_arg[i] = ~(fp->fr_arg[i]);
-      }
+#     if NARGS > 0
+        for (i = 0; i < NARGS; i++) {
+         info[nframes].ci_arg[i] = ~(fp->fr_arg[i]);
+        }
+#     endif /* NARGS > 0 */
   }
   if (nframes < NFRAMES) info[nframes].ci_pc = 0;
 }
 
+#endif /* No builtin backtrace */
+
 #endif /* SAVE_CALL_CHAIN */
-#endif /* SPARC */
 
+#ifdef NEED_CALLINFO
+
+/* Print info to stderr.  We do NOT hold the allocation lock */
+void GC_print_callers (info)
+struct callinfo info[NFRAMES];
+{
+    register int i;
+    static int reentry_count = 0;
+    GC_bool stop = FALSE;
+
+    /* FIXME: This should probably use a different lock, so that we    */
+    /* become callable with or without the allocation lock.            */
+    LOCK();
+      ++reentry_count;
+    UNLOCK();
+    
+#   if NFRAMES == 1
+      GC_err_printf0("\tCaller at allocation:\n");
+#   else
+      GC_err_printf0("\tCall chain at allocation:\n");
+#   endif
+    for (i = 0; i < NFRAMES && !stop ; i++) {
+       if (info[i].ci_pc == 0) break;
+#      if NARGS > 0
+       {
+         int j;
+
+         GC_err_printf0("\t\targs: ");
+         for (j = 0; j < NARGS; j++) {
+           if (j != 0) GC_err_printf0(", ");
+           GC_err_printf2("%d (0x%X)", ~(info[i].ci_arg[j]),
+                                       ~(info[i].ci_arg[j]));
+         }
+         GC_err_printf0("\n");
+       }
+#      endif
+        if (reentry_count > 1) {
+           /* We were called during an allocation during       */
+           /* a previous GC_print_callers call; punt.          */
+           GC_err_printf1("\t\t##PC##= 0x%lx\n", info[i].ci_pc);
+           continue;
+       }
+       {
+#        ifdef LINUX
+           FILE *pipe;
+#        endif
+#        if defined(GC_HAVE_BUILTIN_BACKTRACE) \
+            && !defined(GC_BACKTRACE_SYMBOLS_BROKEN)
+           char **sym_name =
+             backtrace_symbols((void **)(&(info[i].ci_pc)), 1);
+           char *name = sym_name[0];
+#        else
+           char buf[40];
+           char *name = buf;
+           sprintf(buf, "##PC##= 0x%lx", info[i].ci_pc);
+#        endif
+#        if defined(LINUX) && !defined(SMALL_CONFIG)
+           /* Try for a line number. */
+           {
+#              define EXE_SZ 100
+               static char exe_name[EXE_SZ];
+#              define CMD_SZ 200
+               char cmd_buf[CMD_SZ];
+#              define RESULT_SZ 200
+               static char result_buf[RESULT_SZ];
+               size_t result_len;
+               char *old_preload;
+#              define PRELOAD_SZ 200
+               char preload_buf[PRELOAD_SZ];
+               static GC_bool found_exe_name = FALSE;
+               static GC_bool will_fail = FALSE;
+               int ret_code;
+               /* Try to get it via a hairy and expensive scheme.      */
+               /* First we get the name of the executable:             */
+               if (will_fail) goto out;
+               if (!found_exe_name) { 
+                 ret_code = readlink("/proc/self/exe", exe_name, EXE_SZ);
+                 if (ret_code < 0 || ret_code >= EXE_SZ
+                     || exe_name[0] != '/') {
+                   will_fail = TRUE;   /* Dont try again. */
+                   goto out;
+                 }
+                 exe_name[ret_code] = '\0';
+                 found_exe_name = TRUE;
+               }
+               /* Then we use popen to start addr2line -e <exe> <addr> */
+               /* There are faster ways to do this, but hopefully this */
+               /* isn't time critical.                                 */
+               sprintf(cmd_buf, "/usr/bin/addr2line -f -e %s 0x%lx", exe_name,
+                                (unsigned long)info[i].ci_pc);
+               old_preload = getenv ("LD_PRELOAD");
+               if (0 != old_preload) {
+                 if (strlen (old_preload) >= PRELOAD_SZ) {
+                   will_fail = TRUE;
+                   goto out;
+                 }
+                 strcpy (preload_buf, old_preload);
+                 unsetenv ("LD_PRELOAD");
+               }
+               pipe = popen(cmd_buf, "r");
+               if (0 != old_preload
+                   && 0 != setenv ("LD_PRELOAD", preload_buf, 0)) {
+                 WARN("Failed to reset LD_PRELOAD\n", 0);
+               }
+               if (pipe == NULL
+                   || (result_len = fread(result_buf, 1, RESULT_SZ - 1, pipe))
+                      == 0) {
+                 if (pipe != NULL) pclose(pipe);
+                 will_fail = TRUE;
+                 goto out;
+               }
+               if (result_buf[result_len - 1] == '\n') --result_len;
+               result_buf[result_len] = 0;
+               if (result_buf[0] == '?'
+                   || result_buf[result_len-2] == ':' 
+                      && result_buf[result_len-1] == '0') {
+                   pclose(pipe);
+                   goto out;
+               }
+               /* Get rid of embedded newline, if any.  Test for "main" */
+               {
+                  char * nl = strchr(result_buf, '\n');
+                  if (nl != NULL && nl < result_buf + result_len) {
+                    *nl = ':';
+                  }
+                  if (strncmp(result_buf, "main", nl - result_buf) == 0) {
+                    stop = TRUE;
+                  }
+               }
+               if (result_len < RESULT_SZ - 25) {
+                 /* Add in hex address */
+                   sprintf(result_buf + result_len, " [0x%lx]",
+                         (unsigned long)info[i].ci_pc);
+               }
+               name = result_buf;
+               pclose(pipe);
+               out:;
+           }
+#        endif /* LINUX */
+         GC_err_printf1("\t\t%s\n", name);
+#        if defined(GC_HAVE_BUILTIN_BACKTRACE) \
+            && !defined(GC_BACKTRACE_SYMBOLS_BROKEN)
+           free(sym_name);  /* May call GC_free; that's OK */
+#         endif
+       }
+    }
+    LOCK();
+      --reentry_count;
+    UNLOCK();
+}
+
+#endif /* NEED_CALLINFO */
+
+
+
+#if defined(LINUX) && defined(__ELF__) && !defined(SMALL_CONFIG)
+
+/* Dump /proc/self/maps to GC_stderr, to enable looking up names for
+   addresses in FIND_LEAK output. */
+
+static word dump_maps(char *maps)
+{
+    GC_err_write(maps, strlen(maps));
+    return 1;
+}
+
+void GC_print_address_map()
+{
+    GC_err_printf0("---------- Begin address map ----------\n");
+    GC_apply_to_maps(dump_maps);
+    GC_err_printf0("---------- End address map ----------\n");
+}
+
+#endif