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