common-user: Move probe_guest_base from linux-user
Prepare to share probe_guest_base with bsd-user. Create a linux_probe_guest_base wrapper with the portions of probe_guest_base that are linux specific: managing the commpage for various targets. Reviewed-by: Warner Losh <imp@bsdimp.com> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
@@ -6,6 +6,7 @@ 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',
|
||||
|
||||
343
common-user/probe-guest-base.c
Normal file
343
common-user/probe-guest-base.c
Normal file
@@ -0,0 +1,343 @@
|
||||
/* 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-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
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
37
include/user/probe-guest-base.h
Normal file
37
include/user/probe-guest-base.h
Normal file
@@ -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
|
||||
@@ -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,8 +12,6 @@
|
||||
#include "exec/mmap-lock.h"
|
||||
#include "exec/translation-block.h"
|
||||
#include "exec/tswap.h"
|
||||
#include "user/guest-base.h"
|
||||
#include "user/mmap-min-addr.h"
|
||||
#include "user-internals.h"
|
||||
#include "signal-common.h"
|
||||
#include "loader.h"
|
||||
@@ -25,7 +22,6 @@
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/guest-random.h"
|
||||
#include "qemu/units.h"
|
||||
#include "user/selfmap.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
@@ -758,310 +754,8 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
|
||||
return sp;
|
||||
}
|
||||
|
||||
#if !defined(COMMPAGE) && !defined(HAVE_GUEST_COMMPAGE)
|
||||
bool init_guest_commpage(void) { return true; }
|
||||
#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 {
|
||||
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)
|
||||
void linux_probe_guest_base(const char *image_name,
|
||||
const PGBRange *image_range)
|
||||
{
|
||||
#ifdef COMMPAGE
|
||||
const PGBRange * const commpage_range = &(PGBRange){
|
||||
@@ -1071,32 +765,15 @@ void probe_guest_base(const char *image_name, const PGBRange *image_range)
|
||||
const PGBRange * const commpage_range = NULL;
|
||||
#endif
|
||||
|
||||
/* 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);
|
||||
}
|
||||
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 {
|
||||
@@ -1345,10 +1022,10 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
|
||||
* Make sure that the low address does not conflict with
|
||||
* MMAP_MIN_ADDR or the QEMU application itself.
|
||||
*/
|
||||
probe_guest_base(image_name, &range);
|
||||
linux_probe_guest_base(image_name, &range);
|
||||
} else {
|
||||
/* The binary is dynamic; we still need to select guest_base. */
|
||||
probe_guest_base(image_name, NULL);
|
||||
linux_probe_guest_base(image_name, NULL);
|
||||
|
||||
/*
|
||||
* Avoid collision with the loader by providing a different
|
||||
|
||||
@@ -261,7 +261,7 @@ static int load_flat_file(struct linux_binprm * bprm,
|
||||
/*
|
||||
* Allocate the address space.
|
||||
*/
|
||||
probe_guest_base(bprm->filename, NULL);
|
||||
linux_probe_guest_base(bprm->filename, NULL);
|
||||
|
||||
/*
|
||||
* there are a couple of cases here, the separate code/data
|
||||
|
||||
@@ -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];
|
||||
@@ -74,13 +75,8 @@ void clone_fork_end(bool child);
|
||||
void fork_start(void);
|
||||
void fork_end(pid_t pid);
|
||||
|
||||
typedef struct PGBRange {
|
||||
vaddr lo;
|
||||
vaddr hi;
|
||||
} PGBRange;
|
||||
|
||||
/**
|
||||
* probe_guest_base:
|
||||
* linux_probe_guest_base:
|
||||
* @image_name: the executable being loaded
|
||||
* @image_range: the fixed addresses within the executable
|
||||
*
|
||||
@@ -96,7 +92,7 @@ typedef struct PGBRange {
|
||||
*
|
||||
* to succeed.
|
||||
*/
|
||||
void probe_guest_base(const char *image_name, const PGBRange *image_range);
|
||||
void linux_probe_guest_base(const char *image_name, const PGBRange *image_range);
|
||||
|
||||
/* syscall.c */
|
||||
int host_to_target_waitstatus(int status);
|
||||
|
||||
Reference in New Issue
Block a user