#define _GNU_SOURCE /* See feature_test_macros(7) */ #include #include #include #include #include #include #include #include #include "box64context.h" #include "elfloader.h" #include "debug.h" #include "x64trace.h" #include "x64emu.h" #include "librarian.h" #include "bridge.h" #include "library.h" #include "callback.h" #include "wrapper.h" #include "threads.h" #include "x64trace.h" #include "signals.h" #include #include "custommem.h" #include "khash.h" #include "threads.h" #include "rbtree.h" #ifdef DYNAREC #include "dynablock.h" #include "dynarec/dynablock_private.h" #include "dynarec/native_lock.h" #include "dynarec/dynarec_next.h" #define USE_MMAP // init inside dynablocks.c static mmaplist_t *mmaplist = NULL; static uint64_t jmptbl_allocated = 0, jmptbl_allocated1 = 0, jmptbl_allocated2 = 0, jmptbl_allocated3 = 0; #ifdef JMPTABL_SHIFT4 static uint64_t jmptbl_allocated4 = 0; static uintptr_t**** box64_jmptbl4[1<next.size + sizeof(blockmark_t)) #define PREV_BLOCK(b) (blockmark_t*)(((uintptr_t)(b) - (b)->prev.size) - sizeof(blockmark_t)) #define LAST_BLOCK(b, s) (blockmark_t*)(((uintptr_t)(b)+(s))-sizeof(blockmark_t)) void printBlock(blockmark_t* b, void* start) { printf_log(LOG_NONE, "========== Block is:\n"); do { printf_log(LOG_NONE, "%c%p, fill=%d, size=0x%x (prev=%d/0x%x)\n", b==start?'*':' ', b, b->next.fill, b->next.size, b->prev.fill, b->prev.size); b = NEXT_BLOCK(b); } while(b->next.x32); printf_log(LOG_NONE, "===================\n"); } // get first subblock free in block. Return NULL if no block, else first subblock free (mark included), filling size static void* getFirstBlock(void* block, size_t maxsize, size_t* size, void* start) { // get start of block blockmark_t *m = (blockmark_t*)((start)?start:block); while(m->next.x32) { // while there is a subblock if(!m->next.fill && m->next.size>=maxsize) { *size = m->next.size; return m; } m = NEXT_BLOCK(m); } return NULL; } static void* getNextFreeBlock(void* block) { blockmark_t *m = (blockmark_t*)block; while (m->next.fill) { m = NEXT_BLOCK(m); }; return m; } static void* getPrevFreeBlock(void* block) { blockmark_t *m = (blockmark_t*)block; do { m = PREV_BLOCK(m); } while (m->next.fill); return m; } static size_t getMaxFreeBlock(void* block, size_t block_size, void* start) { // get start of block if(start) { blockmark_t *m = (blockmark_t*)start; unsigned int maxsize = 0; while(m->next.x32) { // while there is a subblock if(!m->next.fill && m->next.size>maxsize) { maxsize = m->next.size; } m = NEXT_BLOCK(m); } return (maxsize>=sizeof(blockmark_t))?maxsize:0; } else { blockmark_t *m = LAST_BLOCK(block, block_size); // start with the end unsigned int maxsize = 0; while(m->prev.x32) { // while there is a subblock if(!m->prev.fill && m->prev.size>maxsize) { maxsize = m->prev.size; if((uintptr_t)block+maxsize>(uintptr_t)m) return (maxsize>=sizeof(blockmark_t))?maxsize:0; // no block large enough left... } m = PREV_BLOCK(m); } return (maxsize>=sizeof(blockmark_t))?maxsize:0; } } #define THRESHOLD (128-2*sizeof(blockmark_t)) static void* allocBlock(void* block, void *sub, size_t size, void** pstart) { (void)block; blockmark_t *s = (blockmark_t*)sub; blockmark_t *n = NEXT_BLOCK(s); s->next.fill = 1; // check if a new mark is worth it if(s->next.size>size+2*sizeof(blockmark_t)+THRESHOLD) { size_t old_size = s->next.size; s->next.size = size; blockmark_t *m = NEXT_BLOCK(s); m->prev.fill = 1; m->prev.size = s->next.size; m->next.fill = 0; m->next.size = old_size - (size + sizeof(blockmark_t)); n->prev.fill = 0; n->prev.size = m->next.size; n = m; } else { n->prev.fill = 1; } if(pstart && sub==*pstart) { // get the next free block while(n->next.fill) n = NEXT_BLOCK(n); *pstart = (void*)n; } return (void*)((uintptr_t)sub + sizeof(blockmark_t)); } static size_t freeBlock(void *block, void* sub, void** pstart) { blockmark_t *m = (blockmark_t*)block; blockmark_t *s = (blockmark_t*)sub; blockmark_t *n = NEXT_BLOCK(s); if(block!=sub) m = PREV_BLOCK(s); s->next.fill = 0; n->prev.fill = 0; // check if merge with previous if (m!=s && s->prev.x32 && !s->prev.fill) { // remove s... m->next.size += s->next.size + sizeof(blockmark_t); n->prev.size = m->next.size; s = m; } // check if merge with next if(n->next.x32 && !n->next.fill) { blockmark_t *n2 = NEXT_BLOCK(n); //remove n s->next.size += n->next.size + sizeof(blockmark_t); n2->prev.size = s->next.size; } if(pstart && (uintptr_t)*pstart>(uintptr_t)s) { *pstart = (void*)s; } // return free size at current block (might be bigger) return s->next.size; } // return 1 if block has been expanded to new size, 0 if not static int expandBlock(void* block, void* sub, size_t newsize) { (void)block; newsize = (newsize+3)&~3; blockmark_t *s = (blockmark_t*)sub; blockmark_t *n = NEXT_BLOCK(s); if(s->next.size>=newsize) // big enough, no shrinking... return 1; if(s->next.fill) return 0; // next block is filled // unsigned bitfield of this length gets "promoted" to *signed* int... if((size_t)(s->next.size + n->next.size + sizeof(blockmark_t)) < newsize) return 0; // free space too short // ok, doing the alloc! if((s->next.size+n->next.size+sizeof(blockmark_t))-newsizenext.size += n->next.size+sizeof(blockmark_t); else s->next.size = newsize+sizeof(blockmark_t); blockmark_t *m = NEXT_BLOCK(s); // this is new n m->prev.fill = 1; m->prev.size = s->next.size; if(n!=m) { // new mark m->prev.fill = 1; m->prev.size = s->next.size; m->next.fill = 0; m->next.size = (uintptr_t)n - (uintptr_t)m; n->prev.fill = 0; n->prev.size = m->next.size; } return 1; } // return size of block static size_t sizeBlock(void* sub) { blockmark_t *s = (blockmark_t*)sub; return s->next.size; } // return 1 if block is coherent, 0 if not (and printf the issues) int printBlockCoherent(int i) { if(i<0 || i>=n_blocks) { printf_log(LOG_NONE, "Error, %d should be between 0 and %d\n", i, n_blocks); return 0; } int ret = 1; blockmark_t* m = (blockmark_t*)p_blocks[i].block; // check if first is correct blockmark_t* first = getNextFreeBlock(m); if(p_blocks[i].first && p_blocks[i].first!=first) {printf_log(LOG_NONE, "First %p and stored first %p differs for block %d\n", first, p_blocks[i].first, i); ret = 0;} // check if maxfree is correct, with no hint size_t maxfree = getMaxFreeBlock(m, p_blocks[i].size, NULL); if(maxfree != p_blocks[i].maxfree) {printf_log(LOG_NONE, "Maxfree without hint %zd and stored maxfree %zd differs for block %d\n", maxfree, p_blocks[i].maxfree, i); ret = 0;} // check if maxfree from first is correct maxfree = getMaxFreeBlock(m, p_blocks[i].size, p_blocks[i].first); if(maxfree != p_blocks[i].maxfree) {printf_log(LOG_NONE, "Maxfree with hint %zd and stored maxfree %zd differs for block %d\n", maxfree, p_blocks[i].maxfree, i); ret = 0;} // check chain blockmark_t* last = (blockmark_t*)(((uintptr_t)m)+p_blocks[i].size-sizeof(blockmark_t)); while(mnext.fill && !n->next.fill && n!=last) { printf_log(LOG_NONE, "Chain contains 2 subsequent free blocks %p (%d) and %p (%d) for block %d\n", m, m->next.size, n, n->next.size, i); ret = 0; } m = n; } if(m!=last) { printf_log(LOG_NONE, "Last block %p is behond expexted block %p for block %d\n", m, last, i); ret = 0; } return ret; } void testAllBlocks() { size_t total = 0; size_t fragmented_free = 0; size_t max_free = 0; for(int i=0; inext.x32) { if(!m->next.fill) fragmented_free += m->next.size; m = NEXT_BLOCK(m); } } printf_log(LOG_NONE, "Total %d blocks, for %zd allocated memory, max_free %zd, toatal fragmented free %zd\n", n_blocks, total, max_free, fragmented_free); } static size_t roundSize(size_t size) { if(!size) return size; size = (size+7)&~7LL; // 8 bytes align in size if(size=size) { size_t rsize = 0; sub = getFirstBlock(p_blocks[i].block, size, &rsize, p_blocks[i].first); if(sub) { if(rsize-sizec_blocks) { c_blocks += 4; p_blocks = (blocklist_t*)box_realloc(p_blocks, c_blocks*sizeof(blocklist_t)); } size_t allocsize = (fullsize>MMAPSIZE)?fullsize:MMAPSIZE; #ifdef USE_MMAP void* p = internal_mmap(NULL, allocsize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); memset(p, 0, allocsize); #else void* p = box_calloc(1, allocsize); #endif #ifdef TRACE_MEMSTAT customMalloc_allocated += allocsize; #endif p_blocks[i].block = p; p_blocks[i].first = p; p_blocks[i].size = allocsize; // setup marks blockmark_t* m = (blockmark_t*)p; m->prev.x32 = 0; m->next.fill = 0; m->next.size = allocsize-2*sizeof(blockmark_t); blockmark_t* n = NEXT_BLOCK(m); n->next.x32 = 0; n->prev.fill = 0; n->prev.size = m->next.size; // alloc 1st block void* ret = allocBlock(p_blocks[i].block, p, size, &p_blocks[i].first); p_blocks[i].maxfree = getMaxFreeBlock(p_blocks[i].block, p_blocks[i].size, p_blocks[i].first); mutex_unlock(&mutex_blocks); if(mapallmem) { // defer the setProtection... //setProtection((uintptr_t)p, allocsize, PROT_READ | PROT_WRITE); defered_prot_p = (uintptr_t)p; defered_prot_sz = allocsize; defered_prot_prot = PROT_READ|PROT_WRITE; } return ret; } void* customCalloc(size_t n, size_t size) { size_t newsize = roundSize(n*size); void* ret = customMalloc(newsize); memset(ret, 0, newsize); return ret; } void* customRealloc(void* p, size_t size) { if(!p) return customMalloc(size); size = roundSize(size); uintptr_t addr = (uintptr_t)p; mutex_lock(&mutex_blocks); for(int i=0; i(uintptr_t)p_blocks[i].block) && (addr<((uintptr_t)p_blocks[i].block+p_blocks[i].size))) { void* sub = (void*)(addr-sizeof(blockmark_t)); if(expandBlock(p_blocks[i].block, sub, size)) { if(sub=p_blocks[i].first) p_blocks[i].first = getNextFreeBlock(sub); p_blocks[i].maxfree = getMaxFreeBlock(p_blocks[i].block, p_blocks[i].size, p_blocks[i].first); mutex_unlock(&mutex_blocks); return p; } mutex_unlock(&mutex_blocks); void* newp = customMalloc(size); memcpy(newp, p, sizeBlock(sub)); customFree(p); return newp; } } mutex_unlock(&mutex_blocks); if(n_blocks) dynarec_log(LOG_NONE, "Warning, block %p not found in p_blocks for realloc, malloc'ing again without free\n", (void*)addr); return customMalloc(size); } void customFree(void* p) { if(!p) return; uintptr_t addr = (uintptr_t)p; mutex_lock(&mutex_blocks); for(int i=0; i(uintptr_t)p_blocks[i].block) && (addr<((uintptr_t)p_blocks[i].block+p_blocks[i].size))) { void* sub = (void*)(addr-sizeof(blockmark_t)); size_t newfree = freeBlock(p_blocks[i].block, sub, &p_blocks[i].first); if(p_blocks[i].maxfree < newfree) p_blocks[i].maxfree = newfree; mutex_unlock(&mutex_blocks); return; } } mutex_unlock(&mutex_blocks); if(n_blocks) dynarec_log(LOG_NONE, "Warning, block %p not found in p_blocks for Free\n", (void*)addr); } #ifdef DYNAREC #define NCHUNK 64 typedef struct mmaplist_s { blocklist_t chunks[NCHUNK]; mmaplist_t* next; } mmaplist_t; dynablock_t* FindDynablockFromNativeAddress(void* p) { if(!p) return NULL; uintptr_t addr = (uintptr_t)p; int i= 0; mmaplist_t* list = mmaplist; if(!list) return NULL; while(list) { if ((addr>(uintptr_t)list->chunks[i].block) && (addr<((uintptr_t)list->chunks[i].block+list->chunks[i].size))) { blockmark_t* sub = (blockmark_t*)list->chunks[i].block; while((uintptr_t)subaddr) { // found it! // self is the field of a block return *(dynablock_t**)((uintptr_t)sub+sizeof(blockmark_t)); } sub = n; } return NULL; } ++i; if(i==NCHUNK) { i = 0; list = list->next; } } return NULL; } #ifdef TRACE_MEMSTAT static uint64_t dynarec_allocated = 0; #endif uintptr_t AllocDynarecMap(size_t size) { if(!size) return 0; size = roundSize(size); mmaplist_t* list = mmaplist; if(!list) list = mmaplist = (mmaplist_t*)box_calloc(1, sizeof(mmaplist_t)); // check if there is space in current open ones int i = 0; uintptr_t sz = size + 2*sizeof(blockmark_t); while(1) { if(list->chunks[i].maxfree>=size) { // looks free, try to alloc! size_t rsize = 0; void* sub = getFirstBlock(list->chunks[i].block, size, &rsize, list->chunks[i].first); if(sub) { void* ret = allocBlock(list->chunks[i].block, sub, size, NULL); if(sub==list->chunks[i].first) list->chunks[i].first = getNextFreeBlock(sub); if(rsize==list->chunks[i].maxfree) list->chunks[i].maxfree = getMaxFreeBlock(list->chunks[i].block, list->chunks[i].size, list->chunks[i].first); return (uintptr_t)ret; } } // check if new if(!list->chunks[i].size) { // alloc a new block, aversized or not, we are at the end of the list size_t allocsize = (sz>MMAPSIZE)?sz:MMAPSIZE; // allign sz with pagesize allocsize = (allocsize+(box64_pagesize-1))&~(box64_pagesize-1); #ifndef USE_MMAP void *p = NULL; if(!(p=box_memalign(box64_pagesize, allocsize))) { dynarec_log(LOG_INFO, "Cannot create dynamic map of %zu bytes\n", allocsize); return 0; } mprotect(p, allocsize, PROT_READ | PROT_WRITE | PROT_EXEC); #else void* p = internal_mmap(NULL, allocsize, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if(p==(void*)-1) { dynarec_log(LOG_INFO, "Cannot create dynamic map of %zu bytes\n", allocsize); return 0; } #endif #ifdef TRACE_MEMSTAT dynarec_allocated += allocsize; #endif setProtection((uintptr_t)p, allocsize, PROT_READ | PROT_WRITE | PROT_EXEC); list->chunks[i].block = p; list->chunks[i].first = p; list->chunks[i].size = allocsize; // setup marks blockmark_t* m = (blockmark_t*)p; m->prev.x32 = 0; m->next.fill = 0; m->next.size = allocsize-2*sizeof(blockmark_t); blockmark_t* n = NEXT_BLOCK(m); n->next.x32 = 0; n->prev.fill = 0; n->prev.size = m->next.size; // alloc 1st block void* ret = allocBlock(list->chunks[i].block, p, size, NULL); list->chunks[i].maxfree = getMaxFreeBlock(list->chunks[i].block, list->chunks[i].size, NULL); if(list->chunks[i].maxfree) list->chunks[i].first = getNextFreeBlock(m); return (uintptr_t)ret; } // next chunk... ++i; if(i==NCHUNK) { i = 0; if(!list->next) list->next = (mmaplist_t*)box_calloc(1, sizeof(mmaplist_t)); list = list->next; } } } void FreeDynarecMap(uintptr_t addr) { if(!addr) return; int i= 0; mmaplist_t* list = mmaplist; while(list) { if ((addr>(uintptr_t)list->chunks[i].block) && (addr<((uintptr_t)list->chunks[i].block+list->chunks[i].size))) { void* sub = (void*)(addr-sizeof(blockmark_t)); size_t newfree = freeBlock(list->chunks[i].block, sub, &list->chunks[i].first); if(list->chunks[i].maxfree < newfree) list->chunks[i].maxfree = newfree; return; } ++i; if(i==NCHUNK) { i = 0; list = list->next; } } } static uintptr_t getDBSize(uintptr_t addr, size_t maxsize, dynablock_t** db) { #ifdef JMPTABL_START4 const uintptr_t idx4 = (addr>>JMPTABL_START4)&JMPTABLE_MASK4; #endif const uintptr_t idx3 = (addr>>JMPTABL_START3)&JMPTABLE_MASK3; const uintptr_t idx2 = (addr>>JMPTABL_START2)&JMPTABLE_MASK2; const uintptr_t idx1 = (addr>>JMPTABL_START1)&JMPTABLE_MASK1; uintptr_t idx0 = addr&JMPTABLE_MASK0; #ifdef JMPTABL_START4 *db = *(dynablock_t**)(box64_jmptbl4[idx4][idx3][idx2][idx1][idx0]- sizeof(void*)); #else *db = *(dynablock_t**)(box64_jmptbl3[idx3][idx2][idx1][idx0]- sizeof(void*)); #endif if(*db) return addr+1; #ifdef JMPTABL_START4 if(box64_jmptbl4[idx4] == box64_jmptbldefault3) return ((idx4+1)<>JMPTABL_START3)+1)<>JMPTABL_START2)+1)<>JMPTABL_START2)+1)<>JMPTABL_START1)+1)<JMPTABLE_MASK0) maxsize = JMPTABLE_MASK0; while(block[idx0]==(uintptr_t)native_next) { ++idx0; if(idx0>maxsize) return (addr&~JMPTABLE_MASK0)+idx0; } *db = *(dynablock_t**)(block[idx0]- sizeof(void*)); return (addr&~JMPTABLE_MASK0)+idx0+1; } // each dynmap is 64k of size void addDBFromAddressRange(uintptr_t addr, size_t size) { dynarec_log(LOG_DEBUG, "addDBFromAddressRange %p -> %p\n", (void*)addr, (void*)(addr+size-1)); // do nothing, dynablock are allowed based on memory protection flags } void cleanDBFromAddressRange(uintptr_t addr, size_t size, int destroy) { uintptr_t start_addr = my_context?((addrmax_db_size)?0:(addr-my_context->max_db_size)):addr; dynarec_log(LOG_DEBUG, "cleanDBFromAddressRange %p/%p -> %p %s\n", (void*)addr, (void*)start_addr, (void*)(addr+size-1), destroy?"destroy":"mark"); dynablock_t* db = NULL; uintptr_t end = addr+size; while (start_addrmax_db_size)?0:(addr-my_context->max_db_size)):addr; dynarec_log(LOG_DEBUG, "isDBFromAddressRange %p/%p -> %p => ", (void*)addr, (void*)start_addr, (void*)(addr+size-1)); dynablock_t* db = NULL; uintptr_t end = addr+size; while (start_addr>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = (((uintptr_t)addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = (((uintptr_t)addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = (((uintptr_t)addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = (((uintptr_t)addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 return (native_lock_storeifref(create_jmptbl(idx0, idx1, idx2, idx3, idx4), jmp, native_next)==jmp)?1:0; #else return (native_lock_storeifref(create_jmptbl(idx0, idx1, idx2, idx3), jmp, native_next)==jmp)?1:0; #endif } void setJumpTableDefault64(void* addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4; idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; if(box64_jmptbl4[idx4] == box64_jmptbldefault3) return; uintptr_t ****box64_jmptbl3 = box64_jmptbl4[idx4]; #endif idx3 = (((uintptr_t)addr)>>JMPTABL_START3)&JMPTABLE_MASK3; if(box64_jmptbl3[idx3] == box64_jmptbldefault2) return; idx2 = (((uintptr_t)addr)>>JMPTABL_START2)&JMPTABLE_MASK2; if(box64_jmptbl3[idx3][idx2] == box64_jmptbldefault1) return; idx1 = (((uintptr_t)addr)>>JMPTABL_START1)&JMPTABLE_MASK1; if(box64_jmptbl3[idx3][idx2][idx1] == box64_jmptbldefault0) return; idx0 = (((uintptr_t)addr) )&JMPTABLE_MASK0; native_lock_store_dd(&box64_jmptbl3[idx3][idx2][idx1][idx0], (uintptr_t)native_next); } void setJumpTableDefaultRef64(void* addr, void* jmp) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4; idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; if(box64_jmptbl4[idx4] == box64_jmptbldefault3) return; uintptr_t ****box64_jmptbl3 = box64_jmptbl4[idx4]; #endif idx3 = (((uintptr_t)addr)>>JMPTABL_START3)&JMPTABLE_MASK3; if(box64_jmptbl3[idx3] == box64_jmptbldefault2) return; idx2 = (((uintptr_t)addr)>>JMPTABL_START2)&JMPTABLE_MASK2; if(box64_jmptbl3[idx3][idx2] == box64_jmptbldefault1) return; idx1 = (((uintptr_t)addr)>>JMPTABL_START1)&JMPTABLE_MASK1; if(box64_jmptbl3[idx3][idx2][idx1] == box64_jmptbldefault0) return; idx0 = (((uintptr_t)addr) )&JMPTABLE_MASK0; native_lock_storeifref(&box64_jmptbl3[idx3][idx2][idx1][idx0], native_next, jmp); } int setJumpTableIfRef64(void* addr, void* jmp, void* ref) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = (((uintptr_t)addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = (((uintptr_t)addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = (((uintptr_t)addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = (((uintptr_t)addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 return (native_lock_storeifref(create_jmptbl(idx0, idx1, idx2, idx3, idx4), jmp, ref)==jmp)?1:0; #else return (native_lock_storeifref(create_jmptbl(idx0, idx1, idx2, idx3), jmp, ref)==jmp)?1:0; #endif } int isJumpTableDefault64(void* addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4; idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; if(box64_jmptbl4[idx4] == box64_jmptbldefault3) return 1; uintptr_t ****box64_jmptbl3 = box64_jmptbl4[idx4]; #endif idx3 = (((uintptr_t)addr)>>JMPTABL_START3)&JMPTABLE_MASK3; if(box64_jmptbl3[idx3] == box64_jmptbldefault2) return 1; idx2 = (((uintptr_t)addr)>>JMPTABL_START2)&JMPTABLE_MASK2; if(box64_jmptbl3[idx3][idx2] == box64_jmptbldefault1) return 1; idx1 = (((uintptr_t)addr)>>JMPTABL_START1)&JMPTABLE_MASK1; if(box64_jmptbl3[idx3][idx2][idx1] == box64_jmptbldefault0) return 1; idx0 = (((uintptr_t)addr) )&JMPTABLE_MASK0; return (box64_jmptbl3[idx3][idx2][idx1][idx0]==(uintptr_t)native_next)?1:0; } uintptr_t getJumpTable64() { #ifdef JMPTABL_SHIFT4 return (uintptr_t)box64_jmptbl4; #else return (uintptr_t)box64_jmptbl3; #endif } uintptr_t getJumpTable32() { #ifdef JMPTABL_SHIFT4 return (uintptr_t)box64_jmptbl4[0][0]; #else return (uintptr_t)box64_jmptbl3[0]; #endif } uintptr_t getJumpTableAddress64(uintptr_t addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = ((addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = ((addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = ((addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = ((addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 return (uintptr_t)create_jmptbl(idx0, idx1, idx2, idx3, idx4); #else return (uintptr_t)create_jmptbl(idx0, idx1, idx2, idx3); #endif } dynablock_t* getDB(uintptr_t addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = ((addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = ((addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = ((addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = ((addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 uintptr_t ret = (uintptr_t)box64_jmptbl4[idx4][idx3][idx2][idx1][idx0]; #else uintptr_t ret = (uintptr_t)box64_jmptbl3[idx3][idx2][idx1][idx0]; #endif return *(dynablock_t**)(ret - sizeof(void*)); } int getNeedTest(uintptr_t addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = ((addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = ((addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = ((addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = ((addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 uintptr_t ret = (uintptr_t)box64_jmptbl4[idx4][idx3][idx2][idx1][idx0]; #else uintptr_t ret = (uintptr_t)box64_jmptbl3[idx3][idx2][idx1][idx0]; #endif dynablock_t* db = *(dynablock_t**)(ret - sizeof(void*)); return db?((ret!=(uintptr_t)db->block)?1:0):0; } uintptr_t getJumpAddress64(uintptr_t addr) { uintptr_t idx3, idx2, idx1, idx0; #ifdef JMPTABL_SHIFT4 uintptr_t idx4 = (((uintptr_t)addr)>>JMPTABL_START4)&JMPTABLE_MASK4; #endif idx3 = ((addr)>>JMPTABL_START3)&JMPTABLE_MASK3; idx2 = ((addr)>>JMPTABL_START2)&JMPTABLE_MASK2; idx1 = ((addr)>>JMPTABL_START1)&JMPTABLE_MASK1; idx0 = ((addr) )&JMPTABLE_MASK0; #ifdef JMPTABL_SHIFT4 return (uintptr_t)box64_jmptbl4[idx4][idx3][idx2][idx1][idx0]; #else return (uintptr_t)box64_jmptbl3[idx3][idx2][idx1][idx0]; #endif } // Remove the Write flag from an adress range, so DB can be executed safely void protectDBJumpTable(uintptr_t addr, size_t size, void* jump, void* ref) { dynarec_log(LOG_DEBUG, "protectDBJumpTable %p -> %p\n", (void*)addr, (void*)(addr+size-1)); uintptr_t cur = addr&~(box64_pagesize-1); uintptr_t end = ALIGN(addr+size); LOCK_PROT(); while(cur!=end) { uint32_t prot = 0, oprot; uintptr_t bend = 0; rb_get_end(memprot, cur, &prot, &bend); if(bend>end) bend = end; oprot = prot; uint32_t dyn = prot&PROT_DYN; if(!prot) prot = PROT_READ | PROT_WRITE | PROT_EXEC; if(!(dyn&PROT_NEVERPROT)) { prot&=~PROT_CUSTOM; if(prot&PROT_WRITE) { if(!dyn) mprotect((void*)cur, bend-cur, prot&~PROT_WRITE); prot |= PROT_DYNAREC; } else prot |= PROT_DYNAREC_R; } if (prot != oprot) // If the node doesn't exist, then prot != 0 rb_set(memprot, cur, bend, prot); cur = bend; } if(jump) setJumpTableIfRef64((void*)addr, jump, ref); UNLOCK_PROT(); } // Remove the Write flag from an adress range, so DB can be executed safely void protectDB(uintptr_t addr, uintptr_t size) { dynarec_log(LOG_DEBUG, "protectDB %p -> %p\n", (void*)addr, (void*)(addr+size-1)); uintptr_t cur = addr&~(box64_pagesize-1); uintptr_t end = ALIGN(addr+size); LOCK_PROT(); while(cur!=end) { uint32_t prot = 0, oprot; uintptr_t bend = 0; rb_get_end(memprot, cur, &prot, &bend); if(bend>end) bend = end; oprot = prot; uint32_t dyn = prot&PROT_DYN; if(!prot) prot = PROT_READ | PROT_WRITE | PROT_EXEC; if(!(dyn&PROT_NEVERPROT)) { prot&=~PROT_CUSTOM; if(prot&PROT_WRITE) { if(!dyn) mprotect((void*)cur, bend-cur, prot&~PROT_WRITE); prot |= PROT_DYNAREC; } else prot |= PROT_DYNAREC_R; } if (prot != oprot) // If the node doesn't exist, then prot != 0 rb_set(memprot, cur, bend, prot); cur = bend; } UNLOCK_PROT(); } // Add the Write flag from an adress range, and mark all block as dirty void unprotectDB(uintptr_t addr, size_t size, int mark) { dynarec_log(LOG_DEBUG, "unprotectDB %p -> %p (mark=%d)\n", (void*)addr, (void*)(addr+size-1), mark); uintptr_t cur = addr&~(box64_pagesize-1); uintptr_t end = ALIGN(addr+size); LOCK_PROT(); while(cur!=end) { uint32_t prot = 0, oprot; uintptr_t bend = 0; if (!rb_get_end(memprot, cur, &prot, &bend)) { if(bend>=end) break; else { cur = bend; continue; } } oprot = prot; if(bend>end) bend = end; if(!(prot&PROT_NEVERPROT)) { if(prot&PROT_DYNAREC) { prot&=~PROT_DYN; if(mark) cleanDBFromAddressRange(cur, bend-cur, 0); mprotect((void*)cur, bend-cur, prot); } else if(prot&PROT_DYNAREC_R) prot &= ~PROT_CUSTOM; } if (prot != oprot) rb_set(memprot, cur, bend, prot); cur = bend; } UNLOCK_PROT(); } int isprotectedDB(uintptr_t addr, size_t size) { dynarec_log(LOG_DEBUG, "isprotectedDB %p -> %p => ", (void*)addr, (void*)(addr+size-1)); addr &=~(box64_pagesize-1); uintptr_t end = ALIGN(addr+size); LOCK_PROT_READ(); while (addr < end) { uint32_t prot; uintptr_t bend; if (!rb_get_end(memprot, addr, &prot, &bend) || !(prot&PROT_DYN)) { dynarec_log(LOG_DEBUG, "0\n"); UNLOCK_PROT_READ(); return 0; } else { addr = bend; } } UNLOCK_PROT_READ(); dynarec_log(LOG_DEBUG, "1\n"); return 1; } #endif void updateProtection(uintptr_t addr, size_t size, uint32_t prot) { dynarec_log(LOG_DEBUG, "updateProtection %p:%p 0x%hhx\n", (void*)addr, (void*)(addr+size-1), prot); LOCK_PROT(); uintptr_t cur = addr & ~(box64_pagesize-1); uintptr_t end = ALIGN(cur+size); rb_set(mapallmem, cur, cur+size, 1); while (cur < end) { uintptr_t bend; uint32_t oprot; rb_get_end(memprot, cur, &oprot, &bend); uint32_t dyn=(oprot&PROT_DYN); if(!(dyn&PROT_NEVERPROT)) { if(dyn && (prot&PROT_WRITE)) { // need to remove the write protection from this block dyn = PROT_DYNAREC; mprotect((void*)cur, bend-cur, prot&~PROT_WRITE); } else if(dyn && !(prot&PROT_WRITE)) { dyn = PROT_DYNAREC_R; } } if ((prot|dyn) != oprot) rb_set(memprot, cur, bend, prot|dyn); cur = bend; } UNLOCK_PROT(); } void setProtection(uintptr_t addr, size_t size, uint32_t prot) { size = ALIGN(size); LOCK_PROT(); uintptr_t cur = addr & ~(box64_pagesize-1); uintptr_t end = ALIGN(cur+size); rb_set(mapallmem, cur, end, 1); rb_set(memprot, cur, end, prot); UNLOCK_PROT(); } void setProtection_mmap(uintptr_t addr, size_t size, uint32_t prot) { if(!size) return; addr &= ~(box64_pagesize-1); size = ALIGN(size); LOCK_PROT(); rb_set(mmapmem, addr, addr+size, 1); if(!prot) rb_set(mapallmem, addr, addr+size, 1); UNLOCK_PROT(); if(prot) setProtection(addr, size, prot); } void setProtection_elf(uintptr_t addr, size_t size, uint32_t prot) { size = ALIGN(size); addr &= ~(box64_pagesize-1); if(prot) setProtection(addr, size, prot); else { LOCK_PROT(); rb_set(mapallmem, addr, addr+size, 1); UNLOCK_PROT(); } } void refreshProtection(uintptr_t addr) { LOCK_PROT(); uint32_t prot; uintptr_t bend; if (rb_get_end(memprot, addr, &prot, &bend)) { int ret = mprotect((void*)(addr&~(box64_pagesize-1)), box64_pagesize, prot&~PROT_CUSTOM); dynarec_log(LOG_DEBUG, "refreshProtection(%p): %p/0x%x (ret=%d/%s)\n", (void*)addr, (void*)(addr&~(box64_pagesize-1)), prot, ret, ret?strerror(errno):"ok"); } UNLOCK_PROT(); } void allocProtection(uintptr_t addr, size_t size, uint32_t prot) { dynarec_log(LOG_DEBUG, "allocProtection %p:%p 0x%x\n", (void*)addr, (void*)(addr+size-1), prot); size = ALIGN(size); addr &= ~(box64_pagesize-1); LOCK_PROT(); rb_set(mapallmem, addr, addr+size, 1); UNLOCK_PROT(); // don't need to add precise tracking probably } void loadProtectionFromMap() { if(box64_mapclean) return; char buf[500]; FILE *f = fopen("/proc/self/maps", "r"); if(!f) return; while(!feof(f)) { char* ret = fgets(buf, sizeof(buf), f); (void)ret; char r, w, x; uintptr_t s, e; if(sscanf(buf, "%lx-%lx %c%c%c", &s, &e, &r, &w, &x)==5) { int prot = ((r=='r')?PROT_READ:0)|((w=='w')?PROT_WRITE:0)|((x=='x')?PROT_EXEC:0); allocProtection(s, e-s, prot); if(s>0x7fff00000000LL) have48bits = 1; } } static int shown48bits = 0; if(!shown48bits) { shown48bits = 1; if(have48bits) printf_log(LOG_INFO, "BOX64: Detected 48bits at least of address space\n"); else printf_log(LOG_INFO, "BOX64: Didn't detect 48bits of address space, considering it's 39bits\n"); } fclose(f); box64_mapclean = 1; } void freeProtection(uintptr_t addr, size_t size) { size = ALIGN(size); addr &= ~(box64_pagesize-1); dynarec_log(LOG_DEBUG, "freeProtection %p:%p\n", (void*)addr, (void*)(addr+size-1)); LOCK_PROT(); rb_unset(mapallmem, addr, addr+size); rb_unset(mmapmem, addr, addr+size); rb_unset(memprot, addr, addr+size); UNLOCK_PROT(); } uint32_t getProtection(uintptr_t addr) { LOCK_PROT_READ(); uint32_t ret = rb_get(memprot, addr); UNLOCK_PROT_READ(); return ret; } int getMmapped(uintptr_t addr) { return rb_get(mmapmem, addr); } #define LOWEST (void*)0x10000 #define MEDIUM (void*)0x40000000 #define HIGH (void*)0x60000000 void* find31bitBlockNearHint(void* hint, size_t size, uintptr_t mask) { uint32_t prot; if(hint=size) return (void*)cur; } // granularity 0x10000 cur = (bend+mask)&~mask; } return NULL; } void* find32bitBlock(size_t size) { void* ret = find31bitBlockNearHint(MEDIUM, size, 0); if(ret) return ret; ret = find31bitBlockNearHint(LOWEST, size, 0); return ret?ret:find47bitBlock(size); } void* find47bitBlock(size_t size) { void* ret = find47bitBlockNearHint(HIGH, size, 0); if(!ret) ret = find32bitBlock(size); return ret; } void* find47bitBlockNearHint(void* hint, size_t size, uintptr_t mask) { uint32_t prot; if(hint=size) return (void*)cur; } // granularity 0x10000 cur = (bend+mask)&~mask; } return NULL; } void* find47bitBlockElf(size_t size, int mainbin, uintptr_t mask) { static void* startingpoint = NULL; if(!startingpoint) { startingpoint = (void*)(have48bits?0x7fff00000000LL:0x3f00000000LL); } void* mainaddr = (void*)0x100000000LL; void* ret = find47bitBlockNearHint(mainbin?mainaddr:startingpoint, size, mask); if(!ret) ret = find31bitBlockNearHint(MEDIUM, size, mask); if(!ret) ret = find31bitBlockNearHint(LOWEST, size, mask); if(!mainbin) startingpoint = (void*)(((uintptr_t)startingpoint+size+0x1000000LL)&~0xffffffLL); return ret; } int isBlockFree(void* hint, size_t size) { uint32_t prot; uintptr_t bend = 0; uintptr_t cur = (uintptr_t)hint; if(!rb_get_end(mapallmem, cur, &prot, &bend)) { if(bend-cur>=size) return 1; } return 0; } int unlockCustommemMutex() { int ret = 0; int i = 0; #ifdef USE_CUSTOM_MUTEX uint32_t tid = (uint32_t)GetTID(); #define GO(A, B) \ i = (native_lock_storeifref2_d(&A, 0, tid)==tid); \ if(i) { \ ret|=(1< %p (%s)\n", (void*)cur, bend, ret, strerror(errno)); printf_log(LOG_DEBUG, "mmap %p-%p\n", cur, bend); if(ret!=(void*)-1) { rb_set(mapallmem, cur, bend, 1); } } cur = bend; } } void init_custommem_helper(box64context_t* ctx) { (void)ctx; if(inited) // already initialized return; inited = 1; memprot = init_rbtree(); sigfillset(&critical_prot); init_mutexes(); #ifdef DYNAREC if(box64_dynarec) { #ifdef JMPTABL_SHIFT4 for(int i=0; i<(1< njmps_in_lv1_max) njmps_in_lv1_max = njmps_in_cur_lv1; } } } #ifdef JMPTABL_SHIFT4 } if(box64_log) printf("Allocation:\n- dynarec: %lld kio\n- customMalloc: %lld kio\n- memprot: %lld kio (peak at %lld kio)\n- jump table: %lld kio (%lld level 4, %lld level 3, %lld level 2, %lld level 1 table allocated, for %lld jumps, with at most %lld per level 1)\n", dynarec_allocated / 1024, customMalloc_allocated / 1024, memprot_allocated / 1024, memprot_max_allocated / 1024, jmptbl_allocated / 1024, jmptbl_allocated4, jmptbl_allocated3, jmptbl_allocated2, jmptbl_allocated1, njmps, njmps_in_lv1_max); #else if(box64_log) printf("Allocation:\n- dynarec: %lld kio\n- customMalloc: %lld kio\n- memprot: %lld kio (peak at %lld kio)\n- jump table: %lld kio (%lld level 3, %lld level 2, %lld level 1 table allocated, for %lld jumps, with at most %lld per level 1)\n", dynarec_allocated / 1024, customMalloc_allocated / 1024, memprot_allocated / 1024, memprot_max_allocated / 1024, jmptbl_allocated / 1024, jmptbl_allocated3, jmptbl_allocated2, jmptbl_allocated1, njmps, njmps_in_lv1_max); #endif #endif if(!inited) return; inited = 0; #ifdef DYNAREC if(box64_dynarec) { dynarec_log(LOG_DEBUG, "Free global Dynarecblocks\n"); mmaplist_t* head = mmaplist; mmaplist = NULL; while(head) { for (int i=0; ichunks[i].block) #ifdef USE_MMAP internal_munmap(head->chunks[i].block, head->chunks[i].size); #else box_free(head->chunks[i].block); #endif } mmaplist_t *old = head; head = head->next; free(old); } box_free(mmaplist); #ifdef JMPTABL_SHIFT4 uintptr_t**** box64_jmptbl3; for(int i4 = 0; i4 < (1<< JMPTABL_SHIFT4); ++i4) if (box64_jmptbl4[i4] != box64_jmptbldefault3) { box64_jmptbl3 = box64_jmptbl4[i4]; #endif for (int i3=0; i3<(1<