From: Brendan Hansen Date: Thu, 11 Aug 2022 01:19:05 +0000 (-0500) Subject: line by line stepping and stack trace X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=11ad2d20a590d55fad1d636f48f88db15daa88f6;p=onyx-embedder.git line by line stepping and stack trace --- diff --git a/include/ovm_debug.h b/include/ovm_debug.h index dbf52c3..ff40cc6 100644 --- a/include/ovm_debug.h +++ b/include/ovm_debug.h @@ -24,6 +24,7 @@ typedef struct debug_func_info_t { typedef struct debug_file_info_t { char *name; + u32 file_id; u32 line_count; i32 line_buffer_offset; } debug_file_info_t; @@ -57,6 +58,7 @@ void debug_info_import_func_info(debug_info_t *, u8 *data, u32 len); bool debug_info_lookup_location(debug_info_t *info, u32 instruction, debug_loc_info_t *out); bool debug_info_lookup_file(debug_info_t *info, u32 file_id, debug_file_info_t *out); bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_info_t *out); +bool debug_info_lookup_func(debug_info_t *info, u32 func_id, debug_func_info_t *out); // // This builder is used in conjunction with code builder to output @@ -97,6 +99,18 @@ typedef enum debug_exec_state_t { debug_state_hit_breakpoint, } debug_exec_state_t; +typedef enum debug_pause_reason_t { + debug_pause_entry = 1, + debug_pause_step = 2, +} debug_pause_reason_t; + +typedef struct debug_breakpoint_t { + u32 id; + u32 instr; + u32 file_id; + u32 line; +} debug_breakpoint_t; + typedef struct debug_thread_state_t { u32 id; @@ -106,8 +120,10 @@ typedef struct debug_thread_state_t { i32 run_count; sem_t wait_semaphore; - // :BreakpointID - bh_arr(u32) breakpoints; + bool pause_at_next_line; + debug_pause_reason_t pause_reason; + + bh_arr(debug_breakpoint_t) breakpoints; u32 last_breakpoint_hit; } debug_thread_state_t; @@ -126,6 +142,8 @@ typedef struct debug_state_t { bh_arr(debug_thread_state_t *) threads; u32 next_thread_id; + u32 next_breakpoint_id; + pthread_t debug_thread; bool debug_thread_running; diff --git a/src/debug/debug_host.c b/src/debug/debug_host.c index 31e3f2c..fa1dcf5 100644 --- a/src/debug/debug_host.c +++ b/src/debug/debug_host.c @@ -32,6 +32,7 @@ void debug_host_stop(debug_state_t *debug) { u32 debug_host_register_thread(debug_state_t *debug, ovm_state_t *ovm_state) { debug_thread_state_t *new_thread = bh_alloc(debug->alloc, sizeof(*new_thread)); + memset(new_thread, 0, sizeof(*new_thread)); new_thread->state = debug_state_starting; new_thread->ovm_state = ovm_state; diff --git a/src/debug/debug_info.c b/src/debug/debug_info.c index 98743dd..d50b234 100644 --- a/src/debug/debug_info.c +++ b/src/debug/debug_info.c @@ -34,6 +34,7 @@ void debug_info_import_file_info(debug_info_t *info, u8 *data, u32 len) { file_info.line_buffer_offset = -1; u32 file_id = uleb128_to_uint(data, &offset); + file_info.file_id = file_id; file_info.line_count = uleb128_to_uint(data, &offset); u32 name_length = uleb128_to_uint(data, &offset); @@ -115,3 +116,11 @@ bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_i return false; } + +bool debug_info_lookup_func(debug_info_t *info, u32 func_id, debug_func_info_t *out) { + if (!info || !info->has_debug_info) return false; + + if (func_id > (u32) bh_arr_length(info->funcs)) return false; + *out = info->funcs[func_id]; + return true; +} \ No newline at end of file diff --git a/src/debug/debug_info_builder.c b/src/debug/debug_info_builder.c index 489b5e6..1b84def 100644 --- a/src/debug/debug_info_builder.c +++ b/src/debug/debug_info_builder.c @@ -79,7 +79,7 @@ void debug_info_builder_step(debug_info_builder_t *builder) { builder->next_file_line_offset += file_info->line_count; } - u32 line_index = file_info->line_buffer_offset + builder->current_line; + i32 line_index = file_info->line_buffer_offset + builder->current_line; u32 target = bh_arr_length(builder->info->instruction_reducer); if (line_index >= bh_arr_capacity(builder->info->line_to_instruction) || diff --git a/src/debug/debug_thread.c b/src/debug/debug_thread.c index 5e7716c..7de5aae 100644 --- a/src/debug/debug_thread.c +++ b/src/debug/debug_thread.c @@ -5,13 +5,21 @@ #include #include #include - +#include #define CMD_NOP 0 #define CMD_RES 1 #define CMD_BRK 2 -#define CMD_LOC 3 +#define CMD_CLR_BRK 3 +#define CMD_LOC 4 +#define CMD_STEP 5 +#define CMD_TRACE 6 + +#define EVT_NOP 0 +#define EVT_BRK_HIT 1 +#define EVT_PAUSE 2 +#define EVT_RESPONSE 0xffffffff struct msg_parse_ctx_t { char *data; @@ -20,7 +28,7 @@ struct msg_parse_ctx_t { }; static void send_response_header(debug_state_t *debug, unsigned int message_number) { - unsigned int RESPONSE_HEADER = 0xffffffff; + unsigned int RESPONSE_HEADER = EVT_RESPONSE; send(debug->client_fd, &RESPONSE_HEADER, 4, 0); send(debug->client_fd, &message_number, 4, 0); } @@ -72,22 +80,24 @@ static char *parse_string(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { return buf; } +static void resume_thread(debug_thread_state_t *thread) { + thread->run_count = -1; + sem_post(&thread->wait_semaphore); +} + static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { u32 msg_id = parse_int(debug, ctx); u32 command_id = parse_int(debug, ctx); - printf("[INFO ] Recv command: %d\n", command_id); + + // printf("[INFO ] Recv command: %d\n", command_id); switch (command_id) { case CMD_NOP: { - // Returns: header - send_response_header(debug, msg_id); break; } case CMD_RES: { - // Returns: nothing - u32 thread_id = parse_int(debug, ctx); bool resumed_a_thread = false; @@ -95,10 +105,7 @@ static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { // Release the thread(s) bh_arr_each(debug_thread_state_t *, thread, debug->threads) { if (thread_id == 0xffffffff || (*thread)->id == thread_id) { - // This logic for "how to resume a thread" should be moved - // elsewhere, but now its fine here. - (*thread)->run_count = -1; - sem_post(&(*thread)->wait_semaphore); + resume_thread(*thread); resumed_a_thread = true; } } @@ -109,22 +116,19 @@ static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { } case CMD_BRK: { - // - // Returns: header success:bool breakpoint_handle:u32 - - char *filename = parse_string(debug, ctx); - int line = parse_int(debug, ctx); + char *filename = parse_string(debug, ctx); + unsigned int line = parse_int(debug, ctx); // // TODO: This translation logic will have to be abstracted debug_file_info_t file_info; bool file_found = debug_info_lookup_file_by_name(debug->info, filename, &file_info); if (!file_found) { - goto send_error; + goto brk_send_error; } if (line > file_info.line_count) { - goto send_error; + goto brk_send_error; } u32 instr; @@ -132,23 +136,28 @@ static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { line += 1; if (line > file_info.line_count) { - goto send_error; + goto brk_send_error; } } printf("[INFO ] Setting breakpoint at %s:%d (%xd)\n", filename, line, instr); + debug_breakpoint_t bp; + bp.id = debug->next_breakpoint_id++; + bp.instr = instr; + bp.file_id = file_info.file_id; + bp.line = line; bh_arr_each(debug_thread_state_t *, thread, debug->threads) { - bh_arr_push((*thread)->breakpoints, instr); + bh_arr_push((*thread)->breakpoints, bp); } send_response_header(debug, msg_id); send_bool(debug, true); - send_int(debug, 1); // TODO: This should be a unique breakpoint ID + send_int(debug, bp.id); // TODO: This should be a unique breakpoint ID send_int(debug, line); break; - send_error: + brk_send_error: send_response_header(debug, msg_id); send_bool(debug, false); send_int(debug, -1); @@ -156,6 +165,36 @@ static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { break; } + case CMD_CLR_BRK: { + char *filename = parse_string(debug, ctx); + + debug_file_info_t file_info; + bool file_found = debug_info_lookup_file_by_name(debug->info, filename, &file_info); + if (!file_found) { + goto clr_brk_send_error; + } + + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + bh_arr_each(debug_breakpoint_t, bp, (*thread)->breakpoints) { + if (bp->file_id == file_info.file_id) { + // This is kind of hacky but it does successfully delete + // a single element from the array and move the iterator. + bh_arr_fastdelete((*thread)->breakpoints, bp - (*thread)->breakpoints); + bp--; + } + } + } + + send_response_header(debug, msg_id); + send_bool(debug, true); + break; + + clr_brk_send_error: + send_response_header(debug, msg_id); + send_bool(debug, false); + break; + } + case CMD_LOC: { u32 thread_id = parse_int(debug, ctx); @@ -188,6 +227,77 @@ static void process_command(debug_state_t *debug, struct msg_parse_ctx_t *ctx) { send_int(debug, -1); break; } + + case CMD_STEP: { + u32 granularity = parse_int(debug, ctx); + u32 thread_id = parse_int(debug, ctx); + + if (granularity == 1) { + bh_arr_each(debug_thread_state_t *, thread, debug->threads) { + if ((*thread)->id == thread_id) { + (*thread)->pause_at_next_line = true; + resume_thread(*thread); + } + } + } + + send_response_header(debug, msg_id); + break; + } + + // The trace command should be probably eclipse + // the loc command at some point? + case CMD_TRACE: { + unsigned int thread_id = parse_int(debug, ctx); + + debug_thread_state_t *thread = NULL; + bh_arr_each(debug_thread_state_t *, pthread, debug->threads) { + if ((*pthread)->id == thread_id) { + thread = *pthread; + break; + } + } + + if (thread == NULL) { + send_response_header(debug, msg_id); + send_int(debug, 0); + break; + } + + bh_arr(ovm_stack_frame_t) frames = thread->ovm_state->stack_frames; + + send_response_header(debug, msg_id); + send_int(debug, bh_arr_length(frames)); + + bh_arr_rev_each(ovm_stack_frame_t, frame, frames) { + ovm_func_t *func = frame->func; + + debug_func_info_t func_info; + assert(debug_info_lookup_func(debug->info, func->id, &func_info)); + send_string(debug, func_info.name); + + // :CopyPaste from CMD_LOC case + debug_loc_info_t loc_info; + debug_file_info_t file_info; + + u32 instr; + if (frame == &bh_arr_last(frames)) { + instr = thread->ovm_state->pc; + } else { + instr = (frame + 1)->return_address; + } + + while (!debug_info_lookup_location(debug->info, instr, &loc_info)) + instr++; + + assert(debug_info_lookup_file(debug->info, loc_info.file_id, &file_info)); + + send_string(debug, file_info.name); + send_int(debug, loc_info.line); + } + + break; + } } } @@ -271,8 +381,13 @@ void *__debug_thread_entry(void * data) { if ((*thread)->state == debug_state_hit_breakpoint) { (*thread)->state = debug_state_paused; - u32 bp_id = (*thread)->last_breakpoint_hit; // :BreakpointID - u32 instr = (*thread)->breakpoints[bp_id]; + u32 instr, bp_id = (*thread)->last_breakpoint_hit; + bh_arr_each(debug_breakpoint_t, bp, (*thread)->breakpoints) { + if (bp->id == bp_id) { + instr = bp->instr; + break; + } + } debug_loc_info_t loc_info; debug_info_lookup_location(debug->info, instr, &loc_info); @@ -280,7 +395,7 @@ void *__debug_thread_entry(void * data) { debug_file_info_t file_info; debug_info_lookup_file(debug->info, loc_info.file_id, &file_info); - send_int(debug, 1); + send_int(debug, EVT_BRK_HIT); send_int(debug, bp_id); send_int(debug, (*thread)->id); } @@ -288,8 +403,9 @@ void *__debug_thread_entry(void * data) { if ((*thread)->state == debug_state_pausing) { (*thread)->state = debug_state_paused; - send_int(debug, 2); + send_int(debug, EVT_PAUSE); send_int(debug, (*thread)->id); + send_int(debug, (*thread)->pause_reason); } } diff --git a/src/vm/vm.c b/src/vm/vm.c index 37d03bd..63561a5 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -686,14 +686,27 @@ ovm_value_t ovm_run_code(ovm_engine_t *engine, ovm_state_t *state, ovm_program_t if (state->debug) { if (state->debug->run_count == 0) { state->debug->state = debug_state_pausing; + state->debug->pause_reason = debug_pause_entry; // This is not always due to entry... goto should_wait; } - bh_arr_each(u32, bp, state->debug->breakpoints) { - if (*bp == (u32) state->pc) { - // :BreakpointID + if (state->debug->pause_at_next_line) { + debug_loc_info_t l1, l2; + debug_info_lookup_location(engine->debug->info, state->pc - 1, &l1); + debug_info_lookup_location(engine->debug->info, state->pc, &l2); + + if (l1.file_id != l2.file_id || l1.line != l2.line) { + state->debug->pause_at_next_line = false; + state->debug->pause_reason = debug_pause_step; + state->debug->state = debug_state_pausing; + goto should_wait; + } + } + + bh_arr_each(debug_breakpoint_t, bp, state->debug->breakpoints) { + if (bp->instr == (u32) state->pc) { state->debug->state = debug_state_hit_breakpoint; - state->debug->last_breakpoint_hit = bp - state->debug->breakpoints; + state->debug->last_breakpoint_hit = bp->id; goto should_wait; } }