/** * File: debugger.c * Author: AWTK Develop Team * Brief: debugger * * Copyright (c) 2022 - 2022 Guangzhou ZHIYUAN Electronics Co.,Ltd. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * License file for more details. * */ /** * History: * ================================================================ * 2022-01-11 Li XianJing created * */ #include "tkc/str.h" #include "tkc/buffer.h" #include "tkc/data_reader_mem.h" #include "conf_io/conf_json.h" #include "ubjson/ubjson_parser.h" #include "debugger/debugger_message.h" #include "debugger/debugger_client.h" static ret_t debugger_client_lock(debugger_t* debugger) { return RET_OK; } static ret_t debugger_client_unlock(debugger_t* debugger) { return RET_OK; } static ret_t debugger_client_write_data(tk_ostream_t* out, const void* data, uint32_t size) { return tk_ostream_write_len(out, data, size, DEBUGGER_IO_WRITE_TIMEOUT) == size ? RET_OK : RET_IO; } static ret_t debugger_client_read_data(tk_istream_t* in, void* data, uint32_t size) { return tk_istream_read_len(in, data, size, DEBUGGER_IO_READ_TIMEOUT) == size ? RET_OK : RET_IO; } static ret_t debugger_client_extend_buff(debugger_t* debugger, uint32_t size) { ret_t ret = RET_OK; debugger_client_t* client = DEBUGGER_CLIENT(debugger); if (client->capacity < size) { void* buff = TKMEM_REALLOC(client->buff, size); if (buff != NULL) { client->buff = buff; client->capacity = size; } else { ret = RET_OOM; } } return ret; } static ret_t debugger_client_dispatch_message(debugger_t* debugger, debugger_resp_t* resp) { debugger_client_t* client = DEBUGGER_CLIENT(debugger); switch (resp->code) { case DEBUGGER_RESP_MSG_BREAKED: { uint32_t line = 0; debugger_breaked_event_t event; tk_object_t* obj = ubjson_to_object(client->buff, resp->size); return_value_if_fail(obj != NULL, RET_BAD_PARAMS); line = tk_object_get_prop_int(obj, STR_DEBUGGER_EVENT_PROP_LINE, 0); debugger_set_state(debugger, DEBUGGER_PROGRAM_STATE_PAUSED); emitter_dispatch(EMITTER(debugger), debugger_breaked_event_init(&event, line)); TK_OBJECT_UNREF(obj); break; } case DEBUGGER_RESP_MSG_LOG: { uint32_t line = 0; const char* message = NULL; debugger_log_event_t event; tk_object_t* obj = ubjson_to_object(client->buff, resp->size); return_value_if_fail(obj != NULL, RET_BAD_PARAMS); line = tk_object_get_prop_int(obj, STR_DEBUGGER_EVENT_PROP_LINE, 0); message = tk_object_get_prop_str(obj, STR_DEBUGGER_EVENT_PROP_MESSAGE); emitter_dispatch(EMITTER(debugger), debugger_log_event_init(&event, line, message)); TK_OBJECT_UNREF(obj); break; } case DEBUGGER_RESP_MSG_ERROR: { uint32_t line = 0; const char* message = NULL; debugger_error_event_t event; tk_object_t* obj = ubjson_to_object(client->buff, resp->size); return_value_if_fail(obj != NULL, RET_BAD_PARAMS); line = tk_object_get_prop_int(obj, STR_DEBUGGER_EVENT_PROP_LINE, 0); message = tk_object_get_prop_str(obj, STR_DEBUGGER_EVENT_PROP_MESSAGE); emitter_dispatch(EMITTER(debugger), debugger_error_event_init(&event, line, message)); TK_OBJECT_UNREF(obj); break; } case DEBUGGER_RESP_MSG_COMPLETED: { client->program_completed = TRUE; debugger_set_state(debugger, DEBUGGER_PROGRAM_STATE_TERMINATED); emitter_dispatch_simple_event(EMITTER(debugger), DEBUGGER_RESP_MSG_COMPLETED); break; } default: { emitter_dispatch_simple_event(EMITTER(debugger), resp->code); break; } } return RET_OK; } static ret_t debugger_client_dispatch_one(debugger_t* debugger, debugger_resp_t* resp) { debugger_client_t* client = DEBUGGER_CLIENT(debugger); tk_istream_t* in = tk_iostream_get_istream(client->io); return_value_if_fail(in != NULL, RET_BAD_PARAMS); return_value_if_fail(debugger_client_read_data(in, resp, sizeof(*resp)) == RET_OK, RET_IO); assert(DEBUGGER_VERSION == resp->version); return_value_if_fail(debugger_client_extend_buff(debugger, resp->size) == RET_OK, RET_OOM); if (resp->size > 0) { return_value_if_fail(debugger_client_read_data(in, client->buff, resp->size) == RET_OK, RET_IO); } return debugger_client_dispatch_message(debugger, resp); } static ret_t debugger_client_dispatch_messages(debugger_t* debugger) { debugger_resp_t resp; tk_istream_t* in = NULL; debugger_client_t* client = DEBUGGER_CLIENT(debugger); return_value_if_fail(client != NULL, RET_BAD_PARAMS); memset(&resp, 0x00, sizeof(resp)); in = tk_iostream_get_istream(client->io); while (tk_istream_wait_for_data(in, 10) == RET_OK) { if (debugger_client_dispatch_one(debugger, &resp) != RET_OK) { break; } } return RET_OK; } ret_t debugger_client_dispatch(debugger_t* debugger) { return debugger_client_dispatch_messages(debugger); } ret_t debugger_client_wait_for_completed(debugger_t* debugger) { debugger_resp_t resp; debugger_client_t* client = DEBUGGER_CLIENT(debugger); memset(&resp, 0x00, sizeof(resp)); while (!(client->program_completed)) { break_if_fail(debugger_client_dispatch_one(debugger, &resp) == RET_OK); if (resp.code == DEBUGGER_RESP_MSG_COMPLETED) { break; } } return RET_OK; } static ret_t debugger_client_read_packet(debugger_t* debugger, uint32_t resp_code, void** data, uint32_t* size, ret_t* ret) { debugger_resp_t resp; debugger_client_t* client = DEBUGGER_CLIENT(debugger); memset(&resp, 0x00, sizeof(resp)); while (TRUE) { break_if_fail(debugger_client_dispatch_one(debugger, &resp) == RET_OK); if (resp.code == resp_code) { *data = client->buff; *size = resp.size; *ret = resp.error; break; } } return RET_OK; } static ret_t debugger_client_read_simple(debugger_t* debugger, uint32_t resp_code) { void* data = NULL; uint32_t size = 0; ret_t ret = RET_FAIL; debugger_client_read_packet(debugger, resp_code, &data, &size, &ret); return ret; } static tk_object_t* debugger_client_read_object(debugger_t* debugger, uint32_t resp_code) { void* data = NULL; uint32_t size = 0; ret_t ret = RET_FAIL; if (debugger_client_read_packet(debugger, resp_code, &data, &size, &ret) == RET_OK && ret == RET_OK) { assert(data != NULL && size > 0); return ubjson_to_object(data, size); } return NULL; } static ret_t debugger_client_read_binary(debugger_t* debugger, uint32_t resp_code, binary_data_t* data) { ret_t ret = RET_FAIL; debugger_client_read_packet(debugger, resp_code, &(data->data), &(data->size), &ret); return ret; } static ret_t debugger_client_write_binary(debugger_t* debugger, uint32_t code, const void* data, uint32_t size) { debugger_req_t req; debugger_client_t* client = DEBUGGER_CLIENT(debugger); tk_ostream_t* out = tk_iostream_get_ostream(client->io); memset(&req, 0x00, sizeof(req)); req.code = code; req.size = size; req.version = DEBUGGER_VERSION; debugger_client_write_data(out, &req, sizeof(req)); if (req.size > 0) { return debugger_client_write_data(out, data, size); } else { return RET_OK; } } static ret_t debugger_client_write_simple(debugger_t* debugger, uint32_t code, uint32_t data) { debugger_req_t req; debugger_client_t* client = DEBUGGER_CLIENT(debugger); tk_ostream_t* out = tk_iostream_get_ostream(client->io); memset(&req, 0x00, sizeof(req)); req.code = code; req.data = data; req.size = 0; req.version = DEBUGGER_VERSION; return debugger_client_write_data(out, &req, sizeof(req)); } static ret_t debugger_client_request_simple(debugger_t* debugger, uint32_t code, uint32_t data) { return_value_if_fail(debugger_client_write_simple(debugger, code, data) == RET_OK, RET_FAIL); return debugger_client_read_simple(debugger, code); } static ret_t debugger_client_request_binary(debugger_t* debugger, uint32_t code, const void* data, uint32_t size) { return_value_if_fail(debugger_client_write_binary(debugger, code, data, size) == RET_OK, RET_FAIL); return debugger_client_read_simple(debugger, code); } static ret_t debugger_client_stop(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_STOP, 0); } static ret_t debugger_client_pause(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_PAUSE, 0); } static ret_t debugger_client_restart(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_RESTART, 0); } static ret_t debugger_client_step_over(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_STEP_OVER, 0); } static ret_t debugger_client_step_in(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_STEP_IN, 0); } static ret_t debugger_client_step_out(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_STEP_OUT, 0); } static ret_t debugger_client_step_loop_over(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_STEP_LOOP_OVER, 0); } static ret_t debugger_client_continue(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_CONTINUE, 0); } typedef struct _visit_var_info_t { uint32_t i; str_t* str; }visit_var_info_t; static ret_t visit_var(void* ctx, const void* data) { char buff[64] = {0}; visit_var_info_t* info = (visit_var_info_t*)ctx; str_t* str = info->str; named_value_t* nv = (named_value_t*)data; if(info->i > 0) { str_append(str, ","); } info->i++; str_append(str, "{"); str_append_json_str_pair(str, "name", nv->name); str_append(str, ","); str_append_json_str_pair(str, "type", value_type_name(nv->value.type)); str_append(str, ","); str_append_json_str_pair(str, "evaluateName", nv->name); str_append(str, ","); str_append_json_str_pair(str, "value", value_str_ex(&(nv->value), buff, sizeof(buff))); str_append(str, "}"); return RET_OK; } static tk_object_t* debugger_client_variables_to_dap_format(tk_object_t* obj) { str_t str; char url[MAX_PATH+1] = {0}; visit_var_info_t info = {0, &str}; return_value_if_fail(obj != NULL, NULL); str_init(&str, 1000); str_append(&str, "{\"body\":{\"variables\":["); tk_object_foreach_prop(obj, visit_var, &info); TK_OBJECT_UNREF(obj); str_append(&str, "]}}"); data_reader_mem_build_url(str.str, str.size, url); obj = conf_json_load(url, FALSE); str_reset(&str); return obj; } static tk_object_t* debugger_client_get_local(debugger_t* debugger, uint32_t frame_index) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_LOCAL, frame_index) == RET_OK) { tk_object_t* obj = debugger_client_read_object(debugger, DEBUGGER_RESP_GET_LOCAL); return debugger_client_variables_to_dap_format(obj); } else { return NULL; } } static tk_object_t* debugger_client_get_self(debugger_t* debugger) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_SELF, 0) == RET_OK) { return debugger_client_read_object(debugger, DEBUGGER_RESP_GET_SELF); } else { return NULL; } } static tk_object_t* debugger_client_get_global(debugger_t* debugger) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_GLOBAL, 0) == RET_OK) { return debugger_client_read_object(debugger, DEBUGGER_RESP_GET_GLOBAL); } else { return NULL; } } static ret_t debugger_client_get_callstack(debugger_t* debugger, binary_data_t* callstack) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_CALLSTACK, 0) == RET_OK) { return debugger_client_read_binary(debugger, DEBUGGER_RESP_GET_CALLSTACK, callstack); } else { return RET_FAIL; } } static ret_t debugger_client_clear_break_points(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_CLEAR_BREAK_POINTS, 0); } static ret_t debugger_client_set_break_point(debugger_t* debugger, uint32_t line) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_SET_BREAK_POINT, line); } static ret_t debugger_client_remove_break_point(debugger_t* debugger, uint32_t line) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_REMOVE_BREAK_POINT, line); } static ret_t debugger_client_attach(debugger_t* debugger, const char* lang, const char* code_id) { char data[256]; tk_snprintf(data, sizeof(data) - 1, "%s:%s", lang, code_id); return debugger_client_request_binary(debugger, DEBUGGER_REQ_ATTACH, data, strlen(data) + 1); } static ret_t debugger_client_deinit(debugger_t* debugger) { return debugger_client_request_simple(debugger, DEBUGGER_REQ_DEINIT, 0); } static ret_t debugger_client_update_code(debugger_t* debugger, const binary_data_t* code) { return debugger_client_request_binary(debugger, DEBUGGER_REQ_UPDATE_CODE, code->data, code->size); } static ret_t debugger_client_launch(debugger_t* debugger, const char* lang, const binary_data_t* code) { wbuffer_t wb; ret_t ret = RET_FAIL; wbuffer_init_extendable(&wb); return_value_if_fail(wbuffer_extend_capacity(&wb, strlen(lang) + code->size + 1) == RET_OK, RET_OOM); wbuffer_write_binary(&wb, lang, strlen(lang)); wbuffer_write_binary(&wb, ":", 1); wbuffer_write_binary(&wb, code->data, code->size); ret = debugger_client_request_binary(debugger, DEBUGGER_REQ_LAUNCH, wb.data, wb.cursor); wbuffer_deinit(&wb); return ret; } static ret_t debugger_client_get_code(debugger_t* debugger, binary_data_t* code) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_CODE, 0) == RET_OK) { return debugger_client_read_binary(debugger, DEBUGGER_RESP_GET_CODE, code); } else { return RET_FAIL; } } static ret_t debugger_client_get_debuggers(debugger_t* debugger, binary_data_t* debuggers) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_DEBUGGERS, 0) == RET_OK) { return debugger_client_read_binary(debugger, DEBUGGER_RESP_GET_DEBUGGERS, debuggers); } else { return RET_FAIL; } } static ret_t debugger_client_get_break_points(debugger_t* debugger, binary_data_t* break_points) { if (debugger_client_write_simple(debugger, DEBUGGER_REQ_GET_BREAK_POINTS, 0) == RET_OK) { return debugger_client_read_binary(debugger, DEBUGGER_RESP_GET_BREAK_POINTS, break_points); } else { return RET_FAIL; } } static const debugger_vtable_t s_debugger_client_vtable = { .attach = debugger_client_attach, .launch = debugger_client_launch, .lang = "client", .lock = debugger_client_lock, .unlock = debugger_client_unlock, .stop = debugger_client_stop, .pause = debugger_client_pause, .restart = debugger_client_restart, .step_in = debugger_client_step_in, .step_out = debugger_client_step_out, .step_over = debugger_client_step_over, .step_loop_over = debugger_client_step_loop_over, .continve = debugger_client_continue, .get_local = debugger_client_get_local, .get_self = debugger_client_get_self, .get_global = debugger_client_get_global, .get_code = debugger_client_get_code, .get_debuggers = debugger_client_get_debuggers, .get_break_points = debugger_client_get_break_points, .get_callstack = debugger_client_get_callstack, .update_code = debugger_client_update_code, .set_break_point = debugger_client_set_break_point, .remove_break_point = debugger_client_remove_break_point, .clear_break_points = debugger_client_clear_break_points, .dispatch_messages = debugger_client_dispatch_messages, .deinit = debugger_client_deinit, }; debugger_client_t* debugger_client_cast(debugger_t* debugger) { return_value_if_fail(debugger != NULL && debugger->vt == &s_debugger_client_vtable, NULL); return (debugger_client_t*)debugger; } static ret_t debugger_client_on_destroy(tk_object_t* obj) { debugger_client_t* debugger = DEBUGGER_CLIENT(obj); return_value_if_fail(debugger != NULL, RET_BAD_PARAMS); TKMEM_FREE(debugger->buff); TK_OBJECT_UNREF(debugger->io); return RET_OK; } static const object_vtable_t s_object_debugger_client_vtable = { .type = "object_debugger_client", .desc = "object_debugger_client", .size = sizeof(debugger_client_t), .is_collection = FALSE, .on_destroy = debugger_client_on_destroy}; debugger_t* debugger_client_create(tk_iostream_t* io) { debugger_client_t* debugger = NULL; return_value_if_fail(io != NULL, NULL); debugger = (debugger_client_t*)tk_object_create(&s_object_debugger_client_vtable); return_value_if_fail(debugger != NULL, NULL); debugger->io = io; TK_OBJECT_REF(debugger->io); debugger->debugger.vt = &s_debugger_client_vtable; debugger->capacity = 10 * 1024; debugger->buff = TKMEM_ALLOC(debugger->capacity); return (debugger_t*)debugger; }