GC_INNER char * GC_get_maps(void)
{
    int f;
    ssize_t result;
    static char *maps_buf = NULL;
    static size_t maps_buf_sz = 1;
    size_t maps_size, old_maps_size = 0;

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

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

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

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

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

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