/* 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 /* 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); }