Merge tag 'pull-lu-20260710' of https://gitlab.com/rth7680/qemu into staging

Handle loading of ET_EXEC binaries at address 0.
Share probe_guest_base code between linux-user and bsd-user.

# -----BEGIN PGP SIGNATURE-----
#
# iQFRBAABCgA7FiEEekgeeIaLTbaoWgXAZN846K9+IV8FAmpReQUdHHJpY2hhcmQu
# aGVuZGVyc29uQGxpbmFyby5vcmcACgkQZN846K9+IV8BPQf/UmwW68Nxc8MqtEwx
# 5DRI97pqT6ZDlJGXXjPUYjpeDdJyDS4KIAoI+FRvJlNcaKkrI7nsBO54SCsRGImW
# 0iabkCh4HZMjRCSrYvllxJGE6K4IRQOGPpVAwNKl/0zQsQZuDnMIhi+sI4GfYUTh
# SNXsPLCCidF4LAaC1B/xzzA+GMGoTgNeWXPE01v/Q+EMcnCdztUpmJsB5SAHUqna
# zUl0cTom8QOx71qlFBSzwHht4rapws//ey1rLH4U2EQB8X3hxHtN4xXNwKwyaK2z
# 3AZDTgrM9CxviXmcW3uXAd2AC2X+whXQap6n3/vQOqz2awESRCt97GLMWyqHsvxg
# iExrPQ==
# =W4uK
# -----END PGP SIGNATURE-----
# gpg: Signature made Sat 11 Jul 2026 00:58:13 CEST
# gpg:                using RSA key 7A481E78868B4DB6A85A05C064DF38E8AF7E215F
# gpg:                issuer "richard.henderson@linaro.org"
# gpg: Good signature from "Richard Henderson <richard.henderson@linaro.org>" [full]
# Primary key fingerprint: 7A48 1E78 868B 4DB6 A85A  05C0 64DF 38E8 AF7E 215F

* tag 'pull-lu-20260710' of https://gitlab.com/rth7680/qemu:
  common-user: Move guest_base, have_guest_base to probe-guest-base.c
  bsd-user: Use probe_guest_base
  common-user: Move probe_guest_base from linux-user
  common-user: Implement read_self_maps for FreeBSD
  common-user: Move selfmap from util
  include/user/guest-host: Include missing cpu.h
  common-user: Initialize mmap_min_addr for FreeBSD
  common-user: Move mmap_min_addr from linux-user
  linux-user: Use PGBRange for commpage
  linux-user: Pass image_range to probe_guest_base
  linux-user: Drop hiaddr out-of-range check in probe_guest_base
  linux-user: Use PGBRange in load_elf_image
  linux-user: Introduce PGBRange

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
diff --git a/bsd-user/elfload.c b/bsd-user/elfload.c
index 3bca0cc..a87a892 100644
--- a/bsd-user/elfload.c
+++ b/bsd-user/elfload.c
@@ -22,6 +22,7 @@
 #include "qemu.h"
 #include "disas/disas.h"
 #include "qemu/path.h"
+#include "user/probe-guest-base.h"
 
 static abi_ulong target_auxents;   /* Where the AUX entries are in target */
 static size_t target_auxents_sz;   /* Size of AUX entries including AT_NULL */
@@ -610,8 +611,9 @@
     abi_ulong elf_brk;
     int error, retval;
     char *elf_interpreter;
-    abi_ulong baddr, elf_entry, et_dyn_addr, interp_load_addr = 0;
+    abi_ulong elf_entry, et_dyn_addr, interp_load_addr = 0;
     abi_ulong reloc_func_desc = 0;
+    PGBRange range = { -1, 0 };
 
     load_addr = 0;
     elf_ex = *((struct elfhdr *) bprm->buf);          /* exec-header */
@@ -654,10 +656,10 @@
 
     elf_brk = 0;
 
-
     elf_interpreter = NULL;
-    for (i = 0; i < elf_ex.e_phnum; i++) {
-        if (elf_ppnt->p_type == PT_INTERP) {
+    for (i = 0; i < elf_ex.e_phnum; i++, elf_ppnt++) {
+        switch (elf_ppnt->p_type) {
+        case PT_INTERP:
             if (elf_interpreter != NULL) {
                 free(elf_phdata);
                 free(elf_interpreter);
@@ -709,8 +711,14 @@
                 close(bprm->fd);
                 return retval;
             }
+            break;
+
+        case PT_LOAD:
+            range.lo = MIN(range.lo, elf_ppnt->p_vaddr);
+            range.hi = MAX(range.hi,
+                           elf_ppnt->p_vaddr + elf_ppnt->p_memsz - 1);
+            break;
         }
-        elf_ppnt++;
     }
 
     /* Some simple consistency checks for the interpreter */
@@ -740,19 +748,12 @@
     info->end_code = 0;
     elf_entry = (abi_ulong) elf_ex.e_entry;
 
-    /* XXX Join this with PT_INTERP search? */
-    baddr = 0;
-    for (i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
-        if (elf_ppnt->p_type != PT_LOAD) {
-            continue;
-        }
-        baddr = elf_ppnt->p_vaddr;
-        break;
-    }
-
     et_dyn_addr = 0;
-    if (elf_ex.e_type == ET_DYN && baddr == 0) {
-        et_dyn_addr = ELF_ET_DYN_LOAD_ADDR;
+    if (elf_ex.e_type == ET_DYN) {
+        probe_guest_base(bprm->filename, NULL, NULL);
+        et_dyn_addr = ELF_ET_DYN_LOAD_ADDR - range.lo;
+    } else {
+        probe_guest_base(bprm->filename, &range, NULL);
     }
 
     /*
@@ -766,7 +767,7 @@
     info->elf_flags = elf_ex.e_flags;
 
     error = load_elf_sections(&elf_ex, elf_phdata, bprm->fd, et_dyn_addr,
-        &load_addr);
+                              &load_addr);
     for (i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
         if (elf_ppnt->p_type != PT_LOAD) {
             continue;
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 4f15443..694481a 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -63,8 +63,7 @@
 
 static bool opt_one_insn_per_tb;
 static unsigned long opt_tb_size;
-uintptr_t guest_base;
-bool have_guest_base;
+
 /*
  * When running 32-on-64 we should make sure we can fit all of the possible
  * guest address space into a contiguous chunk of virtual host memory.
@@ -541,41 +540,10 @@
         }
     }
 
-    /*
-     * If reserving host virtual address space, do so now.
-     * Combined with '-B', ensure that the chosen range is free.
-     */
-    if (reserved_va) {
-        void *p;
-
-        if (have_guest_base) {
-            p = mmap((void *)guest_base, reserved_va + 1, PROT_NONE,
-                     MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_EXCL, -1, 0);
-        } else {
-            p = mmap(NULL, reserved_va + 1, PROT_NONE,
-                     MAP_ANON | MAP_PRIVATE, -1, 0);
-        }
-        if (p == MAP_FAILED) {
-            const char *err = strerror(errno);
-            char *sz = size_to_str(reserved_va + 1);
-
-            if (have_guest_base) {
-                error_report("Cannot allocate %s bytes at -B %p for guest "
-                             "address space: %s", sz, (void *)guest_base, err);
-            } else {
-                error_report("Cannot allocate %s bytes for guest "
-                             "address space: %s", sz, err);
-            }
-            exit(1);
-        }
-        guest_base = (uintptr_t)p;
-        have_guest_base = true;
-
-        /* Ensure that mmap_next_start is within range. */
-        if (reserved_va <= mmap_next_start) {
-            mmap_next_start = (reserved_va / 4 * 3)
-                              & TARGET_PAGE_MASK & qemu_host_page_mask;
-        }
+    /* Ensure that mmap_next_start is within range. */
+    if (reserved_va && reserved_va <= mmap_next_start) {
+        mmap_next_start = ((reserved_va / 4 * 3)
+                           & TARGET_PAGE_MASK & qemu_host_page_mask);
     }
 
     if (loader_exec(filename, argv + optind, target_environ, regs, info,
diff --git a/common-user/meson.build b/common-user/meson.build
index ac9de5b..78dea61 100644
--- a/common-user/meson.build
+++ b/common-user/meson.build
@@ -5,6 +5,9 @@
 common_user_inc += include_directories('host/' / host_arch)
 
 user_ss.add(files(
+  'mmap-min-addr.c',
+  'probe-guest-base.c',
   'safe-syscall.S',
   'safe-syscall-error.c',
+  'selfmap.c',
 ))
diff --git a/common-user/mmap-min-addr.c b/common-user/mmap-min-addr.c
new file mode 100644
index 0000000..830c6f6
--- /dev/null
+++ b/common-user/mmap-min-addr.c
@@ -0,0 +1,50 @@
+/*
+ * Utility function to get the minimum mmap address.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "user/mmap-min-addr.h"
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#endif
+
+uintptr_t mmap_min_addr;
+
+static void __attribute__((constructor)) init(void)
+{
+#ifdef __linux__
+    /*
+     * We prefer to not make NULL pointers accessible to QEMU.
+     * If something goes wrong below, fall back to 1 page.
+     */
+    size_t min_addr = qemu_real_host_page_size();
+    /*
+     * Read in mmap_min_addr kernel parameter.  This value is used
+     * When loading the ELF image to determine whether guest_base
+     * is needed.  It is also used in mmap_find_vma.
+     */
+    FILE *fp = fopen("/proc/sys/vm/mmap_min_addr", "r");
+
+    if (fp) {
+        unsigned long tmp;
+        if (fscanf(fp, "%lu", &tmp) == 1 && tmp != 0) {
+            min_addr = MAX(min_addr, tmp);
+        }
+        fclose(fp);
+    }
+    mmap_min_addr = min_addr;
+#elif defined(__FreeBSD__)
+    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_VM_LAYOUT, getpid() };
+    struct kinfo_vm_layout info;
+    size_t info_len = sizeof(info);
+
+    mmap_min_addr =
+        (sysctl(mib, ARRAY_SIZE(mib), &info, &info_len, NULL, 0) < 0
+         ? qemu_real_host_page_size()
+         : info.kvm_min_user_addr);
+#else
+# error
+#endif
+}
diff --git a/common-user/probe-guest-base.c b/common-user/probe-guest-base.c
new file mode 100644
index 0000000..6f1247c
--- /dev/null
+++ b/common-user/probe-guest-base.c
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/units.h"
+#include "qemu/target-info.h"
+#include "qemu/log.h"
+#include "user/guest-base.h"
+#include "user/mmap-min-addr.h"
+#include "user/guest-base.h"
+#include "user/guest-host.h"
+#include "user/probe-guest-base.h"
+#include "user/selfmap.h"
+#include "exec/target_page.h"
+#include <sys/shm.h>
+
+/* Linux and FreeBSD use different flags to express NOREPLACE. */
+#ifdef __FreeBSD__
+#define MAP_FIXED_NOREPLACE  (MAP_FIXED | MAP_EXCL)
+#endif
+
+uintptr_t guest_base;
+bool have_guest_base;
+
+/**
+ * pgb_try_mmap:
+ * @addr: host start address
+ * @addr_last: host last address
+ * @keep: do not unmap the probe region
+ *
+ * Return 1 if [@addr, @addr_last] is not mapped in the host,
+ * return 0 if it is not available to map, and -1 on mmap error.
+ * If @keep, the region is left mapped on success, otherwise unmapped.
+ */
+static int pgb_try_mmap(uintptr_t addr, uintptr_t addr_last, bool keep)
+{
+    size_t size = addr_last - addr + 1;
+    void *p = mmap((void *)addr, size, PROT_NONE,
+                   MAP_ANONYMOUS | MAP_PRIVATE |
+                   MAP_NORESERVE | MAP_FIXED_NOREPLACE, -1, 0);
+    int ret;
+
+    if (p == MAP_FAILED) {
+        return errno == EEXIST ? 0 : -1;
+    }
+    ret = p == (void *)addr;
+    if (!keep || !ret) {
+        munmap(p, size);
+    }
+    return ret;
+}
+
+/**
+ * pgb_try_mmap_skip_brk(uintptr_t addr, uintptr_t size, uintptr_t brk)
+ * @addr: host address
+ * @addr_last: host last address
+ * @brk: host brk
+ *
+ * Like pgb_try_mmap, but additionally reserve some memory following brk.
+ */
+static int pgb_try_mmap_skip_brk(uintptr_t addr, uintptr_t addr_last,
+                                 uintptr_t brk, bool keep)
+{
+    uintptr_t brk_last = brk + 16 * MiB - 1;
+
+    /* Do not map anything close to the host brk. */
+    if (addr <= brk_last && brk <= addr_last) {
+        return 0;
+    }
+    return pgb_try_mmap(addr, addr_last, keep);
+}
+
+/**
+ * pgb_try_mmap_set:
+ * @ga: set of guest addrs
+ * @base: guest_base
+ * @brk: host brk
+ *
+ * Return true if all @ga can be mapped by the host at @base.
+ * On success, retain the mapping at index 0 for reserved_va.
+ */
+
+typedef struct PGBAddrs {
+    PGBRange bounds[3];
+    int nbounds;
+} PGBAddrs;
+
+static bool pgb_try_mmap_set(const PGBAddrs *ga, uintptr_t base, uintptr_t brk)
+{
+    for (int i = ga->nbounds - 1; i >= 0; --i) {
+        if (pgb_try_mmap_skip_brk(ga->bounds[i].lo + base,
+                                  ga->bounds[i].hi + base,
+                                  brk, i == 0 && reserved_va) <= 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * pgb_addr_set:
+ * @ga: output set of guest addrs
+ * @image_range: fixed guest image addresses
+ * @identity: create for identity mapping
+ *
+ * Fill in @ga with the image, COMMPAGE and NULL page.
+ */
+static bool pgb_addr_set(PGBAddrs *ga, const PGBRange *image_range,
+                         const PGBRange *commpage_range, bool try_identity)
+{
+    int n;
+
+    /*
+     * With a low commpage, or a guest mapped very low,
+     * we may not be able to use the identity map.
+     */
+    if (try_identity) {
+        if (commpage_range && commpage_range->lo < mmap_min_addr) {
+            return false;
+        }
+        if (image_range && image_range->lo < mmap_min_addr) {
+            return false;
+        }
+    }
+
+    memset(ga, 0, sizeof(*ga));
+    n = 0;
+
+    if (reserved_va) {
+        ga->bounds[n].lo = try_identity ? mmap_min_addr : 0;
+        ga->bounds[n].hi = reserved_va;
+        n++;
+        /* Low COMMPAGE and NULL handled by reserving from 0. */
+    } else {
+        /* Add any low COMMPAGE or NULL page. */
+        if (!try_identity || (commpage_range && commpage_range->lo == 0)) {
+            ga->bounds[n].lo = 0;
+            ga->bounds[n].hi = TARGET_PAGE_SIZE - 1;
+            n++;
+        }
+
+        /* Add the guest image for ET_EXEC. */
+        if (image_range) {
+            ga->bounds[n++] = *image_range;
+        }
+    }
+
+    /* Add any high COMMPAGE not covered by reserved_va. */
+    if (commpage_range && reserved_va < commpage_range->hi) {
+        ga->bounds[n].lo = commpage_range->lo & qemu_real_host_page_mask();
+        ga->bounds[n].hi = commpage_range->hi;
+        n++;
+    }
+
+    ga->nbounds = n;
+    return true;
+}
+
+static void pgb_fail_in_use(const char *image_name)
+{
+    error_report("%s: requires virtual address space that is in use "
+                 "(omit the -B option or choose a different value)",
+                 image_name);
+    exit(EXIT_FAILURE);
+}
+
+static void pgb_fixed(const char *image_name, const PGBRange *image_range,
+                      const PGBRange *commpage_range, uintptr_t align)
+{
+    PGBAddrs ga;
+    uintptr_t brk = (uintptr_t)sbrk(0);
+
+    if (!QEMU_IS_ALIGNED(guest_base, align)) {
+        fprintf(stderr, "Requested guest base %p does not satisfy "
+                "host minimum alignment (0x%" PRIxPTR ")\n",
+                (void *)guest_base, align);
+        exit(EXIT_FAILURE);
+    }
+
+    if (!pgb_addr_set(&ga, image_range, commpage_range, !guest_base)
+        || !pgb_try_mmap_set(&ga, guest_base, brk)) {
+        pgb_fail_in_use(image_name);
+    }
+}
+
+/**
+ * pgb_find_fallback:
+ *
+ * This is a fallback method for finding holes in the host address space
+ * if we don't have the benefit of being able to access /proc/self/map.
+ * It can potentially take a very long time as we can only dumbly iterate
+ * up the host address space seeing if the allocation would work.
+ */
+static uintptr_t pgb_find_fallback(const PGBAddrs *ga, uintptr_t align,
+                                   uintptr_t brk)
+{
+    /* TODO: come up with a better estimate of how much to skip. */
+    uintptr_t skip = sizeof(uintptr_t) == 4 ? MiB : GiB;
+
+    for (uintptr_t base = skip; ; base += skip) {
+        base = ROUND_UP(base, align);
+        if (pgb_try_mmap_set(ga, base, brk)) {
+            return base;
+        }
+        if (base >= -skip) {
+            return -1;
+        }
+    }
+}
+
+static uintptr_t pgb_try_itree(const PGBAddrs *ga, uintptr_t base,
+                               IntervalTreeRoot *root)
+{
+    for (int i = ga->nbounds - 1; i >= 0; --i) {
+        uintptr_t s = base + ga->bounds[i].lo;
+        uintptr_t l = base + ga->bounds[i].hi;
+        IntervalTreeNode *n;
+
+        if (l < s) {
+            /* Wraparound. Skip to advance S to mmap_min_addr. */
+            return mmap_min_addr - s;
+        }
+
+        n = interval_tree_iter_first(root, s, l);
+        if (n != NULL) {
+            /* Conflict.  Skip to advance S to LAST + 1. */
+            return n->last - s + 1;
+        }
+    }
+    return 0;  /* success */
+}
+
+static uintptr_t pgb_find_itree(const PGBAddrs *ga, IntervalTreeRoot *root,
+                                uintptr_t align, uintptr_t brk)
+{
+    uintptr_t last = sizeof(uintptr_t) == 4 ? MiB : GiB;
+    uintptr_t base, skip;
+
+    while (true) {
+        base = ROUND_UP(last, align);
+        if (base < last) {
+            return -1;
+        }
+
+        skip = pgb_try_itree(ga, base, root);
+        if (skip == 0) {
+            break;
+        }
+
+        last = base + skip;
+        if (last < base) {
+            return -1;
+        }
+    }
+
+    /*
+     * We've chosen 'base' based on holes in the interval tree,
+     * but we don't yet know if it is a valid host address.
+     * Because it is the first matching hole, if the host addresses
+     * are invalid we know there are no further matches.
+     */
+    return pgb_try_mmap_set(ga, base, brk) ? base : -1;
+}
+
+static void pgb_dynamic(const char *image_name, const PGBRange *image_range,
+                        const PGBRange *commpage_range, uintptr_t align)
+{
+    IntervalTreeRoot *root;
+    uintptr_t brk, ret;
+    PGBAddrs ga;
+
+    /* Try the identity map first. */
+    if (pgb_addr_set(&ga, image_range, commpage_range, true)) {
+        brk = (uintptr_t)sbrk(0);
+        if (pgb_try_mmap_set(&ga, 0, brk)) {
+            guest_base = 0;
+            return;
+        }
+    }
+
+    /*
+     * Rebuild the address set for non-identity map.
+     * This differs in the mapping of the guest NULL page.
+     */
+    pgb_addr_set(&ga, image_range, commpage_range, false);
+
+    root = read_self_maps();
+
+    /* Read brk after we've read the maps, which will malloc. */
+    brk = (uintptr_t)sbrk(0);
+
+    if (!root) {
+        ret = pgb_find_fallback(&ga, align, brk);
+    } else {
+        /*
+         * Reserve the area close to the host brk.
+         * This will be freed with the rest of the tree.
+         */
+        IntervalTreeNode *b = g_new0(IntervalTreeNode, 1);
+        b->start = brk;
+        b->last = brk + 16 * MiB - 1;
+        interval_tree_insert(b, root);
+
+        ret = pgb_find_itree(&ga, root, align, brk);
+        free_self_maps(root);
+    }
+
+    if (ret == -1) {
+        int w = target_long_bits() / 4;
+
+        error_report("%s: Unable to find a guest_base to satisfy all "
+                     "guest address mapping requirements", image_name);
+
+        for (int i = 0; i < ga.nbounds; ++i) {
+            error_printf("  %0*" VADDR_PRIx "-%0*" VADDR_PRIx "\n",
+                         w, ga.bounds[i].lo,
+                         w, ga.bounds[i].hi);
+        }
+        exit(EXIT_FAILURE);
+    }
+    guest_base = ret;
+}
+
+void probe_guest_base(const char *image_name, const PGBRange *image_range,
+                      const PGBRange *commpage_range)
+{
+    /* In order to use host shmat, we must be able to honor SHMLBA.  */
+    uintptr_t align = MAX(SHMLBA, TARGET_PAGE_SIZE);
+
+    /* Sanity check the guest binary. */
+    if (reserved_va && image_range && image_range->hi > reserved_va) {
+        error_report("%s: requires more than reserved virtual "
+                     "address space (0x%" VADDR_PRIx " > 0x%lx)",
+                     image_name, image_range->hi, reserved_va);
+        exit(EXIT_FAILURE);
+    }
+
+    if (have_guest_base) {
+        pgb_fixed(image_name, image_range, commpage_range, align);
+    } else {
+        pgb_dynamic(image_name, image_range, commpage_range, align);
+    }
+
+    assert(QEMU_IS_ALIGNED(guest_base, align));
+    qemu_log_mask(CPU_LOG_PAGE, "Locating guest address space "
+                  "@ 0x%" PRIx64 "\n", (uint64_t)guest_base);
+}
diff --git a/util/selfmap.c b/common-user/selfmap.c
similarity index 68%
rename from util/selfmap.c
rename to common-user/selfmap.c
index 483cb61..139dae1 100644
--- a/util/selfmap.c
+++ b/common-user/selfmap.c
@@ -8,10 +8,15 @@
 
 #include "qemu/osdep.h"
 #include "qemu/cutils.h"
-#include "qemu/selfmap.h"
+#include "user/selfmap.h"
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#endif
 
 IntervalTreeRoot *read_self_maps(void)
 {
+#ifdef __linux__
     IntervalTreeRoot *root;
     gchar *maps, **lines;
     guint i, nlines;
@@ -80,6 +85,48 @@
     g_free(maps);
 
     return root;
+#elif defined(__FreeBSD__)
+    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid() };
+    size_t len = 0;
+    g_autofree void *buf = NULL;
+    IntervalTreeRoot *root;
+
+    /* Probe for buffer size. */
+    if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) < 0) {
+        return NULL;
+    }
+
+    buf = g_malloc(len);
+    if (sysctl(mib, ARRAY_SIZE(mib), buf, &len, NULL, 0) < 0) {
+        return NULL;
+    }
+
+    root = g_new0(IntervalTreeRoot, 1);
+
+    for (size_t i = 0; i < len; ) {
+        struct kinfo_vmentry *k = buf + i;
+        MapInfo *e = g_new0(MapInfo, 1);
+
+        e->itree.start = k->kve_start;
+        e->itree.last = k->kve_end - 1;
+
+        /*
+         * TODO: The rest of the fields in MapInfo are used by linux-user
+         * for the implementation of open_self_maps().  These fields are
+         * quite specific to the textual format of /proc/self/maps.
+         *
+         * We may need something different to emulate KERN_PROC_VMMAP
+         * in bsd-user, but so far they're unused -- leave them zeroed.
+         */
+
+        interval_tree_insert(&e->itree, root);
+        i += k->kve_structsize;
+    }
+
+    return root;
+#else
+# error
+#endif
 }
 
 /**
diff --git a/include/user/guest-host.h b/include/user/guest-host.h
index ef83ad8..506efc0 100644
--- a/include/user/guest-host.h
+++ b/include/user/guest-host.h
@@ -10,6 +10,7 @@
 
 #include "exec/vaddr.h"
 #include "user/guest-base.h"
+#include "hw/core/cpu.h"
 #include "accel/tcg/cpu-ops.h"
 
 /*
diff --git a/include/user/mmap-min-addr.h b/include/user/mmap-min-addr.h
new file mode 100644
index 0000000..ded0b69
--- /dev/null
+++ b/include/user/mmap-min-addr.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef USER_MMAP_MIN_ADDR_H
+#define USER_MMAP_MIN_ADDR_H
+
+#ifndef CONFIG_USER_ONLY
+#error Cannot include this header from system emulation
+#endif
+
+extern uintptr_t mmap_min_addr;
+
+#endif
diff --git a/include/user/probe-guest-base.h b/include/user/probe-guest-base.h
new file mode 100644
index 0000000..d7492ef
--- /dev/null
+++ b/include/user/probe-guest-base.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef USER_PROBE_GUEST_BASE_H
+#define USER_PROBE_GUEST_BASE_H
+
+#ifndef CONFIG_USER_ONLY
+#error Cannot include this header from system emulation
+#endif
+
+#include "exec/vaddr.h"
+
+typedef struct PGBRange {
+    vaddr lo;
+    vaddr hi;
+} PGBRange;
+
+/**
+ * probe_guest_base:
+ * @image_name: the executable being loaded
+ * @image_range: the fixed addresses within the executable
+ *
+ * Creates the initial guest address space in the host memory space.
+ *
+ * If @image_range is NULL, then no address in the executable is fixed,
+ * i.e. it is fully relocatable.
+ *
+ * This function will not return if a valid value for guest_base
+ * cannot be chosen.  On return, the executable loader can expect
+ *
+ *    target_mmap(i->lo, i->hi - i->lo + 1, ...)
+ *
+ * to succeed.
+ */
+void probe_guest_base(const char *image_name, const PGBRange *image_range,
+                      const PGBRange *commpage_range);
+
+#endif
diff --git a/include/qemu/selfmap.h b/include/user/selfmap.h
similarity index 100%
rename from include/qemu/selfmap.h
rename to include/user/selfmap.h
diff --git a/linux-user/arm/elfload.c b/linux-user/arm/elfload.c
index 13b589e..ce69bf0 100644
--- a/linux-user/arm/elfload.c
+++ b/linux-user/arm/elfload.c
@@ -220,7 +220,7 @@
         return true;
     }
 
-    commpage = HI_COMMPAGE & -host_page_size;
+    commpage = COMMPAGE & -host_page_size;
     want = g2h_untagged(commpage);
     addr = mmap(want, host_page_size, PROT_READ | PROT_WRITE,
                 MAP_ANONYMOUS | MAP_PRIVATE |
diff --git a/linux-user/arm/target_elf.h b/linux-user/arm/target_elf.h
index 12cdc8e..531b486 100644
--- a/linux-user/arm/target_elf.h
+++ b/linux-user/arm/target_elf.h
@@ -20,7 +20,7 @@
 #define HAVE_ELF_CORE_DUMP      1
 #define HAVE_VDSO_IMAGE_INFO    1
 
-#define HI_COMMPAGE             ((intptr_t)0xffff0f00u)
+#define COMMPAGE                ((intptr_t)0xffff0f00u)
 
 /*
  * See linux kernel: arch/arm/include/asm/elf.h, where
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index 8049c8a..e7c56af 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -4,7 +4,6 @@
 
 #include <sys/prctl.h>
 #include <sys/resource.h>
-#include <sys/shm.h>
 
 #include "qemu.h"
 #include "user/tswap-target.h"
@@ -13,7 +12,6 @@
 #include "exec/mmap-lock.h"
 #include "exec/translation-block.h"
 #include "exec/tswap.h"
-#include "user/guest-base.h"
 #include "user-internals.h"
 #include "signal-common.h"
 #include "loader.h"
@@ -24,7 +22,6 @@
 #include "qemu/queue.h"
 #include "qemu/guest-random.h"
 #include "qemu/units.h"
-#include "qemu/selfmap.h"
 #include "qemu/lockable.h"
 #include "qapi/error.h"
 #include "qemu/error-report.h"
@@ -757,372 +754,26 @@
     return sp;
 }
 
-#if defined(HI_COMMPAGE)
-#define LO_COMMPAGE -1
-#elif defined(LO_COMMPAGE)
-#define HI_COMMPAGE 0
+void linux_probe_guest_base(const char *image_name,
+                            const PGBRange *image_range)
+{
+#ifdef COMMPAGE
+    const PGBRange * const commpage_range = &(PGBRange){
+        COMMPAGE, COMMPAGE + TARGET_PAGE_SIZE - 1
+    };
 #else
-#define HI_COMMPAGE 0
-#define LO_COMMPAGE -1
-#ifndef HAVE_GUEST_COMMPAGE
-bool init_guest_commpage(void) { return true; }
-#endif
+    const PGBRange * const commpage_range = NULL;
 #endif
 
-/**
- * pgb_try_mmap:
- * @addr: host start address
- * @addr_last: host last address
- * @keep: do not unmap the probe region
- *
- * Return 1 if [@addr, @addr_last] is not mapped in the host,
- * return 0 if it is not available to map, and -1 on mmap error.
- * If @keep, the region is left mapped on success, otherwise unmapped.
- */
-static int pgb_try_mmap(uintptr_t addr, uintptr_t addr_last, bool keep)
-{
-    size_t size = addr_last - addr + 1;
-    void *p = mmap((void *)addr, size, PROT_NONE,
-                   MAP_ANONYMOUS | MAP_PRIVATE |
-                   MAP_NORESERVE | MAP_FIXED_NOREPLACE, -1, 0);
-    int ret;
-
-    if (p == MAP_FAILED) {
-        return errno == EEXIST ? 0 : -1;
-    }
-    ret = p == (void *)addr;
-    if (!keep || !ret) {
-        munmap(p, size);
-    }
-    return ret;
-}
-
-/**
- * pgb_try_mmap_skip_brk(uintptr_t addr, uintptr_t size, uintptr_t brk)
- * @addr: host address
- * @addr_last: host last address
- * @brk: host brk
- *
- * Like pgb_try_mmap, but additionally reserve some memory following brk.
- */
-static int pgb_try_mmap_skip_brk(uintptr_t addr, uintptr_t addr_last,
-                                 uintptr_t brk, bool keep)
-{
-    uintptr_t brk_last = brk + 16 * MiB - 1;
-
-    /* Do not map anything close to the host brk. */
-    if (addr <= brk_last && brk <= addr_last) {
-        return 0;
-    }
-    return pgb_try_mmap(addr, addr_last, keep);
-}
-
-/**
- * pgb_try_mmap_set:
- * @ga: set of guest addrs
- * @base: guest_base
- * @brk: host brk
- *
- * Return true if all @ga can be mapped by the host at @base.
- * On success, retain the mapping at index 0 for reserved_va.
- */
-
-typedef struct PGBAddrs {
-    uintptr_t bounds[3][2]; /* start/last pairs */
-    int nbounds;
-} PGBAddrs;
-
-static bool pgb_try_mmap_set(const PGBAddrs *ga, uintptr_t base, uintptr_t brk)
-{
-    for (int i = ga->nbounds - 1; i >= 0; --i) {
-        if (pgb_try_mmap_skip_brk(ga->bounds[i][0] + base,
-                                  ga->bounds[i][1] + base,
-                                  brk, i == 0 && reserved_va) <= 0) {
-            return false;
-        }
-    }
-    return true;
-}
-
-/**
- * pgb_addr_set:
- * @ga: output set of guest addrs
- * @guest_loaddr: guest image low address
- * @guest_hiaddr: guest image high address
- * @identity: create for identity mapping
- *
- * Fill in @ga with the image, COMMPAGE and NULL page.
- */
-static bool pgb_addr_set(PGBAddrs *ga, abi_ulong guest_loaddr,
-                         abi_ulong guest_hiaddr, bool try_identity)
-{
-    int n;
-
-    /*
-     * With a low commpage, or a guest mapped very low,
-     * we may not be able to use the identity map.
-     */
-    if (try_identity) {
-        if (LO_COMMPAGE != -1 && LO_COMMPAGE < mmap_min_addr) {
-            return false;
-        }
-        if (guest_loaddr != 0 && guest_loaddr < mmap_min_addr) {
-            return false;
-        }
-    }
-
-    memset(ga, 0, sizeof(*ga));
-    n = 0;
-
-    if (reserved_va) {
-        ga->bounds[n][0] = try_identity ? mmap_min_addr : 0;
-        ga->bounds[n][1] = reserved_va;
-        n++;
-        /* LO_COMMPAGE and NULL handled by reserving from 0. */
-    } else {
-        /* Add any LO_COMMPAGE or NULL page. */
-        if (LO_COMMPAGE != -1) {
-            ga->bounds[n][0] = 0;
-            ga->bounds[n][1] = LO_COMMPAGE + TARGET_PAGE_SIZE - 1;
-            n++;
-        } else if (!try_identity) {
-            ga->bounds[n][0] = 0;
-            ga->bounds[n][1] = TARGET_PAGE_SIZE - 1;
-            n++;
-        }
-
-        /* Add the guest image for ET_EXEC. */
-        if (guest_loaddr) {
-            ga->bounds[n][0] = guest_loaddr;
-            ga->bounds[n][1] = guest_hiaddr;
-            n++;
-        }
-    }
-
-    /*
-     * Temporarily disable
-     *   "comparison is always false due to limited range of data type"
-     * due to comparison between unsigned and (possible) 0.
-     */
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wtype-limits"
-
-    /* Add any HI_COMMPAGE not covered by reserved_va. */
-    if (reserved_va < HI_COMMPAGE) {
-        ga->bounds[n][0] = HI_COMMPAGE & qemu_real_host_page_mask();
-        ga->bounds[n][1] = HI_COMMPAGE + TARGET_PAGE_SIZE - 1;
-        n++;
-    }
-
-#pragma GCC diagnostic pop
-
-    ga->nbounds = n;
-    return true;
-}
-
-static void pgb_fail_in_use(const char *image_name)
-{
-    error_report("%s: requires virtual address space that is in use "
-                 "(omit the -B option or choose a different value)",
-                 image_name);
-    exit(EXIT_FAILURE);
-}
-
-static void pgb_fixed(const char *image_name, uintptr_t guest_loaddr,
-                      uintptr_t guest_hiaddr, uintptr_t align)
-{
-    PGBAddrs ga;
-    uintptr_t brk = (uintptr_t)sbrk(0);
-
-    if (!QEMU_IS_ALIGNED(guest_base, align)) {
-        fprintf(stderr, "Requested guest base %p does not satisfy "
-                "host minimum alignment (0x%" PRIxPTR ")\n",
-                (void *)guest_base, align);
-        exit(EXIT_FAILURE);
-    }
-
-    if (!pgb_addr_set(&ga, guest_loaddr, guest_hiaddr, !guest_base)
-        || !pgb_try_mmap_set(&ga, guest_base, brk)) {
-        pgb_fail_in_use(image_name);
-    }
-}
-
-/**
- * pgb_find_fallback:
- *
- * This is a fallback method for finding holes in the host address space
- * if we don't have the benefit of being able to access /proc/self/map.
- * It can potentially take a very long time as we can only dumbly iterate
- * up the host address space seeing if the allocation would work.
- */
-static uintptr_t pgb_find_fallback(const PGBAddrs *ga, uintptr_t align,
-                                   uintptr_t brk)
-{
-    /* TODO: come up with a better estimate of how much to skip. */
-    uintptr_t skip = sizeof(uintptr_t) == 4 ? MiB : GiB;
-
-    for (uintptr_t base = skip; ; base += skip) {
-        base = ROUND_UP(base, align);
-        if (pgb_try_mmap_set(ga, base, brk)) {
-            return base;
-        }
-        if (base >= -skip) {
-            return -1;
-        }
-    }
-}
-
-static uintptr_t pgb_try_itree(const PGBAddrs *ga, uintptr_t base,
-                               IntervalTreeRoot *root)
-{
-    for (int i = ga->nbounds - 1; i >= 0; --i) {
-        uintptr_t s = base + ga->bounds[i][0];
-        uintptr_t l = base + ga->bounds[i][1];
-        IntervalTreeNode *n;
-
-        if (l < s) {
-            /* Wraparound. Skip to advance S to mmap_min_addr. */
-            return mmap_min_addr - s;
-        }
-
-        n = interval_tree_iter_first(root, s, l);
-        if (n != NULL) {
-            /* Conflict.  Skip to advance S to LAST + 1. */
-            return n->last - s + 1;
-        }
-    }
-    return 0;  /* success */
-}
-
-static uintptr_t pgb_find_itree(const PGBAddrs *ga, IntervalTreeRoot *root,
-                                uintptr_t align, uintptr_t brk)
-{
-    uintptr_t last = sizeof(uintptr_t) == 4 ? MiB : GiB;
-    uintptr_t base, skip;
-
-    while (true) {
-        base = ROUND_UP(last, align);
-        if (base < last) {
-            return -1;
-        }
-
-        skip = pgb_try_itree(ga, base, root);
-        if (skip == 0) {
-            break;
-        }
-
-        last = base + skip;
-        if (last < base) {
-            return -1;
-        }
-    }
-
-    /*
-     * We've chosen 'base' based on holes in the interval tree,
-     * but we don't yet know if it is a valid host address.
-     * Because it is the first matching hole, if the host addresses
-     * are invalid we know there are no further matches.
-     */
-    return pgb_try_mmap_set(ga, base, brk) ? base : -1;
-}
-
-static void pgb_dynamic(const char *image_name, uintptr_t guest_loaddr,
-                        uintptr_t guest_hiaddr, uintptr_t align)
-{
-    IntervalTreeRoot *root;
-    uintptr_t brk, ret;
-    PGBAddrs ga;
-
-    /* Try the identity map first. */
-    if (pgb_addr_set(&ga, guest_loaddr, guest_hiaddr, true)) {
-        brk = (uintptr_t)sbrk(0);
-        if (pgb_try_mmap_set(&ga, 0, brk)) {
-            guest_base = 0;
-            return;
-        }
-    }
-
-    /*
-     * Rebuild the address set for non-identity map.
-     * This differs in the mapping of the guest NULL page.
-     */
-    pgb_addr_set(&ga, guest_loaddr, guest_hiaddr, false);
-
-    root = read_self_maps();
-
-    /* Read brk after we've read the maps, which will malloc. */
-    brk = (uintptr_t)sbrk(0);
-
-    if (!root) {
-        ret = pgb_find_fallback(&ga, align, brk);
-    } else {
-        /*
-         * Reserve the area close to the host brk.
-         * This will be freed with the rest of the tree.
-         */
-        IntervalTreeNode *b = g_new0(IntervalTreeNode, 1);
-        b->start = brk;
-        b->last = brk + 16 * MiB - 1;
-        interval_tree_insert(b, root);
-
-        ret = pgb_find_itree(&ga, root, align, brk);
-        free_self_maps(root);
-    }
-
-    if (ret == -1) {
-        int w = TARGET_LONG_BITS / 4;
-
-        error_report("%s: Unable to find a guest_base to satisfy all "
-                     "guest address mapping requirements", image_name);
-
-        for (int i = 0; i < ga.nbounds; ++i) {
-            error_printf("  %0*" PRIx64 "-%0*" PRIx64 "\n",
-                         w, (uint64_t)ga.bounds[i][0],
-                         w, (uint64_t)ga.bounds[i][1]);
-        }
-        exit(EXIT_FAILURE);
-    }
-    guest_base = ret;
-}
-
-void probe_guest_base(const char *image_name, abi_ulong guest_loaddr,
-                      abi_ulong guest_hiaddr)
-{
-    /* In order to use host shmat, we must be able to honor SHMLBA.  */
-    uintptr_t align = MAX(SHMLBA, TARGET_PAGE_SIZE);
-
-    /* Sanity check the guest binary. */
-    if (reserved_va) {
-        if (guest_hiaddr > reserved_va) {
-            error_report("%s: requires more than reserved virtual "
-                         "address space (0x%" PRIx64 " > 0x%lx)",
-                         image_name, (uint64_t)guest_hiaddr, reserved_va);
-            exit(EXIT_FAILURE);
-        }
-    } else {
-        if (guest_hiaddr != (uintptr_t)guest_hiaddr) {
-            error_report("%s: requires more virtual address space "
-                         "than the host can provide (0x%" PRIx64 ")",
-                         image_name, (uint64_t)guest_hiaddr + 1);
-            exit(EXIT_FAILURE);
-        }
-    }
-
-    if (have_guest_base) {
-        pgb_fixed(image_name, guest_loaddr, guest_hiaddr, align);
-    } else {
-        pgb_dynamic(image_name, guest_loaddr, guest_hiaddr, align);
-    }
+    probe_guest_base(image_name, image_range, commpage_range);
 
     /* Reserve and initialize the commpage. */
+#if defined(COMMPAGE) || defined(HAVE_GUEST_COMMPAGE)
     if (!init_guest_commpage()) {
         /* We have already probed for the commpage being free. */
         g_assert_not_reached();
     }
-
-    assert(QEMU_IS_ALIGNED(guest_base, align));
-    qemu_log_mask(CPU_LOG_PAGE, "Locating guest address space "
-                  "@ 0x%" PRIx64 "\n", (uint64_t)guest_base);
+#endif
 }
 
 enum {
@@ -1278,7 +929,8 @@
                            char **pinterp_name)
 {
     g_autofree struct elf_phdr *phdr = NULL;
-    abi_ulong load_addr, load_bias, loaddr, hiaddr, error, align;
+    PGBRange range = { -1, 0 };
+    abi_ulong load_addr, load_bias, error, align;
     size_t reserve_size, align_size;
     int i, prot_exec;
     Error *err = NULL;
@@ -1318,19 +970,18 @@
      * Find the maximum size of the image and allocate an appropriate
      * amount of memory to handle that.  Locate the interpreter, if any.
      */
-    loaddr = -1, hiaddr = 0;
     align = 0;
     info->exec_stack = EXSTACK_DEFAULT;
     for (i = 0; i < ehdr->e_phnum; ++i) {
         struct elf_phdr *eppnt = phdr + i;
         if (eppnt->p_type == PT_LOAD) {
             abi_ulong a = eppnt->p_vaddr & TARGET_PAGE_MASK;
-            if (a < loaddr) {
-                loaddr = a;
+            if (a < range.lo) {
+                range.lo = a;
             }
             a = eppnt->p_vaddr + eppnt->p_memsz - 1;
-            if (a > hiaddr) {
-                hiaddr = a;
+            if (a > range.hi) {
+                range.hi = a;
             }
             ++info->nsegs;
             align |= eppnt->p_align;
@@ -1361,7 +1012,7 @@
         }
     }
 
-    load_addr = loaddr;
+    load_addr = range.lo;
 
     align = pow2ceil(align);
 
@@ -1371,13 +1022,10 @@
              * Make sure that the low address does not conflict with
              * MMAP_MIN_ADDR or the QEMU application itself.
              */
-            probe_guest_base(image_name, loaddr, hiaddr);
+            linux_probe_guest_base(image_name, &range);
         } else {
-            /*
-             * The binary is dynamic, but we still need to
-             * select guest_base.  In this case we pass a size.
-             */
-            probe_guest_base(image_name, 0, hiaddr - loaddr);
+            /* The binary is dynamic; we still need to select guest_base. */
+            linux_probe_guest_base(image_name, NULL);
 
             /*
              * Avoid collision with the loader by providing a different
@@ -1414,7 +1062,7 @@
      * In both cases, we will overwrite pages in this range with mappings
      * from the executable.
      */
-    reserve_size = (size_t)hiaddr - loaddr + 1;
+    reserve_size = range.hi - range.lo + 1;
     align_size = reserve_size;
 
     if (ehdr->e_type != ET_EXEC && align > qemu_real_host_page_size()) {
@@ -1443,7 +1091,7 @@
         load_addr = align_addr;
     }
 
-    load_bias = load_addr - loaddr;
+    load_bias = load_addr - range.lo;
 
     if (elf_is_fdpic(ehdr)) {
         struct elf32_fdpic_loadseg *loadsegs = info->loadsegs =
@@ -1480,7 +1128,7 @@
     info->start_data = -1;
     info->end_data = 0;
     /* Usual start for brk is after all sections of the main executable. */
-    info->brk = TARGET_PAGE_ALIGN(hiaddr + load_bias);
+    info->brk = TARGET_PAGE_ALIGN(range.hi + load_bias);
     info->elf_flags = ehdr->e_flags;
 #ifdef TARGET_MIPS
     info->use_k0_tls = (ehdr->e_flags & EF_MIPS_MACH) == EF_MIPS_MACH_OCTEON;
diff --git a/linux-user/flatload.c b/linux-user/flatload.c
index 4beb3ed..a551f08 100644
--- a/linux-user/flatload.c
+++ b/linux-user/flatload.c
@@ -261,8 +261,7 @@
     /*
      * Allocate the address space.
      */
-    probe_guest_base(bprm->filename, 0,
-                     text_len + data_len + extra + indx_len - 1);
+    linux_probe_guest_base(bprm->filename, NULL);
 
     /*
      * there are a couple of cases here,  the separate code/data
diff --git a/linux-user/hppa/elfload.c b/linux-user/hppa/elfload.c
index 2ebb992..ff4301b 100644
--- a/linux-user/hppa/elfload.c
+++ b/linux-user/hppa/elfload.c
@@ -36,7 +36,7 @@
     if (!reserved_va) {
         void *want, *addr;
 
-        want = g2h_untagged(LO_COMMPAGE);
+        want = g2h_untagged(COMMPAGE);
         addr = mmap(want, TARGET_PAGE_SIZE, PROT_NONE,
                     MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);
         if (addr == MAP_FAILED) {
@@ -55,7 +55,7 @@
      * and implement syscalls.  Here, simply mark the page executable.
      * Special case the entry points during translation (see do_page_zero).
      */
-    page_set_flags(LO_COMMPAGE, LO_COMMPAGE | ~TARGET_PAGE_MASK,
+    page_set_flags(COMMPAGE, COMMPAGE | ~TARGET_PAGE_MASK,
                    PAGE_EXEC | PAGE_VALID, PAGE_VALID);
     return true;
 }
diff --git a/linux-user/hppa/target_elf.h b/linux-user/hppa/target_elf.h
index e1c5033..22547b1 100644
--- a/linux-user/hppa/target_elf.h
+++ b/linux-user/hppa/target_elf.h
@@ -34,7 +34,7 @@
     abi_ulong pad[16];     /* pad to 80 elements                 [64..79] */
 } target_elf_gregset_t;
 
-#define LO_COMMPAGE             0
+#define COMMPAGE                0
 #define STACK_GROWS_DOWN        0
 #define STACK_ALIGNMENT         64
 #define VDSO_HEADER             "vdso.c.inc"
diff --git a/linux-user/main.c b/linux-user/main.c
index c08c73f..60a695b 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -39,6 +39,7 @@
 #include "qemu/module.h"
 #include "qemu/plugin.h"
 #include "user/guest-base.h"
+#include "user/mmap-min-addr.h"
 #include "user/page-protection.h"
 #include "exec/gdbstub.h"
 #include "gdbstub/user.h"
@@ -78,9 +79,6 @@
 static const char *cpu_model;
 static const char *cpu_type;
 static const char *seed_optarg;
-unsigned long mmap_min_addr;
-uintptr_t guest_base;
-bool have_guest_base;
 
 /*
  * Used to implement backwards-compatibility for the `-strace`, and
@@ -914,35 +912,8 @@
     target_environ = envlist_to_environ(envlist, NULL);
     envlist_free(envlist);
 
-    /*
-     * Read in mmap_min_addr kernel parameter.  This value is used
-     * When loading the ELF image to determine whether guest_base
-     * is needed.  It is also used in mmap_find_vma.
-     */
-    {
-        FILE *fp;
-
-        if ((fp = fopen("/proc/sys/vm/mmap_min_addr", "r")) != NULL) {
-            unsigned long tmp;
-            if (fscanf(fp, "%lu", &tmp) == 1 && tmp != 0) {
-                mmap_min_addr = MAX(tmp, host_page_size);
-                qemu_log_mask(CPU_LOG_PAGE, "host mmap_min_addr=0x%lx\n",
-                              mmap_min_addr);
-            }
-            fclose(fp);
-        }
-    }
-
-    /*
-     * We prefer to not make NULL pointers accessible to QEMU.
-     * If we're in a chroot with no /proc, fall back to 1 page.
-     */
-    if (mmap_min_addr == 0) {
-        mmap_min_addr = host_page_size;
-        qemu_log_mask(CPU_LOG_PAGE,
-                      "host mmap_min_addr=0x%lx (fallback)\n",
-                      mmap_min_addr);
-    }
+    qemu_log_mask(CPU_LOG_PAGE, "host mmap_min_addr=0x%" PRIxPTR "\n",
+                  mmap_min_addr);
 
     /*
      * Prepare copy of argv vector for target.
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 55277f7..cc0c2ee 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -24,6 +24,7 @@
 #include "exec/mmap-lock.h"
 #include "qemu.h"
 #include "user/page-protection.h"
+#include "user/mmap-min-addr.h"
 #include "user-internals.h"
 #include "user-mmap.h"
 #include "target_mman.h"
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index d257fb9..b3572a6 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -141,7 +141,7 @@
 #include "user/safe-syscall.h"
 #include "user/signal.h"
 #include "qemu/guest-random.h"
-#include "qemu/selfmap.h"
+#include "user/selfmap.h"
 #include "special-errno.h"
 #include "qapi/error.h"
 #include "fd-trans.h"
diff --git a/linux-user/user-internals.h b/linux-user/user-internals.h
index 0380d44..e7fbddd 100644
--- a/linux-user/user-internals.h
+++ b/linux-user/user-internals.h
@@ -22,6 +22,7 @@
 #include "qemu/log.h"
 #include "exec/tb-flush.h"
 #include "exec/translation-block.h"
+#include "user/probe-guest-base.h"
 
 extern char *exec_path;
 extern char real_exec_path[PATH_MAX];
@@ -29,7 +30,6 @@
 void task_settid(TaskState *);
 void stop_all_tasks(void);
 extern const char *qemu_uname_release;
-extern unsigned long mmap_min_addr;
 
 typedef struct IOCTLEntry IOCTLEntry;
 
@@ -76,26 +76,23 @@
 void fork_end(pid_t pid);
 
 /**
- * probe_guest_base:
+ * linux_probe_guest_base:
  * @image_name: the executable being loaded
- * @loaddr: the lowest fixed address within the executable
- * @hiaddr: the highest fixed address within the executable
+ * @image_range: the fixed addresses within the executable
  *
  * Creates the initial guest address space in the host memory space.
  *
- * If @loaddr == 0, then no address in the executable is fixed, i.e.
- * it is fully relocatable.  In that case @hiaddr is the size of the
- * executable minus one.
+ * If @image_range is NULL, then no address in the executable is fixed,
+ * i.e. it is fully relocatable.
  *
  * This function will not return if a valid value for guest_base
  * cannot be chosen.  On return, the executable loader can expect
  *
- *    target_mmap(loaddr, hiaddr - loaddr + 1, ...)
+ *    target_mmap(i->lo, i->hi - i->lo + 1, ...)
  *
  * to succeed.
  */
-void probe_guest_base(const char *image_name,
-                      abi_ulong loaddr, abi_ulong hiaddr);
+void linux_probe_guest_base(const char *image_name, const PGBRange *image_range);
 
 /* syscall.c */
 int host_to_target_waitstatus(int status);
diff --git a/util/meson.build b/util/meson.build
index e29cbd9..fa174c0 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -67,10 +67,6 @@
 util_ss.add(files('interval-tree.c'))
 util_ss.add(files('lockcnt.c'))
 
-if have_user
-  util_ss.add(files('selfmap.c'))
-endif
-
 if have_system
   util_ss.add(files('crc-ccitt.c'))
   util_ss.add(when: gio, if_true: files('dbus.c'))