From fd6ce8f6604359e60283e6d4dfc935ca57c556e5 Mon Sep 17 00:00:00 2001 From: bellard Date: Wed, 14 May 2003 19:00:11 +0000 Subject: [PATCH] self-modifying code support git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@163 c046a42c-6fe2-441c-8c8c-71466251a162 --- cpu-i386.h | 82 +++++++++++++- exec-i386.c | 128 ++++----------------- exec.c | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 400 insertions(+), 123 deletions(-) diff --git a/cpu-i386.h b/cpu-i386.h index 6058252869..44411b0caa 100644 --- a/cpu-i386.h +++ b/cpu-i386.h @@ -445,16 +445,21 @@ extern unsigned long host_page_mask; #define HOST_PAGE_ALIGN(addr) (((addr) + host_page_size - 1) & host_page_mask) /* same as PROT_xxx */ -#define PAGE_READ 0x0001 -#define PAGE_WRITE 0x0002 -#define PAGE_EXEC 0x0004 -#define PAGE_BITS (PAGE_READ | PAGE_WRITE | PAGE_EXEC) -#define PAGE_VALID 0x0008 +#define PAGE_READ 0x0001 +#define PAGE_WRITE 0x0002 +#define PAGE_EXEC 0x0004 +#define PAGE_BITS (PAGE_READ | PAGE_WRITE | PAGE_EXEC) +#define PAGE_VALID 0x0008 +/* original state of the write flag (used when tracking self-modifying + code */ +#define PAGE_WRITE_ORG 0x0010 void page_dump(FILE *f); int page_get_flags(unsigned long address); void page_set_flags(unsigned long start, unsigned long end, int flags); +void page_unprotect_range(uint8_t *data, unsigned long data_size); +/***************************************************/ /* internal functions */ #define GEN_FLAG_CODE32_SHIFT 0 @@ -468,8 +473,73 @@ void page_set_flags(unsigned long start, unsigned long end, int flags); int cpu_x86_gen_code(uint8_t *gen_code_buf, int max_code_size, int *gen_code_size_ptr, - uint8_t *pc_start, uint8_t *cs_base, int flags); + uint8_t *pc_start, uint8_t *cs_base, int flags, + int *code_size_ptr); void cpu_x86_tblocks_init(void); void page_init(void); +int page_unprotect(unsigned long address); + +#define CODE_GEN_MAX_SIZE 65536 +#define CODE_GEN_ALIGN 16 /* must be >= of the size of a icache line */ + +#define CODE_GEN_HASH_BITS 15 +#define CODE_GEN_HASH_SIZE (1 << CODE_GEN_HASH_BITS) + +/* maximum total translate dcode allocated */ +#define CODE_GEN_BUFFER_SIZE (2048 * 1024) +//#define CODE_GEN_BUFFER_SIZE (128 * 1024) + +typedef struct TranslationBlock { + unsigned long pc; /* simulated PC corresponding to this block (EIP + CS base) */ + unsigned long cs_base; /* CS base for this block */ + unsigned int flags; /* flags defining in which context the code was generated */ + uint16_t size; /* size of target code for this block (1 <= + size <= TARGET_PAGE_SIZE) */ + uint8_t *tc_ptr; /* pointer to the translated code */ + struct TranslationBlock *hash_next; /* next matching block */ + struct TranslationBlock *page_next[2]; /* next blocks in even/odd page */ +} TranslationBlock; + +static inline unsigned int tb_hash_func(unsigned long pc) +{ + return pc & (CODE_GEN_HASH_SIZE - 1); +} + +void tb_flush(void); +TranslationBlock *tb_alloc(unsigned long pc, + unsigned long size); + +extern TranslationBlock *tb_hash[CODE_GEN_HASH_SIZE]; + +extern uint8_t code_gen_buffer[CODE_GEN_BUFFER_SIZE]; +extern uint8_t *code_gen_ptr; + +/* find a translation block in the translation cache. If not found, + return NULL and the pointer to the last element of the list in pptb */ +static inline TranslationBlock *tb_find(TranslationBlock ***pptb, + unsigned long pc, + unsigned long cs_base, + unsigned int flags) +{ + TranslationBlock **ptb, *tb; + unsigned int h; + + h = tb_hash_func(pc); + ptb = &tb_hash[h]; + for(;;) { + tb = *ptb; + if (!tb) + break; + if (tb->pc == pc && tb->cs_base == cs_base && tb->flags == flags) + return tb; + ptb = &tb->hash_next; + } + *pptb = ptb; + return NULL; +} + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif #endif /* CPU_I386_H */ diff --git a/exec-i386.c b/exec-i386.c index 8a20718e25..abc06b4946 100644 --- a/exec-i386.c +++ b/exec-i386.c @@ -21,39 +21,10 @@ #include "disas.h" //#define DEBUG_EXEC -#define DEBUG_FLUSH //#define DEBUG_SIGNAL /* main execution loop */ -/* maximum total translate dcode allocated */ -#define CODE_GEN_BUFFER_SIZE (2048 * 1024) -//#define CODE_GEN_BUFFER_SIZE (128 * 1024) -#define CODE_GEN_MAX_SIZE 65536 -#define CODE_GEN_ALIGN 16 /* must be >= of the size of a icache line */ - -/* threshold to flush the translated code buffer */ -#define CODE_GEN_BUFFER_MAX_SIZE (CODE_GEN_BUFFER_SIZE - CODE_GEN_MAX_SIZE) - -#define CODE_GEN_MAX_BLOCKS (CODE_GEN_BUFFER_SIZE / 64) -#define CODE_GEN_HASH_BITS 15 -#define CODE_GEN_HASH_SIZE (1 << CODE_GEN_HASH_BITS) - -typedef struct TranslationBlock { - unsigned long pc; /* simulated PC corresponding to this block (EIP + CS base) */ - unsigned long cs_base; /* CS base for this block */ - unsigned int flags; /* flags defining in which context the code was generated */ - uint8_t *tc_ptr; /* pointer to the translated code */ - struct TranslationBlock *hash_next; /* next matching block */ -} TranslationBlock; - -TranslationBlock tbs[CODE_GEN_MAX_BLOCKS]; -TranslationBlock *tb_hash[CODE_GEN_HASH_SIZE]; -int nb_tbs; - -uint8_t code_gen_buffer[CODE_GEN_BUFFER_SIZE]; -uint8_t *code_gen_ptr; - /* thread support */ #ifdef __powerpc__ @@ -195,70 +166,6 @@ void raise_exception(int exception_index) raise_exception_err(exception_index, 0); } -void cpu_x86_tblocks_init(void) -{ - if (!code_gen_ptr) { - code_gen_ptr = code_gen_buffer; - } -} - -/* flush all the translation blocks */ -static void tb_flush(void) -{ - int i; -#ifdef DEBUG_FLUSH - printf("gemu: flush code_size=%d nb_tbs=%d avg_tb_size=%d\n", - code_gen_ptr - code_gen_buffer, - nb_tbs, - (code_gen_ptr - code_gen_buffer) / nb_tbs); -#endif - nb_tbs = 0; - for(i = 0;i < CODE_GEN_HASH_SIZE; i++) - tb_hash[i] = NULL; - code_gen_ptr = code_gen_buffer; - /* XXX: flush processor icache at this point */ -} - -/* find a translation block in the translation cache. If not found, - return NULL and the pointer to the last element of the list in pptb */ -static inline TranslationBlock *tb_find(TranslationBlock ***pptb, - unsigned long pc, - unsigned long cs_base, - unsigned int flags) -{ - TranslationBlock **ptb, *tb; - unsigned int h; - - h = pc & (CODE_GEN_HASH_SIZE - 1); - ptb = &tb_hash[h]; -#if 0 - /* XXX: hack to handle 16 bit modyfing code */ - if (flags & (1 << GEN_FLAG_CODE32_SHIFT)) -#endif - for(;;) { - tb = *ptb; - if (!tb) - break; - if (tb->pc == pc && tb->cs_base == cs_base && tb->flags == flags) - return tb; - ptb = &tb->hash_next; - } - *pptb = ptb; - return NULL; -} - -/* allocate a new translation block. flush the translation buffer if - too many translation blocks or too much generated code */ -static inline TranslationBlock *tb_alloc(void) -{ - TranslationBlock *tb; - if (nb_tbs >= CODE_GEN_MAX_BLOCKS || - (code_gen_ptr - code_gen_buffer) >= CODE_GEN_BUFFER_MAX_SIZE) - tb_flush(); - tb = &tbs[nb_tbs++]; - return tb; -} - int cpu_x86_exec(CPUX86State *env1) { int saved_T0, saved_T1, saved_A0; @@ -287,7 +194,7 @@ int cpu_x86_exec(CPUX86State *env1) #ifdef reg_EDI int saved_EDI; #endif - int code_gen_size, ret; + int code_gen_size, ret, code_size; void (*gen_func)(void); TranslationBlock *tb, **ptb; uint8_t *tc_ptr, *cs_base, *pc; @@ -390,15 +297,15 @@ int cpu_x86_exec(CPUX86State *env1) cpu_lock(); tc_ptr = code_gen_ptr; ret = cpu_x86_gen_code(code_gen_ptr, CODE_GEN_MAX_SIZE, - &code_gen_size, pc, cs_base, flags); + &code_gen_size, pc, cs_base, flags, + &code_size); /* if invalid instruction, signal it */ if (ret != 0) { cpu_unlock(); raise_exception(EXCP06_ILLOP); } - tb = tb_alloc(); + tb = tb_alloc((unsigned long)pc, code_size); *ptb = tb; - tb->pc = (unsigned long)pc; tb->cs_base = (unsigned long)cs_base; tb->flags = flags; tb->tc_ptr = tc_ptr; @@ -493,15 +400,22 @@ void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector) #include /* 'pc' is the host PC at which the exception was raised. 'address' is - the effective address of the memory exception */ + the effective address of the memory exception. 'is_write' is 1 if a + write caused the exception and otherwise 0'. 'old_set' is the + signal set which should be restored */ static inline int handle_cpu_signal(unsigned long pc, unsigned long address, + int is_write, sigset_t *old_set) { -#ifdef DEBUG_SIGNAL - printf("gemu: SIGSEGV pc=0x%08lx oldset=0x%08lx\n", - pc, *(unsigned long *)old_set); +#if defined(DEBUG_SIGNAL) + printf("qemu: SIGSEGV pc=0x%08lx address=%08lx wr=%d oldset=0x%08lx\n", + pc, address, is_write, *(unsigned long *)old_set); #endif + if (is_write && page_unprotect(address)) { + sigprocmask(SIG_SETMASK, old_set, NULL); + return 1; + } if (pc >= (unsigned long)code_gen_buffer && pc < (unsigned long)code_gen_buffer + CODE_GEN_BUFFER_SIZE) { /* the PC is inside the translated code. It means that we have @@ -512,8 +426,7 @@ static inline int handle_cpu_signal(unsigned long pc, /* XXX: need to compute virtual pc position by retranslating code. The rest of the CPU state should be correct. */ env->cr2 = address; - /* XXX: more precise exception code */ - raise_exception_err(EXCP0E_PAGE, 4); + raise_exception_err(EXCP0E_PAGE, 4 | (is_write << 1)); /* never comes here */ return 1; } else { @@ -531,11 +444,16 @@ int cpu_x86_signal_handler(int host_signum, struct siginfo *info, #ifndef REG_EIP /* for glibc 2.1 */ -#define REG_EIP EIP +#define REG_EIP EIP +#define REG_ERR ERR +#define REG_TRAPNO TRAPNO #endif pc = uc->uc_mcontext.gregs[REG_EIP]; pold_set = &uc->uc_sigmask; - return handle_cpu_signal(pc, (unsigned long)info->si_addr, pold_set); + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + uc->uc_mcontext.gregs[REG_TRAPNO] == 0xe ? + (uc->uc_mcontext.gregs[REG_ERR] >> 1) & 1 : 0, + pold_set); #else #warning No CPU specific signal handler: cannot handle target SIGSEGV events return 0; diff --git a/exec.c b/exec.c index a917143d60..0ed49c6957 100644 --- a/exec.c +++ b/exec.c @@ -1,5 +1,5 @@ /* - * virtual page mapping + * virtual page mapping and translated block handling * * Copyright (c) 2003 Fabrice Bellard * @@ -24,13 +24,32 @@ #include #include #include +#include #include "cpu-i386.h" +//#define DEBUG_TB_INVALIDATE +#define DEBUG_FLUSH + +/* make various TB consistency checks */ +//#define DEBUG_TB_CHECK + +/* threshold to flush the translated code buffer */ +#define CODE_GEN_BUFFER_MAX_SIZE (CODE_GEN_BUFFER_SIZE - CODE_GEN_MAX_SIZE) + +#define CODE_GEN_MAX_BLOCKS (CODE_GEN_BUFFER_SIZE / 64) + +TranslationBlock tbs[CODE_GEN_MAX_BLOCKS]; +TranslationBlock *tb_hash[CODE_GEN_HASH_SIZE]; +int nb_tbs; + +uint8_t code_gen_buffer[CODE_GEN_BUFFER_SIZE]; +uint8_t *code_gen_ptr; + /* XXX: pack the flags in the low bits of the pointer ? */ typedef struct PageDesc { - struct TranslationBlock *first_tb; unsigned long flags; + TranslationBlock *first_tb; } PageDesc; #define L2_BITS 10 @@ -39,6 +58,8 @@ typedef struct PageDesc { #define L1_SIZE (1 << L1_BITS) #define L2_SIZE (1 << L2_BITS) +static void tb_invalidate_page(unsigned long address); + unsigned long real_host_page_size; unsigned long host_page_bits; unsigned long host_page_size; @@ -104,36 +125,44 @@ void page_dump(FILE *f) } } - -static inline PageDesc *page_find_alloc(unsigned long address) +static inline PageDesc *page_find_alloc(unsigned int index) { - unsigned int index; PageDesc **lp, *p; - index = address >> TARGET_PAGE_BITS; lp = &l1_map[index >> L2_BITS]; p = *lp; if (!p) { /* allocate if not found */ p = malloc(sizeof(PageDesc) * L2_SIZE); - memset(p, 0, sizeof(sizeof(PageDesc) * L2_SIZE)); + memset(p, 0, sizeof(PageDesc) * L2_SIZE); *lp = p; } return p + (index & (L2_SIZE - 1)); } -int page_get_flags(unsigned long address) +static inline PageDesc *page_find(unsigned int index) { - unsigned int index; PageDesc *p; - index = address >> TARGET_PAGE_BITS; p = l1_map[index >> L2_BITS]; if (!p) return 0; - return p[index & (L2_SIZE - 1)].flags; + return p + (index & (L2_SIZE - 1)); +} + +int page_get_flags(unsigned long address) +{ + PageDesc *p; + + p = page_find(address >> TARGET_PAGE_BITS); + if (!p) + return 0; + return p->flags; } +/* modify the flags of a page and invalidate the code if + necessary. The flag PAGE_WRITE_ORG is positionned automatically + depending on PAGE_WRITE */ void page_set_flags(unsigned long start, unsigned long end, int flags) { PageDesc *p; @@ -141,8 +170,268 @@ void page_set_flags(unsigned long start, unsigned long end, int flags) start = start & TARGET_PAGE_MASK; end = TARGET_PAGE_ALIGN(end); + if (flags & PAGE_WRITE) + flags |= PAGE_WRITE_ORG; for(addr = start; addr < end; addr += TARGET_PAGE_SIZE) { - p = page_find_alloc(addr); + p = page_find_alloc(addr >> TARGET_PAGE_BITS); + /* if the write protection is set, then we invalidate the code + inside */ + if (!(p->flags & PAGE_WRITE) && + (flags & PAGE_WRITE) && + p->first_tb) { + tb_invalidate_page(addr); + } p->flags = flags; } } + +void cpu_x86_tblocks_init(void) +{ + if (!code_gen_ptr) { + code_gen_ptr = code_gen_buffer; + } +} + +/* set to NULL all the 'first_tb' fields in all PageDescs */ +static void page_flush_tb(void) +{ + int i, j; + PageDesc *p; + + for(i = 0; i < L1_SIZE; i++) { + p = l1_map[i]; + if (p) { + for(j = 0; j < L2_SIZE; j++) + p[j].first_tb = NULL; + } + } +} + +/* flush all the translation blocks */ +void tb_flush(void) +{ + int i; +#ifdef DEBUG_FLUSH + printf("qemu: flush code_size=%d nb_tbs=%d avg_tb_size=%d\n", + code_gen_ptr - code_gen_buffer, + nb_tbs, + (code_gen_ptr - code_gen_buffer) / nb_tbs); +#endif + nb_tbs = 0; + for(i = 0;i < CODE_GEN_HASH_SIZE; i++) + tb_hash[i] = NULL; + page_flush_tb(); + code_gen_ptr = code_gen_buffer; + /* XXX: flush processor icache at this point */ +} + +#ifdef DEBUG_TB_CHECK + +static void tb_invalidate_check(unsigned long address) +{ + TranslationBlock *tb; + int i; + address &= TARGET_PAGE_MASK; + for(i = 0;i < CODE_GEN_HASH_SIZE; i++) { + for(tb = tb_hash[i]; tb != NULL; tb = tb->hash_next) { + if (!(address + TARGET_PAGE_SIZE <= tb->pc || + address >= tb->pc + tb->size)) { + printf("ERROR invalidate: address=%08lx PC=%08lx size=%04x\n", + address, tb->pc, tb->size); + } + } + } +} + +/* verify that all the pages have correct rights for code */ +static void tb_page_check(void) +{ + TranslationBlock *tb; + int i, flags1, flags2; + + for(i = 0;i < CODE_GEN_HASH_SIZE; i++) { + for(tb = tb_hash[i]; tb != NULL; tb = tb->hash_next) { + flags1 = page_get_flags(tb->pc); + flags2 = page_get_flags(tb->pc + tb->size - 1); + if ((flags1 & PAGE_WRITE) || (flags2 & PAGE_WRITE)) { + printf("ERROR page flags: PC=%08lx size=%04x f1=%x f2=%x\n", + tb->pc, tb->size, flags1, flags2); + } + } + } +} + +#endif + +/* invalidate one TB */ +static inline void tb_remove(TranslationBlock **ptb, TranslationBlock *tb, + int next_offset) +{ + TranslationBlock *tb1; + for(;;) { + tb1 = *ptb; + if (tb1 == tb) { + *ptb = *(TranslationBlock **)((char *)tb1 + next_offset); + break; + } + ptb = (TranslationBlock **)((char *)tb1 + next_offset); + } +} + +static inline void tb_invalidate(TranslationBlock *tb, int parity) +{ + PageDesc *p; + unsigned int page_index1, page_index2; + unsigned int h; + + /* remove the TB from the hash list */ + h = tb_hash_func(tb->pc); + tb_remove(&tb_hash[h], tb, + offsetof(TranslationBlock, hash_next)); + /* remove the TB from the page list */ + page_index1 = tb->pc >> TARGET_PAGE_BITS; + if ((page_index1 & 1) == parity) { + p = page_find(page_index1); + tb_remove(&p->first_tb, tb, + offsetof(TranslationBlock, page_next[page_index1 & 1])); + } + page_index2 = (tb->pc + tb->size - 1) >> TARGET_PAGE_BITS; + if ((page_index2 & 1) == parity) { + p = page_find(page_index2); + tb_remove(&p->first_tb, tb, + offsetof(TranslationBlock, page_next[page_index2 & 1])); + } +} + +/* invalidate all TBs which intersect with the target page starting at addr */ +static void tb_invalidate_page(unsigned long address) +{ + TranslationBlock *tb_next, *tb; + unsigned int page_index; + int parity1, parity2; + PageDesc *p; +#ifdef DEBUG_TB_INVALIDATE + printf("tb_invalidate_page: %lx\n", address); +#endif + + page_index = address >> TARGET_PAGE_BITS; + p = page_find(page_index); + if (!p) + return; + tb = p->first_tb; + parity1 = page_index & 1; + parity2 = parity1 ^ 1; + while (tb != NULL) { + tb_next = tb->page_next[parity1]; + tb_invalidate(tb, parity2); + tb = tb_next; + } + p->first_tb = NULL; +} + +/* add the tb in the target page and protect it if necessary */ +static inline void tb_alloc_page(TranslationBlock *tb, unsigned int page_index) +{ + PageDesc *p; + unsigned long host_start, host_end, addr, page_addr; + int prot; + + p = page_find_alloc(page_index); + tb->page_next[page_index & 1] = p->first_tb; + p->first_tb = tb; + if (p->flags & PAGE_WRITE) { + /* force the host page as non writable (writes will have a + page fault + mprotect overhead) */ + page_addr = (page_index << TARGET_PAGE_BITS); + host_start = page_addr & host_page_mask; + host_end = host_start + host_page_size; + prot = 0; + for(addr = host_start; addr < host_end; addr += TARGET_PAGE_SIZE) + prot |= page_get_flags(addr); + mprotect((void *)host_start, host_page_size, + (prot & PAGE_BITS) & ~PAGE_WRITE); +#ifdef DEBUG_TB_INVALIDATE + printf("protecting code page: 0x%08lx\n", + host_start); +#endif + p->flags &= ~PAGE_WRITE; +#ifdef DEBUG_TB_CHECK + tb_page_check(); +#endif + } +} + +/* Allocate a new translation block. Flush the translation buffer if + too many translation blocks or too much generated code. */ +TranslationBlock *tb_alloc(unsigned long pc, + unsigned long size) +{ + TranslationBlock *tb; + unsigned int page_index1, page_index2; + + if (nb_tbs >= CODE_GEN_MAX_BLOCKS || + (code_gen_ptr - code_gen_buffer) >= CODE_GEN_BUFFER_MAX_SIZE) + tb_flush(); + tb = &tbs[nb_tbs++]; + tb->pc = pc; + tb->size = size; + + /* add in the page list */ + page_index1 = pc >> TARGET_PAGE_BITS; + tb_alloc_page(tb, page_index1); + page_index2 = (pc + size - 1) >> TARGET_PAGE_BITS; + if (page_index2 != page_index1) { + tb_alloc_page(tb, page_index2); + } + return tb; +} + +/* called from signal handler: invalidate the code and unprotect the + page. Return TRUE if the fault was succesfully handled. */ +int page_unprotect(unsigned long address) +{ + unsigned int page_index, prot; + PageDesc *p; + unsigned long host_start, host_end, addr; + + page_index = address >> TARGET_PAGE_BITS; + p = page_find(page_index); + if (!p) + return 0; + if ((p->flags & (PAGE_WRITE_ORG | PAGE_WRITE)) == PAGE_WRITE_ORG) { + /* if the page was really writable, then we change its + protection back to writable */ + host_start = address & host_page_mask; + host_end = host_start + host_page_size; + prot = 0; + for(addr = host_start; addr < host_end; addr += TARGET_PAGE_SIZE) + prot |= page_get_flags(addr); + mprotect((void *)host_start, host_page_size, + (prot & PAGE_BITS) | PAGE_WRITE); + p->flags |= PAGE_WRITE; + + /* and since the content will be modified, we must invalidate + the corresponding translated code. */ + tb_invalidate_page(address); +#ifdef DEBUG_TB_CHECK + tb_invalidate_check(address); +#endif + return 1; + } else { + return 0; + } +} + +/* call this function when system calls directly modify a memory area */ +void page_unprotect_range(uint8_t *data, unsigned long data_size) +{ + unsigned long start, end, addr; + + start = (unsigned long)data; + end = start + data_size; + start &= TARGET_PAGE_MASK; + end = TARGET_PAGE_ALIGN(end); + for(addr = start; addr < end; addr += TARGET_PAGE_SIZE) { + page_unprotect(addr); + } +} -- GitLab