summary refs log tree commit diff stats
path: root/linux-user/mmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux-user/mmap.c')
-rw-r--r--linux-user/mmap.c120
1 files changed, 114 insertions, 6 deletions
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 */