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