summary refs log tree commit diff stats
path: root/linux-user
diff options
context:
space:
mode:
Diffstat (limited to 'linux-user')
-rw-r--r--linux-user/elfload.c2
-rw-r--r--linux-user/main.c63
-rw-r--r--linux-user/mmap.c120
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 */