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