From 6658ffb81ee56a510d7d77025872a508a9adce3a Mon Sep 17 00:00:00 2001 From: pbrook Date: Fri, 16 Mar 2007 23:58:11 +0000 Subject: [PATCH] Watchpoint support (previous commit got eaten by Savannah server crash). git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2479 c046a42c-6fe2-441c-8c8c-71466251a162 --- cpu-all.h | 3 + cpu-defs.h | 8 +++ cpu-exec.c | 5 ++ exec.c | 142 +++++++++++++++++++++++++++++++++++++++++ gdbstub.c | 18 ++++++ target-arm/translate.c | 10 +++ 6 files changed, 186 insertions(+) diff --git a/cpu-all.h b/cpu-all.h index 86acc36d6b..9b617fcc7c 100644 --- a/cpu-all.h +++ b/cpu-all.h @@ -775,10 +775,13 @@ extern int code_copy_enabled; #define CPU_INTERRUPT_FIQ 0x10 /* Fast interrupt pending. */ #define CPU_INTERRUPT_HALT 0x20 /* CPU halt wanted */ #define CPU_INTERRUPT_SMI 0x40 /* (x86 only) SMI interrupt pending */ +#define CPU_INTERRUPT_DEBUG 0x80 /* Debug event occured. */ void cpu_interrupt(CPUState *s, int mask); void cpu_reset_interrupt(CPUState *env, int mask); +int cpu_watchpoint_insert(CPUState *env, target_ulong addr); +int cpu_watchpoint_remove(CPUState *env, target_ulong addr); int cpu_breakpoint_insert(CPUState *env, target_ulong pc); int cpu_breakpoint_remove(CPUState *env, target_ulong pc); void cpu_single_step(CPUState *env, int enabled); diff --git a/cpu-defs.h b/cpu-defs.h index 0b49c89913..04fde7e892 100644 --- a/cpu-defs.h +++ b/cpu-defs.h @@ -76,6 +76,7 @@ typedef unsigned long ram_addr_t; #define EXCP_DEBUG 0x10002 /* cpu stopped after a breakpoint or singlestep */ #define EXCP_HALTED 0x10003 /* cpu is halted (waiting for external event) */ #define MAX_BREAKPOINTS 32 +#define MAX_WATCHPOINTS 32 #define TB_JMP_CACHE_BITS 12 #define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS) @@ -125,6 +126,13 @@ typedef struct CPUTLBEntry { int nb_breakpoints; \ int singlestep_enabled; \ \ + struct { \ + target_ulong vaddr; \ + int is_ram; \ + } watchpoint[MAX_WATCHPOINTS]; \ + int nb_watchpoints; \ + int watchpoint_hit; \ + \ void *next_cpu; /* next CPU sharing TB cache */ \ int cpu_index; /* CPU index (informative) */ \ /* user data */ \ diff --git a/cpu-exec.c b/cpu-exec.c index 058688fc77..48c2a93a79 100644 --- a/cpu-exec.c +++ b/cpu-exec.c @@ -409,6 +409,11 @@ int cpu_exec(CPUState *env1) #endif interrupt_request = env->interrupt_request; if (__builtin_expect(interrupt_request, 0)) { + if (interrupt_request & CPU_INTERRUPT_DEBUG) { + env->interrupt_request &= ~CPU_INTERRUPT_DEBUG; + env->exception_index = EXCP_DEBUG; + cpu_loop_exit(); + } #if defined(TARGET_I386) if ((interrupt_request & CPU_INTERRUPT_SMI) && !(env->hflags & HF_SMM_MASK)) { diff --git a/exec.c b/exec.c index 37d58a43ea..6deaf49279 100644 --- a/exec.c +++ b/exec.c @@ -128,6 +128,9 @@ CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4]; CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4]; void *io_mem_opaque[IO_MEM_NB_ENTRIES]; static int io_mem_nb; +#if defined(CONFIG_SOFTMMU) +static int io_mem_watch; +#endif /* log support */ char *logfilename = "/tmp/qemu.log"; @@ -274,6 +277,7 @@ void cpu_exec_init(CPUState *env) cpu_index++; } env->cpu_index = cpu_index; + env->nb_watchpoints = 0; *penv = env; } @@ -1029,6 +1033,44 @@ static void breakpoint_invalidate(CPUState *env, target_ulong pc) } #endif +/* Add a watchpoint. */ +int cpu_watchpoint_insert(CPUState *env, target_ulong addr) +{ + int i; + + for (i = 0; i < env->nb_watchpoints; i++) { + if (addr == env->watchpoint[i].vaddr) + return 0; + } + if (env->nb_watchpoints >= MAX_WATCHPOINTS) + return -1; + + i = env->nb_watchpoints++; + env->watchpoint[i].vaddr = addr; + tlb_flush_page(env, addr); + /* FIXME: This flush is needed because of the hack to make memory ops + terminate the TB. It can be removed once the proper IO trap and + re-execute bits are in. */ + tb_flush(env); + return i; +} + +/* Remove a watchpoint. */ +int cpu_watchpoint_remove(CPUState *env, target_ulong addr) +{ + int i; + + for (i = 0; i < env->nb_watchpoints; i++) { + if (addr == env->watchpoint[i].vaddr) { + env->nb_watchpoints--; + env->watchpoint[i] = env->watchpoint[env->nb_watchpoints]; + tlb_flush_page(env, addr); + return 0; + } + } + return -1; +} + /* add a breakpoint. EXCP_DEBUG is returned by the CPU loop if a breakpoint is reached */ int cpu_breakpoint_insert(CPUState *env, target_ulong pc) @@ -1484,6 +1526,7 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr, target_phys_addr_t addend; int ret; CPUTLBEntry *te; + int i; p = phys_page_find(paddr >> TARGET_PAGE_BITS); if (!p) { @@ -1510,6 +1553,22 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr, address = vaddr; addend = (unsigned long)phys_ram_base + (pd & TARGET_PAGE_MASK); } + + /* Make accesses to pages with watchpoints go via the + watchpoint trap routines. */ + for (i = 0; i < env->nb_watchpoints; i++) { + if (vaddr == (env->watchpoint[i].vaddr & TARGET_PAGE_MASK)) { + if (address & ~TARGET_PAGE_MASK) { + env->watchpoint[i].is_ram = 0; + address = vaddr | io_mem_watch; + } else { + env->watchpoint[i].is_ram = 1; + /* TODO: Figure out how to make read watchpoints coexist + with code. */ + pd = (pd & TARGET_PAGE_MASK) | io_mem_watch | IO_MEM_ROMD; + } + } + } index = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); addend -= vaddr; @@ -1960,6 +2019,85 @@ static CPUWriteMemoryFunc *notdirty_mem_write[3] = { notdirty_mem_writel, }; +#if defined(CONFIG_SOFTMMU) +/* Watchpoint access routines. Watchpoints are inserted using TLB tricks, + so these check for a hit then pass through to the normal out-of-line + phys routines. */ +static uint32_t watch_mem_readb(void *opaque, target_phys_addr_t addr) +{ + return ldub_phys(addr); +} + +static uint32_t watch_mem_readw(void *opaque, target_phys_addr_t addr) +{ + return lduw_phys(addr); +} + +static uint32_t watch_mem_readl(void *opaque, target_phys_addr_t addr) +{ + return ldl_phys(addr); +} + +/* Generate a debug exception if a watchpoint has been hit. + Returns the real physical address of the access. addr will be a host + address in the is_ram case. */ +static target_ulong check_watchpoint(target_phys_addr_t addr) +{ + CPUState *env = cpu_single_env; + target_ulong watch; + target_ulong retaddr; + int i; + + retaddr = addr; + for (i = 0; i < env->nb_watchpoints; i++) { + watch = env->watchpoint[i].vaddr; + if (((env->mem_write_vaddr ^ watch) & TARGET_PAGE_MASK) == 0) { + if (env->watchpoint[i].is_ram) + retaddr = addr - (unsigned long)phys_ram_base; + if (((addr ^ watch) & ~TARGET_PAGE_MASK) == 0) { + cpu_single_env->watchpoint_hit = i + 1; + cpu_interrupt(cpu_single_env, CPU_INTERRUPT_DEBUG); + break; + } + } + } + return retaddr; +} + +static void watch_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + addr = check_watchpoint(addr); + stb_phys(addr, val); +} + +static void watch_mem_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + addr = check_watchpoint(addr); + stw_phys(addr, val); +} + +static void watch_mem_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + addr = check_watchpoint(addr); + stl_phys(addr, val); +} + +static CPUReadMemoryFunc *watch_mem_read[3] = { + watch_mem_readb, + watch_mem_readw, + watch_mem_readl, +}; + +static CPUWriteMemoryFunc *watch_mem_write[3] = { + watch_mem_writeb, + watch_mem_writew, + watch_mem_writel, +}; +#endif + static void io_mem_init(void) { cpu_register_io_memory(IO_MEM_ROM >> IO_MEM_SHIFT, error_mem_read, unassigned_mem_write, NULL); @@ -1967,6 +2105,10 @@ static void io_mem_init(void) cpu_register_io_memory(IO_MEM_NOTDIRTY >> IO_MEM_SHIFT, error_mem_read, notdirty_mem_write, NULL); io_mem_nb = 5; +#if defined(CONFIG_SOFTMMU) + io_mem_watch = cpu_register_io_memory(-1, watch_mem_read, + watch_mem_write, NULL); +#endif /* alloc dirty bits array */ phys_ram_dirty = qemu_vmalloc(phys_ram_size >> TARGET_PAGE_BITS); memset(phys_ram_dirty, 0xff, phys_ram_size >> TARGET_PAGE_BITS); diff --git a/gdbstub.c b/gdbstub.c index 57d97e340c..4d62a88916 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -856,6 +856,12 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (cpu_breakpoint_insert(env, addr) < 0) goto breakpoint_error; put_packet(s, "OK"); +#ifndef CONFIG_USER_ONLY + } else if (type == 2) { + if (cpu_watchpoint_insert(env, addr) < 0) + goto breakpoint_error; + put_packet(s, "OK"); +#endif } else { breakpoint_error: put_packet(s, "E22"); @@ -872,6 +878,11 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (type == 0 || type == 1) { cpu_breakpoint_remove(env, addr); put_packet(s, "OK"); +#ifndef CONFIG_USER_ONLY + } else if (type == 2) { + cpu_watchpoint_remove(env, addr); + put_packet(s, "OK"); +#endif } else { goto breakpoint_error; } @@ -914,6 +925,13 @@ static void gdb_vm_stopped(void *opaque, int reason) cpu_single_step(s->env, 0); if (reason == EXCP_DEBUG) { + if (s->env->watchpoint_hit) { + snprintf(buf, sizeof(buf), "T%02xwatch:%x;", SIGTRAP, + s->env->watchpoint[s->env->watchpoint_hit - 1].vaddr); + put_packet(s, buf); + s->env->watchpoint_hit = 0; + return; + } tb_flush(s->env); ret = SIGTRAP; } else if (reason == EXCP_INTERRUPT) { diff --git a/target-arm/translate.c b/target-arm/translate.c index cf46e346ec..055ccfaa0f 100644 --- a/target-arm/translate.c +++ b/target-arm/translate.c @@ -45,6 +45,7 @@ typedef struct DisasContext { struct TranslationBlock *tb; int singlestep_enabled; int thumb; + int is_mem; #if !defined(CONFIG_USER_ONLY) int user; #endif @@ -290,6 +291,7 @@ static inline void gen_bx(DisasContext *s) #define gen_ldst(name, s) gen_op_##name##_raw() #else #define gen_ldst(name, s) do { \ + s->is_mem = 1; \ if (IS_USER(s)) \ gen_op_##name##_user(); \ else \ @@ -1612,6 +1614,7 @@ static void disas_arm_insn(CPUState * env, DisasContext *s) gen_add_data_offset(s, insn); if (insn & (1 << 20)) { /* load */ + s->is_mem = 1; #if defined(CONFIG_USER_ONLY) if (insn & (1 << 22)) gen_op_ldub_raw(); @@ -2409,6 +2412,7 @@ static inline int gen_intermediate_code_internal(CPUState *env, dc->singlestep_enabled = env->singlestep_enabled; dc->condjmp = 0; dc->thumb = env->thumb; + dc->is_mem = 0; #if !defined(CONFIG_USER_ONLY) dc->user = (env->uncached_cpsr & 0x1f) == ARM_CPU_MODE_USR; #endif @@ -2447,6 +2451,12 @@ static inline int gen_intermediate_code_internal(CPUState *env, gen_set_label(dc->condlabel); dc->condjmp = 0; } + /* Terminate the TB on memory ops if watchpoints are present. */ + /* FIXME: This should be replacd by the deterministic execution + * IRQ raising bits. */ + if (dc->is_mem && env->nb_watchpoints) + break; + /* Translation stops when a conditional branch is enoutered. * Otherwise the subsequent code could get translated several times. * Also stop translation when a page boundary is reached. This