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'))