diff options
author | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2003-05-14 19:00:11 +0000 |
---|---|---|
committer | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2003-05-14 19:00:11 +0000 |
commit | fd6ce8f6604359e60283e6d4dfc935ca57c556e5 (patch) | |
tree | b41ce33abb8e2d228b66118923aab4e2afcb25b1 /exec.c | |
parent | 727d01d4f6846708f0f32dcf9b086a2fba15bd8c (diff) |
self-modifying code support
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@163 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'exec.c')
-rw-r--r-- | exec.c | 313 |
1 files changed, 301 insertions, 12 deletions
@@ -1,5 +1,5 @@ /* - * virtual page mapping + * virtual page mapping and translated block handling * * Copyright (c) 2003 Fabrice Bellard * @@ -24,13 +24,32 @@ #include <errno.h> #include <unistd.h> #include <inttypes.h> +#include <sys/mman.h> #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); + } +} |