diff options
Diffstat (limited to 'linux-user')
-rw-r--r-- | linux-user/elfload.c | 2 | ||||
-rw-r--r-- | linux-user/main.c | 63 | ||||
-rw-r--r-- | linux-user/mmap.c | 120 |
3 files changed, 178 insertions, 7 deletions
diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 1f27918beb..2d920f2017 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -1682,7 +1682,7 @@ int load_elf_binary(struct linux_binprm * bprm, struct target_pt_regs * regs, * In case where user has not explicitly set the guest_base, we * probe here that should we set it automatically. */ - if (!have_guest_base) { + if (!(have_guest_base || reserved_va)) { /* * Go through ELF program header table and find the address * range used by loadable segments. Check that this is available on diff --git a/linux-user/main.c b/linux-user/main.c index de1076b0af..0f23fc9cd9 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -44,6 +44,7 @@ unsigned long mmap_min_addr; #if defined(CONFIG_USE_GUEST_BASE) unsigned long guest_base; int have_guest_base; +unsigned long reserved_va; #endif static const char *interp_prefix = CONFIG_QEMU_PREFIX; @@ -2610,6 +2611,7 @@ static void usage(void) "-0 argv0 forces target process argv[0] to be argv0\n" #if defined(CONFIG_USE_GUEST_BASE) "-B address set guest_base address to address\n" + "-R size reserve size bytes for guest virtual address space\n" #endif "\n" "Debug options:\n" @@ -2805,6 +2807,39 @@ int main(int argc, char **argv, char **envp) } else if (!strcmp(r, "B")) { guest_base = strtol(argv[optind++], NULL, 0); have_guest_base = 1; + } else if (!strcmp(r, "R")) { + char *p; + int shift = 0; + reserved_va = strtoul(argv[optind++], &p, 0); + switch (*p) { + case 'k': + case 'K': + shift = 10; + break; + case 'M': + shift = 20; + break; + case 'G': + shift = 30; + break; + } + if (shift) { + unsigned long unshifted = reserved_va; + p++; + reserved_va <<= shift; + if (((reserved_va >> shift) != unshifted) +#if HOST_LONG_BITS > TARGET_VIRT_ADDR_SPACE_BITS + || (reserved_va > (1ul << TARGET_VIRT_ADDR_SPACE_BITS)) +#endif + ) { + fprintf(stderr, "Reserved virtual address too big\n"); + exit(1); + } + } + if (*p) { + fprintf(stderr, "Unrecognised -R size suffix '%s'\n", p); + exit(1); + } #endif } else if (!strcmp(r, "drop-ld-preload")) { (void) envlist_unsetenv(envlist, "LD_PRELOAD"); @@ -2893,6 +2928,34 @@ int main(int argc, char **argv, char **envp) * proper page alignment for guest_base. */ guest_base = HOST_PAGE_ALIGN(guest_base); + + if (reserved_va) { + void *p; + int flags; + + flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE; + if (have_guest_base) { + flags |= MAP_FIXED; + } + p = mmap((void *)guest_base, reserved_va, PROT_NONE, flags, -1, 0); + if (p == MAP_FAILED) { + fprintf(stderr, "Unable to reserve guest address space\n"); + exit(1); + } + guest_base = (unsigned long)p; + /* Make sure the address is properly aligned. */ + if (guest_base & ~qemu_host_page_mask) { + munmap(p, reserved_va); + p = mmap((void *)guest_base, reserved_va + qemu_host_page_size, + PROT_NONE, flags, -1, 0); + if (p == MAP_FAILED) { + fprintf(stderr, "Unable to reserve guest address space\n"); + exit(1); + } + guest_base = HOST_PAGE_ALIGN((unsigned long)p); + } + qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va); + } #endif /* CONFIG_USE_GUEST_BASE */ /* diff --git a/linux-user/mmap.c b/linux-user/mmap.c index fd315aaabb..39da6dfb40 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -216,6 +216,40 @@ static abi_ulong mmap_next_start = TASK_UNMAPPED_BASE; unsigned long last_brk; +/* Subroutine of mmap_find_vma, used when we have pre-allocated a chunk + of guest address space. */ +static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size) +{ + abi_ulong addr; + abi_ulong last_addr; + int prot; + int looped = 0; + + if (size > reserved_va) { + return (abi_ulong)-1; + } + + last_addr = start; + for (addr = start; last_addr + size != addr; addr += qemu_host_page_size) { + if (last_addr + size >= reserved_va + || (abi_ulong)(last_addr + size) < last_addr) { + if (looped) { + return (abi_ulong)-1; + } + last_addr = qemu_host_page_size; + addr = 0; + looped = 1; + continue; + } + prot = page_get_flags(addr); + if (prot) { + last_addr = addr + qemu_host_page_size; + } + } + mmap_next_start = addr; + return last_addr; +} + /* * Find and reserve a free memory area of size 'size'. The search * starts at 'start'. @@ -237,6 +271,10 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size) size = HOST_PAGE_ALIGN(size); + if (reserved_va) { + return mmap_find_vma_reserved(start, size); + } + addr = start; wrapped = repeat = 0; prev = 0; @@ -525,6 +563,47 @@ fail: return -1; } +static void mmap_reserve(abi_ulong start, abi_ulong size) +{ + abi_ulong real_start; + abi_ulong real_end; + abi_ulong addr; + abi_ulong end; + int prot; + + real_start = start & qemu_host_page_mask; + real_end = HOST_PAGE_ALIGN(start + size); + end = start + size; + if (start > real_start) { + /* handle host page containing start */ + prot = 0; + for (addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (real_end == real_start + qemu_host_page_size) { + for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + end = real_end; + } + if (prot != 0) + real_start += qemu_host_page_size; + } + if (end < real_end) { + prot = 0; + for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (prot != 0) + real_end -= qemu_host_page_size; + } + if (real_start != real_end) { + mmap(g2h(real_start), real_end - real_start, PROT_NONE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, + -1, 0); + } +} + int target_munmap(abi_ulong start, abi_ulong len) { abi_ulong end, real_start, real_end, addr; @@ -572,7 +651,11 @@ int target_munmap(abi_ulong start, abi_ulong len) ret = 0; /* unmap what we can */ if (real_start < real_end) { - ret = munmap(g2h(real_start), real_end - real_start); + if (reserved_va) { + mmap_reserve(real_start, real_end - real_start); + } else { + ret = munmap(g2h(real_start), real_end - real_start); + } } if (ret == 0) @@ -590,12 +673,18 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, mmap_lock(); - if (flags & MREMAP_FIXED) + if (flags & MREMAP_FIXED) { host_addr = (void *) syscall(__NR_mremap, g2h(old_addr), old_size, new_size, flags, - new_addr); - else if (flags & MREMAP_MAYMOVE) { + g2h(new_addr)); + + if (reserved_va && host_addr != MAP_FAILED) { + /* If new and old addresses overlap then the above mremap will + already have failed with EINVAL. */ + mmap_reserve(old_addr, old_size); + } + } else if (flags & MREMAP_MAYMOVE) { abi_ulong mmap_start; mmap_start = mmap_find_vma(0, new_size); @@ -603,13 +692,32 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, if (mmap_start == -1) { errno = ENOMEM; host_addr = MAP_FAILED; - } else + } else { host_addr = (void *) syscall(__NR_mremap, g2h(old_addr), old_size, new_size, flags | MREMAP_FIXED, g2h(mmap_start)); + mmap_reserve(old_addr, old_size); + } } else { - host_addr = mremap(g2h(old_addr), old_size, new_size, flags); + int prot = 0; + if (reserved_va && old_size < new_size) { + abi_ulong addr; + for (addr = old_addr + old_size; + addr < old_addr + new_size; + addr++) { + prot |= page_get_flags(addr); + } + } + if (prot == 0) { + host_addr = mremap(g2h(old_addr), old_size, new_size, flags); + if (host_addr != MAP_FAILED && reserved_va && old_size > new_size) { + mmap_reserve(old_addr + old_size, new_size - old_size); + } + } else { + errno = ENOMEM; + host_addr = MAP_FAILED; + } /* Check if address fits target address space */ if ((unsigned long)host_addr + new_size > (abi_ulong)-1) { /* Revert mremap() changes */ |