123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488 |
- /*
- * mmap support for qemu
- *
- * Copyright (c) 2003 Fabrice Bellard
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
- #include "qemu/osdep.h"
- #include <sys/shm.h>
- #include "trace.h"
- #include "exec/log.h"
- #include "exec/page-protection.h"
- #include "exec/tb-flush.h"
- #include "exec/translation-block.h"
- #include "qemu.h"
- #include "user/page-protection.h"
- #include "user-internals.h"
- #include "user-mmap.h"
- #include "target_mman.h"
- #include "qemu/interval-tree.h"
- #ifdef TARGET_ARM
- #include "target/arm/cpu-features.h"
- #endif
- static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;
- static __thread int mmap_lock_count;
- void mmap_lock(void)
- {
- if (mmap_lock_count++ == 0) {
- pthread_mutex_lock(&mmap_mutex);
- }
- }
- void mmap_unlock(void)
- {
- assert(mmap_lock_count > 0);
- if (--mmap_lock_count == 0) {
- pthread_mutex_unlock(&mmap_mutex);
- }
- }
- bool have_mmap_lock(void)
- {
- return mmap_lock_count > 0 ? true : false;
- }
- /* Grab lock to make sure things are in a consistent state after fork(). */
- void mmap_fork_start(void)
- {
- if (mmap_lock_count)
- abort();
- pthread_mutex_lock(&mmap_mutex);
- }
- void mmap_fork_end(int child)
- {
- if (child) {
- pthread_mutex_init(&mmap_mutex, NULL);
- } else {
- pthread_mutex_unlock(&mmap_mutex);
- }
- }
- /* Protected by mmap_lock. */
- static IntervalTreeRoot shm_regions;
- static void shm_region_add(abi_ptr start, abi_ptr last)
- {
- IntervalTreeNode *i = g_new0(IntervalTreeNode, 1);
- i->start = start;
- i->last = last;
- interval_tree_insert(i, &shm_regions);
- }
- static abi_ptr shm_region_find(abi_ptr start)
- {
- IntervalTreeNode *i;
- for (i = interval_tree_iter_first(&shm_regions, start, start); i;
- i = interval_tree_iter_next(i, start, start)) {
- if (i->start == start) {
- return i->last;
- }
- }
- return 0;
- }
- static void shm_region_rm_complete(abi_ptr start, abi_ptr last)
- {
- IntervalTreeNode *i, *n;
- for (i = interval_tree_iter_first(&shm_regions, start, last); i; i = n) {
- n = interval_tree_iter_next(i, start, last);
- if (i->start >= start && i->last <= last) {
- interval_tree_remove(i, &shm_regions);
- g_free(i);
- }
- }
- }
- /*
- * Validate target prot bitmask.
- * Return the prot bitmask for the host in *HOST_PROT.
- * Return 0 if the target prot bitmask is invalid, otherwise
- * the internal qemu page_flags (which will include PAGE_VALID).
- */
- static int validate_prot_to_pageflags(int prot)
- {
- int valid = PROT_READ | PROT_WRITE | PROT_EXEC | TARGET_PROT_SEM;
- int page_flags = (prot & PAGE_RWX) | PAGE_VALID;
- #ifdef TARGET_AARCH64
- {
- ARMCPU *cpu = ARM_CPU(thread_cpu);
- /*
- * The PROT_BTI bit is only accepted if the cpu supports the feature.
- * Since this is the unusual case, don't bother checking unless
- * the bit has been requested. If set and valid, record the bit
- * within QEMU's page_flags.
- */
- if ((prot & TARGET_PROT_BTI) && cpu_isar_feature(aa64_bti, cpu)) {
- valid |= TARGET_PROT_BTI;
- page_flags |= PAGE_BTI;
- }
- /* Similarly for the PROT_MTE bit. */
- if ((prot & TARGET_PROT_MTE) && cpu_isar_feature(aa64_mte, cpu)) {
- valid |= TARGET_PROT_MTE;
- page_flags |= PAGE_MTE;
- }
- }
- #elif defined(TARGET_HPPA)
- valid |= PROT_GROWSDOWN | PROT_GROWSUP;
- #endif
- return prot & ~valid ? 0 : page_flags;
- }
- /*
- * For the host, we need not pass anything except read/write/exec.
- * While PROT_SEM is allowed by all hosts, it is also ignored, so
- * don't bother transforming guest bit to host bit. Any other
- * target-specific prot bits will not be understood by the host
- * and will need to be encoded into page_flags for qemu emulation.
- *
- * Pages that are executable by the guest will never be executed
- * by the host, but the host will need to be able to read them.
- */
- static int target_to_host_prot(int prot)
- {
- return (prot & (PROT_READ | PROT_WRITE)) |
- (prot & PROT_EXEC ? PROT_READ : 0);
- }
- /* NOTE: all the constants are the HOST ones, but addresses are target. */
- int target_mprotect(abi_ulong start, abi_ulong len, int target_prot)
- {
- int host_page_size = qemu_real_host_page_size();
- abi_ulong starts[3];
- abi_ulong lens[3];
- int prots[3];
- abi_ulong host_start, host_last, last;
- int prot1, ret, page_flags, nranges;
- trace_target_mprotect(start, len, target_prot);
- if ((start & ~TARGET_PAGE_MASK) != 0) {
- return -TARGET_EINVAL;
- }
- page_flags = validate_prot_to_pageflags(target_prot);
- if (!page_flags) {
- return -TARGET_EINVAL;
- }
- if (len == 0) {
- return 0;
- }
- len = TARGET_PAGE_ALIGN(len);
- if (!guest_range_valid_untagged(start, len)) {
- return -TARGET_ENOMEM;
- }
- last = start + len - 1;
- host_start = start & -host_page_size;
- host_last = ROUND_UP(last, host_page_size) - 1;
- nranges = 0;
- mmap_lock();
- if (host_last - host_start < host_page_size) {
- /* Single host page contains all guest pages: sum the prot. */
- prot1 = target_prot;
- for (abi_ulong a = host_start; a < start; a += TARGET_PAGE_SIZE) {
- prot1 |= page_get_flags(a);
- }
- for (abi_ulong a = last; a < host_last; a += TARGET_PAGE_SIZE) {
- prot1 |= page_get_flags(a + 1);
- }
- starts[nranges] = host_start;
- lens[nranges] = host_page_size;
- prots[nranges] = prot1;
- nranges++;
- } else {
- if (host_start < start) {
- /* Host page contains more than one guest page: sum the prot. */
- prot1 = target_prot;
- for (abi_ulong a = host_start; a < start; a += TARGET_PAGE_SIZE) {
- prot1 |= page_get_flags(a);
- }
- /* If the resulting sum differs, create a new range. */
- if (prot1 != target_prot) {
- starts[nranges] = host_start;
- lens[nranges] = host_page_size;
- prots[nranges] = prot1;
- nranges++;
- host_start += host_page_size;
- }
- }
- if (last < host_last) {
- /* Host page contains more than one guest page: sum the prot. */
- prot1 = target_prot;
- for (abi_ulong a = last; a < host_last; a += TARGET_PAGE_SIZE) {
- prot1 |= page_get_flags(a + 1);
- }
- /* If the resulting sum differs, create a new range. */
- if (prot1 != target_prot) {
- host_last -= host_page_size;
- starts[nranges] = host_last + 1;
- lens[nranges] = host_page_size;
- prots[nranges] = prot1;
- nranges++;
- }
- }
- /* Create a range for the middle, if any remains. */
- if (host_start < host_last) {
- starts[nranges] = host_start;
- lens[nranges] = host_last - host_start + 1;
- prots[nranges] = target_prot;
- nranges++;
- }
- }
- for (int i = 0; i < nranges; ++i) {
- ret = mprotect(g2h_untagged(starts[i]), lens[i],
- target_to_host_prot(prots[i]));
- if (ret != 0) {
- goto error;
- }
- }
- page_set_flags(start, last, page_flags);
- ret = 0;
- error:
- mmap_unlock();
- return ret;
- }
- /*
- * Perform munmap on behalf of the target, with host parameters.
- * If reserved_va, we must replace the memory reservation.
- */
- static int do_munmap(void *addr, size_t len)
- {
- if (reserved_va) {
- void *ptr = mmap(addr, len, PROT_NONE,
- MAP_FIXED | MAP_ANONYMOUS
- | MAP_PRIVATE | MAP_NORESERVE, -1, 0);
- return ptr == addr ? 0 : -1;
- }
- return munmap(addr, len);
- }
- /*
- * Perform a pread on behalf of target_mmap. We can reach EOF, we can be
- * interrupted by signals, and in general there's no good error return path.
- * If @zero, zero the rest of the block at EOF.
- * Return true on success.
- */
- static bool mmap_pread(int fd, void *p, size_t len, off_t offset, bool zero)
- {
- while (1) {
- ssize_t r = pread(fd, p, len, offset);
- if (likely(r == len)) {
- /* Complete */
- return true;
- }
- if (r == 0) {
- /* EOF */
- if (zero) {
- memset(p, 0, len);
- }
- return true;
- }
- if (r > 0) {
- /* Short read */
- p += r;
- len -= r;
- offset += r;
- } else if (errno != EINTR) {
- /* Error */
- return false;
- }
- }
- }
- /*
- * Map an incomplete host page.
- *
- * Here be dragons. This case will not work if there is an existing
- * overlapping host page, which is file mapped, and for which the mapping
- * is beyond the end of the file. In that case, we will see SIGBUS when
- * trying to write a portion of this page.
- *
- * FIXME: Work around this with a temporary signal handler and longjmp.
- */
- static bool mmap_frag(abi_ulong real_start, abi_ulong start, abi_ulong last,
- int prot, int flags, int fd, off_t offset)
- {
- int host_page_size = qemu_real_host_page_size();
- abi_ulong real_last;
- void *host_start;
- int prot_old, prot_new;
- int host_prot_old, host_prot_new;
- if (!(flags & MAP_ANONYMOUS)
- && (flags & MAP_TYPE) == MAP_SHARED
- && (prot & PROT_WRITE)) {
- /*
- * msync() won't work with the partial page, so we return an
- * error if write is possible while it is a shared mapping.
- */
- errno = EINVAL;
- return false;
- }
- real_last = real_start + host_page_size - 1;
- host_start = g2h_untagged(real_start);
- /* Get the protection of the target pages outside the mapping. */
- prot_old = 0;
- for (abi_ulong a = real_start; a < start; a += TARGET_PAGE_SIZE) {
- prot_old |= page_get_flags(a);
- }
- for (abi_ulong a = real_last; a > last; a -= TARGET_PAGE_SIZE) {
- prot_old |= page_get_flags(a);
- }
- if (prot_old == 0) {
- /*
- * Since !(prot_old & PAGE_VALID), there were no guest pages
- * outside of the fragment we need to map. Allocate a new host
- * page to cover, discarding whatever else may have been present.
- */
- void *p = mmap(host_start, host_page_size,
- target_to_host_prot(prot),
- flags | MAP_ANONYMOUS, -1, 0);
- if (p != host_start) {
- if (p != MAP_FAILED) {
- do_munmap(p, host_page_size);
- errno = EEXIST;
- }
- return false;
- }
- prot_old = prot;
- }
- prot_new = prot | prot_old;
- host_prot_old = target_to_host_prot(prot_old);
- host_prot_new = target_to_host_prot(prot_new);
- /* Adjust protection to be able to write. */
- if (!(host_prot_old & PROT_WRITE)) {
- host_prot_old |= PROT_WRITE;
- mprotect(host_start, host_page_size, host_prot_old);
- }
- /* Read or zero the new guest pages. */
- if (flags & MAP_ANONYMOUS) {
- memset(g2h_untagged(start), 0, last - start + 1);
- } else if (!mmap_pread(fd, g2h_untagged(start), last - start + 1,
- offset, true)) {
- return false;
- }
- /* Put final protection */
- if (host_prot_new != host_prot_old) {
- mprotect(host_start, host_page_size, host_prot_new);
- }
- return true;
- }
- abi_ulong task_unmapped_base;
- abi_ulong elf_et_dyn_base;
- abi_ulong mmap_next_start;
- /*
- * 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,
- abi_ulong align)
- {
- target_ulong ret;
- ret = page_find_range_empty(start, reserved_va, size, align);
- if (ret == -1 && start > mmap_min_addr) {
- /* Restart at the beginning of the address space. */
- ret = page_find_range_empty(mmap_min_addr, start - 1, size, align);
- }
- return ret;
- }
- /*
- * Find and reserve a free memory area of size 'size'. The search
- * starts at 'start'.
- * 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 align)
- {
- int host_page_size = qemu_real_host_page_size();
- void *ptr, *prev;
- abi_ulong addr;
- int wrapped, repeat;
- align = MAX(align, host_page_size);
- /* If 'start' == 0, then a default start address is used. */
- if (start == 0) {
- start = mmap_next_start;
- } else {
- start &= -host_page_size;
- }
- start = ROUND_UP(start, align);
- size = ROUND_UP(size, host_page_size);
- if (reserved_va) {
- return mmap_find_vma_reserved(start, size, align);
- }
- addr = start;
- wrapped = repeat = 0;
- prev = 0;
- for (;; prev = ptr) {
- /*
- * Reserve needed memory area to avoid a race.
- * It should be discarded using:
- * - mmap() with MAP_FIXED flag
- * - mremap() with MREMAP_FIXED flag
- * - shmat() with SHM_REMAP flag
- */
- ptr = mmap(g2h_untagged(addr), size, PROT_NONE,
- MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0);
- /* ENOMEM, if host address space has no memory */
- if (ptr == MAP_FAILED) {
- return (abi_ulong)-1;
- }
- /*
- * Count the number of sequential returns of the same address.
- * This is used to modify the search algorithm below.
- */
- repeat = (ptr == prev ? repeat + 1 : 0);
- if (h2g_valid(ptr + size - 1)) {
- addr = h2g(ptr);
- if ((addr & (align - 1)) == 0) {
- /* Success. */
- if (start == mmap_next_start && addr >= task_unmapped_base) {
- mmap_next_start = addr + size;
- }
- return addr;
- }
- /* The address is not properly aligned for the target. */
- switch (repeat) {
- case 0:
- /*
- * 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 = ROUND_UP(addr, align);
- break;
- case 1:
- /*
- * Sometimes the kernel decides to perform the allocation
- * at the top end of memory instead.
- */
- addr &= -align;
- break;
- case 2:
- /* Start over at low memory. */
- addr = 0;
- break;
- default:
- /* Fail. This unaligned block must the last. */
- addr = -1;
- break;
- }
- } else {
- /*
- * Since the result the kernel gave didn't fit, start
- * again at low memory. If any repetition, fail.
- */
- addr = (repeat ? -1 : 0);
- }
- /* Unmap and try again. */
- munmap(ptr, size);
- /* ENOMEM if we checked the whole of the target address space. */
- if (addr == (abi_ulong)-1) {
- return (abi_ulong)-1;
- } else if (addr == 0) {
- if (wrapped) {
- return (abi_ulong)-1;
- }
- wrapped = 1;
- /*
- * Don't actually use 0 when wrapping, instead indicate
- * that we'd truly like an allocation in low memory.
- */
- addr = (mmap_min_addr > TARGET_PAGE_SIZE
- ? TARGET_PAGE_ALIGN(mmap_min_addr)
- : TARGET_PAGE_SIZE);
- } else if (wrapped && addr >= start) {
- return (abi_ulong)-1;
- }
- }
- }
- /*
- * Record a successful mmap within the user-exec interval tree.
- */
- static abi_long mmap_end(abi_ulong start, abi_ulong last,
- abi_ulong passthrough_start,
- abi_ulong passthrough_last,
- int flags, int page_flags)
- {
- if (flags & MAP_ANONYMOUS) {
- page_flags |= PAGE_ANON;
- }
- page_flags |= PAGE_RESET;
- if (passthrough_start > passthrough_last) {
- page_set_flags(start, last, page_flags);
- } else {
- if (start < passthrough_start) {
- page_set_flags(start, passthrough_start - 1, page_flags);
- }
- page_set_flags(passthrough_start, passthrough_last,
- page_flags | PAGE_PASSTHROUGH);
- if (passthrough_last < last) {
- page_set_flags(passthrough_last + 1, last, page_flags);
- }
- }
- shm_region_rm_complete(start, last);
- trace_target_mmap_complete(start);
- if (qemu_loglevel_mask(CPU_LOG_PAGE)) {
- FILE *f = qemu_log_trylock();
- if (f) {
- fprintf(f, "page layout changed following mmap\n");
- page_dump(f);
- qemu_log_unlock(f);
- }
- }
- return start;
- }
- /*
- * Special case host page size == target page size,
- * where there are no edge conditions.
- */
- static abi_long mmap_h_eq_g(abi_ulong start, abi_ulong len,
- int host_prot, int flags, int page_flags,
- int fd, off_t offset)
- {
- void *p, *want_p = NULL;
- abi_ulong last;
- if (start || (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
- want_p = g2h_untagged(start);
- }
- p = mmap(want_p, len, host_prot, flags, fd, offset);
- if (p == MAP_FAILED) {
- return -1;
- }
- /* If the host kernel does not support MAP_FIXED_NOREPLACE, emulate. */
- if ((flags & MAP_FIXED_NOREPLACE) && p != want_p) {
- do_munmap(p, len);
- errno = EEXIST;
- return -1;
- }
- start = h2g(p);
- last = start + len - 1;
- return mmap_end(start, last, start, last, flags, page_flags);
- }
- /*
- * Special case host page size < target page size.
- *
- * The two special cases are increased guest alignment, and mapping
- * past the end of a file.
- *
- * When mapping files into a memory area larger than the file,
- * accesses to pages beyond the file size will cause a SIGBUS.
- *
- * For example, if mmaping a file of 100 bytes on a host with 4K
- * pages emulating a target with 8K pages, the target expects to
- * be able to access the first 8K. But the host will trap us on
- * any access beyond 4K.
- *
- * When emulating a target with a larger page-size than the hosts,
- * we may need to truncate file maps at EOF and add extra anonymous
- * pages up to the targets page boundary.
- *
- * This workaround only works for files that do not change.
- * If the file is later extended (e.g. ftruncate), the SIGBUS
- * vanishes and the proper behaviour is that changes within the
- * anon page should be reflected in the file.
- *
- * However, this case is rather common with executable images,
- * so the workaround is important for even trivial tests, whereas
- * the mmap of of a file being extended is less common.
- */
- static abi_long mmap_h_lt_g(abi_ulong start, abi_ulong len, int host_prot,
- int mmap_flags, int page_flags, int fd,
- off_t offset, int host_page_size)
- {
- void *p, *want_p = NULL;
- off_t fileend_adj = 0;
- int flags = mmap_flags;
- abi_ulong last, pass_last;
- if (start || (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
- want_p = g2h_untagged(start);
- }
- if (!(flags & MAP_ANONYMOUS)) {
- struct stat sb;
- if (fstat(fd, &sb) == -1) {
- return -1;
- }
- if (offset >= sb.st_size) {
- /*
- * The entire map is beyond the end of the file.
- * Transform it to an anonymous mapping.
- */
- flags |= MAP_ANONYMOUS;
- fd = -1;
- offset = 0;
- } else if (offset + len > sb.st_size) {
- /*
- * A portion of the map is beyond the end of the file.
- * Truncate the file portion of the allocation.
- */
- fileend_adj = offset + len - sb.st_size;
- }
- }
- if (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE)) {
- if (fileend_adj) {
- p = mmap(want_p, len, host_prot, flags | MAP_ANONYMOUS, -1, 0);
- } else {
- p = mmap(want_p, len, host_prot, flags, fd, offset);
- }
- if (p != want_p) {
- if (p != MAP_FAILED) {
- /* Host does not support MAP_FIXED_NOREPLACE: emulate. */
- do_munmap(p, len);
- errno = EEXIST;
- }
- return -1;
- }
- if (fileend_adj) {
- void *t = mmap(p, len - fileend_adj, host_prot,
- (flags & ~MAP_FIXED_NOREPLACE) | MAP_FIXED,
- fd, offset);
- if (t == MAP_FAILED) {
- int save_errno = errno;
- /*
- * We failed a map over the top of the successful anonymous
- * mapping above. The only failure mode is running out of VMAs,
- * and there's nothing that we can do to detect that earlier.
- * If we have replaced an existing mapping with MAP_FIXED,
- * then we cannot properly recover. It's a coin toss whether
- * it would be better to exit or continue here.
- */
- if (!(flags & MAP_FIXED_NOREPLACE) &&
- !page_check_range_empty(start, start + len - 1)) {
- qemu_log("QEMU target_mmap late failure: %s",
- strerror(save_errno));
- }
- do_munmap(want_p, len);
- errno = save_errno;
- return -1;
- }
- }
- } else {
- size_t host_len, part_len;
- /*
- * Take care to align the host memory. Perform a larger anonymous
- * allocation and extract the aligned portion. Remap the file on
- * top of that.
- */
- host_len = len + TARGET_PAGE_SIZE - host_page_size;
- p = mmap(want_p, host_len, host_prot, flags | MAP_ANONYMOUS, -1, 0);
- if (p == MAP_FAILED) {
- return -1;
- }
- part_len = (uintptr_t)p & (TARGET_PAGE_SIZE - 1);
- if (part_len) {
- part_len = TARGET_PAGE_SIZE - part_len;
- do_munmap(p, part_len);
- p += part_len;
- host_len -= part_len;
- }
- if (len < host_len) {
- do_munmap(p + len, host_len - len);
- }
- if (!(flags & MAP_ANONYMOUS)) {
- void *t = mmap(p, len - fileend_adj, host_prot,
- flags | MAP_FIXED, fd, offset);
- if (t == MAP_FAILED) {
- int save_errno = errno;
- do_munmap(p, len);
- errno = save_errno;
- return -1;
- }
- }
- start = h2g(p);
- }
- last = start + len - 1;
- if (fileend_adj) {
- pass_last = ROUND_UP(last - fileend_adj, host_page_size) - 1;
- } else {
- pass_last = last;
- }
- return mmap_end(start, last, start, pass_last, mmap_flags, page_flags);
- }
- /*
- * Special case host page size > target page size.
- *
- * The two special cases are address and file offsets that are valid
- * for the guest that cannot be directly represented by the host.
- */
- static abi_long mmap_h_gt_g(abi_ulong start, abi_ulong len,
- int target_prot, int host_prot,
- int flags, int page_flags, int fd,
- off_t offset, int host_page_size)
- {
- void *p, *want_p = NULL;
- off_t host_offset = offset & -host_page_size;
- abi_ulong last, real_start, real_last;
- bool misaligned_offset = false;
- size_t host_len;
- if (start || (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
- want_p = g2h_untagged(start);
- }
- if (!(flags & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
- /*
- * Adjust the offset to something representable on the host.
- */
- host_len = len + offset - host_offset;
- p = mmap(want_p, host_len, host_prot, flags, fd, host_offset);
- if (p == MAP_FAILED) {
- return -1;
- }
- /* Update start to the file position at offset. */
- p += offset - host_offset;
- start = h2g(p);
- last = start + len - 1;
- return mmap_end(start, last, start, last, flags, page_flags);
- }
- if (!(flags & MAP_ANONYMOUS)) {
- misaligned_offset = (start ^ offset) & (host_page_size - 1);
- /*
- * The fallback for misalignment is a private mapping + read.
- * This carries none of semantics required of MAP_SHARED.
- */
- if (misaligned_offset && (flags & MAP_TYPE) != MAP_PRIVATE) {
- errno = EINVAL;
- return -1;
- }
- }
- last = start + len - 1;
- real_start = start & -host_page_size;
- real_last = ROUND_UP(last, host_page_size) - 1;
- /*
- * Handle the start and end of the mapping.
- */
- if (real_start < start) {
- abi_ulong real_page_last = real_start + host_page_size - 1;
- if (last <= real_page_last) {
- /* Entire allocation a subset of one host page. */
- if (!mmap_frag(real_start, start, last, target_prot,
- flags, fd, offset)) {
- return -1;
- }
- return mmap_end(start, last, -1, 0, flags, page_flags);
- }
- if (!mmap_frag(real_start, start, real_page_last, target_prot,
- flags, fd, offset)) {
- return -1;
- }
- real_start = real_page_last + 1;
- }
- if (last < real_last) {
- abi_ulong real_page_start = real_last - host_page_size + 1;
- if (!mmap_frag(real_page_start, real_page_start, last,
- target_prot, flags, fd,
- offset + real_page_start - start)) {
- return -1;
- }
- real_last = real_page_start - 1;
- }
- if (real_start > real_last) {
- return mmap_end(start, last, -1, 0, flags, page_flags);
- }
- /*
- * Handle the middle of the mapping.
- */
- host_len = real_last - real_start + 1;
- want_p += real_start - start;
- if (flags & MAP_ANONYMOUS) {
- p = mmap(want_p, host_len, host_prot, flags, -1, 0);
- } else if (!misaligned_offset) {
- p = mmap(want_p, host_len, host_prot, flags, fd,
- offset + real_start - start);
- } else {
- p = mmap(want_p, host_len, host_prot | PROT_WRITE,
- flags | MAP_ANONYMOUS, -1, 0);
- }
- if (p != want_p) {
- if (p != MAP_FAILED) {
- do_munmap(p, host_len);
- errno = EEXIST;
- }
- return -1;
- }
- if (misaligned_offset) {
- if (!mmap_pread(fd, p, host_len, offset + real_start - start, false)) {
- do_munmap(p, host_len);
- return -1;
- }
- if (!(host_prot & PROT_WRITE)) {
- mprotect(p, host_len, host_prot);
- }
- }
- return mmap_end(start, last, -1, 0, flags, page_flags);
- }
- static abi_long target_mmap__locked(abi_ulong start, abi_ulong len,
- int target_prot, int flags, int page_flags,
- int fd, off_t offset)
- {
- int host_page_size = qemu_real_host_page_size();
- int host_prot;
- /*
- * For reserved_va, we are in full control of the allocation.
- * Find a suitable hole and convert to MAP_FIXED.
- */
- if (reserved_va) {
- if (flags & MAP_FIXED_NOREPLACE) {
- /* Validate that the chosen range is empty. */
- if (!page_check_range_empty(start, start + len - 1)) {
- errno = EEXIST;
- return -1;
- }
- flags = (flags & ~MAP_FIXED_NOREPLACE) | MAP_FIXED;
- } else if (!(flags & MAP_FIXED)) {
- abi_ulong real_start = start & -host_page_size;
- off_t host_offset = offset & -host_page_size;
- size_t real_len = len + offset - host_offset;
- abi_ulong align = MAX(host_page_size, TARGET_PAGE_SIZE);
- start = mmap_find_vma(real_start, real_len, align);
- if (start == (abi_ulong)-1) {
- errno = ENOMEM;
- return -1;
- }
- start += offset - host_offset;
- flags |= MAP_FIXED;
- }
- }
- host_prot = target_to_host_prot(target_prot);
- if (host_page_size == TARGET_PAGE_SIZE) {
- return mmap_h_eq_g(start, len, host_prot, flags,
- page_flags, fd, offset);
- } else if (host_page_size < TARGET_PAGE_SIZE) {
- return mmap_h_lt_g(start, len, host_prot, flags,
- page_flags, fd, offset, host_page_size);
- } else {
- return mmap_h_gt_g(start, len, target_prot, host_prot, flags,
- page_flags, fd, offset, host_page_size);
- }
- }
- /* NOTE: all the constants are the HOST ones */
- abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot,
- int flags, int fd, off_t offset)
- {
- abi_long ret;
- int page_flags;
- trace_target_mmap(start, len, target_prot, flags, fd, offset);
- if (!len) {
- errno = EINVAL;
- return -1;
- }
- page_flags = validate_prot_to_pageflags(target_prot);
- if (!page_flags) {
- errno = EINVAL;
- return -1;
- }
- /* Also check for overflows... */
- len = TARGET_PAGE_ALIGN(len);
- if (!len || len != (size_t)len) {
- errno = ENOMEM;
- return -1;
- }
- if (offset & ~TARGET_PAGE_MASK) {
- errno = EINVAL;
- return -1;
- }
- if (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE)) {
- if (start & ~TARGET_PAGE_MASK) {
- errno = EINVAL;
- return -1;
- }
- if (!guest_range_valid_untagged(start, len)) {
- errno = ENOMEM;
- return -1;
- }
- }
- mmap_lock();
- ret = target_mmap__locked(start, len, target_prot, flags,
- page_flags, fd, offset);
- mmap_unlock();
- /*
- * If we're mapping shared memory, ensure we generate code for parallel
- * execution and flush old translations. This will work up to the level
- * supported by the host -- anything that requires EXCP_ATOMIC will not
- * be atomic with respect to an external process.
- */
- if (ret != -1 && (flags & MAP_TYPE) != MAP_PRIVATE) {
- CPUState *cpu = thread_cpu;
- if (!tcg_cflags_has(cpu, CF_PARALLEL)) {
- tcg_cflags_set(cpu, CF_PARALLEL);
- tb_flush(cpu);
- }
- }
- return ret;
- }
- static int mmap_reserve_or_unmap(abi_ulong start, abi_ulong len)
- {
- int host_page_size = qemu_real_host_page_size();
- abi_ulong real_start;
- abi_ulong real_last;
- abi_ulong real_len;
- abi_ulong last;
- abi_ulong a;
- void *host_start;
- int prot;
- last = start + len - 1;
- real_start = start & -host_page_size;
- real_last = ROUND_UP(last, host_page_size) - 1;
- /*
- * If guest pages remain on the first or last host pages,
- * adjust the deallocation to retain those guest pages.
- * The single page special case is required for the last page,
- * lest real_start overflow to zero.
- */
- if (real_last - real_start < host_page_size) {
- prot = 0;
- for (a = real_start; a < start; a += TARGET_PAGE_SIZE) {
- prot |= page_get_flags(a);
- }
- for (a = last; a < real_last; a += TARGET_PAGE_SIZE) {
- prot |= page_get_flags(a + 1);
- }
- if (prot != 0) {
- return 0;
- }
- } else {
- for (prot = 0, a = real_start; a < start; a += TARGET_PAGE_SIZE) {
- prot |= page_get_flags(a);
- }
- if (prot != 0) {
- real_start += host_page_size;
- }
- for (prot = 0, a = last; a < real_last; a += TARGET_PAGE_SIZE) {
- prot |= page_get_flags(a + 1);
- }
- if (prot != 0) {
- real_last -= host_page_size;
- }
- if (real_last < real_start) {
- return 0;
- }
- }
- real_len = real_last - real_start + 1;
- host_start = g2h_untagged(real_start);
- return do_munmap(host_start, real_len);
- }
- int target_munmap(abi_ulong start, abi_ulong len)
- {
- int ret;
- trace_target_munmap(start, len);
- if (start & ~TARGET_PAGE_MASK) {
- errno = EINVAL;
- return -1;
- }
- len = TARGET_PAGE_ALIGN(len);
- if (len == 0 || !guest_range_valid_untagged(start, len)) {
- errno = EINVAL;
- return -1;
- }
- mmap_lock();
- ret = mmap_reserve_or_unmap(start, len);
- if (likely(ret == 0)) {
- page_set_flags(start, start + len - 1, 0);
- shm_region_rm_complete(start, start + len - 1);
- }
- mmap_unlock();
- return ret;
- }
- abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
- abi_ulong new_size, unsigned long flags,
- abi_ulong new_addr)
- {
- int prot;
- void *host_addr;
- if (!guest_range_valid_untagged(old_addr, old_size) ||
- ((flags & MREMAP_FIXED) &&
- !guest_range_valid_untagged(new_addr, new_size)) ||
- ((flags & MREMAP_MAYMOVE) == 0 &&
- !guest_range_valid_untagged(old_addr, new_size))) {
- errno = ENOMEM;
- return -1;
- }
- mmap_lock();
- if (flags & MREMAP_FIXED) {
- host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
- flags, g2h_untagged(new_addr));
- if (reserved_va && host_addr != MAP_FAILED) {
- /*
- * If new and old addresses overlap then the above mremap will
- * already have failed with EINVAL.
- */
- mmap_reserve_or_unmap(old_addr, old_size);
- }
- } else if (flags & MREMAP_MAYMOVE) {
- abi_ulong mmap_start;
- mmap_start = mmap_find_vma(0, new_size, TARGET_PAGE_SIZE);
- if (mmap_start == -1) {
- errno = ENOMEM;
- host_addr = MAP_FAILED;
- } else {
- host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
- flags | MREMAP_FIXED,
- g2h_untagged(mmap_start));
- if (reserved_va) {
- mmap_reserve_or_unmap(old_addr, old_size);
- }
- }
- } else {
- int page_flags = 0;
- if (reserved_va && old_size < new_size) {
- abi_ulong addr;
- for (addr = old_addr + old_size;
- addr < old_addr + new_size;
- addr++) {
- page_flags |= page_get_flags(addr);
- }
- }
- if (page_flags == 0) {
- host_addr = mremap(g2h_untagged(old_addr),
- old_size, new_size, flags);
- if (host_addr != MAP_FAILED) {
- /* Check if address fits target address space */
- if (!guest_range_valid_untagged(h2g(host_addr), new_size)) {
- /* Revert mremap() changes */
- host_addr = mremap(g2h_untagged(old_addr),
- new_size, old_size, flags);
- errno = ENOMEM;
- host_addr = MAP_FAILED;
- } else if (reserved_va && old_size > new_size) {
- mmap_reserve_or_unmap(old_addr + old_size,
- old_size - new_size);
- }
- }
- } else {
- errno = ENOMEM;
- host_addr = MAP_FAILED;
- }
- }
- if (host_addr == MAP_FAILED) {
- new_addr = -1;
- } else {
- new_addr = h2g(host_addr);
- prot = page_get_flags(old_addr);
- page_set_flags(old_addr, old_addr + old_size - 1, 0);
- shm_region_rm_complete(old_addr, old_addr + old_size - 1);
- page_set_flags(new_addr, new_addr + new_size - 1,
- prot | PAGE_VALID | PAGE_RESET);
- shm_region_rm_complete(new_addr, new_addr + new_size - 1);
- }
- mmap_unlock();
- return new_addr;
- }
- abi_long target_madvise(abi_ulong start, abi_ulong len_in, int advice)
- {
- abi_ulong len;
- int ret = 0;
- if (start & ~TARGET_PAGE_MASK) {
- return -TARGET_EINVAL;
- }
- if (len_in == 0) {
- return 0;
- }
- len = TARGET_PAGE_ALIGN(len_in);
- if (len == 0 || !guest_range_valid_untagged(start, len)) {
- return -TARGET_EINVAL;
- }
- /* Translate for some architectures which have different MADV_xxx values */
- switch (advice) {
- case TARGET_MADV_DONTNEED: /* alpha */
- advice = MADV_DONTNEED;
- break;
- case TARGET_MADV_WIPEONFORK: /* parisc */
- advice = MADV_WIPEONFORK;
- break;
- case TARGET_MADV_KEEPONFORK: /* parisc */
- advice = MADV_KEEPONFORK;
- break;
- /* we do not care about the other MADV_xxx values yet */
- }
- /*
- * Most advice values are hints, so ignoring and returning success is ok.
- *
- * However, some advice values such as MADV_DONTNEED, MADV_WIPEONFORK and
- * MADV_KEEPONFORK are not hints and need to be emulated.
- *
- * A straight passthrough for those may not be safe because qemu sometimes
- * turns private file-backed mappings into anonymous mappings.
- * If all guest pages have PAGE_PASSTHROUGH set, mappings have the
- * same semantics for the host as for the guest.
- *
- * We pass through MADV_WIPEONFORK and MADV_KEEPONFORK if possible and
- * return failure if not.
- *
- * MADV_DONTNEED is passed through as well, if possible.
- * If passthrough isn't possible, we nevertheless (wrongly!) return
- * success, which is broken but some userspace programs fail to work
- * otherwise. Completely implementing such emulation is quite complicated
- * though.
- */
- mmap_lock();
- switch (advice) {
- case MADV_WIPEONFORK:
- case MADV_KEEPONFORK:
- ret = -EINVAL;
- /* fall through */
- case MADV_DONTNEED:
- if (page_check_range(start, len, PAGE_PASSTHROUGH)) {
- ret = get_errno(madvise(g2h_untagged(start), len, advice));
- if ((advice == MADV_DONTNEED) && (ret == 0)) {
- page_reset_target_data(start, start + len - 1);
- }
- }
- }
- mmap_unlock();
- return ret;
- }
- #ifndef TARGET_FORCE_SHMLBA
- /*
- * For most architectures, SHMLBA is the same as the page size;
- * some architectures have larger values, in which case they should
- * define TARGET_FORCE_SHMLBA and provide a target_shmlba() function.
- * This corresponds to the kernel arch code defining __ARCH_FORCE_SHMLBA
- * and defining its own value for SHMLBA.
- *
- * The kernel also permits SHMLBA to be set by the architecture to a
- * value larger than the page size without setting __ARCH_FORCE_SHMLBA;
- * this means that addresses are rounded to the large size if
- * SHM_RND is set but addresses not aligned to that size are not rejected
- * as long as they are at least page-aligned. Since the only architecture
- * which uses this is ia64 this code doesn't provide for that oddity.
- */
- static inline abi_ulong target_shmlba(CPUArchState *cpu_env)
- {
- return TARGET_PAGE_SIZE;
- }
- #endif
- #if defined(__arm__) || defined(__mips__) || defined(__sparc__)
- #define HOST_FORCE_SHMLBA 1
- #else
- #define HOST_FORCE_SHMLBA 0
- #endif
- abi_ulong target_shmat(CPUArchState *cpu_env, int shmid,
- abi_ulong shmaddr, int shmflg)
- {
- CPUState *cpu = env_cpu(cpu_env);
- struct shmid_ds shm_info;
- int ret;
- int h_pagesize;
- int t_shmlba, h_shmlba, m_shmlba;
- size_t t_len, h_len, m_len;
- /* shmat pointers are always untagged */
- /*
- * Because we can't use host shmat() unless the address is sufficiently
- * aligned for the host, we'll need to check both.
- * TODO: Could be fixed with softmmu.
- */
- t_shmlba = target_shmlba(cpu_env);
- h_pagesize = qemu_real_host_page_size();
- h_shmlba = (HOST_FORCE_SHMLBA ? SHMLBA : h_pagesize);
- m_shmlba = MAX(t_shmlba, h_shmlba);
- if (shmaddr) {
- if (shmaddr & (m_shmlba - 1)) {
- if (shmflg & SHM_RND) {
- /*
- * The guest is allowing the kernel to round the address.
- * Assume that the guest is ok with us rounding to the
- * host required alignment too. Anyway if we don't, we'll
- * get an error from the kernel.
- */
- shmaddr &= ~(m_shmlba - 1);
- if (shmaddr == 0 && (shmflg & SHM_REMAP)) {
- return -TARGET_EINVAL;
- }
- } else {
- int require = TARGET_PAGE_SIZE;
- #ifdef TARGET_FORCE_SHMLBA
- require = t_shmlba;
- #endif
- /*
- * Include host required alignment, as otherwise we cannot
- * use host shmat at all.
- */
- require = MAX(require, h_shmlba);
- if (shmaddr & (require - 1)) {
- return -TARGET_EINVAL;
- }
- }
- }
- } else {
- if (shmflg & SHM_REMAP) {
- return -TARGET_EINVAL;
- }
- }
- /* All rounding now manually concluded. */
- shmflg &= ~SHM_RND;
- /* Find out the length of the shared memory segment. */
- ret = get_errno(shmctl(shmid, IPC_STAT, &shm_info));
- if (is_error(ret)) {
- /* can't get length, bail out */
- return ret;
- }
- t_len = TARGET_PAGE_ALIGN(shm_info.shm_segsz);
- h_len = ROUND_UP(shm_info.shm_segsz, h_pagesize);
- m_len = MAX(t_len, h_len);
- if (!guest_range_valid_untagged(shmaddr, m_len)) {
- return -TARGET_EINVAL;
- }
- WITH_MMAP_LOCK_GUARD() {
- bool mapped = false;
- void *want, *test;
- abi_ulong last;
- if (!shmaddr) {
- shmaddr = mmap_find_vma(0, m_len, m_shmlba);
- if (shmaddr == -1) {
- return -TARGET_ENOMEM;
- }
- mapped = !reserved_va;
- } else if (shmflg & SHM_REMAP) {
- /*
- * If host page size > target page size, the host shmat may map
- * more memory than the guest expects. Reject a mapping that
- * would replace memory in the unexpected gap.
- * TODO: Could be fixed with softmmu.
- */
- if (t_len < h_len &&
- !page_check_range_empty(shmaddr + t_len,
- shmaddr + h_len - 1)) {
- return -TARGET_EINVAL;
- }
- } else {
- if (!page_check_range_empty(shmaddr, shmaddr + m_len - 1)) {
- return -TARGET_EINVAL;
- }
- }
- /* All placement is now complete. */
- want = (void *)g2h_untagged(shmaddr);
- /*
- * Map anonymous pages across the entire range, then remap with
- * the shared memory. This is required for a number of corner
- * cases for which host and guest page sizes differ.
- */
- if (h_len != t_len) {
- int mmap_p = PROT_READ | (shmflg & SHM_RDONLY ? 0 : PROT_WRITE);
- int mmap_f = MAP_PRIVATE | MAP_ANONYMOUS
- | (reserved_va || mapped || (shmflg & SHM_REMAP)
- ? MAP_FIXED : MAP_FIXED_NOREPLACE);
- test = mmap(want, m_len, mmap_p, mmap_f, -1, 0);
- if (unlikely(test != want)) {
- /* shmat returns EINVAL not EEXIST like mmap. */
- ret = (test == MAP_FAILED && errno != EEXIST
- ? get_errno(-1) : -TARGET_EINVAL);
- if (mapped) {
- do_munmap(want, m_len);
- }
- return ret;
- }
- mapped = true;
- }
- if (reserved_va || mapped) {
- shmflg |= SHM_REMAP;
- }
- test = shmat(shmid, want, shmflg);
- if (test == MAP_FAILED) {
- ret = get_errno(-1);
- if (mapped) {
- do_munmap(want, m_len);
- }
- return ret;
- }
- assert(test == want);
- last = shmaddr + m_len - 1;
- page_set_flags(shmaddr, last,
- PAGE_VALID | PAGE_RESET | PAGE_READ |
- (shmflg & SHM_RDONLY ? 0 : PAGE_WRITE) |
- (shmflg & SHM_EXEC ? PAGE_EXEC : 0));
- shm_region_rm_complete(shmaddr, last);
- shm_region_add(shmaddr, last);
- }
- /*
- * We're mapping shared memory, so ensure we generate code for parallel
- * execution and flush old translations. This will work up to the level
- * supported by the host -- anything that requires EXCP_ATOMIC will not
- * be atomic with respect to an external process.
- */
- if (!tcg_cflags_has(cpu, CF_PARALLEL)) {
- tcg_cflags_set(cpu, CF_PARALLEL);
- tb_flush(cpu);
- }
- if (qemu_loglevel_mask(CPU_LOG_PAGE)) {
- FILE *f = qemu_log_trylock();
- if (f) {
- fprintf(f, "page layout changed following shmat\n");
- page_dump(f);
- qemu_log_unlock(f);
- }
- }
- return shmaddr;
- }
- abi_long target_shmdt(abi_ulong shmaddr)
- {
- abi_long rv;
- /* shmdt pointers are always untagged */
- WITH_MMAP_LOCK_GUARD() {
- abi_ulong last = shm_region_find(shmaddr);
- if (last == 0) {
- return -TARGET_EINVAL;
- }
- rv = get_errno(shmdt(g2h_untagged(shmaddr)));
- if (rv == 0) {
- abi_ulong size = last - shmaddr + 1;
- page_set_flags(shmaddr, last, 0);
- shm_region_rm_complete(shmaddr, last);
- mmap_reserve_or_unmap(shmaddr, size);
- }
- }
- return rv;
- }
|