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