1 /* 2 * mmap support for qemu 3 * 4 * Copyright (c) 2003 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, write to the Free Software 18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 */ 20 #include <stdlib.h> 21 #include <stdio.h> 22 #include <stdarg.h> 23 #include <string.h> 24 #include <unistd.h> 25 #include <errno.h> 26 #include <sys/mman.h> 27 28 #include "qemu.h" 29 30 //#define DEBUG_MMAP 31 32 /* NOTE: all the constants are the HOST ones, but addresses are target. */ 33 int target_mprotect(target_ulong start, target_ulong len, int prot) 34 { 35 target_ulong end, host_start, host_end, addr; 36 int prot1, ret; 37 38 #ifdef DEBUG_MMAP 39 printf("mprotect: start=0x%lx len=0x%lx prot=%c%c%c\n", start, len, 40 prot & PROT_READ ? 'r' : '-', 41 prot & PROT_WRITE ? 'w' : '-', 42 prot & PROT_EXEC ? 'x' : '-'); 43 #endif 44 45 if ((start & ~TARGET_PAGE_MASK) != 0) 46 return -EINVAL; 47 len = TARGET_PAGE_ALIGN(len); 48 end = start + len; 49 if (end < start) 50 return -EINVAL; 51 if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) 52 return -EINVAL; 53 if (len == 0) 54 return 0; 55 56 host_start = start & qemu_host_page_mask; 57 host_end = HOST_PAGE_ALIGN(end); 58 if (start > host_start) { 59 /* handle host page containing start */ 60 prot1 = prot; 61 for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { 62 prot1 |= page_get_flags(addr); 63 } 64 if (host_end == host_start + qemu_host_page_size) { 65 for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 66 prot1 |= page_get_flags(addr); 67 } 68 end = host_end; 69 } 70 ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS); 71 if (ret != 0) 72 return ret; 73 host_start += qemu_host_page_size; 74 } 75 if (end < host_end) { 76 prot1 = prot; 77 for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 78 prot1 |= page_get_flags(addr); 79 } 80 ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size, 81 prot1 & PAGE_BITS); 82 if (ret != 0) 83 return ret; 84 host_end -= qemu_host_page_size; 85 } 86 87 /* handle the pages in the middle */ 88 if (host_start < host_end) { 89 ret = mprotect(g2h(host_start), host_end - host_start, prot); 90 if (ret != 0) 91 return ret; 92 } 93 page_set_flags(start, start + len, prot | PAGE_VALID); 94 return 0; 95 } 96 97 /* map an incomplete host page */ 98 static int mmap_frag(target_ulong real_start, 99 target_ulong start, target_ulong end, 100 int prot, int flags, int fd, target_ulong offset) 101 { 102 target_ulong real_end, ret, addr; 103 void *host_start; 104 int prot1, prot_new; 105 106 real_end = real_start + qemu_host_page_size; 107 host_start = g2h(real_start); 108 109 /* get the protection of the target pages outside the mapping */ 110 prot1 = 0; 111 for(addr = real_start; addr < real_end; addr++) { 112 if (addr < start || addr >= end) 113 prot1 |= page_get_flags(addr); 114 } 115 116 if (prot1 == 0) { 117 /* no page was there, so we allocate one */ 118 ret = (long)mmap(host_start, qemu_host_page_size, prot, 119 flags | MAP_ANONYMOUS, -1, 0); 120 if (ret == -1) 121 return ret; 122 prot1 = prot; 123 } 124 prot1 &= PAGE_BITS; 125 126 prot_new = prot | prot1; 127 if (!(flags & MAP_ANONYMOUS)) { 128 /* msync() won't work here, so we return an error if write is 129 possible while it is a shared mapping */ 130 if ((flags & MAP_TYPE) == MAP_SHARED && 131 (prot & PROT_WRITE)) 132 return -EINVAL; 133 134 /* adjust protection to be able to read */ 135 if (!(prot1 & PROT_WRITE)) 136 mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE); 137 138 /* read the corresponding file data */ 139 pread(fd, g2h(start), end - start, offset); 140 141 /* put final protection */ 142 if (prot_new != (prot1 | PROT_WRITE)) 143 mprotect(host_start, qemu_host_page_size, prot_new); 144 } else { 145 /* just update the protection */ 146 if (prot_new != prot1) { 147 mprotect(host_start, qemu_host_page_size, prot_new); 148 } 149 } 150 return 0; 151 } 152 153 /* NOTE: all the constants are the HOST ones */ 154 long target_mmap(target_ulong start, target_ulong len, int prot, 155 int flags, int fd, target_ulong offset) 156 { 157 target_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len; 158 long host_start; 159 #if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) || \ 160 defined(__ia64) 161 static target_ulong last_start = 0x40000000; 162 #elif defined(__CYGWIN__) 163 /* Cygwin doesn't have a whole lot of address space. */ 164 static target_ulong last_start = 0x18000000; 165 #endif 166 167 #ifdef DEBUG_MMAP 168 { 169 printf("mmap: start=0x%lx len=0x%lx prot=%c%c%c flags=", 170 start, len, 171 prot & PROT_READ ? 'r' : '-', 172 prot & PROT_WRITE ? 'w' : '-', 173 prot & PROT_EXEC ? 'x' : '-'); 174 if (flags & MAP_FIXED) 175 printf("MAP_FIXED "); 176 if (flags & MAP_ANONYMOUS) 177 printf("MAP_ANON "); 178 switch(flags & MAP_TYPE) { 179 case MAP_PRIVATE: 180 printf("MAP_PRIVATE "); 181 break; 182 case MAP_SHARED: 183 printf("MAP_SHARED "); 184 break; 185 default: 186 printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE); 187 break; 188 } 189 printf("fd=%d offset=%lx\n", fd, offset); 190 } 191 #endif 192 193 if (offset & ~TARGET_PAGE_MASK) { 194 errno = EINVAL; 195 return -1; 196 } 197 198 len = TARGET_PAGE_ALIGN(len); 199 if (len == 0) 200 return start; 201 real_start = start & qemu_host_page_mask; 202 203 if (!(flags & MAP_FIXED)) { 204 #if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) || \ 205 defined(__ia64) || defined(__CYGWIN__) 206 /* tell the kenel to search at the same place as i386 */ 207 if (real_start == 0) { 208 real_start = last_start; 209 last_start += HOST_PAGE_ALIGN(len); 210 } 211 #endif 212 if (0 && qemu_host_page_size != qemu_real_host_page_size) { 213 /* NOTE: this code is only for debugging with '-p' option */ 214 /* ??? Can also occur when TARGET_PAGE_SIZE > host page size. */ 215 /* reserve a memory area */ 216 /* ??? This needs fixing for remapping. */ 217 abort(); 218 host_len = HOST_PAGE_ALIGN(len) + qemu_host_page_size - TARGET_PAGE_SIZE; 219 real_start = (long)mmap(g2h(real_start), host_len, PROT_NONE, 220 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 221 if (real_start == -1) 222 return real_start; 223 real_end = real_start + host_len; 224 start = HOST_PAGE_ALIGN(real_start); 225 end = start + HOST_PAGE_ALIGN(len); 226 if (start > real_start) 227 munmap((void *)real_start, start - real_start); 228 if (end < real_end) 229 munmap((void *)end, real_end - end); 230 /* use it as a fixed mapping */ 231 flags |= MAP_FIXED; 232 } else { 233 /* if not fixed, no need to do anything */ 234 host_offset = offset & qemu_host_page_mask; 235 host_len = len + offset - host_offset; 236 host_start = (long)mmap(real_start ? g2h(real_start) : NULL, 237 host_len, prot, flags, fd, host_offset); 238 if (host_start == -1) 239 return host_start; 240 /* update start so that it points to the file position at 'offset' */ 241 if (!(flags & MAP_ANONYMOUS)) 242 host_start += offset - host_offset; 243 start = h2g(host_start); 244 goto the_end1; 245 } 246 } 247 248 if (start & ~TARGET_PAGE_MASK) { 249 errno = EINVAL; 250 return -1; 251 } 252 end = start + len; 253 real_end = HOST_PAGE_ALIGN(end); 254 255 /* worst case: we cannot map the file because the offset is not 256 aligned, so we read it */ 257 if (!(flags & MAP_ANONYMOUS) && 258 (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) { 259 /* msync() won't work here, so we return an error if write is 260 possible while it is a shared mapping */ 261 if ((flags & MAP_TYPE) == MAP_SHARED && 262 (prot & PROT_WRITE)) { 263 errno = EINVAL; 264 return -1; 265 } 266 retaddr = target_mmap(start, len, prot | PROT_WRITE, 267 MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 268 -1, 0); 269 if (retaddr == -1) 270 return retaddr; 271 pread(fd, g2h(start), len, offset); 272 if (!(prot & PROT_WRITE)) { 273 ret = target_mprotect(start, len, prot); 274 if (ret != 0) 275 return ret; 276 } 277 goto the_end; 278 } 279 280 /* handle the start of the mapping */ 281 if (start > real_start) { 282 if (real_end == real_start + qemu_host_page_size) { 283 /* one single host page */ 284 ret = mmap_frag(real_start, start, end, 285 prot, flags, fd, offset); 286 if (ret == -1) 287 return ret; 288 goto the_end1; 289 } 290 ret = mmap_frag(real_start, start, real_start + qemu_host_page_size, 291 prot, flags, fd, offset); 292 if (ret == -1) 293 return ret; 294 real_start += qemu_host_page_size; 295 } 296 /* handle the end of the mapping */ 297 if (end < real_end) { 298 ret = mmap_frag(real_end - qemu_host_page_size, 299 real_end - qemu_host_page_size, real_end, 300 prot, flags, fd, 301 offset + real_end - qemu_host_page_size - start); 302 if (ret == -1) 303 return ret; 304 real_end -= qemu_host_page_size; 305 } 306 307 /* map the middle (easier) */ 308 if (real_start < real_end) { 309 unsigned long offset1; 310 if (flags & MAP_ANONYMOUS) 311 offset1 = 0; 312 else 313 offset1 = offset + real_start - start; 314 ret = (long)mmap(g2h(real_start), real_end - real_start, 315 prot, flags, fd, offset1); 316 if (ret == -1) 317 return ret; 318 } 319 the_end1: 320 page_set_flags(start, start + len, prot | PAGE_VALID); 321 the_end: 322 #ifdef DEBUG_MMAP 323 printf("ret=0x%lx\n", (long)start); 324 page_dump(stdout); 325 printf("\n"); 326 #endif 327 return start; 328 } 329 330 int target_munmap(target_ulong start, target_ulong len) 331 { 332 target_ulong end, real_start, real_end, addr; 333 int prot, ret; 334 335 #ifdef DEBUG_MMAP 336 printf("munmap: start=0x%lx len=0x%lx\n", start, len); 337 #endif 338 if (start & ~TARGET_PAGE_MASK) 339 return -EINVAL; 340 len = TARGET_PAGE_ALIGN(len); 341 if (len == 0) 342 return -EINVAL; 343 end = start + len; 344 real_start = start & qemu_host_page_mask; 345 real_end = HOST_PAGE_ALIGN(end); 346 347 if (start > real_start) { 348 /* handle host page containing start */ 349 prot = 0; 350 for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) { 351 prot |= page_get_flags(addr); 352 } 353 if (real_end == real_start + qemu_host_page_size) { 354 for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { 355 prot |= page_get_flags(addr); 356 } 357 end = real_end; 358 } 359 if (prot != 0) 360 real_start += qemu_host_page_size; 361 } 362 if (end < real_end) { 363 prot = 0; 364 for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { 365 prot |= page_get_flags(addr); 366 } 367 if (prot != 0) 368 real_end -= qemu_host_page_size; 369 } 370 371 /* unmap what we can */ 372 if (real_start < real_end) { 373 ret = munmap((void *)real_start, real_end - real_start); 374 if (ret != 0) 375 return ret; 376 } 377 378 page_set_flags(start, start + len, 0); 379 return 0; 380 } 381 382 /* XXX: currently, we only handle MAP_ANONYMOUS and not MAP_FIXED 383 blocks which have been allocated starting on a host page */ 384 long target_mremap(target_ulong old_addr, target_ulong old_size, 385 target_ulong new_size, unsigned long flags, 386 target_ulong new_addr) 387 { 388 int prot; 389 390 /* XXX: use 5 args syscall */ 391 new_addr = (long)mremap(g2h(old_addr), old_size, new_size, flags); 392 if (new_addr == -1) 393 return new_addr; 394 new_addr = h2g(new_addr); 395 prot = page_get_flags(old_addr); 396 page_set_flags(old_addr, old_addr + old_size, 0); 397 page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID); 398 return new_addr; 399 } 400 401 int target_msync(target_ulong start, target_ulong len, int flags) 402 { 403 target_ulong end; 404 405 if (start & ~TARGET_PAGE_MASK) 406 return -EINVAL; 407 len = TARGET_PAGE_ALIGN(len); 408 end = start + len; 409 if (end < start) 410 return -EINVAL; 411 if (end == start) 412 return 0; 413 414 start &= qemu_host_page_mask; 415 return msync(g2h(start), end - start, flags); 416 } 417 418