#define _XOPEN_SOURCE 500 #define _GNU_SOURCE // for MAP_32BIT #include #include #include #include #include #include #include #include #include "vx32.h" #include "vx32impl.h" extern int vx_elfbigmem; typedef struct vxmem_chunk vxmem_chunk; struct vxmem_chunk { vxmem mem; int fd; off_t size; uint8_t *perm; }; static void chunk_free(vxmem *mem) { vxmem_chunk *chunk = (vxmem_chunk*)mem; if(mem->mapped){ vxmem_unmap(mem, mem->mapped); mem->mapped = NULL; } free(chunk->perm); close(chunk->fd); free(chunk); } static int chunk_resize(vxmem *mem, size_t size) { vxmem_chunk *chunk = (vxmem_chunk*)mem; uint8_t *perm; uint32_t onpage, npage; if(size == chunk->size) return 0; if(mem->mapped){ assert(!vx_elfbigmem); vxmem_unmap(mem, mem->mapped); mem->mapped = NULL; } if(ftruncate(chunk->fd, size) < 0) return -1; onpage = VXPAGEROUND(chunk->size) / VXPAGESIZE; npage = VXPAGEROUND(size) / VXPAGESIZE; perm = realloc(chunk->perm, npage); if (perm == NULL) return -1; if(npage > onpage) memset(perm + onpage, 0, npage-onpage); chunk->perm = perm; chunk->size = size; return 0; } vxmem *vxmem_chunk_copy(vxmem *mem) { vxmem_chunk *chunk = (vxmem_chunk*)mem; uint8_t *perm; uint32_t i, npage; assert(mem->free == chunk_free); vxmem *nmem = vxmem_chunk_new(chunk->size); if (nmem == NULL) return NULL; npage = VXPAGEROUND(chunk->size) / VXPAGESIZE; int n = 0; vxmem_chunk *nchunk = (vxmem_chunk*)nmem; char *buf = malloc(4096); memmove(nchunk->perm, chunk->perm, npage); for (i=0; iperm[i]) { vxmem_read(mem, buf, i*VXPAGESIZE, VXPAGESIZE); vxmem_write(nmem, buf, i*VXPAGESIZE, VXPAGESIZE); n++; } } free(buf); return nmem; } static ssize_t chunk_read(vxmem *mem, void *data, uint32_t addr, uint32_t len) { vxmem_chunk *chunk = (vxmem_chunk*)mem; if (addr >= chunk->size) return -1; if (len > chunk->size - addr) len = chunk->size - addr; return pread(chunk->fd, data, len, addr); } static ssize_t chunk_write(vxmem *mem, const void *data, uint32_t addr, uint32_t len) { vxmem_chunk *chunk = (vxmem_chunk*)mem; if (addr >= chunk->size) return -1; if (len > chunk->size - addr) len = chunk->size - addr; return pwrite(chunk->fd, data, len, addr); } static int chunk_checkperm(vxmem *mem, uint32_t addr, uint32_t len, uint32_t perm, uint32_t *out_faultva) { uint32_t pn, pe, cpe, va; vxmem_chunk *chunk = (vxmem_chunk*)mem; if (addr + len < addr) return 0; if (len == 0) return 1; pn = addr / VXPAGESIZE; pe = VXPAGEROUND(addr + len-1) / VXPAGESIZE; cpe = VXPAGEROUND(chunk->size) / VXPAGESIZE; for (; pn < pe; pn++) { if (pn >= cpe) { if (out_faultva) *out_faultva = chunk->size; return 0; } if ((chunk->perm[pn] & perm) != perm) { if (out_faultva) { va = pn * VXPAGESIZE; if (va < addr) va = addr; *out_faultva = va; } return 0; } } return 1; } static int mmperm(int perm) { int m; if (perm == 0) m = PROT_NONE; else { m = 0; if (perm & VXPERM_READ) m |= PROT_READ; if (perm & VXPERM_WRITE) m |= PROT_WRITE; if (perm & VXPERM_EXEC) m |= PROT_EXEC; } return m; } static int chunk_setperm(vxmem *mem, uint32_t addr, uint32_t len, uint32_t perm) { vxmem_chunk *chunk = (vxmem_chunk*)mem; uint32_t a; if (addr + len < addr || addr + len > chunk->size) { errno = EINVAL; return -1; } if (mem->mapped) { if (mprotect(mem->mapped->base + addr, len, mmperm(perm)) < 0) return -1; } for (a = addr; a < addr + len; a += VXPAGESIZE) chunk->perm[a/VXPAGESIZE] = perm; return 0; } static vxmmap *chunk_map(vxmem *mem, uint32_t flags) { vxmmap *mm; void *v; vxmem_chunk *chunk = (vxmem_chunk*)mem; if (mem->mapped) { mem->mapped->ref++; // XXX get rid of ref? return mem->mapped; } mm = malloc(sizeof *mm); if (mm == NULL) return NULL; if ((v = mmap(0, chunk->size, PROT_NONE, MAP_32BIT | (vx_elfbigmem ? MAP_PRIVATE : MAP_SHARED), chunk->fd, 0)) == (void*)-1) { free(mm); return NULL; } // vxprint("chunk_map %p size %08x\n", v, chunk->size); // Now set the permissions on all pages that should be accessible. unsigned npages = chunk->size / VXPAGESIZE; for (unsigned i = 0; i < npages; ) { uint8_t pageperm = chunk->perm[i]; // For efficiency, merge mprotect calls on page ranges. unsigned j; for (j = i+1; j < npages; j++) if (chunk->perm[j] != pageperm) break; if (pageperm == 0) { i = j; // nothing to do for this range continue; } // Calculate the effective permission to map with in the host. int prot = mmperm(pageperm); assert(prot >= 0); // Set the permissions on this range. if (mprotect((char*)v + i*VXPAGESIZE, (j-i)*VXPAGESIZE, prot) < 0) { munmap(v, chunk->size); free(mm); return NULL; } i = j; } mm->base = v; mm->size = chunk->size; mm->ref = 2; // XXX get rid of ref? mem->mapped = mm; return mm; } static void chunk_unmap(vxmem *mem, vxmmap *mm) { vxmem_chunk *chunk = (vxmem_chunk*)mem; if(mm == NULL) return; // if(--mm->ref > 0) // XXX get rid of ref? // return; if(mm == mem->mapped) mem->mapped = NULL; // vxprint("chunk_unmap %p size %08x\n", mm->base, mm->size); munmap(mm->base, mm->size); free(mm); } static vxmem chunk_proto = { chunk_read, chunk_write, chunk_map, chunk_unmap, chunk_checkperm, chunk_setperm, chunk_resize, chunk_free, }; static vxmem *vxmem_chunk_fromfd(int fd, off_t size) { vxmem_chunk *chunk; chunk = calloc(sizeof *chunk, 1); if (chunk == NULL) return NULL; chunk->mem = chunk_proto; chunk->fd = fd; if (chunk_resize((vxmem*)chunk, size) < 0) { free(chunk); return NULL; } return (vxmem*)chunk; } vxmem *vxmem_chunk_new(int size) { int fd; char tmpfn[] = "/var/tmp/vxXXXXXX"; vxmem *mem; if ((fd = mkstemp(tmpfn)) < 0) return NULL; unlink(tmpfn); if ((mem = vxmem_chunk_fromfd(fd, size)) == NULL) { close(fd); return NULL; } return mem; }