Commit 6658ffb81ee56a510d7d77025872a508a9adce3a
1 parent
b35d7448
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
Showing
6 changed files
with
186 additions
and
0 deletions
cpu-all.h
... | ... | @@ -775,10 +775,13 @@ extern int code_copy_enabled; |
775 | 775 | #define CPU_INTERRUPT_FIQ 0x10 /* Fast interrupt pending. */ |
776 | 776 | #define CPU_INTERRUPT_HALT 0x20 /* CPU halt wanted */ |
777 | 777 | #define CPU_INTERRUPT_SMI 0x40 /* (x86 only) SMI interrupt pending */ |
778 | +#define CPU_INTERRUPT_DEBUG 0x80 /* Debug event occured. */ | |
778 | 779 | |
779 | 780 | void cpu_interrupt(CPUState *s, int mask); |
780 | 781 | void cpu_reset_interrupt(CPUState *env, int mask); |
781 | 782 | |
783 | +int cpu_watchpoint_insert(CPUState *env, target_ulong addr); | |
784 | +int cpu_watchpoint_remove(CPUState *env, target_ulong addr); | |
782 | 785 | int cpu_breakpoint_insert(CPUState *env, target_ulong pc); |
783 | 786 | int cpu_breakpoint_remove(CPUState *env, target_ulong pc); |
784 | 787 | void cpu_single_step(CPUState *env, int enabled); | ... | ... |
cpu-defs.h
... | ... | @@ -76,6 +76,7 @@ typedef unsigned long ram_addr_t; |
76 | 76 | #define EXCP_DEBUG 0x10002 /* cpu stopped after a breakpoint or singlestep */ |
77 | 77 | #define EXCP_HALTED 0x10003 /* cpu is halted (waiting for external event) */ |
78 | 78 | #define MAX_BREAKPOINTS 32 |
79 | +#define MAX_WATCHPOINTS 32 | |
79 | 80 | |
80 | 81 | #define TB_JMP_CACHE_BITS 12 |
81 | 82 | #define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS) |
... | ... | @@ -125,6 +126,13 @@ typedef struct CPUTLBEntry { |
125 | 126 | int nb_breakpoints; \ |
126 | 127 | int singlestep_enabled; \ |
127 | 128 | \ |
129 | + struct { \ | |
130 | + target_ulong vaddr; \ | |
131 | + int is_ram; \ | |
132 | + } watchpoint[MAX_WATCHPOINTS]; \ | |
133 | + int nb_watchpoints; \ | |
134 | + int watchpoint_hit; \ | |
135 | + \ | |
128 | 136 | void *next_cpu; /* next CPU sharing TB cache */ \ |
129 | 137 | int cpu_index; /* CPU index (informative) */ \ |
130 | 138 | /* user data */ \ | ... | ... |
cpu-exec.c
... | ... | @@ -409,6 +409,11 @@ int cpu_exec(CPUState *env1) |
409 | 409 | #endif |
410 | 410 | interrupt_request = env->interrupt_request; |
411 | 411 | if (__builtin_expect(interrupt_request, 0)) { |
412 | + if (interrupt_request & CPU_INTERRUPT_DEBUG) { | |
413 | + env->interrupt_request &= ~CPU_INTERRUPT_DEBUG; | |
414 | + env->exception_index = EXCP_DEBUG; | |
415 | + cpu_loop_exit(); | |
416 | + } | |
412 | 417 | #if defined(TARGET_I386) |
413 | 418 | if ((interrupt_request & CPU_INTERRUPT_SMI) && |
414 | 419 | !(env->hflags & HF_SMM_MASK)) { | ... | ... |
exec.c
... | ... | @@ -128,6 +128,9 @@ CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4]; |
128 | 128 | CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4]; |
129 | 129 | void *io_mem_opaque[IO_MEM_NB_ENTRIES]; |
130 | 130 | static int io_mem_nb; |
131 | +#if defined(CONFIG_SOFTMMU) | |
132 | +static int io_mem_watch; | |
133 | +#endif | |
131 | 134 | |
132 | 135 | /* log support */ |
133 | 136 | char *logfilename = "/tmp/qemu.log"; |
... | ... | @@ -274,6 +277,7 @@ void cpu_exec_init(CPUState *env) |
274 | 277 | cpu_index++; |
275 | 278 | } |
276 | 279 | env->cpu_index = cpu_index; |
280 | + env->nb_watchpoints = 0; | |
277 | 281 | *penv = env; |
278 | 282 | } |
279 | 283 | |
... | ... | @@ -1029,6 +1033,44 @@ static void breakpoint_invalidate(CPUState *env, target_ulong pc) |
1029 | 1033 | } |
1030 | 1034 | #endif |
1031 | 1035 | |
1036 | +/* Add a watchpoint. */ | |
1037 | +int cpu_watchpoint_insert(CPUState *env, target_ulong addr) | |
1038 | +{ | |
1039 | + int i; | |
1040 | + | |
1041 | + for (i = 0; i < env->nb_watchpoints; i++) { | |
1042 | + if (addr == env->watchpoint[i].vaddr) | |
1043 | + return 0; | |
1044 | + } | |
1045 | + if (env->nb_watchpoints >= MAX_WATCHPOINTS) | |
1046 | + return -1; | |
1047 | + | |
1048 | + i = env->nb_watchpoints++; | |
1049 | + env->watchpoint[i].vaddr = addr; | |
1050 | + tlb_flush_page(env, addr); | |
1051 | + /* FIXME: This flush is needed because of the hack to make memory ops | |
1052 | + terminate the TB. It can be removed once the proper IO trap and | |
1053 | + re-execute bits are in. */ | |
1054 | + tb_flush(env); | |
1055 | + return i; | |
1056 | +} | |
1057 | + | |
1058 | +/* Remove a watchpoint. */ | |
1059 | +int cpu_watchpoint_remove(CPUState *env, target_ulong addr) | |
1060 | +{ | |
1061 | + int i; | |
1062 | + | |
1063 | + for (i = 0; i < env->nb_watchpoints; i++) { | |
1064 | + if (addr == env->watchpoint[i].vaddr) { | |
1065 | + env->nb_watchpoints--; | |
1066 | + env->watchpoint[i] = env->watchpoint[env->nb_watchpoints]; | |
1067 | + tlb_flush_page(env, addr); | |
1068 | + return 0; | |
1069 | + } | |
1070 | + } | |
1071 | + return -1; | |
1072 | +} | |
1073 | + | |
1032 | 1074 | /* add a breakpoint. EXCP_DEBUG is returned by the CPU loop if a |
1033 | 1075 | breakpoint is reached */ |
1034 | 1076 | int cpu_breakpoint_insert(CPUState *env, target_ulong pc) |
... | ... | @@ -1484,6 +1526,7 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr, |
1484 | 1526 | target_phys_addr_t addend; |
1485 | 1527 | int ret; |
1486 | 1528 | CPUTLBEntry *te; |
1529 | + int i; | |
1487 | 1530 | |
1488 | 1531 | p = phys_page_find(paddr >> TARGET_PAGE_BITS); |
1489 | 1532 | if (!p) { |
... | ... | @@ -1510,6 +1553,22 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr, |
1510 | 1553 | address = vaddr; |
1511 | 1554 | addend = (unsigned long)phys_ram_base + (pd & TARGET_PAGE_MASK); |
1512 | 1555 | } |
1556 | + | |
1557 | + /* Make accesses to pages with watchpoints go via the | |
1558 | + watchpoint trap routines. */ | |
1559 | + for (i = 0; i < env->nb_watchpoints; i++) { | |
1560 | + if (vaddr == (env->watchpoint[i].vaddr & TARGET_PAGE_MASK)) { | |
1561 | + if (address & ~TARGET_PAGE_MASK) { | |
1562 | + env->watchpoint[i].is_ram = 0; | |
1563 | + address = vaddr | io_mem_watch; | |
1564 | + } else { | |
1565 | + env->watchpoint[i].is_ram = 1; | |
1566 | + /* TODO: Figure out how to make read watchpoints coexist | |
1567 | + with code. */ | |
1568 | + pd = (pd & TARGET_PAGE_MASK) | io_mem_watch | IO_MEM_ROMD; | |
1569 | + } | |
1570 | + } | |
1571 | + } | |
1513 | 1572 | |
1514 | 1573 | index = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); |
1515 | 1574 | addend -= vaddr; |
... | ... | @@ -1960,6 +2019,85 @@ static CPUWriteMemoryFunc *notdirty_mem_write[3] = { |
1960 | 2019 | notdirty_mem_writel, |
1961 | 2020 | }; |
1962 | 2021 | |
2022 | +#if defined(CONFIG_SOFTMMU) | |
2023 | +/* Watchpoint access routines. Watchpoints are inserted using TLB tricks, | |
2024 | + so these check for a hit then pass through to the normal out-of-line | |
2025 | + phys routines. */ | |
2026 | +static uint32_t watch_mem_readb(void *opaque, target_phys_addr_t addr) | |
2027 | +{ | |
2028 | + return ldub_phys(addr); | |
2029 | +} | |
2030 | + | |
2031 | +static uint32_t watch_mem_readw(void *opaque, target_phys_addr_t addr) | |
2032 | +{ | |
2033 | + return lduw_phys(addr); | |
2034 | +} | |
2035 | + | |
2036 | +static uint32_t watch_mem_readl(void *opaque, target_phys_addr_t addr) | |
2037 | +{ | |
2038 | + return ldl_phys(addr); | |
2039 | +} | |
2040 | + | |
2041 | +/* Generate a debug exception if a watchpoint has been hit. | |
2042 | + Returns the real physical address of the access. addr will be a host | |
2043 | + address in the is_ram case. */ | |
2044 | +static target_ulong check_watchpoint(target_phys_addr_t addr) | |
2045 | +{ | |
2046 | + CPUState *env = cpu_single_env; | |
2047 | + target_ulong watch; | |
2048 | + target_ulong retaddr; | |
2049 | + int i; | |
2050 | + | |
2051 | + retaddr = addr; | |
2052 | + for (i = 0; i < env->nb_watchpoints; i++) { | |
2053 | + watch = env->watchpoint[i].vaddr; | |
2054 | + if (((env->mem_write_vaddr ^ watch) & TARGET_PAGE_MASK) == 0) { | |
2055 | + if (env->watchpoint[i].is_ram) | |
2056 | + retaddr = addr - (unsigned long)phys_ram_base; | |
2057 | + if (((addr ^ watch) & ~TARGET_PAGE_MASK) == 0) { | |
2058 | + cpu_single_env->watchpoint_hit = i + 1; | |
2059 | + cpu_interrupt(cpu_single_env, CPU_INTERRUPT_DEBUG); | |
2060 | + break; | |
2061 | + } | |
2062 | + } | |
2063 | + } | |
2064 | + return retaddr; | |
2065 | +} | |
2066 | + | |
2067 | +static void watch_mem_writeb(void *opaque, target_phys_addr_t addr, | |
2068 | + uint32_t val) | |
2069 | +{ | |
2070 | + addr = check_watchpoint(addr); | |
2071 | + stb_phys(addr, val); | |
2072 | +} | |
2073 | + | |
2074 | +static void watch_mem_writew(void *opaque, target_phys_addr_t addr, | |
2075 | + uint32_t val) | |
2076 | +{ | |
2077 | + addr = check_watchpoint(addr); | |
2078 | + stw_phys(addr, val); | |
2079 | +} | |
2080 | + | |
2081 | +static void watch_mem_writel(void *opaque, target_phys_addr_t addr, | |
2082 | + uint32_t val) | |
2083 | +{ | |
2084 | + addr = check_watchpoint(addr); | |
2085 | + stl_phys(addr, val); | |
2086 | +} | |
2087 | + | |
2088 | +static CPUReadMemoryFunc *watch_mem_read[3] = { | |
2089 | + watch_mem_readb, | |
2090 | + watch_mem_readw, | |
2091 | + watch_mem_readl, | |
2092 | +}; | |
2093 | + | |
2094 | +static CPUWriteMemoryFunc *watch_mem_write[3] = { | |
2095 | + watch_mem_writeb, | |
2096 | + watch_mem_writew, | |
2097 | + watch_mem_writel, | |
2098 | +}; | |
2099 | +#endif | |
2100 | + | |
1963 | 2101 | static void io_mem_init(void) |
1964 | 2102 | { |
1965 | 2103 | 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) |
1967 | 2105 | cpu_register_io_memory(IO_MEM_NOTDIRTY >> IO_MEM_SHIFT, error_mem_read, notdirty_mem_write, NULL); |
1968 | 2106 | io_mem_nb = 5; |
1969 | 2107 | |
2108 | +#if defined(CONFIG_SOFTMMU) | |
2109 | + io_mem_watch = cpu_register_io_memory(-1, watch_mem_read, | |
2110 | + watch_mem_write, NULL); | |
2111 | +#endif | |
1970 | 2112 | /* alloc dirty bits array */ |
1971 | 2113 | phys_ram_dirty = qemu_vmalloc(phys_ram_size >> TARGET_PAGE_BITS); |
1972 | 2114 | memset(phys_ram_dirty, 0xff, phys_ram_size >> TARGET_PAGE_BITS); | ... | ... |
gdbstub.c
... | ... | @@ -856,6 +856,12 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) |
856 | 856 | if (cpu_breakpoint_insert(env, addr) < 0) |
857 | 857 | goto breakpoint_error; |
858 | 858 | put_packet(s, "OK"); |
859 | +#ifndef CONFIG_USER_ONLY | |
860 | + } else if (type == 2) { | |
861 | + if (cpu_watchpoint_insert(env, addr) < 0) | |
862 | + goto breakpoint_error; | |
863 | + put_packet(s, "OK"); | |
864 | +#endif | |
859 | 865 | } else { |
860 | 866 | breakpoint_error: |
861 | 867 | put_packet(s, "E22"); |
... | ... | @@ -872,6 +878,11 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) |
872 | 878 | if (type == 0 || type == 1) { |
873 | 879 | cpu_breakpoint_remove(env, addr); |
874 | 880 | put_packet(s, "OK"); |
881 | +#ifndef CONFIG_USER_ONLY | |
882 | + } else if (type == 2) { | |
883 | + cpu_watchpoint_remove(env, addr); | |
884 | + put_packet(s, "OK"); | |
885 | +#endif | |
875 | 886 | } else { |
876 | 887 | goto breakpoint_error; |
877 | 888 | } |
... | ... | @@ -914,6 +925,13 @@ static void gdb_vm_stopped(void *opaque, int reason) |
914 | 925 | cpu_single_step(s->env, 0); |
915 | 926 | |
916 | 927 | if (reason == EXCP_DEBUG) { |
928 | + if (s->env->watchpoint_hit) { | |
929 | + snprintf(buf, sizeof(buf), "T%02xwatch:%x;", SIGTRAP, | |
930 | + s->env->watchpoint[s->env->watchpoint_hit - 1].vaddr); | |
931 | + put_packet(s, buf); | |
932 | + s->env->watchpoint_hit = 0; | |
933 | + return; | |
934 | + } | |
917 | 935 | tb_flush(s->env); |
918 | 936 | ret = SIGTRAP; |
919 | 937 | } else if (reason == EXCP_INTERRUPT) { | ... | ... |
target-arm/translate.c
... | ... | @@ -45,6 +45,7 @@ typedef struct DisasContext { |
45 | 45 | struct TranslationBlock *tb; |
46 | 46 | int singlestep_enabled; |
47 | 47 | int thumb; |
48 | + int is_mem; | |
48 | 49 | #if !defined(CONFIG_USER_ONLY) |
49 | 50 | int user; |
50 | 51 | #endif |
... | ... | @@ -290,6 +291,7 @@ static inline void gen_bx(DisasContext *s) |
290 | 291 | #define gen_ldst(name, s) gen_op_##name##_raw() |
291 | 292 | #else |
292 | 293 | #define gen_ldst(name, s) do { \ |
294 | + s->is_mem = 1; \ | |
293 | 295 | if (IS_USER(s)) \ |
294 | 296 | gen_op_##name##_user(); \ |
295 | 297 | else \ |
... | ... | @@ -1612,6 +1614,7 @@ static void disas_arm_insn(CPUState * env, DisasContext *s) |
1612 | 1614 | gen_add_data_offset(s, insn); |
1613 | 1615 | if (insn & (1 << 20)) { |
1614 | 1616 | /* load */ |
1617 | + s->is_mem = 1; | |
1615 | 1618 | #if defined(CONFIG_USER_ONLY) |
1616 | 1619 | if (insn & (1 << 22)) |
1617 | 1620 | gen_op_ldub_raw(); |
... | ... | @@ -2409,6 +2412,7 @@ static inline int gen_intermediate_code_internal(CPUState *env, |
2409 | 2412 | dc->singlestep_enabled = env->singlestep_enabled; |
2410 | 2413 | dc->condjmp = 0; |
2411 | 2414 | dc->thumb = env->thumb; |
2415 | + dc->is_mem = 0; | |
2412 | 2416 | #if !defined(CONFIG_USER_ONLY) |
2413 | 2417 | dc->user = (env->uncached_cpsr & 0x1f) == ARM_CPU_MODE_USR; |
2414 | 2418 | #endif |
... | ... | @@ -2447,6 +2451,12 @@ static inline int gen_intermediate_code_internal(CPUState *env, |
2447 | 2451 | gen_set_label(dc->condlabel); |
2448 | 2452 | dc->condjmp = 0; |
2449 | 2453 | } |
2454 | + /* Terminate the TB on memory ops if watchpoints are present. */ | |
2455 | + /* FIXME: This should be replacd by the deterministic execution | |
2456 | + * IRQ raising bits. */ | |
2457 | + if (dc->is_mem && env->nb_watchpoints) | |
2458 | + break; | |
2459 | + | |
2450 | 2460 | /* Translation stops when a conditional branch is enoutered. |
2451 | 2461 | * Otherwise the subsequent code could get translated several times. |
2452 | 2462 | * Also stop translation when a page boundary is reached. This | ... | ... |