From c3d66f916ef8093e5c8cacf3329ed968f807cf58 Mon Sep 17 00:00:00 2001 From: Callum Macmillan Date: Wed, 7 Dec 2022 09:18:28 +0000 Subject: [PATCH] Add memory watchpoint support for source debugger (#1762) Allow to add watchpoints to variables for source debugging. For instance: `breakpoint set variable var` will pause WAMR execution when the address at var is written to. Can also set read/write watchpoints by passing r/w flags. This will pause execution when the address at var is read: `watchpoint set variable -w read var` Add two linked lists for read/write watchpoints. When the debug message handler receives a watchpoint request, it adds/removes to one/both of these lists. In the interpreter, when an address is read or stored to, check whether the address is in these lists. If so, throw a sigtrap and suspend the process. --- core/iwasm/interpreter/wasm_interp_classic.c | 49 ++++++- .../libraries/debug-engine/debug_engine.c | 82 +++++++++++ .../libraries/debug-engine/debug_engine.h | 24 ++++ core/iwasm/libraries/debug-engine/handler.c | 135 ++++++++++++++---- 4 files changed, 265 insertions(+), 25 deletions(-) diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index 39c23f9f..aa15b41a 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -70,6 +70,35 @@ typedef float64 CellType_F64; goto unaligned_atomic; \ } while (0) +#if WASM_ENABLE_DEBUG_INTERP != 0 +#define TRIGGER_WATCHPOINT_SIGTRAP() \ + do { \ + wasm_cluster_thread_send_signal(exec_env, WAMR_SIG_TRAP); \ + CHECK_SUSPEND_FLAGS(); \ + } while (0) + +#define CHECK_WATCHPOINT(list, current_addr) \ + do { \ + WASMDebugWatchPoint *watchpoint = bh_list_first_elem(list); \ + while (watchpoint) { \ + WASMDebugWatchPoint *next = bh_list_elem_next(watchpoint); \ + if (watchpoint->addr <= current_addr \ + && watchpoint->addr + watchpoint->length > current_addr) { \ + TRIGGER_WATCHPOINT_SIGTRAP(); \ + } \ + watchpoint = next; \ + } \ + } while (0) + +#define CHECK_READ_WATCHPOINT(addr, offset) \ + CHECK_WATCHPOINT(watch_point_list_read, WASM_ADDR_OFFSET(addr + offset)) +#define CHECK_WRITE_WATCHPOINT(addr, offset) \ + CHECK_WATCHPOINT(watch_point_list_write, WASM_ADDR_OFFSET(addr + offset)) +#else +#define CHECK_READ_WATCHPOINT(addr, offset) +#define CHECK_WRITE_WATCHPOINT(addr, offset) +#endif + static inline uint32 rotl32(uint32 n, uint32 c) { @@ -1127,6 +1156,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, #if WASM_ENABLE_DEBUG_INTERP != 0 uint8 *frame_ip_orig = NULL; + WASMDebugInstance *debug_instance = wasm_exec_env_get_instance(exec_env); + bh_list *watch_point_list_read = &debug_instance->watch_point_list_read; + bh_list *watch_point_list_write = &debug_instance->watch_point_list_write; #endif #if WASM_ENABLE_LABELS_AS_VALUES != 0 @@ -1792,6 +1824,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(4); PUSH_I32(LOAD_I32(maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1806,6 +1839,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(8); PUSH_I64(LOAD_I64(maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1819,6 +1853,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(1); PUSH_I32(sign_ext_8_32(*(int8 *)maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1832,6 +1867,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(1); PUSH_I32((uint32)(*(uint8 *)maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1845,6 +1881,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(2); PUSH_I32(sign_ext_16_32(LOAD_I16(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1858,6 +1895,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(2); PUSH_I32((uint32)(LOAD_U16(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1871,6 +1909,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(1); PUSH_I64(sign_ext_8_64(*(int8 *)maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1884,6 +1923,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(1); PUSH_I64((uint64)(*(uint8 *)maddr)); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1897,6 +1937,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(2); PUSH_I64(sign_ext_16_64(LOAD_I16(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1910,6 +1951,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(2); PUSH_I64((uint64)(LOAD_U16(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1924,6 +1966,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(4); PUSH_I64(sign_ext_32_64(LOAD_I32(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1937,6 +1980,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(4); PUSH_I64((uint64)(LOAD_U32(maddr))); + CHECK_READ_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1953,6 +1997,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_I32(); CHECK_MEMORY_OVERFLOW(4); STORE_U32(maddr, frame_sp[1]); + CHECK_WRITE_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1969,6 +2014,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, CHECK_MEMORY_OVERFLOW(8); PUT_I64_TO_ADDR((uint32 *)maddr, GET_I64_FROM_ADDR(frame_sp + 1)); + CHECK_WRITE_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -1993,7 +2039,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, CHECK_MEMORY_OVERFLOW(2); STORE_U16(maddr, (uint16)sval); } - + CHECK_WRITE_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } @@ -2023,6 +2069,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, CHECK_MEMORY_OVERFLOW(4); STORE_U32(maddr, (uint32)sval); } + CHECK_WRITE_WATCHPOINT(addr, offset); (void)flags; HANDLE_OP_END(); } diff --git a/core/iwasm/libraries/debug-engine/debug_engine.c b/core/iwasm/libraries/debug-engine/debug_engine.c index 61b29da8..1b3db1d4 100644 --- a/core/iwasm/libraries/debug-engine/debug_engine.c +++ b/core/iwasm/libraries/debug-engine/debug_engine.c @@ -392,6 +392,8 @@ wasm_debug_instance_create(WASMCluster *cluster, int32 port) } bh_list_init(&instance->break_point_list); + bh_list_init(&instance->watch_point_list_read); + bh_list_init(&instance->watch_point_list_write); instance->cluster = cluster; exec_env = bh_list_first_elem(&cluster->exec_env_list); @@ -452,6 +454,23 @@ wasm_debug_instance_destroy_breakpoints(WASMDebugInstance *instance) } } +static void +wasm_debug_instance_destroy_watchpoints(WASMDebugInstance *instance, + bh_list *watchpoints) +{ + WASMDebugWatchPoint *watchpoint, *next; + + watchpoint = bh_list_first_elem(watchpoints); + while (watchpoint) { + next = bh_list_elem_next(watchpoint); + + bh_list_remove(watchpoints, watchpoint); + wasm_runtime_free(watchpoint); + + watchpoint = next; + } +} + void wasm_debug_instance_destroy(WASMCluster *cluster) { @@ -472,6 +491,10 @@ wasm_debug_instance_destroy(WASMCluster *cluster) /* destroy all breakpoints */ wasm_debug_instance_destroy_breakpoints(instance); + wasm_debug_instance_destroy_watchpoints( + instance, &instance->watch_point_list_read); + wasm_debug_instance_destroy_watchpoints( + instance, &instance->watch_point_list_write); os_mutex_destroy(&instance->wait_lock); os_cond_destroy(&instance->wait_cond); @@ -995,6 +1018,65 @@ wasm_debug_instance_remove_breakpoint(WASMDebugInstance *instance, uint64 addr, return true; } +static bool +add_watchpoint(bh_list *list, uint64 addr, uint64 length) +{ + WASMDebugWatchPoint *watchpoint; + if (!(watchpoint = wasm_runtime_malloc(sizeof(WASMDebugWatchPoint)))) { + LOG_ERROR("WASM Debug Engine error: failed to allocate memory for " + "watchpoint"); + return false; + } + memset(watchpoint, 0, sizeof(WASMDebugWatchPoint)); + watchpoint->addr = addr; + watchpoint->length = length; + bh_list_insert(list, watchpoint); + return true; +} + +static bool +remove_watchpoint(bh_list *list, uint64 addr, uint64 length) +{ + WASMDebugWatchPoint *watchpoint = bh_list_first_elem(list); + while (watchpoint) { + WASMDebugWatchPoint *next = bh_list_elem_next(watchpoint); + if (watchpoint->addr == addr && watchpoint->length == length) { + bh_list_remove(list, watchpoint); + wasm_runtime_free(watchpoint); + } + watchpoint = next; + } + return true; +} + +bool +wasm_debug_instance_watchpoint_write_add(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + return add_watchpoint(&instance->watch_point_list_write, addr, length); +} + +bool +wasm_debug_instance_watchpoint_write_remove(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + return remove_watchpoint(&instance->watch_point_list_write, addr, length); +} + +bool +wasm_debug_instance_watchpoint_read_add(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + return add_watchpoint(&instance->watch_point_list_read, addr, length); +} + +bool +wasm_debug_instance_watchpoint_read_remove(WASMDebugInstance *instance, + uint64 addr, uint64 length) +{ + return remove_watchpoint(&instance->watch_point_list_read, addr, length); +} + bool wasm_debug_instance_on_failure(WASMDebugInstance *instance) { diff --git a/core/iwasm/libraries/debug-engine/debug_engine.h b/core/iwasm/libraries/debug-engine/debug_engine.h index f7580a7b..e12f827b 100644 --- a/core/iwasm/libraries/debug-engine/debug_engine.h +++ b/core/iwasm/libraries/debug-engine/debug_engine.h @@ -36,6 +36,12 @@ typedef struct WASMDebugBreakPoint { uint64 orignal_data; } WASMDebugBreakPoint; +typedef struct WASMDebugWatchPoint { + bh_list_link next; + uint64 addr; + uint64 length; +} WASMDebugWatchPoint; + typedef enum debug_state_t { /* Debugger state conversion sequence: * DBG_LAUNCHING ---> APP_STOPPED <---> APP_RUNNING @@ -56,6 +62,8 @@ struct WASMDebugInstance { struct WASMDebugInstance *next; WASMDebugControlThread *control_thread; bh_list break_point_list; + bh_list watch_point_list_read; + bh_list watch_point_list_write; WASMCluster *cluster; uint32 id; korp_tid current_tid; @@ -184,6 +192,22 @@ bool wasm_debug_instance_remove_breakpoint(WASMDebugInstance *instance, uint64 addr, uint64 length); +bool +wasm_debug_instance_watchpoint_write_add(WASMDebugInstance *instance, + uint64 addr, uint64 length); + +bool +wasm_debug_instance_watchpoint_write_remove(WASMDebugInstance *instance, + uint64 addr, uint64 length); + +bool +wasm_debug_instance_watchpoint_read_add(WASMDebugInstance *instance, + uint64 addr, uint64 length); + +bool +wasm_debug_instance_watchpoint_read_remove(WASMDebugInstance *instance, + uint64 addr, uint64 length); + bool wasm_debug_instance_on_failure(WASMDebugInstance *instance); diff --git a/core/iwasm/libraries/debug-engine/handler.c b/core/iwasm/libraries/debug-engine/handler.c index 701dd0c0..cf1a87c5 100644 --- a/core/iwasm/libraries/debug-engine/handler.c +++ b/core/iwasm/libraries/debug-engine/handler.c @@ -358,6 +358,14 @@ handle_general_query(WASMGDBServer *server, char *payload) send_thread_stop_status(server, status, tid); } + + if (!strcmp(name, "WatchpointSupportInfo")) { + os_mutex_lock(&tmpbuf_lock); + // Any uint32 is OK for the watchpoint support + snprintf(tmpbuf, MAX_PACKET_SIZE, "num:32;"); + write_packet(server, tmpbuf); + os_mutex_unlock(&tmpbuf_lock); + } } void @@ -643,46 +651,125 @@ handle_get_write_memory(WASMGDBServer *server, char *payload) os_mutex_unlock(&tmpbuf_lock); } +void +handle_breakpoint_software_add(WASMGDBServer *server, uint64 addr, + size_t length) +{ + bool ret = wasm_debug_instance_add_breakpoint( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + +void +handle_breakpoint_software_remove(WASMGDBServer *server, uint64 addr, + size_t length) +{ + bool ret = wasm_debug_instance_remove_breakpoint( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + +void +handle_watchpoint_write_add(WASMGDBServer *server, uint64 addr, size_t length) +{ + bool ret = wasm_debug_instance_watchpoint_write_add( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + +void +handle_watchpoint_write_remove(WASMGDBServer *server, uint64 addr, + size_t length) +{ + bool ret = wasm_debug_instance_watchpoint_write_remove( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + +void +handle_watchpoint_read_add(WASMGDBServer *server, uint64 addr, size_t length) +{ + bool ret = wasm_debug_instance_watchpoint_read_add( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + +void +handle_watchpoint_read_remove(WASMGDBServer *server, uint64 addr, size_t length) +{ + bool ret = wasm_debug_instance_watchpoint_read_remove( + (WASMDebugInstance *)server->thread->debug_instance, addr, length); + write_packet(server, ret ? "OK" : "EO1"); +} + void handle_add_break(WASMGDBServer *server, char *payload) { + int arg_c; size_t type, length; uint64 addr; - if (sscanf(payload, "%zx,%" SCNx64 ",%zx", &type, &addr, &length) == 3) { - if (type == eBreakpointSoftware) { - bool ret = wasm_debug_instance_add_breakpoint( - (WASMDebugInstance *)server->thread->debug_instance, addr, - length); - if (ret) - write_packet(server, "OK"); - else - write_packet(server, "E01"); - return; - } + if ((arg_c = sscanf(payload, "%zx,%" SCNx64 ",%zx", &type, &addr, &length)) + != 3) { + LOG_ERROR("Unsupported number of add break arguments %d", arg_c); + write_packet(server, ""); + return; + } + + switch (type) { + case eBreakpointSoftware: + handle_breakpoint_software_add(server, addr, length); + break; + case eWatchpointWrite: + handle_watchpoint_write_add(server, addr, length); + break; + case eWatchpointRead: + handle_watchpoint_read_add(server, addr, length); + break; + case eWatchpointReadWrite: + handle_watchpoint_write_add(server, addr, length); + handle_watchpoint_read_add(server, addr, length); + break; + default: + LOG_ERROR("Unsupported breakpoint type %d", type); + write_packet(server, ""); + break; } - write_packet(server, ""); } void handle_remove_break(WASMGDBServer *server, char *payload) { + int arg_c; size_t type, length; uint64 addr; - if (sscanf(payload, "%zx,%" SCNx64 ",%zx", &type, &addr, &length) == 3) { - if (type == eBreakpointSoftware) { - bool ret = wasm_debug_instance_remove_breakpoint( - (WASMDebugInstance *)server->thread->debug_instance, addr, - length); - if (ret) - write_packet(server, "OK"); - else - write_packet(server, "E01"); - return; - } + if ((arg_c = sscanf(payload, "%zx,%" SCNx64 ",%zx", &type, &addr, &length)) + != 3) { + LOG_ERROR("Unsupported number of remove break arguments %d", arg_c); + write_packet(server, ""); + return; + } + + switch (type) { + case eBreakpointSoftware: + handle_breakpoint_software_remove(server, addr, length); + break; + case eWatchpointWrite: + handle_watchpoint_write_remove(server, addr, length); + break; + case eWatchpointRead: + handle_watchpoint_read_remove(server, addr, length); + break; + case eWatchpointReadWrite: + handle_watchpoint_write_remove(server, addr, length); + handle_watchpoint_read_remove(server, addr, length); + break; + default: + LOG_ERROR("Unsupported breakpoint type %d", type); + write_packet(server, ""); + break; } - write_packet(server, ""); } void