/* * Copyright (C) 2021 Ant Group. All rights reserved. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include "bh_platform.h" #include "handler.h" #include "debug_engine.h" #include "packets.h" #include "utils.h" #include "wasm_runtime.h" /* * Note: A moderate MAX_PACKET_SIZE is ok because * LLDB queries our buffer size (via qSupported PacketSize) * and limits packet sizes accordingly. */ #if defined(DEBUG_MAX_PACKET_SIZE) #define MAX_PACKET_SIZE DEBUG_MAX_PACKET_SIZE #else #define MAX_PACKET_SIZE (4096) #endif /* * Note: It's assumed that MAX_PACKET_SIZE is reasonably large. * See GetWorkingDir, WasmCallStack, etc. */ #if MAX_PACKET_SIZE < PATH_MAX || MAX_PACKET_SIZE < (2048 + 1) #error MAX_PACKET_SIZE is too small #endif static char *tmpbuf; static korp_mutex tmpbuf_lock; int wasm_debug_handler_init() { int ret; tmpbuf = wasm_runtime_malloc(MAX_PACKET_SIZE); if (tmpbuf == NULL) { LOG_ERROR("debug-engine: Packet buffer allocation failure"); return BHT_ERROR; } ret = os_mutex_init(&tmpbuf_lock); if (ret != BHT_OK) { wasm_runtime_free(tmpbuf); tmpbuf = NULL; } return ret; } void wasm_debug_handler_deinit() { wasm_runtime_free(tmpbuf); tmpbuf = NULL; os_mutex_destroy(&tmpbuf_lock); } void handle_interrupt(WASMGDBServer *server) { wasm_debug_instance_interrupt_all_threads(server->thread->debug_instance); } void handle_general_set(WASMGDBServer *server, char *payload) { const char *name; char *args; args = strchr(payload, ':'); if (args) *args++ = '\0'; name = payload; LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); if (!strcmp(name, "StartNoAckMode")) { server->noack = true; write_packet(server, "OK"); } if (!strcmp(name, "ThreadSuffixSupported")) { write_packet(server, ""); } if (!strcmp(name, "ListThreadsInStopReply")) { write_packet(server, ""); } if (!strcmp(name, "EnableErrorStrings")) { write_packet(server, "OK"); } } static void process_xfer(WASMGDBServer *server, const char *name, char *args) { const char *mode = args; args = strchr(args, ':'); if (args) *args++ = '\0'; if (!strcmp(name, "libraries") && !strcmp(mode, "read")) { // TODO: how to get current wasm file name? uint64 addr = wasm_debug_instance_get_load_addr( (WASMDebugInstance *)server->thread->debug_instance); os_mutex_lock(&tmpbuf_lock); #if WASM_ENABLE_LIBC_WASI != 0 char objname[128]; if (!wasm_debug_instance_get_current_object_name( (WASMDebugInstance *)server->thread->debug_instance, objname, 128)) { objname[0] = 0; /* use an empty string */ } snprintf(tmpbuf, MAX_PACKET_SIZE, "l
", objname, addr); #else snprintf(tmpbuf, MAX_PACKET_SIZE, "l
", "nobody.wasm", addr); #endif write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } } void process_wasm_local(WASMGDBServer *server, char *args) { int32 frame_index; int32 local_index; char buf[16]; int32 size = 16; bool ret; os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "E01"); if (sscanf(args, "%" PRId32 ";%" PRId32, &frame_index, &local_index) == 2) { ret = wasm_debug_instance_get_local( (WASMDebugInstance *)server->thread->debug_instance, frame_index, local_index, buf, &size); if (ret && size > 0) { mem2hex(buf, tmpbuf, size); } } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void process_wasm_global(WASMGDBServer *server, char *args) { int32 frame_index; int32 global_index; char buf[16]; int32 size = 16; bool ret; os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "E01"); if (sscanf(args, "%" PRId32 ";%" PRId32, &frame_index, &global_index) == 2) { ret = wasm_debug_instance_get_global( (WASMDebugInstance *)server->thread->debug_instance, frame_index, global_index, buf, &size); if (ret && size > 0) { mem2hex(buf, tmpbuf, size); } } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void handle_general_query(WASMGDBServer *server, char *payload) { const char *name; char *args; char triple[256]; args = strchr(payload, ':'); if (args) *args++ = '\0'; name = payload; LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); if (!strcmp(name, "C")) { uint64 pid, tid; pid = wasm_debug_instance_get_pid( (WASMDebugInstance *)server->thread->debug_instance); tid = (uint64)(uintptr_t)wasm_debug_instance_get_tid( (WASMDebugInstance *)server->thread->debug_instance); os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "QCp%" PRIx64 ".%" PRIx64 "", pid, tid); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } if (!strcmp(name, "Supported")) { os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "qXfer:libraries:read+;PacketSize=%" PRIx32 ";", MAX_PACKET_SIZE); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } if (!strcmp(name, "Xfer")) { name = args; if (!args) { LOG_ERROR("payload parse error during handle_general_query"); return; } args = strchr(args, ':'); if (args) { *args++ = '\0'; process_xfer(server, name, args); } } if (!strcmp(name, "HostInfo")) { mem2hex("wasm32-wamr-wasi-wasm", triple, strlen("wasm32-wamr-wasi-wasm")); os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "vendor:wamr;ostype:wasi;arch:wasm32;" "triple:%s;endian:little;ptrsize:4;", triple); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } if (!strcmp(name, "ModuleInfo")) { write_packet(server, ""); } if (!strcmp(name, "GetWorkingDir")) { os_mutex_lock(&tmpbuf_lock); if (getcwd(tmpbuf, PATH_MAX)) write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } if (!strcmp(name, "QueryGDBServer")) { write_packet(server, ""); } if (!strcmp(name, "VAttachOrWaitSupported")) { write_packet(server, ""); } if (!strcmp(name, "ProcessInfo")) { // Todo: process id parent-pid uint64 pid; pid = wasm_debug_instance_get_pid( (WASMDebugInstance *)server->thread->debug_instance); mem2hex("wasm32-wamr-wasi-wasm", triple, strlen("wasm32-wamr-wasi-wasm")); os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "pid:%" PRIx64 ";parent-pid:%" PRIx64 ";vendor:wamr;ostype:wasi;arch:wasm32;" "triple:%s;endian:little;ptrsize:4;", pid, pid, triple); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } if (!strcmp(name, "RegisterInfo0")) { os_mutex_lock(&tmpbuf_lock); snprintf( tmpbuf, MAX_PACKET_SIZE, "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;" "set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } else if (!strncmp(name, "RegisterInfo", strlen("RegisterInfo"))) { write_packet(server, "E45"); } if (!strcmp(name, "StructuredDataPlugins")) { write_packet(server, ""); } if (args && (!strcmp(name, "MemoryRegionInfo"))) { uint64 addr = strtoll(args, NULL, 16); WASMDebugMemoryInfo *mem_info = wasm_debug_instance_get_memregion( (WASMDebugInstance *)server->thread->debug_instance, addr); if (mem_info) { char name_buf[256]; mem2hex(mem_info->name, name_buf, strlen(mem_info->name)); os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "start:%" PRIx64 ";size:%" PRIx64 ";permissions:%s;name:%s;", (uint64)mem_info->start, mem_info->size, mem_info->permisson, name_buf); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); wasm_debug_instance_destroy_memregion( (WASMDebugInstance *)server->thread->debug_instance, mem_info); } } if (!strcmp(name, "WasmData")) { write_packet(server, ""); } if (!strcmp(name, "WasmMem")) { write_packet(server, ""); } if (!strcmp(name, "Symbol")) { write_packet(server, ""); } if (args && (!strcmp(name, "WasmCallStack"))) { uint64 tid = strtoll(args, NULL, 16); uint64 buf[1024 / sizeof(uint64)]; uint32 count = wasm_debug_instance_get_call_stack_pcs( (WASMDebugInstance *)server->thread->debug_instance, (korp_tid)(uintptr_t)tid, buf, 1024 / sizeof(uint64)); if (count > 0) { os_mutex_lock(&tmpbuf_lock); mem2hex((char *)buf, tmpbuf, count * sizeof(uint64)); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } else write_packet(server, ""); } if (args && (!strcmp(name, "WasmLocal"))) { process_wasm_local(server, args); } if (args && (!strcmp(name, "WasmGlobal"))) { process_wasm_global(server, args); } if (!strcmp(name, "Offsets")) { write_packet(server, ""); } if (!strncmp(name, "ThreadStopInfo", strlen("ThreadStopInfo"))) { int32 prefix_len = strlen("ThreadStopInfo"); uint64 tid_number = strtoll(name + prefix_len, NULL, 16); korp_tid tid = (korp_tid)(uintptr_t)tid_number; uint32 status; status = wasm_debug_instance_get_thread_status( server->thread->debug_instance, tid); 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 send_thread_stop_status(WASMGDBServer *server, uint32 status, korp_tid tid) { int32 len = 0; uint64 pc; korp_tid tids[20]; char pc_string[17]; uint32 tids_count, i = 0; uint32 gdb_status = status; WASMExecEnv *exec_env; const char *exception; if (status == 0) { os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "W%02x", status); write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); return; } tids_count = wasm_debug_instance_get_tids( (WASMDebugInstance *)server->thread->debug_instance, tids, 20); pc = wasm_debug_instance_get_pc( (WASMDebugInstance *)server->thread->debug_instance); if (status == WAMR_SIG_SINGSTEP) { gdb_status = WAMR_SIG_TRAP; } os_mutex_lock(&tmpbuf_lock); // TODO: how name a wasm thread? len += snprintf(tmpbuf, MAX_PACKET_SIZE, "T%02xthread:%" PRIx64 ";name:%s;", gdb_status, (uint64)(uintptr_t)tid, "nobody"); if (tids_count > 0) { len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "threads:"); while (i < tids_count) { if (i == tids_count - 1) len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "%" PRIx64 ";", (uint64)(uintptr_t)tids[i]); else len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "%" PRIx64 ",", (uint64)(uintptr_t)tids[i]); i++; } } mem2hex((void *)&pc, pc_string, 8); pc_string[8 * 2] = '\0'; exec_env = wasm_debug_instance_get_current_env( (WASMDebugInstance *)server->thread->debug_instance); bh_assert(exec_env); exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(exec_env)); if (exception) { /* When exception occurs, use reason:exception so the description can be * correctly processed by LLDB */ uint32 exception_len = strlen(exception); len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "thread-pcs:%" PRIx64 ";00:%s;reason:%s;description:", pc, pc_string, "exception"); /* The description should be encoded as HEX */ for (i = 0; i < exception_len; i++) { len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "%02x", exception[i]); } len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, ";"); } else { if (status == WAMR_SIG_TRAP) { len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "thread-pcs:%" PRIx64 ";00:%s;reason:%s;", pc, pc_string, "breakpoint"); } else if (status == WAMR_SIG_SINGSTEP) { len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "thread-pcs:%" PRIx64 ";00:%s;reason:%s;", pc, pc_string, "trace"); } else { /* status > 0 (== 0 is checked at the function beginning) */ len += snprintf(tmpbuf + len, MAX_PACKET_SIZE - len, "thread-pcs:%" PRIx64 ";00:%s;reason:%s;", pc, pc_string, "signal"); } } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void handle_v_packet(WASMGDBServer *server, char *payload) { const char *name; char *args; args = strchr(payload, ';'); if (args) *args++ = '\0'; name = payload; LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); if (!strcmp("Cont?", name)) write_packet(server, "vCont;c;C;s;S;"); if (!strcmp("Cont", name)) { if (args) { if (args[0] == 's' || args[0] == 'c') { char *numstring = strchr(args, ':'); if (numstring) { uint64 tid_number; korp_tid tid; *numstring++ = '\0'; tid_number = strtoll(numstring, NULL, 16); tid = (korp_tid)(uintptr_t)tid_number; wasm_debug_instance_set_cur_thread( (WASMDebugInstance *)server->thread->debug_instance, tid); if (args[0] == 's') { wasm_debug_instance_singlestep( (WASMDebugInstance *)server->thread->debug_instance, tid); } else { wasm_debug_instance_continue( (WASMDebugInstance *) server->thread->debug_instance); } } } } } } void handle_threadstop_request(WASMGDBServer *server, char *payload) { korp_tid tid; uint32 status; WASMDebugInstance *debug_inst = (WASMDebugInstance *)server->thread->debug_instance; bh_assert(debug_inst); /* According to https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets, the "?" package should be sent when connection is first established to query the reason the target halted */ bh_assert(debug_inst->current_state == DBG_LAUNCHING); /* Waiting for the stop event */ os_mutex_lock(&debug_inst->wait_lock); while (!debug_inst->stopped_thread) { os_cond_wait(&debug_inst->wait_cond, &debug_inst->wait_lock); } os_mutex_unlock(&debug_inst->wait_lock); tid = debug_inst->stopped_thread->handle; status = (uint32)debug_inst->stopped_thread->current_status->signal_flag; wasm_debug_instance_set_cur_thread(debug_inst, tid); send_thread_stop_status(server, status, tid); debug_inst->current_state = APP_STOPPED; debug_inst->stopped_thread = NULL; } void handle_set_current_thread(WASMGDBServer *server, char *payload) { LOG_VERBOSE("%s:%s\n", __FUNCTION__, payload); if ('g' == *payload++) { uint64 tid = strtoll(payload, NULL, 16); if (tid > 0) wasm_debug_instance_set_cur_thread( (WASMDebugInstance *)server->thread->debug_instance, (korp_tid)(uintptr_t)tid); } write_packet(server, "OK"); } void handle_get_register(WASMGDBServer *server, char *payload) { uint64 regdata; int32 i = strtol(payload, NULL, 16); if (i != 0) { write_packet(server, "E01"); return; } regdata = wasm_debug_instance_get_pc( (WASMDebugInstance *)server->thread->debug_instance); os_mutex_lock(&tmpbuf_lock); mem2hex((void *)®data, tmpbuf, 8); tmpbuf[8 * 2] = '\0'; write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void handle_get_json_request(WASMGDBServer *server, char *payload) { char *args; args = strchr(payload, ':'); if (args) *args++ = '\0'; write_packet(server, ""); } void handle_get_read_binary_memory(WASMGDBServer *server, char *payload) { write_packet(server, ""); } void handle_get_read_memory(WASMGDBServer *server, char *payload) { uint64 maddr, mlen; bool ret; os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", ""); if (sscanf(payload, "%" SCNx64 ",%" SCNx64, &maddr, &mlen) == 2) { char *buff; if (mlen * 2 > MAX_PACKET_SIZE) { LOG_ERROR("Buffer overflow!"); mlen = MAX_PACKET_SIZE / 2; } buff = wasm_runtime_malloc(mlen); if (buff) { ret = wasm_debug_instance_get_mem( (WASMDebugInstance *)server->thread->debug_instance, maddr, buff, &mlen); if (ret) { mem2hex(buff, tmpbuf, mlen); } wasm_runtime_free(buff); } } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void handle_get_write_memory(WASMGDBServer *server, char *payload) { size_t hex_len; int32 offset, act_len; uint64 maddr, mlen; char *buff; bool ret; os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", ""); if (sscanf(payload, "%" SCNx64 ",%" SCNx64 ":%n", &maddr, &mlen, &offset) == 2) { payload += offset; hex_len = strlen(payload); act_len = hex_len / 2 < mlen ? hex_len / 2 : mlen; buff = wasm_runtime_malloc(act_len); if (buff) { hex2mem(payload, buff, act_len); ret = wasm_debug_instance_set_mem( (WASMDebugInstance *)server->thread->debug_instance, maddr, buff, &mlen); if (ret) { snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", "OK"); } wasm_runtime_free(buff); } } write_packet(server, tmpbuf); 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 ((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 %zu", type); write_packet(server, ""); break; } } void handle_remove_break(WASMGDBServer *server, char *payload) { int arg_c; size_t type, length; uint64 addr; 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 %zu", type); write_packet(server, ""); break; } } void handle_continue_request(WASMGDBServer *server, char *payload) { wasm_debug_instance_continue( (WASMDebugInstance *)server->thread->debug_instance); } void handle_kill_request(WASMGDBServer *server, char *payload) { wasm_debug_instance_kill( (WASMDebugInstance *)server->thread->debug_instance); } static void handle_malloc(WASMGDBServer *server, char *payload) { char *args; uint64 addr, size; int32 map_prot = MMAP_PROT_NONE; args = strstr(payload, ","); if (args) { *args++ = '\0'; } else { LOG_ERROR("Payload parse error during handle malloc"); return; } os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", "E03"); size = strtoll(payload, NULL, 16); if (size > 0) { while (*args) { if (*args == 'r') { map_prot |= MMAP_PROT_READ; } if (*args == 'w') { map_prot |= MMAP_PROT_WRITE; } if (*args == 'x') { map_prot |= MMAP_PROT_EXEC; } args++; } addr = wasm_debug_instance_mmap( (WASMDebugInstance *)server->thread->debug_instance, size, map_prot); if (addr) { snprintf(tmpbuf, MAX_PACKET_SIZE, "%" PRIx64, addr); } } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } static void handle_free(WASMGDBServer *server, char *payload) { uint64 addr; bool ret; os_mutex_lock(&tmpbuf_lock); snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", "E03"); addr = strtoll(payload, NULL, 16); ret = wasm_debug_instance_ummap( (WASMDebugInstance *)server->thread->debug_instance, addr); if (ret) { snprintf(tmpbuf, MAX_PACKET_SIZE, "%s", "OK"); } write_packet(server, tmpbuf); os_mutex_unlock(&tmpbuf_lock); } void handle____request(WASMGDBServer *server, char *payload) { char *args; if (payload[0] == 'M') { args = payload + 1; handle_malloc(server, args); } if (payload[0] == 'm') { args = payload + 1; handle_free(server, args); } } void handle_detach_request(WASMGDBServer *server, char *payload) { if (payload != NULL) { write_packet(server, "OK"); } wasm_debug_instance_detach( (WASMDebugInstance *)server->thread->debug_instance); }