diff mbox series

[12/13] linux-user: Fix shmat emulation by honoring host SHMLBA

Message ID 20190519201953.20161-13-richard.henderson@linaro.org
State New
Headers show
Series linux-user: path, clone, sparc, shmat fixes | expand

Commit Message

Richard Henderson May 19, 2019, 8:19 p.m. UTC
For those hosts with SHMLBA > getpagesize, we don't automatically
select a guest address that is compatible with the host.  We can
achieve this by boosting the alignment of guest_base and by adding
an extra alignment argument to mmap_find_vma.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>

---
 linux-user/qemu.h    |  2 +-
 linux-user/elfload.c | 17 +++++-----
 linux-user/mmap.c    | 74 +++++++++++++++++++++++---------------------
 linux-user/syscall.c |  3 +-
 4 files changed, 52 insertions(+), 44 deletions(-)

-- 
2.17.1

Comments

Laurent Vivier May 22, 2019, 9:40 a.m. UTC | #1
On 19/05/2019 22:19, Richard Henderson wrote:
> For those hosts with SHMLBA > getpagesize, we don't automatically

> select a guest address that is compatible with the host.  We can

> achieve this by boosting the alignment of guest_base and by adding

> an extra alignment argument to mmap_find_vma.

> 

> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>

> ---

>   linux-user/qemu.h    |  2 +-

>   linux-user/elfload.c | 17 +++++-----

>   linux-user/mmap.c    | 74 +++++++++++++++++++++++---------------------

>   linux-user/syscall.c |  3 +-

>   4 files changed, 52 insertions(+), 44 deletions(-)

> 

> diff --git a/linux-user/qemu.h b/linux-user/qemu.h

> index ef400cb78a..82d33d7e93 100644

> --- a/linux-user/qemu.h

> +++ b/linux-user/qemu.h

> @@ -443,7 +443,7 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,

>                          abi_ulong new_addr);

>   extern unsigned long last_brk;

>   extern abi_ulong mmap_next_start;

> -abi_ulong mmap_find_vma(abi_ulong, abi_ulong);

> +abi_ulong mmap_find_vma(abi_ulong, abi_ulong, abi_ulong);

>   void mmap_fork_start(void);

>   void mmap_fork_end(int child);

>   

> diff --git a/linux-user/elfload.c b/linux-user/elfload.c

> index ef42e02d82..fe9f07843e 100644

> --- a/linux-user/elfload.c

> +++ b/linux-user/elfload.c

> @@ -3,6 +3,7 @@

>   #include <sys/param.h>

>   

>   #include <sys/resource.h>

> +#include <sys/shm.h>

>   

>   #include "qemu.h"

>   #include "disas/disas.h"

> @@ -2012,6 +2013,8 @@ unsigned long init_guest_space(unsigned long host_start,

>                                  unsigned long guest_start,

>                                  bool fixed)

>   {

> +    /* In order to use host shmat, we must be able to honor SHMLBA.  */

> +    unsigned long align = MAX(SHMLBA, qemu_host_page_size);

>       unsigned long current_start, aligned_start;

>       int flags;

>   

> @@ -2029,7 +2032,7 @@ unsigned long init_guest_space(unsigned long host_start,

>       }

>   

>       /* Setup the initial flags and start address.  */

> -    current_start = host_start & qemu_host_page_mask;

> +    current_start = host_start & -align;

>       flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;

>       if (fixed) {

>           flags |= MAP_FIXED;

> @@ -2065,8 +2068,8 @@ unsigned long init_guest_space(unsigned long host_start,

>               return (unsigned long)-1;

>           }

>           munmap((void *)real_start, host_full_size);

> -        if (real_start & ~qemu_host_page_mask) {

> -            /* The same thing again, but with an extra qemu_host_page_size

> +        if (real_start & (align - 1)) {

> +            /* The same thing again, but with extra

>                * so that we can shift around alignment.

>                */

>               unsigned long real_size = host_full_size + qemu_host_page_size;

> @@ -2079,7 +2082,7 @@ unsigned long init_guest_space(unsigned long host_start,

>                   return (unsigned long)-1;

>               }

>               munmap((void *)real_start, real_size);

> -            real_start = HOST_PAGE_ALIGN(real_start);

> +            real_start = ROUND_UP(real_start, align);

>           }

>           current_start = real_start;

>       }

> @@ -2106,7 +2109,7 @@ unsigned long init_guest_space(unsigned long host_start,

>           }

>   

>           /* Ensure the address is properly aligned.  */

> -        if (real_start & ~qemu_host_page_mask) {

> +        if (real_start & (align - 1)) {

>               /* Ideally, we adjust like

>                *

>                *    pages: [  ][  ][  ][  ][  ]

> @@ -2134,7 +2137,7 @@ unsigned long init_guest_space(unsigned long host_start,

>               if (real_start == (unsigned long)-1) {

>                   return (unsigned long)-1;

>               }

> -            aligned_start = HOST_PAGE_ALIGN(real_start);

> +            aligned_start = ROUND_UP(real_start, align);

>           } else {

>               aligned_start = real_start;

>           }

> @@ -2171,7 +2174,7 @@ unsigned long init_guest_space(unsigned long host_start,

>            * because of trouble with ARM commpage setup.

>            */

>           munmap((void *)real_start, real_size);

> -        current_start += qemu_host_page_size;

> +        current_start += align;

>           if (host_start == current_start) {

>               /* Theoretically possible if host doesn't have any suitably

>                * aligned areas.  Normally the first mmap will fail.

> diff --git a/linux-user/mmap.c b/linux-user/mmap.c

> index e0249efe4f..10796b37ac 100644

> --- a/linux-user/mmap.c

> +++ b/linux-user/mmap.c

> @@ -202,49 +202,52 @@ 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)

> +static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size,

> +                                        abi_ulong align)

>   {

> -    abi_ulong addr;

> -    abi_ulong end_addr;

> +    abi_ulong addr, end_addr, incr = qemu_host_page_size;

>       int prot;

> -    int looped = 0;

> +    bool looped = false;

>   

>       if (size > reserved_va) {

>           return (abi_ulong)-1;

>       }

>   

> -    size = HOST_PAGE_ALIGN(size);

> -    end_addr = start + size;

> -    if (end_addr > reserved_va) {

> -        end_addr = reserved_va;

> -    }

> -    addr = end_addr - qemu_host_page_size;

> +    /* Note that start and size have already been aligned by mmap_find_vma. */

>   

> +    end_addr = start + size;

> +    if (start > reserved_va - size) {

> +        /* Start at the top of the address space.  */

> +        end_addr = ((reserved_va - size) & -align) + size;

> +        looped = true;

> +    }

> +

> +    /* Search downward from END_ADDR, checking to see if a page is in use.  */

> +    addr = end_addr;

>       while (1) {

> +        addr -= incr;

>           if (addr > end_addr) {

>               if (looped) {

> +                /* Failure.  The entire address space has been searched.  */

>                   return (abi_ulong)-1;

>               }

> -            end_addr = reserved_va;

> -            addr = end_addr - qemu_host_page_size;

> -            looped = 1;

> -            continue;

> +            /* Re-start at the top of the address space.  */

> +            addr = end_addr = ((reserved_va - size) & -align) + size;

> +            looped = true;

> +        } else {

> +            prot = page_get_flags(addr);

> +            if (prot) {

> +                /* Page in use.  Restart below this page.  */

> +                addr = end_addr = ((addr - size) & -align) + size;

> +            } else if (addr && addr + size == end_addr) {

> +                /* Success!  All pages between ADDR and END_ADDR are free.  */

> +                if (start == mmap_next_start) {

> +                    mmap_next_start = addr;

> +                }

> +                return addr;

> +            }

>           }

> -        prot = page_get_flags(addr);

> -        if (prot) {

> -            end_addr = addr;

> -        }

> -        if (addr && addr + size == end_addr) {

> -            break;

> -        }

> -        addr -= qemu_host_page_size;

>       }

> -

> -    if (start == mmap_next_start) {

> -        mmap_next_start = addr;

> -    }

> -

> -    return addr;

>   }

>   

>   /*

> @@ -253,7 +256,7 @@ static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size)

>    * It must be called with mmap_lock() held.

>    * Return -1 if error.

>    */

> -abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)

> +abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong align)

>   {

>       void *ptr, *prev;

>       abi_ulong addr;

> @@ -265,11 +268,12 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)

>       } else {

>           start &= qemu_host_page_mask;

>       }

> +    start = ROUND_UP(start, align);

>   

>       size = HOST_PAGE_ALIGN(size);

>   

>       if (reserved_va) {

> -        return mmap_find_vma_reserved(start, size);

> +        return mmap_find_vma_reserved(start, size, align);

>       }

>   

>       addr = start;

> @@ -299,7 +303,7 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)

>           if (h2g_valid(ptr + size - 1)) {

>               addr = h2g(ptr);

>   

> -            if ((addr & ~TARGET_PAGE_MASK) == 0) {

> +            if ((addr & (align - 1)) == 0) {

>                   /* Success.  */

>                   if (start == mmap_next_start && addr >= TASK_UNMAPPED_BASE) {

>                       mmap_next_start = addr + size;

> @@ -313,12 +317,12 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)

>                   /* Assume the result that the kernel gave us is the

>                      first with enough free space, so start again at the

>                      next higher target page.  */

> -                addr = TARGET_PAGE_ALIGN(addr);

> +                addr = ROUND_UP(addr, align);

>                   break;

>               case 1:

>                   /* Sometimes the kernel decides to perform the allocation

>                      at the top end of memory instead.  */

> -                addr &= TARGET_PAGE_MASK;

> +                addr &= -align;

>                   break;

>               case 2:

>                   /* Start over at low memory.  */

> @@ -416,7 +420,7 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,

>       if (!(flags & MAP_FIXED)) {

>           host_len = len + offset - host_offset;

>           host_len = HOST_PAGE_ALIGN(host_len);

> -        start = mmap_find_vma(real_start, host_len);

> +        start = mmap_find_vma(real_start, host_len, TARGET_PAGE_SIZE);

>           if (start == (abi_ulong)-1) {

>               errno = ENOMEM;

>               goto fail;

> @@ -710,7 +714,7 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,

>       } else if (flags & MREMAP_MAYMOVE) {

>           abi_ulong mmap_start;

>   

> -        mmap_start = mmap_find_vma(0, new_size);

> +        mmap_start = mmap_find_vma(0, new_size, TARGET_PAGE_SIZE);

>   

>           if (mmap_start == -1) {

>               errno = ENOMEM;

> diff --git a/linux-user/syscall.c b/linux-user/syscall.c

> index f960556bf8..1feb740f66 100644

> --- a/linux-user/syscall.c

> +++ b/linux-user/syscall.c

> @@ -3912,7 +3912,8 @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env,

>       else {

>           abi_ulong mmap_start;

>   

> -        mmap_start = mmap_find_vma(0, shm_info.shm_segsz);

> +        /* In order to use the host shmat, we need to honor host SHMLBA.  */

> +        mmap_start = mmap_find_vma(0, shm_info.shm_segsz, MAX(SHMLBA, shmlba));

>   

>           if (mmap_start == -1) {

>               errno = ENOMEM;

> 


Applied to my linux-user branch.

Thanks,
Laurent
diff mbox series

Patch

diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index ef400cb78a..82d33d7e93 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -443,7 +443,7 @@  abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
                        abi_ulong new_addr);
 extern unsigned long last_brk;
 extern abi_ulong mmap_next_start;
-abi_ulong mmap_find_vma(abi_ulong, abi_ulong);
+abi_ulong mmap_find_vma(abi_ulong, abi_ulong, abi_ulong);
 void mmap_fork_start(void);
 void mmap_fork_end(int child);
 
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index ef42e02d82..fe9f07843e 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -3,6 +3,7 @@ 
 #include <sys/param.h>
 
 #include <sys/resource.h>
+#include <sys/shm.h>
 
 #include "qemu.h"
 #include "disas/disas.h"
@@ -2012,6 +2013,8 @@  unsigned long init_guest_space(unsigned long host_start,
                                unsigned long guest_start,
                                bool fixed)
 {
+    /* In order to use host shmat, we must be able to honor SHMLBA.  */
+    unsigned long align = MAX(SHMLBA, qemu_host_page_size);
     unsigned long current_start, aligned_start;
     int flags;
 
@@ -2029,7 +2032,7 @@  unsigned long init_guest_space(unsigned long host_start,
     }
 
     /* Setup the initial flags and start address.  */
-    current_start = host_start & qemu_host_page_mask;
+    current_start = host_start & -align;
     flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
     if (fixed) {
         flags |= MAP_FIXED;
@@ -2065,8 +2068,8 @@  unsigned long init_guest_space(unsigned long host_start,
             return (unsigned long)-1;
         }
         munmap((void *)real_start, host_full_size);
-        if (real_start & ~qemu_host_page_mask) {
-            /* The same thing again, but with an extra qemu_host_page_size
+        if (real_start & (align - 1)) {
+            /* The same thing again, but with extra
              * so that we can shift around alignment.
              */
             unsigned long real_size = host_full_size + qemu_host_page_size;
@@ -2079,7 +2082,7 @@  unsigned long init_guest_space(unsigned long host_start,
                 return (unsigned long)-1;
             }
             munmap((void *)real_start, real_size);
-            real_start = HOST_PAGE_ALIGN(real_start);
+            real_start = ROUND_UP(real_start, align);
         }
         current_start = real_start;
     }
@@ -2106,7 +2109,7 @@  unsigned long init_guest_space(unsigned long host_start,
         }
 
         /* Ensure the address is properly aligned.  */
-        if (real_start & ~qemu_host_page_mask) {
+        if (real_start & (align - 1)) {
             /* Ideally, we adjust like
              *
              *    pages: [  ][  ][  ][  ][  ]
@@ -2134,7 +2137,7 @@  unsigned long init_guest_space(unsigned long host_start,
             if (real_start == (unsigned long)-1) {
                 return (unsigned long)-1;
             }
-            aligned_start = HOST_PAGE_ALIGN(real_start);
+            aligned_start = ROUND_UP(real_start, align);
         } else {
             aligned_start = real_start;
         }
@@ -2171,7 +2174,7 @@  unsigned long init_guest_space(unsigned long host_start,
          * because of trouble with ARM commpage setup.
          */
         munmap((void *)real_start, real_size);
-        current_start += qemu_host_page_size;
+        current_start += align;
         if (host_start == current_start) {
             /* Theoretically possible if host doesn't have any suitably
              * aligned areas.  Normally the first mmap will fail.
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index e0249efe4f..10796b37ac 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -202,49 +202,52 @@  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)
+static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size,
+                                        abi_ulong align)
 {
-    abi_ulong addr;
-    abi_ulong end_addr;
+    abi_ulong addr, end_addr, incr = qemu_host_page_size;
     int prot;
-    int looped = 0;
+    bool looped = false;
 
     if (size > reserved_va) {
         return (abi_ulong)-1;
     }
 
-    size = HOST_PAGE_ALIGN(size);
-    end_addr = start + size;
-    if (end_addr > reserved_va) {
-        end_addr = reserved_va;
-    }
-    addr = end_addr - qemu_host_page_size;
+    /* Note that start and size have already been aligned by mmap_find_vma. */
 
+    end_addr = start + size;
+    if (start > reserved_va - size) {
+        /* Start at the top of the address space.  */
+        end_addr = ((reserved_va - size) & -align) + size;
+        looped = true;
+    }
+
+    /* Search downward from END_ADDR, checking to see if a page is in use.  */
+    addr = end_addr;
     while (1) {
+        addr -= incr;
         if (addr > end_addr) {
             if (looped) {
+                /* Failure.  The entire address space has been searched.  */
                 return (abi_ulong)-1;
             }
-            end_addr = reserved_va;
-            addr = end_addr - qemu_host_page_size;
-            looped = 1;
-            continue;
+            /* Re-start at the top of the address space.  */
+            addr = end_addr = ((reserved_va - size) & -align) + size;
+            looped = true;
+        } else {
+            prot = page_get_flags(addr);
+            if (prot) {
+                /* Page in use.  Restart below this page.  */
+                addr = end_addr = ((addr - size) & -align) + size;
+            } else if (addr && addr + size == end_addr) {
+                /* Success!  All pages between ADDR and END_ADDR are free.  */
+                if (start == mmap_next_start) {
+                    mmap_next_start = addr;
+                }
+                return addr;
+            }
         }
-        prot = page_get_flags(addr);
-        if (prot) {
-            end_addr = addr;
-        }
-        if (addr && addr + size == end_addr) {
-            break;
-        }
-        addr -= qemu_host_page_size;
     }
-
-    if (start == mmap_next_start) {
-        mmap_next_start = addr;
-    }
-
-    return addr;
 }
 
 /*
@@ -253,7 +256,7 @@  static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size)
  * It must be called with mmap_lock() held.
  * Return -1 if error.
  */
-abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
+abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong align)
 {
     void *ptr, *prev;
     abi_ulong addr;
@@ -265,11 +268,12 @@  abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
     } else {
         start &= qemu_host_page_mask;
     }
+    start = ROUND_UP(start, align);
 
     size = HOST_PAGE_ALIGN(size);
 
     if (reserved_va) {
-        return mmap_find_vma_reserved(start, size);
+        return mmap_find_vma_reserved(start, size, align);
     }
 
     addr = start;
@@ -299,7 +303,7 @@  abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
         if (h2g_valid(ptr + size - 1)) {
             addr = h2g(ptr);
 
-            if ((addr & ~TARGET_PAGE_MASK) == 0) {
+            if ((addr & (align - 1)) == 0) {
                 /* Success.  */
                 if (start == mmap_next_start && addr >= TASK_UNMAPPED_BASE) {
                     mmap_next_start = addr + size;
@@ -313,12 +317,12 @@  abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
                 /* Assume the result that the kernel gave us is the
                    first with enough free space, so start again at the
                    next higher target page.  */
-                addr = TARGET_PAGE_ALIGN(addr);
+                addr = ROUND_UP(addr, align);
                 break;
             case 1:
                 /* Sometimes the kernel decides to perform the allocation
                    at the top end of memory instead.  */
-                addr &= TARGET_PAGE_MASK;
+                addr &= -align;
                 break;
             case 2:
                 /* Start over at low memory.  */
@@ -416,7 +420,7 @@  abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
     if (!(flags & MAP_FIXED)) {
         host_len = len + offset - host_offset;
         host_len = HOST_PAGE_ALIGN(host_len);
-        start = mmap_find_vma(real_start, host_len);
+        start = mmap_find_vma(real_start, host_len, TARGET_PAGE_SIZE);
         if (start == (abi_ulong)-1) {
             errno = ENOMEM;
             goto fail;
@@ -710,7 +714,7 @@  abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
     } else if (flags & MREMAP_MAYMOVE) {
         abi_ulong mmap_start;
 
-        mmap_start = mmap_find_vma(0, new_size);
+        mmap_start = mmap_find_vma(0, new_size, TARGET_PAGE_SIZE);
 
         if (mmap_start == -1) {
             errno = ENOMEM;
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index f960556bf8..1feb740f66 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -3912,7 +3912,8 @@  static inline abi_ulong do_shmat(CPUArchState *cpu_env,
     else {
         abi_ulong mmap_start;
 
-        mmap_start = mmap_find_vma(0, shm_info.shm_segsz);
+        /* In order to use the host shmat, we need to honor host SHMLBA.  */
+        mmap_start = mmap_find_vma(0, shm_info.shm_segsz, MAX(SHMLBA, shmlba));
 
         if (mmap_start == -1) {
             errno = ENOMEM;