qemu/bsd-user/mmap.c
<<
>>
Prefs
   1/*
   2 *  mmap support for qemu
   3 *
   4 *  Copyright (c) 2003 - 2008 Fabrice Bellard
   5 *
   6 *  This program is free software; you can redistribute it and/or modify
   7 *  it under the terms of the GNU General Public License as published by
   8 *  the Free Software Foundation; either version 2 of the License, or
   9 *  (at your option) any later version.
  10 *
  11 *  This program is distributed in the hope that it will be useful,
  12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 *  GNU General Public License for more details.
  15 *
  16 *  You should have received a copy of the GNU General Public License
  17 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
  18 */
  19#include "qemu/osdep.h"
  20
  21#include "qemu.h"
  22#include "qemu-common.h"
  23#include "bsd-mman.h"
  24
  25//#define DEBUG_MMAP
  26
  27#if defined(CONFIG_USE_NPTL)
  28pthread_mutex_t mmap_mutex;
  29static int __thread mmap_lock_count;
  30
  31void mmap_lock(void)
  32{
  33    if (mmap_lock_count++ == 0) {
  34        pthread_mutex_lock(&mmap_mutex);
  35    }
  36}
  37
  38void mmap_unlock(void)
  39{
  40    if (--mmap_lock_count == 0) {
  41        pthread_mutex_unlock(&mmap_mutex);
  42    }
  43}
  44
  45/* Grab lock to make sure things are in a consistent state after fork().  */
  46void mmap_fork_start(void)
  47{
  48    if (mmap_lock_count)
  49        abort();
  50    pthread_mutex_lock(&mmap_mutex);
  51}
  52
  53void mmap_fork_end(int child)
  54{
  55    if (child)
  56        pthread_mutex_init(&mmap_mutex, NULL);
  57    else
  58        pthread_mutex_unlock(&mmap_mutex);
  59}
  60#else
  61/* We aren't threadsafe to start with, so no need to worry about locking.  */
  62void mmap_lock(void)
  63{
  64}
  65
  66void mmap_unlock(void)
  67{
  68}
  69#endif
  70
  71/* NOTE: all the constants are the HOST ones, but addresses are target. */
  72int target_mprotect(abi_ulong start, abi_ulong len, int prot)
  73{
  74    abi_ulong end, host_start, host_end, addr;
  75    int prot1, ret;
  76
  77#ifdef DEBUG_MMAP
  78    printf("mprotect: start=0x" TARGET_FMT_lx
  79           " len=0x" TARGET_FMT_lx " prot=%c%c%c\n", start, len,
  80           prot & PROT_READ ? 'r' : '-',
  81           prot & PROT_WRITE ? 'w' : '-',
  82           prot & PROT_EXEC ? 'x' : '-');
  83#endif
  84
  85    if ((start & ~TARGET_PAGE_MASK) != 0)
  86        return -EINVAL;
  87    len = TARGET_PAGE_ALIGN(len);
  88    end = start + len;
  89    if (end < start)
  90        return -EINVAL;
  91    prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
  92    if (len == 0)
  93        return 0;
  94
  95    mmap_lock();
  96    host_start = start & qemu_host_page_mask;
  97    host_end = HOST_PAGE_ALIGN(end);
  98    if (start > host_start) {
  99        /* handle host page containing start */
 100        prot1 = prot;
 101        for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
 102            prot1 |= page_get_flags(addr);
 103        }
 104        if (host_end == host_start + qemu_host_page_size) {
 105            for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
 106                prot1 |= page_get_flags(addr);
 107            }
 108            end = host_end;
 109        }
 110        ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
 111        if (ret != 0)
 112            goto error;
 113        host_start += qemu_host_page_size;
 114    }
 115    if (end < host_end) {
 116        prot1 = prot;
 117        for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
 118            prot1 |= page_get_flags(addr);
 119        }
 120        ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
 121                       prot1 & PAGE_BITS);
 122        if (ret != 0)
 123            goto error;
 124        host_end -= qemu_host_page_size;
 125    }
 126
 127    /* handle the pages in the middle */
 128    if (host_start < host_end) {
 129        ret = mprotect(g2h(host_start), host_end - host_start, prot);
 130        if (ret != 0)
 131            goto error;
 132    }
 133    page_set_flags(start, start + len, prot | PAGE_VALID);
 134    mmap_unlock();
 135    return 0;
 136error:
 137    mmap_unlock();
 138    return ret;
 139}
 140
 141/* map an incomplete host page */
 142static int mmap_frag(abi_ulong real_start,
 143                     abi_ulong start, abi_ulong end,
 144                     int prot, int flags, int fd, abi_ulong offset)
 145{
 146    abi_ulong real_end, addr;
 147    void *host_start;
 148    int prot1, prot_new;
 149
 150    real_end = real_start + qemu_host_page_size;
 151    host_start = g2h(real_start);
 152
 153    /* get the protection of the target pages outside the mapping */
 154    prot1 = 0;
 155    for(addr = real_start; addr < real_end; addr++) {
 156        if (addr < start || addr >= end)
 157            prot1 |= page_get_flags(addr);
 158    }
 159
 160    if (prot1 == 0) {
 161        /* no page was there, so we allocate one */
 162        void *p = mmap(host_start, qemu_host_page_size, prot,
 163                       flags | MAP_ANON, -1, 0);
 164        if (p == MAP_FAILED)
 165            return -1;
 166        prot1 = prot;
 167    }
 168    prot1 &= PAGE_BITS;
 169
 170    prot_new = prot | prot1;
 171    if (!(flags & MAP_ANON)) {
 172        /* msync() won't work here, so we return an error if write is
 173           possible while it is a shared mapping */
 174        if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED &&
 175            (prot & PROT_WRITE))
 176            return -1;
 177
 178        /* adjust protection to be able to read */
 179        if (!(prot1 & PROT_WRITE))
 180            mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
 181
 182        /* read the corresponding file data */
 183        pread(fd, g2h(start), end - start, offset);
 184
 185        /* put final protection */
 186        if (prot_new != (prot1 | PROT_WRITE))
 187            mprotect(host_start, qemu_host_page_size, prot_new);
 188    } else {
 189        /* just update the protection */
 190        if (prot_new != prot1) {
 191            mprotect(host_start, qemu_host_page_size, prot_new);
 192        }
 193    }
 194    return 0;
 195}
 196
 197#if defined(__CYGWIN__)
 198/* Cygwin doesn't have a whole lot of address space.  */
 199static abi_ulong mmap_next_start = 0x18000000;
 200#else
 201static abi_ulong mmap_next_start = 0x40000000;
 202#endif
 203
 204unsigned long last_brk;
 205
 206/* find a free memory area of size 'size'. The search starts at
 207   'start'. If 'start' == 0, then a default start address is used.
 208   Return -1 if error.
 209*/
 210/* page_init() marks pages used by the host as reserved to be sure not
 211   to use them. */
 212static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
 213{
 214    abi_ulong addr, addr1, addr_start;
 215    int prot;
 216    unsigned long new_brk;
 217
 218    new_brk = (unsigned long)sbrk(0);
 219    if (last_brk && last_brk < new_brk && last_brk == (target_ulong)last_brk) {
 220        /* This is a hack to catch the host allocating memory with brk().
 221           If it uses mmap then we loose.
 222           FIXME: We really want to avoid the host allocating memory in
 223           the first place, and maybe leave some slack to avoid switching
 224           to mmap.  */
 225        page_set_flags(last_brk & TARGET_PAGE_MASK,
 226                       TARGET_PAGE_ALIGN(new_brk),
 227                       PAGE_RESERVED);
 228    }
 229    last_brk = new_brk;
 230
 231    size = HOST_PAGE_ALIGN(size);
 232    start = start & qemu_host_page_mask;
 233    addr = start;
 234    if (addr == 0)
 235        addr = mmap_next_start;
 236    addr_start = addr;
 237    for(;;) {
 238        prot = 0;
 239        for(addr1 = addr; addr1 < (addr + size); addr1 += TARGET_PAGE_SIZE) {
 240            prot |= page_get_flags(addr1);
 241        }
 242        if (prot == 0)
 243            break;
 244        addr += qemu_host_page_size;
 245        /* we found nothing */
 246        if (addr == addr_start)
 247            return (abi_ulong)-1;
 248    }
 249    if (start == 0)
 250        mmap_next_start = addr + size;
 251    return addr;
 252}
 253
 254/* NOTE: all the constants are the HOST ones */
 255abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
 256                     int flags, int fd, abi_ulong offset)
 257{
 258    abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
 259    unsigned long host_start;
 260
 261    mmap_lock();
 262#ifdef DEBUG_MMAP
 263    {
 264        printf("mmap: start=0x" TARGET_FMT_lx
 265               " len=0x" TARGET_FMT_lx " prot=%c%c%c flags=",
 266               start, len,
 267               prot & PROT_READ ? 'r' : '-',
 268               prot & PROT_WRITE ? 'w' : '-',
 269               prot & PROT_EXEC ? 'x' : '-');
 270        if (flags & MAP_FIXED)
 271            printf("MAP_FIXED ");
 272        if (flags & MAP_ANON)
 273            printf("MAP_ANON ");
 274        switch(flags & TARGET_BSD_MAP_FLAGMASK) {
 275        case MAP_PRIVATE:
 276            printf("MAP_PRIVATE ");
 277            break;
 278        case MAP_SHARED:
 279            printf("MAP_SHARED ");
 280            break;
 281        default:
 282            printf("[MAP_FLAGMASK=0x%x] ", flags & TARGET_BSD_MAP_FLAGMASK);
 283            break;
 284        }
 285        printf("fd=%d offset=" TARGET_FMT_lx "\n", fd, offset);
 286    }
 287#endif
 288
 289    if (offset & ~TARGET_PAGE_MASK) {
 290        errno = EINVAL;
 291        goto fail;
 292    }
 293
 294    len = TARGET_PAGE_ALIGN(len);
 295    if (len == 0)
 296        goto the_end;
 297    real_start = start & qemu_host_page_mask;
 298
 299    if (!(flags & MAP_FIXED)) {
 300        abi_ulong mmap_start;
 301        void *p;
 302        host_offset = offset & qemu_host_page_mask;
 303        host_len = len + offset - host_offset;
 304        host_len = HOST_PAGE_ALIGN(host_len);
 305        mmap_start = mmap_find_vma(real_start, host_len);
 306        if (mmap_start == (abi_ulong)-1) {
 307            errno = ENOMEM;
 308            goto fail;
 309        }
 310        /* Note: we prefer to control the mapping address. It is
 311           especially important if qemu_host_page_size >
 312           qemu_real_host_page_size */
 313        p = mmap(g2h(mmap_start),
 314                 host_len, prot, flags | MAP_FIXED, fd, host_offset);
 315        if (p == MAP_FAILED)
 316            goto fail;
 317        /* update start so that it points to the file position at 'offset' */
 318        host_start = (unsigned long)p;
 319        if (!(flags & MAP_ANON))
 320            host_start += offset - host_offset;
 321        start = h2g(host_start);
 322    } else {
 323        int flg;
 324        target_ulong addr;
 325
 326        if (start & ~TARGET_PAGE_MASK) {
 327            errno = EINVAL;
 328            goto fail;
 329        }
 330        end = start + len;
 331        real_end = HOST_PAGE_ALIGN(end);
 332
 333        for(addr = real_start; addr < real_end; addr += TARGET_PAGE_SIZE) {
 334            flg = page_get_flags(addr);
 335            if (flg & PAGE_RESERVED) {
 336                errno = ENXIO;
 337                goto fail;
 338            }
 339        }
 340
 341        /* worst case: we cannot map the file because the offset is not
 342           aligned, so we read it */
 343        if (!(flags & MAP_ANON) &&
 344            (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
 345            /* msync() won't work here, so we return an error if write is
 346               possible while it is a shared mapping */
 347            if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED &&
 348                (prot & PROT_WRITE)) {
 349                errno = EINVAL;
 350                goto fail;
 351            }
 352            retaddr = target_mmap(start, len, prot | PROT_WRITE,
 353                                  MAP_FIXED | MAP_PRIVATE | MAP_ANON,
 354                                  -1, 0);
 355            if (retaddr == -1)
 356                goto fail;
 357            pread(fd, g2h(start), len, offset);
 358            if (!(prot & PROT_WRITE)) {
 359                ret = target_mprotect(start, len, prot);
 360                if (ret != 0) {
 361                    start = ret;
 362                    goto the_end;
 363                }
 364            }
 365            goto the_end;
 366        }
 367
 368        /* handle the start of the mapping */
 369        if (start > real_start) {
 370            if (real_end == real_start + qemu_host_page_size) {
 371                /* one single host page */
 372                ret = mmap_frag(real_start, start, end,
 373                                prot, flags, fd, offset);
 374                if (ret == -1)
 375                    goto fail;
 376                goto the_end1;
 377            }
 378            ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
 379                            prot, flags, fd, offset);
 380            if (ret == -1)
 381                goto fail;
 382            real_start += qemu_host_page_size;
 383        }
 384        /* handle the end of the mapping */
 385        if (end < real_end) {
 386            ret = mmap_frag(real_end - qemu_host_page_size,
 387                            real_end - qemu_host_page_size, real_end,
 388                            prot, flags, fd,
 389                            offset + real_end - qemu_host_page_size - start);
 390            if (ret == -1)
 391                goto fail;
 392            real_end -= qemu_host_page_size;
 393        }
 394
 395        /* map the middle (easier) */
 396        if (real_start < real_end) {
 397            void *p;
 398            unsigned long offset1;
 399            if (flags & MAP_ANON)
 400                offset1 = 0;
 401            else
 402                offset1 = offset + real_start - start;
 403            p = mmap(g2h(real_start), real_end - real_start,
 404                     prot, flags, fd, offset1);
 405            if (p == MAP_FAILED)
 406                goto fail;
 407        }
 408    }
 409 the_end1:
 410    page_set_flags(start, start + len, prot | PAGE_VALID);
 411 the_end:
 412#ifdef DEBUG_MMAP
 413    printf("ret=0x" TARGET_FMT_lx "\n", start);
 414    page_dump(stdout);
 415    printf("\n");
 416#endif
 417    mmap_unlock();
 418    return start;
 419fail:
 420    mmap_unlock();
 421    return -1;
 422}
 423
 424int target_munmap(abi_ulong start, abi_ulong len)
 425{
 426    abi_ulong end, real_start, real_end, addr;
 427    int prot, ret;
 428
 429#ifdef DEBUG_MMAP
 430    printf("munmap: start=0x%lx len=0x%lx\n", start, len);
 431#endif
 432    if (start & ~TARGET_PAGE_MASK)
 433        return -EINVAL;
 434    len = TARGET_PAGE_ALIGN(len);
 435    if (len == 0)
 436        return -EINVAL;
 437    mmap_lock();
 438    end = start + len;
 439    real_start = start & qemu_host_page_mask;
 440    real_end = HOST_PAGE_ALIGN(end);
 441
 442    if (start > real_start) {
 443        /* handle host page containing start */
 444        prot = 0;
 445        for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
 446            prot |= page_get_flags(addr);
 447        }
 448        if (real_end == real_start + qemu_host_page_size) {
 449            for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
 450                prot |= page_get_flags(addr);
 451            }
 452            end = real_end;
 453        }
 454        if (prot != 0)
 455            real_start += qemu_host_page_size;
 456    }
 457    if (end < real_end) {
 458        prot = 0;
 459        for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
 460            prot |= page_get_flags(addr);
 461        }
 462        if (prot != 0)
 463            real_end -= qemu_host_page_size;
 464    }
 465
 466    ret = 0;
 467    /* unmap what we can */
 468    if (real_start < real_end) {
 469        ret = munmap(g2h(real_start), real_end - real_start);
 470    }
 471
 472    if (ret == 0)
 473        page_set_flags(start, start + len, 0);
 474    mmap_unlock();
 475    return ret;
 476}
 477
 478int target_msync(abi_ulong start, abi_ulong len, int flags)
 479{
 480    abi_ulong end;
 481
 482    if (start & ~TARGET_PAGE_MASK)
 483        return -EINVAL;
 484    len = TARGET_PAGE_ALIGN(len);
 485    end = start + len;
 486    if (end < start)
 487        return -EINVAL;
 488    if (end == start)
 489        return 0;
 490
 491    start &= qemu_host_page_mask;
 492    return msync(g2h(start), end - start, flags);
 493}
 494