diff --git a/project.raddbg b/project.raddbg index eccd0b71..7ba34992 100644 --- a/project.raddbg +++ b/project.raddbg @@ -41,6 +41,6 @@ target: { executable: "build/torture.exe" working_directory: build + arguments: "raddbg/*" enabled: 1 - arguments: "raddbg/* --gui" } diff --git a/src/base/base_test.h b/src/base/base_test.h index d9350812..19e87a88 100644 --- a/src/base/base_test.h +++ b/src/base/base_test.h @@ -23,7 +23,7 @@ struct TestResult char *fail_cond; }; -#define TEST_FUNCTION_SIG(name) void name(Arena *arena, CmdLine *cmdline, String8 test_artifacts_path, TestResult *result_out, String8List *test_out) +#define TEST_FUNCTION_SIG(name) void name(Arena *arena, CmdLine *cmdline, String8 test_exemplars_path, String8 test_artifacts_path, TestResult *result_out, String8List *test_out) #define TEST_FUNCTION_DEF(name) TEST_FUNCTION_SIG(test__##name) typedef TEST_FUNCTION_SIG(TestFunctionType); @@ -76,10 +76,18 @@ TEST_FUNCTION_DEF(name) #define SkippedTest(name) DeclareTest(name, 1)\ TEST_FUNCTION_DEF(name) +#define test_out(string) str8_list_push(arena, test_out, (string)) +#define test_outf(...) str8_list_pushf(arena, test_out, __VA_ARGS__) + #define TestCheck(c) do { if (!(c)) {\ *result_out = (TestResult){ .fail_file = __FILE__, .fail_line = __LINE__, .fail_cond = Stringify(c) };\ if(debugger_is_attached()) { Trap(); }\ return;\ } } while(0) +#define TestSkip() do {\ +*result_out = (TestResult){ .status = TestStatus_Skip };\ +return;\ +} while(0) + #endif // BASE_TEST_H diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index b590c2ac..98193d7e 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -21,6 +21,7 @@ D_CmdTable: // | | | | {StepIntoLine 1 1 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 StepInto "step_into_line" "Step Into (Line)" "Performs a step that goes into calls, at the source code line level." "step,thread" "" } {StepOverLine 1 1 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 StepOver "step_over_line" "Step Over (Line)" "Performs a step that skips calls, at the source code line level." "step,thread" "" } {StepOut 1 1 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 StepOut "step_out" "Step Out" "Runs to the first instruction after the current scope is exited." "" "" } + {StepToExit 1 1 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 StepOut "step_to_exit" "Step To Exit" "Runs to the first encountered instruction which will exit the current scope." "" "" } {Halt 1 1 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 Pause "halt" "Halt" "Halts all attached processes." "pause" "" } {SoftHaltRefresh 0 0 0 0 "" Null null Nil Null 0 0 0 0 0 0 0 Refresh "soft_halt_refresh" "Soft Halt Refresh" "Interrupts all attached processes to collect data, and then resumes them." "" "" } {SetThreadIP 0 1 0 0 "" Vaddr null Nil Null 0 0 0 0 1 1 1 Null "set_thread_ip" "Set Thread IP" "Sets the specified thread's instruction pointer at the specified address." "" "" } diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index dbef306e..7c2ec311 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -447,6 +447,7 @@ d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread) DASM_InstFlag_UnconditionalJump| DASM_InstFlag_ChangesStackPointer| DASM_InstFlag_Return, + 0, arch, line_vaddr_rng.min, machine_code); @@ -641,6 +642,7 @@ d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) DASM_InstFlag_UnconditionalJump| DASM_InstFlag_ChangesStackPointer| DASM_InstFlag_Return, + 0, arch, line_vaddr_rng.min, machine_code); @@ -807,7 +809,7 @@ d_trap_net_from_thread__step_out_scope(Arena *arena, D_Entity *thread) { result.good_read = 1; String8 code = code_slice.data; - DASM_CtrlFlowInfo ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, DASM_InstFlag_Branch|DASM_InstFlag_UnconditionalJump|DASM_InstFlag_Return, arch, vaddr_range.min, code); + DASM_CtrlFlowInfo ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, DASM_InstFlag_Branch|DASM_InstFlag_UnconditionalJump|DASM_InstFlag_Return, 0, arch, vaddr_range.min, code); // rjf: add traps at all jump destinations which do *not* fall into any of the scope's ranges for EachNode(exit_pt_n, DASM_CtrlFlowPointNode, ctrl_flow_info.exit_points.first) @@ -878,6 +880,105 @@ d_trap_net_from_thread__step_out_scope(Arena *arena, D_Entity *thread) return result; } +internal D_TrapNet +d_trap_net_from_thread__step_to_exit(Arena *arena, D_Entity *thread) +{ + D_TrapNet result = {0}; + { + U64 read_endt_us = now_time_us() + 1000000; + Temp scratch = scratch_begin(&arena, 1); + Access *access = access_open(); + + // rjf: unpack thread + Arch arch = thread->arch; + U64 ip_vaddr = d_cached_ip_from_thread(thread->handle); + D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); + D_Entity *module = d_module_from_process_vaddr(process, ip_vaddr); + DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_CallStack callstack = d_call_stack_from_thread(access, thread->handle, 1, read_endt_us); + + // rjf: ip => enclosing scope's list(voff_range) + Rng1U64List scope_voff_rngs = {0}; + { + U64 ip_voff = d_voff_from_vaddr(module, ip_vaddr); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); + if(rdi != &rdi_parsed_nil) + { + RDI_Scope *scope = rdi_scope_from_voff(rdi, ip_voff); + U64 all_scope_voffs_count = 0; + U64 *all_scope_voffs = rdi_table_from_name(rdi, ScopeVOffData, &all_scope_voffs_count); + for(U64 voff_idx = scope->voff_range_first; voff_idx+1 < scope->voff_range_opl; voff_idx += 1) + { + Rng1U64 voff_range = r1u64(all_scope_voffs[voff_idx], all_scope_voffs[voff_idx+1]); + rng1u64_list_push(scratch.arena, &scope_voff_rngs, voff_range); + } + } + } + + // rjf: place traps at all possible exit points of all scope's ranges + result.good_read = 1; + for EachNode(n, Rng1U64Node, scope_voff_rngs.first) + { + Rng1U64 voff_range = n->v; + Rng1U64 vaddr_range = d_vaddr_range_from_voff_range(module, voff_range); + D_ProcessMemorySlice code_slice = d_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, vaddr_range, 0, read_endt_us); + if(!code_slice.any_byte_bad) + { + result.good_read = 1; + String8 code = code_slice.data; + DASM_CtrlFlowInfo ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, DASM_InstFlag_Branch|DASM_InstFlag_UnconditionalJump|DASM_InstFlag_Return, 1, arch, vaddr_range.min, code); + + // rjf: add traps at all jumps whose destinations do *not* fall into any of the scope's ranges + for EachNode(exit_pt_n, DASM_CtrlFlowPointNode, ctrl_flow_info.exit_points.first) + { + U64 jump_vaddr = exit_pt_n->v.vaddr; + U64 jump_dest_vaddr = exit_pt_n->v.jump_dest_vaddr; + B32 jump_dest_vaddr_is_out_of_scope = 1; + for EachNode(scope_n, Rng1U64Node, scope_voff_rngs.first) + { + if(contains_1u64(scope_n->v, d_voff_from_vaddr(module, jump_dest_vaddr))) + { + jump_dest_vaddr_is_out_of_scope = 0; + break; + } + } + if(jump_dest_vaddr_is_out_of_scope) + { + D_Trap trap = {D_TrapFlag_EndStepping|D_TrapFlag_IgnoreStackPointerCheck, jump_vaddr}; + d_trap_list_push(arena, &result.traps, &trap); + } + } + } + else + { + result.good_read = 0; + break; + } + } + + // rjf: use first unwind frame to generate trap + if(result.good_read && callstack.concrete_frames_count > 1) + { + ARCH_Info *arch_info = arch_info_from_arch(thread->arch); + U64 vaddr = arch_ip_from_reg_block(arch_info, callstack.concrete_frames[1]->regs); + D_Trap trap = {D_TrapFlag_EndStepping|D_TrapFlag_IgnoreStackPointerCheck, vaddr}; + d_trap_list_push(arena, &result.traps, &trap); + } + else + { + result.good_read = 0; + } + + // NOTE(rjf): we always want to support missing line info for "step to exit", because + // we should always be able to fall back to just using the unwind, so just always + // mark this bit as a success. + result.good_line_info = 1; + access_close(access); + scratch_end(scratch); + } + return result; +} + //////////////////////////////// //~ rjf: Debug Info Lookups @@ -1315,6 +1416,12 @@ d_ctrl_targets_running(void) return d_user_state->ctrl_is_running; } +internal U64 +d_stop_count(void) +{ + return d_user_state->stop_count; +} + //- rjf: active entity based queries internal DI_KeyList @@ -1610,6 +1717,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_EventKind_Stopped: { + d_user_state->stop_count += 1; d_user_state->ctrl_is_running = 0; d_user_state->ctrl_thread_run_state = 0; d_user_state->ctrl_soft_halt_issued = 0; @@ -1903,6 +2011,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_CmdKind_StepIntoLine: case D_CmdKind_StepOverLine: case D_CmdKind_StepOut: + case D_CmdKind_StepToExit: { D_Entity *thread = d_entity_from_handle(params->thread); if(thread == &d_entity_nil) @@ -1931,6 +2040,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_CmdKind_StepIntoLine: {trap_net = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; case D_CmdKind_StepOverLine: {trap_net = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; case D_CmdKind_StepOut: {trap_net = d_trap_net_from_thread__step_out_scope(scratch.arena, thread);}break; + case D_CmdKind_StepToExit: {trap_net = d_trap_net_from_thread__step_to_exit(scratch.arena, thread);}break; } B32 good_trap_net = (trap_net.good_read || !trap_net.good_line_info); if(good_trap_net && trap_net.traps.count != 0) diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 5bffd3f4..8b57b40a 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -378,6 +378,7 @@ struct D_UserState D_EntityCtxRWStore *ctrl_entity_store; Arena *ctrl_stop_arena; D_Event ctrl_last_stop_event; + U64 stop_count; }; //////////////////////////////// @@ -424,6 +425,7 @@ internal D_TrapNet d_trap_net_from_thread__step_over_inst(Arena *arena, D_Entity internal D_TrapNet d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread); internal D_TrapNet d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread); internal D_TrapNet d_trap_net_from_thread__step_out_scope(Arena *arena, D_Entity *thread); +internal D_TrapNet d_trap_net_from_thread__step_to_exit(Arena *arena, D_Entity *thread); //////////////////////////////// //~ rjf: Debug Info Lookups @@ -460,6 +462,7 @@ internal U64 d_frame_index(void); internal D_RunKind d_ctrl_last_run_kind(void); internal U64 d_ctrl_last_run_frame_idx(void); internal B32 d_ctrl_targets_running(void); +internal U64 d_stop_count(void); //- rjf: active entity based queries internal DI_KeyList d_push_active_dbgi_key_list(Arena *arena); diff --git a/src/dbg_engine/generated/dbg_engine.meta.h b/src/dbg_engine/generated/dbg_engine.meta.h index ddb3b8ee..39032533 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.h +++ b/src/dbg_engine/generated/dbg_engine.meta.h @@ -20,6 +20,7 @@ D_CmdKind_StepOverInst, D_CmdKind_StepIntoLine, D_CmdKind_StepOverLine, D_CmdKind_StepOut, +D_CmdKind_StepToExit, D_CmdKind_Halt, D_CmdKind_SoftHaltRefresh, D_CmdKind_SetThreadIP, diff --git a/src/disasm/disasm.c b/src/disasm/disasm.c index dbac0a96..58532b5e 100644 --- a/src/disasm/disasm.c +++ b/src/disasm/disasm.c @@ -28,7 +28,7 @@ dasm_inst_from_code(Arena *arena, Arch arch, U64 vaddr, String8 code, DASM_Synta //~ rjf: Control Flow Analysis internal DASM_CtrlFlowInfo -dasm_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DASM_InstFlags exit_points_mask, Arch arch, U64 vaddr, String8 code) +dasm_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DASM_InstFlags exit_points_mask, B32 include_last_inst, Arch arch, U64 vaddr, String8 code) { Temp scratch = scratch_begin(&arena, 1); DASM_CtrlFlowInfo info = {0}; @@ -38,7 +38,7 @@ dasm_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DASM_InstFlags exit_point U64 inst_vaddr = vaddr+offset; offset += inst.size; info.total_size += inst.size; - if(inst.flags & exit_points_mask) + if(inst.flags & exit_points_mask || (include_last_inst && offset >= code.size)) { DASM_CtrlFlowPoint point = {0}; point.inst_flags = inst.flags; diff --git a/src/disasm/disasm.h b/src/disasm/disasm.h index dbdef297..816e9c19 100644 --- a/src/disasm/disasm.h +++ b/src/disasm/disasm.h @@ -183,7 +183,7 @@ internal DASM_Inst dasm_inst_from_code(Arena *arena, Arch arch, U64 vaddr, Strin //////////////////////////////// //~ rjf: Control Flow Analysis -internal DASM_CtrlFlowInfo dasm_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DASM_InstFlags exit_points_mask, Arch arch, U64 vaddr, String8 code); +internal DASM_CtrlFlowInfo dasm_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DASM_InstFlags exit_points_mask, B32 include_last_inst, Arch arch, U64 vaddr, String8 code); //////////////////////////////// //~ rjf: Parameter Type Functions diff --git a/src/mdesk/mdesk.c b/src/mdesk/mdesk.c index efe2a52f..46af783d 100644 --- a/src/mdesk/mdesk.c +++ b/src/mdesk/mdesk.c @@ -1205,6 +1205,110 @@ md_debug_string_list_from_tree(Arena *arena, MD_Node *root) return strings; } +internal String8List +md_string_list_from_tree(Arena *arena, MD_Node *root) +{ + String8List strings = {0}; + { + char *indentation = " "; + S32 depth = 0; + B32 delimit_with_newlines = 1; + for(MD_Node *node = root, *next = &md_nil_node; !md_node_is_nil(node); node = next) + { + Temp scratch = scratch_begin(&arena, 1); + + // rjf: get next recursion + MD_NodeRec rec = md_node_rec_depth_first_pre(node, root); + next = rec.next; + + // rjf: sanitize string + String8 node_string_sanitized = node->string; + { + MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, node_string_sanitized); + B32 is_simple_string = (tokenize.tokens.count == 1 && (tokenize.tokens.v[0].flags & (MD_TokenFlag_Identifier| + MD_TokenFlag_Numeric))); + if(tokenize.tokens.count > 0 && !is_simple_string) + { + node_string_sanitized = str8f(scratch.arena, "\"%S\"", escaped_from_raw_str8(scratch.arena, node_string_sanitized)); + } + if(node_string_sanitized.size == 0 && node->first == &md_nil_node) + { + node_string_sanitized = s("\"\""); + } + } + + // rjf: turn off newline delimiting for implicit child sets + B32 next_delimit_with_newlines = delimit_with_newlines; + if(node->first != &md_nil_node && !(node->flags & (MD_NodeFlag_HasParenLeft|MD_NodeFlag_HasBracketLeft|MD_NodeFlag_HasBraceLeft))) + { + next_delimit_with_newlines = 0; + } + + // rjf: push node line + if(node_string_sanitized.size != 0) + { + str8_list_pushf(arena, &strings, "%.*s%S%S%S", delimit_with_newlines ? depth : 1, indentation, node_string_sanitized, + (rec.push_count != 0 && node_string_sanitized.size != 0) ? s(":") : s(""), + next_delimit_with_newlines ? s("\n") : s("")); + } + + // rjf: children -> open brace + if(rec.push_count != 0) + { + String8 opener_symbol = s(""); + if(node->flags & MD_NodeFlag_HasParenLeft) { opener_symbol = s("("); } + if(node->flags & MD_NodeFlag_HasBracketLeft) { opener_symbol = s("["); } + if(node->flags & MD_NodeFlag_HasBraceLeft) { opener_symbol = s("{"); } + if(opener_symbol.size != 0) + { + str8_list_pushf(arena, &strings, "%.*s%S\n", depth, indentation, opener_symbol); + } + } + + // rjf: descend + depth += rec.push_count; + delimit_with_newlines = next_delimit_with_newlines; + + // rjf: popping -> close braces + { + MD_Node *popped = node->parent; + for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1, popped = popped->parent) + { + String8 closer_symbol = s(""); + if(popped->flags & MD_NodeFlag_HasParenRight) { closer_symbol = s(")"); } + if(popped->flags & MD_NodeFlag_HasBracketRight) { closer_symbol = s("]"); } + if(popped->flags & MD_NodeFlag_HasBraceRight) { closer_symbol = s("}"); } + if(!delimit_with_newlines) + { + str8_list_push(arena, &strings, s("\n")); + delimit_with_newlines = 1; + } + else if(closer_symbol.size != 0) + { + str8_list_pushf(arena, &strings, "%.*s%S\n", (depth-1-pop_idx), indentation, closer_symbol); + } + } + } + + // rjf: ascend + depth -= rec.pop_count; + + scratch_end(scratch); + } + } + return strings; +} + +internal String8 +md_string_from_tree(Arena *arena, MD_Node *root) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List strings = md_string_list_from_tree(scratch.arena, root); + String8 string = str8_list_join(arena, &strings, 0); + scratch_end(scratch); + return string; +} + //////////////////////////////// //~ rjf: Node Pointer List Functions diff --git a/src/mdesk/mdesk.h b/src/mdesk/mdesk.h index e7a55473..d6c6e5fe 100644 --- a/src/mdesk/mdesk.h +++ b/src/mdesk/mdesk.h @@ -336,6 +336,8 @@ internal MD_ParseResult md_parse_from_text(Arena *arena, String8 filename, Strin //~ rjf: Tree -> Text Functions internal String8List md_debug_string_list_from_tree(Arena *arena, MD_Node *root); +internal String8List md_string_list_from_tree(Arena *arena, MD_Node *root); +internal String8 md_string_from_tree(Arena *arena, MD_Node *root); //////////////////////////////// //~ rjf: Node Pointer List Functions diff --git a/src/mule/mule_main.cpp b/src/mule/mule_main.cpp index c48283fe..8aa12f8b 100644 --- a/src/mule/mule_main.cpp +++ b/src/mule/mule_main.cpp @@ -700,12 +700,14 @@ type_coverage_eval_tests(void) int_vector.push_back(6); int_vector.push_back(7); +#if 0 std::unordered_map people = { {"Peter", 1}, {"Oliver", 2}, {"Jack", 3}, }; +#endif std::vector *pint_vector = &int_vector; std::vector &rint_vector = int_vector; @@ -718,9 +720,9 @@ type_coverage_eval_tests(void) SizedKind sized_kind = SizedKind_C; - variadic_params("foo", 123, 456); - int x = (int)(Anonymous_D); + + variadic_params("foo", 123, 456); } //////////////////////////////// @@ -3198,7 +3200,6 @@ mule_main(int argc, char** argv) mule_init(); - // NOTE(allen): Eval Tests type_coverage_eval_tests(); mutating_variables_eval_tests(); @@ -3231,9 +3232,6 @@ mule_main(int argc, char** argv) exception_filter_test(); - markup_tests(); - - // NOTE(allen): Stepping Tests control_flow_stepping_tests(); indirect_call_jump_stepping_tests(); @@ -3264,6 +3262,8 @@ mule_main(int argc, char** argv) interrupt_stepping_tests(); + markup_tests(); + exception_stepping_tests(); return(0); diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index fdd74a1c..6332cb8c 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[364] = +RD_VocabInfo rd_vocab_info_table[365] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -188,6 +188,7 @@ RD_VocabInfo rd_vocab_info_table[364] = {str8_lit_comp("step_into_line"), str8_lit_comp(""), str8_lit_comp("Step Into (Line)"), str8_lit_comp(""), RD_IconKind_StepInto}, {str8_lit_comp("step_over_line"), str8_lit_comp(""), str8_lit_comp("Step Over (Line)"), str8_lit_comp(""), RD_IconKind_StepOver}, {str8_lit_comp("step_out"), str8_lit_comp(""), str8_lit_comp("Step Out"), str8_lit_comp(""), RD_IconKind_StepOut}, +{str8_lit_comp("step_to_exit"), str8_lit_comp(""), str8_lit_comp("Step To Exit"), str8_lit_comp(""), RD_IconKind_StepOut}, {str8_lit_comp("halt"), str8_lit_comp(""), str8_lit_comp("Halt"), str8_lit_comp(""), RD_IconKind_Pause}, {str8_lit_comp("soft_halt_refresh"), str8_lit_comp(""), str8_lit_comp("Soft Halt Refresh"), str8_lit_comp(""), RD_IconKind_Refresh}, {str8_lit_comp("set_thread_ip"), str8_lit_comp(""), str8_lit_comp("Set Thread IP"), str8_lit_comp(""), RD_IconKind_Null}, @@ -473,7 +474,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("thread"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'call_stack':set}")}, }; -String8 rd_reg_slot_code_name_table[49] = +String8 rd_reg_slot_code_name_table[50] = { {0}, str8_lit_comp("machine"), @@ -520,13 +521,14 @@ str8_lit_comp("all_windows"), str8_lit_comp("non_graphical"), str8_lit_comp("prefer_new_tab"), str8_lit_comp("activate_with_single_click"), +str8_lit_comp("disable_addresses"), str8_lit_comp("dir2"), str8_lit_comp("string"), str8_lit_comp("cmd_name"), str8_lit_comp("wm_event"), }; -Rng1U64 rd_reg_slot_range_table[49] = +Rng1U64 rd_reg_slot_range_table[50] = { {0}, {OffsetOf(RD_Regs, machine), OffsetOf(RD_Regs, machine) + sizeof(D_Handle)}, @@ -573,13 +575,14 @@ Rng1U64 rd_reg_slot_range_table[49] = {OffsetOf(RD_Regs, non_graphical), OffsetOf(RD_Regs, non_graphical) + sizeof(B32)}, {OffsetOf(RD_Regs, prefer_new_tab), OffsetOf(RD_Regs, prefer_new_tab) + sizeof(B32)}, {OffsetOf(RD_Regs, activate_with_single_click), OffsetOf(RD_Regs, activate_with_single_click) + sizeof(B32)}, +{OffsetOf(RD_Regs, disable_addresses), OffsetOf(RD_Regs, disable_addresses) + sizeof(B32)}, {OffsetOf(RD_Regs, dir2), OffsetOf(RD_Regs, dir2) + sizeof(Dir2)}, {OffsetOf(RD_Regs, string), OffsetOf(RD_Regs, string) + sizeof(String8)}, {OffsetOf(RD_Regs, cmd_name), OffsetOf(RD_Regs, cmd_name) + sizeof(String8)}, {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[252] = +RD_CmdKindInfo rd_cmd_kind_info_table[253] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets"), str8_lit_comp(""), D_EntityKind_Null}}, @@ -593,6 +596,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[252] = { str8_lit_comp("step_into_line"), str8_lit_comp("Performs a step that goes into calls, at the source code line level."), str8_lit_comp("step,thread"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, { str8_lit_comp("step_over_line"), str8_lit_comp("Performs a step that skips calls, at the source code line level."), str8_lit_comp("step,thread"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, { str8_lit_comp("step_out"), str8_lit_comp("Runs to the first instruction after the current scope is exited."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, +{ str8_lit_comp("step_to_exit"), str8_lit_comp("Runs to the first encountered instruction which will exit the current scope."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, { str8_lit_comp("halt"), str8_lit_comp("Halts all attached processes."), str8_lit_comp("pause"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, { str8_lit_comp("soft_halt_refresh"), str8_lit_comp("Interrupts all attached processes to collect data, and then resumes them."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, { str8_lit_comp("set_thread_ip"), str8_lit_comp("Sets the specified thread's instruction pointer at the specified address."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1)|(RD_CmdKindFlag_ListInTextPt*0)|(RD_CmdKindFlag_ListInTextRng*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Vaddr, str8_lit_comp(""), str8_lit_comp(""), D_EntityKind_Null}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 32e79fdf..ef417015 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -53,6 +53,7 @@ RD_RegSlot_AllWindows, RD_RegSlot_NonGraphical, RD_RegSlot_PreferNewTab, RD_RegSlot_ActivateWithSingleClick, +RD_RegSlot_DisableAddresses, RD_RegSlot_Dir2, RD_RegSlot_String, RD_RegSlot_CmdName, @@ -74,6 +75,7 @@ RD_CmdKind_StepOverInst, RD_CmdKind_StepIntoLine, RD_CmdKind_StepOverLine, RD_CmdKind_StepOut, +RD_CmdKind_StepToExit, RD_CmdKind_Halt, RD_CmdKind_SoftHaltRefresh, RD_CmdKind_SetThreadIP, @@ -498,6 +500,7 @@ B32 all_windows; B32 non_graphical; B32 prefer_new_tab; B32 activate_with_single_click; +B32 disable_addresses; Dir2 dir2; String8 string; String8 cmd_name; @@ -598,6 +601,7 @@ Z(getting_started)\ .non_graphical = rd_regs()->non_graphical,\ .prefer_new_tab = rd_regs()->prefer_new_tab,\ .activate_with_single_click = rd_regs()->activate_with_single_click,\ +.disable_addresses = rd_regs()->disable_addresses,\ .dir2 = rd_regs()->dir2,\ .string = rd_regs()->string,\ .cmd_name = rd_regs()->cmd_name,\ @@ -606,10 +610,10 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[364]; +extern RD_VocabInfo rd_vocab_info_table[365]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; -extern String8 rd_reg_slot_code_name_table[49]; -extern Rng1U64 rd_reg_slot_range_table[49]; +extern String8 rd_reg_slot_code_name_table[50]; +extern Rng1U64 rd_reg_slot_range_table[50]; extern String8 rd_binding_version_remap_old_name_table[9]; extern String8 rd_binding_version_remap_new_name_table[9]; extern String8 rd_icon_kind_text_table[75]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 2fb9b4ef..0278a5e7 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -891,6 +891,7 @@ RD_RegTable: {B32 non_graphical NonGraphical } {B32 prefer_new_tab PreferNewTab } {B32 activate_with_single_click ActivateWithSingleClick } + {B32 disable_addresses DisableAddresses } {Dir2 dir2 Dir2 } {String8 string String } {String8 cmd_name CmdName } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index df12f7be..ce778538 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -10302,6 +10302,7 @@ rd_init(CmdLine *cmdln) rd_state->arena = arena; rd_state->quit_after_success = (cmd_line_has_flag(cmdln, str8_lit("quit_after_success")) || cmd_line_has_flag(cmdln, str8_lit("q"))); + rd_state->override_focus_on_stop = (cmd_line_has_flag(cmdln, s("no_focus_on_stop"))); rd_state->user_path_arena = arena_alloc(); rd_state->project_path_arena = arena_alloc(); rd_state->theme_path_arena = arena_alloc(); @@ -12584,6 +12585,7 @@ rd_frame(void) { String8 msg = rd_regs()->string; String8List msg_parts = str8_split(scratch.arena, msg, (U8 *)" ", 1, 0); + str8_list_push_front(scratch.arena, &msg_parts, s("ipc_command")); CmdLine msg_cmd_line = cmd_line_from_string_list(scratch.arena, msg_parts); String8 cmd_kind_name = str8_list_first(&msg_cmd_line.inputs); RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_kind_name); @@ -12609,8 +12611,8 @@ rd_frame(void) primary_args_string = str8_list_join(scratch.arena, &primary_args_strings, &(StringJoin){.sep = str8_lit(" ")}); } rd_regs_fill_slot_from_string(cmd_kind_info->query.slot, cmd_kind_info->query.expr, primary_args_string); + rd_regs()->disable_addresses = 1; rd_push_cmd(cmd_kind_name, rd_regs()); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "ack:{cmd:%S}\n", cmd_kind_name); } else { @@ -12622,15 +12624,32 @@ rd_frame(void) str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "state:\n{\n"); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " running: %i\n", d_ctrl_targets_running()); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " run_gen: %I64u\n", d_run_gen()); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip: 0x%I64x\n", d_ctrl_last_stop_event().rip_vaddr); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stop_count: %I64u\n", d_stop_count()); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip: 0x%I64x\n", d_ctrl_last_stop_event().rip_vaddr); + { + Access *access = access_open(); + D_Event evt = d_ctrl_last_stop_event(); + U64 vaddr = evt.rip_vaddr; + D_Entity *thread = d_entity_from_handle(rd_base_regs()->thread); + D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); + D_Entity *module = d_module_from_process_vaddr(process, vaddr); + U64 voff = d_voff_from_vaddr(module, vaddr); + DI_Key dbgi_key = d_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + RDI_Symbol *procedure = rdi_procedure_from_voff(rdi, voff); + String8 name = fully_qualified_str8_from_rdi_symbol(scratch.arena, rdi, procedure); + name = escaped_from_raw_str8(scratch.arena, name); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_voff: 0x%I64x\n", voff); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_voff_symbol: \"%S\"\n", name); + access_close(access); + } { D_Event evt = d_ctrl_last_stop_event(); DR_FStrList explanation_fstrs = rd_stop_explanation_fstrs_from_ctrl_event(scratch.arena, &evt); String8 explanation_string = dr_string_from_fstrs(scratch.arena, &explanation_fstrs); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stop_event:\n {\n"); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " arch: %S\n", string_from_arch(evt.arch)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range_min: 0x%I64x\n", evt.vaddr_rng.min); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range_max: 0x%I64x\n", evt.vaddr_rng.max); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range: [0x%I64x, 0x%I64x)\n", evt.vaddr_rng.min, evt.vaddr_rng.max); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_vaddr: 0x%I64x\n", evt.rip_vaddr); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stack_base: 0x%I64x\n", evt.stack_base); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " tls_root: 0x%I64x\n", evt.tls_root); @@ -12643,6 +12662,25 @@ rd_frame(void) str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " explanation: \"%S\"\n", escaped_from_raw_str8(scratch.arena, explanation_string)); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); } + { + Access *access = access_open(); + D_Event evt = d_ctrl_last_stop_event(); + U64 vaddr = evt.rip_vaddr; + D_Entity *thread = d_entity_from_handle(rd_base_regs()->thread); + D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); + D_Entity *module = d_module_from_process_vaddr(process, vaddr); + U64 voff = d_voff_from_vaddr(module, vaddr); + DI_Key dbgi_key = d_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + E_String2NumMap *locals_map = e_push_locals_map_from_rdi_voff(scratch.arena, rdi, voff); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " locals:\n {\n"); + for(E_String2NumMapNode *n = locals_map->first; n != 0; n = n->order_next) + { + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " %S\n", n->string); + } + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + access_close(access); + } { D_Event evt = d_ctrl_last_stop_event(); U64 vaddr = evt.rip_vaddr; @@ -12656,12 +12694,12 @@ rd_frame(void) for EachNode(n, D_LineNode, lines.first) { D_Line line = n->v; + String8 line_file_path_relative = path_relative_dst_from_absolute_dst_src(scratch.arena, line.file_path, module->string); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " {\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " file_path: \"%S\"\n", line.file_path); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " file_path: \"%S\"\n", line_file_path_relative); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " line_num: %I64d\n", line.pt.line); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " column_num: %I64d\n", line.pt.column); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " voff_range_min: 0x%I64x\n", line.voff_range.min); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " voff_range_max: 0x%I64x\n", line.voff_range.max); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " voff_range: [0x%I64x, 0x%I64x)\n", line.voff_range.min, line.voff_range.max); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); @@ -12688,8 +12726,7 @@ rd_frame(void) D_Entity *module = modules.v[idx]; str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " {\n"); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " name: \"%S\"\n", escaped_from_raw_str8(scratch.arena, module->string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range_min: 0x%I64x\n", module->vaddr_range.min); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range_max: 0x%I64x\n", module->vaddr_range.max); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range: [0x%I64x, 0x%I64x)\n", module->vaddr_range.min, module->vaddr_range.max); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); @@ -12698,6 +12735,12 @@ rd_frame(void) }break; case RD_CmdKind_Eval: { + // TODO(rjf): to use this for evaluation testing, we need some way to temporarily + // call the eval system with ctrl-thread-like evaluation rules (e.g. waiting for + // memory, never accepting stale results, not looking into caches, etc.). currently + // we'd only be able to do that by setting up an entirely new evaluation context, + // which is currently a ton of code & laborious & duplicative, so we need to tighten + // that up first. @eval_regressions String8 expr = rd_regs()->expr; E_Eval eval = e_eval_from_string(expr); E_Eval type_eval = e_eval_from_stringf("typeof(%S)", expr); @@ -12706,6 +12749,10 @@ rd_frame(void) .flags = EV_StringFlag_ReadOnlyDisplayRules | rd_state->eval_viz_base_string_flags, .radix = 10, }; + if(rd_regs()->disable_addresses) + { + string_params.flags |= EV_StringFlag_DisableAddresses; + } String8 value_string = ev_value_string_from_eval(scratch.arena, &string_params, eval, 512); String8 type_value_string = ev_value_string_from_eval(scratch.arena, &string_params, type_eval, 512); String8List msgs_strings = {0}; @@ -16903,7 +16950,7 @@ rd_frame(void) } // rjf: focus window if none focused, and if we have a thread to snap to - if(need_refocus && (selected_thread != &d_entity_nil || thread != &d_entity_nil)) + if(!rd_state->override_focus_on_stop && need_refocus && (selected_thread != &d_entity_nil || thread != &d_entity_nil)) { B32 any_window_is_focused = 0; for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next) diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index e278f87e..60ef35e1 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -388,6 +388,7 @@ struct RD_State Arena *arena; B32 quit; B32 quit_after_success; + B32 override_focus_on_stop; S32 frame_depth; U64 frame_eval_memread_endt_us; @@ -444,7 +445,7 @@ struct RD_State Access *frame_access; String8 last_window_title; - // rjf: evaluation cache + // rjf: evaluation caches E_Cache *eval_cache; // rjf: ambiguous path table (constructed from-scratch each frame) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index d736c3f8..c0f994cd 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -426,7 +426,7 @@ struct IPCInfo //~ rjf: Globals //- rjf: IPC resources -#define IPC_SHARED_MEMORY_BUFFER_SIZE MB(4) +#define IPC_SHARED_MEMORY_BUFFER_SIZE KB(64) StaticAssert(IPC_SHARED_MEMORY_BUFFER_SIZE > sizeof(IPCInfo), ipc_buffer_size_requirement); global Semaphore ipc_sender2main_signal_semaphore = {0}; global Semaphore ipc_sender2main_lock_semaphore = {0}; @@ -434,7 +434,7 @@ global U8 *ipc_sender2main_shared_memory_base = 0; global Semaphore ipc_main2sender_signal_semaphore = {0}; global Semaphore ipc_main2sender_lock_semaphore = {0}; global U8 *ipc_main2sender_shared_memory_base = 0; -global U8 ipc_s2m_ring_buffer[MB(4)] = {0}; +global U8 ipc_s2m_ring_buffer[KB(64)] = {0}; global U64 ipc_s2m_ring_write_pos = 0; global U64 ipc_s2m_ring_read_pos = 0; global Mutex ipc_s2m_ring_mutex = {0}; @@ -666,6 +666,7 @@ entry_point(CmdLine *cmd_line) if(msg.size != 0) { log_infof("ipc_msg: \"%S\"", msg); + String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); RD_WindowState *dst_ws = rd_state->first_window_state; for(RD_WindowState *ws = dst_ws; ws != &rd_nil_window_state; ws = ws->order_next) { @@ -690,7 +691,10 @@ entry_point(CmdLine *cmd_line) rd_regs()->view = panel_tree.focused->selected_tab->id; scratch_end(scratch); } - rd_cmd(RD_CmdKind_RunExternalDriverTextCommand, .string = msg); + for EachNode(n, String8Node, cmd_parts_of_msg.first) + { + rd_cmd(RD_CmdKind_RunExternalDriverTextCommand, .string = n->string); + } rd_request_frame(); } } diff --git a/src/raddbg/tests/raddbg_tests.c b/src/raddbg/tests/raddbg_tests.c index 2a3a607d..8f9c7bf7 100644 --- a/src/raddbg/tests/raddbg_tests.c +++ b/src/raddbg/tests/raddbg_tests.c @@ -1,6 +1,16 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: Debugger Testing IPC Driving Helpers + +internal String8 +rd_test__mule_main_path(Arena *arena) +{ + String8 mule_main_exe_path = str8f(arena, "%S/%S%S", get_process_info()->binary_path, s("mule_main"), program_ext_postfix_from_os(OperatingSystem_CURRENT)); + return mule_main_exe_path; +} + internal String8 rd_test__raddbg_path(Arena *arena, CmdLine *cmdline) { @@ -14,9 +24,9 @@ rd_test__open_debugger(CmdLine *cmdline, String8 test_artifacts_path, String8 ta { Temp scratch = scratch_begin(0, 0); String8 raddbg_path = rd_test__raddbg_path(scratch.arena, cmdline); - String8 user_file_path = str8f(scratch.arena, "%S/test.raddbg_user", test_artifacts_path);\ + String8 user_file_path = str8f(scratch.arena, "%S/test.raddbg_user", test_artifacts_path); delete_file_at_path(user_file_path); - Process process = launch_cmd_linef("%S --gen_crash_dump --user:test.raddbg_user --logs:logs %S", raddbg_path, target_cmd_line); + Process process = launch_cmd_linef("%S --gen_crash_dump --no_focus_on_stop --user:%S --logs:logs %S", raddbg_path, user_file_path, target_cmd_line); scratch_end(scratch); return process; } @@ -41,64 +51,249 @@ rd_test__ipc_cmd(Arena *arena, CmdLine *cmdline, String8 test_artifacts_path, U6 return response; } -Test(stepping) +//////////////////////////////// +//~ rjf: Exemplar Test Helpers + +internal void +rd_test__exemplar_test_finish(Arena *arena, String8List test_log_strings, String8 test_exemplars_path, String8 test_artifacts_path, TestResult *result_out, String8List *test_out) { Temp scratch = scratch_begin(&arena, 1); - String8 mule_main_exe_path = str8f(scratch.arena, "%S/%S%S", get_process_info()->binary_path, s("mule_main"), program_ext_postfix_from_os(OperatingSystem_CURRENT)); - struct + + // rjf: combine all current test log output; write to 'current' data + String8 test_log_current = str8_list_join(scratch.arena, &test_log_strings, 0); + String8 test_log_current_path = str8f(scratch.arena, "%S/current", test_artifacts_path); + write_data_to_file_path(test_log_current_path, test_log_current); + + // rjf: load test log exemplar + make_directory(test_exemplars_path); + String8 exemplar_path = str8f(scratch.arena, "%S/exemplar", test_exemplars_path); + String8 exemplar_data = data_from_file_path(scratch.arena, exemplar_path); + + // rjf: if exemplar data is empty -> just save our output as the new exemplar + if(exemplar_data.size == 0) { - String8 cmdline; - String8 start_symbol; - B32 step_over_on_even; + copy_file_path(exemplar_path, test_log_current_path); } - test_cases[] = + + // rjf: if we have exemplar data, then we need to check that it matches + if(exemplar_data.size != 0) { - { mule_main_exe_path, s("mule_main"), 0 }, - { mule_main_exe_path, s("mule_main"), 1 }, - { mule_main_exe_path, s("control_flow_stepping_tests"), 0 }, - { mule_main_exe_path, s("control_flow_stepping_tests"), 1 }, - }; - for EachElement(test_case_idx, test_cases) - { - String8 test_cmdline = test_cases[test_case_idx].cmdline; - String8 start_symbol = test_cases[test_case_idx].start_symbol; - B32 step_over_on_even = test_cases[test_case_idx].step_over_on_even; - Process debugger = rd_test__open_debugger(cmdline, test_artifacts_path, test_cmdline); - U64 debugger_pid = pid_from_process(debugger); - String8 next_step_cmd = {0}; - rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, str8f(scratch.arena, "run_to_name %S", start_symbol)); - for(U64 cmd_idx = 0;; cmd_idx += 1) + B32 current_matches_exemplar = str8_match(test_log_current, exemplar_data, 0); + if(!current_matches_exemplar) { - String8 step_cmd = s("step_into"); - if((cmd_idx % 2 == 0 && step_over_on_even) || (cmd_idx % 2 == 1 && !step_over_on_even)) + String8 diff_cmd = str8f(scratch.arena, "diff %S %S", + path_normalized_from_string(scratch.arena, exemplar_path), + path_normalized_from_string(scratch.arena, test_log_current_path)); + test_outf("Current log does not match exemplar; run `%S`\n", diff_cmd); + } + TestCheck(current_matches_exemplar); + } + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Test Helpers + +internal void +rd_test__stepping_regressions(Arena *arena, CmdLine *cmdline, String8 test_exemplars_path, String8 test_artifacts_path, TestResult *result_out, String8List *test_out, String8 target_cmdline, String8 start_symbol, B32 step_over_on_even) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List test_log_strings = {0}; + + // rjf: start debugger + Process debugger = rd_test__open_debugger(cmdline, test_artifacts_path, target_cmdline); + U64 debugger_pid = pid_from_process(debugger); + + // rjf: step & gather info + { + U64 last_stop_count = max_U64; + B32 ready_for_next_operation = 0; + String8 next_step_cmd = {0}; + for(U64 cmd_idx = 0;;) + { + Temp scratch2 = scratch_begin(&scratch.arena, 1); + + // rjf: need next operation -> do op + if(ready_for_next_operation) { - step_cmd = s("step_over"); + ready_for_next_operation = 0; + String8 step_cmd = s("step_into"); + if(cmd_idx == 0) + { + step_cmd = str8f(scratch2.arena, "run_to_name %S", start_symbol); + } + else if((cmd_idx % 2 == 0 && step_over_on_even) || (cmd_idx % 2 == 1 && !step_over_on_even)) + { + step_cmd = s("step_over"); + } + if(next_step_cmd.size != 0) + { + step_cmd = next_step_cmd; + MemoryZeroStruct(&next_step_cmd); + } + String8 step_response = rd_test__ipc_cmd(scratch2.arena, cmdline, test_artifacts_path, debugger_pid, step_cmd); + (void)step_response; + cmd_idx += 1; } - if(next_step_cmd.size != 0) - { - step_cmd = next_step_cmd; - MemoryZeroStruct(&next_step_cmd); - } - String8 step_response = rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, step_cmd); - String8 state_response = rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, s("state")); - MD_Node *state_response_tree = md_tree_from_string(scratch.arena, state_response); + + // rjf: query state, parse + String8 state_response = rd_test__ipc_cmd(scratch2.arena, cmdline, test_artifacts_path, debugger_pid, s("state")); + MD_Node *state_response_tree = md_tree_from_string(scratch2.arena, state_response); MD_Node *state = md_child_from_string(state_response_tree, s("state"), 0); - U64 ip_vaddr = 0; - try_u64_from_str8_c_rules(md_child_from_string(state, s("ip"), 0)->first->string, &ip_vaddr); - if(ip_vaddr != 0 && md_child_from_string(state, s("lines"), 0)->first == &md_nil_node) + + // rjf: we are only ready for next step command when the stop count has changed { - next_step_cmd = s("step_out"); + U64 stop_count = 0; + try_u64_from_str8_c_rules(md_child_from_string(state, s("stop_count"), 0)->first->string, &stop_count); + if(stop_count != last_stop_count) + { + ready_for_next_operation = 1; + last_stop_count = stop_count; + } } + + // rjf: if we are ready for the next operation, then accumulate deterministic log info + if(ready_for_next_operation) + { + MD_Node *lines = md_child_from_string(state, s("lines"), 0); + MD_Node *ip_voff = md_child_from_string(state, s("ip_voff"), 0); + MD_Node *ip_voff_symbol = md_child_from_string(state, s("ip_voff_symbol"), 0); + String8 lines_dump = md_string_from_tree(scratch.arena, lines); + String8 ip_voff_dump = md_string_from_tree(scratch.arena, ip_voff); + String8 ip_voff_symbol_dump = md_string_from_tree(scratch.arena, ip_voff_symbol); + str8_list_push(scratch.arena, &test_log_strings, lines_dump); + str8_list_push(scratch.arena, &test_log_strings, ip_voff_dump); + str8_list_push(scratch.arena, &test_log_strings, ip_voff_symbol_dump); + } + + // rjf: are we without line info? -> step out + if(ready_for_next_operation) + { + U64 ip_vaddr = 0; + try_u64_from_str8_c_rules(md_child_from_string(state, s("ip"), 0)->first->string, &ip_vaddr); + if(ip_vaddr != 0 && md_child_from_string(state, s("lines"), 0)->first == &md_nil_node) + { + next_step_cmd = s("step_out"); + } + } + + scratch_end(scratch2); if(cmd_idx >= 100) { break; } } - rd_test__close_debugger(debugger); } + + // rjf: end exemplar test + rd_test__exemplar_test_finish(arena, test_log_strings, test_exemplars_path, test_artifacts_path, result_out, test_out); + + // rjf: close debugger + rd_test__close_debugger(debugger); + scratch_end(scratch); } +internal void +rd_test__eval_regressions(Arena *arena, CmdLine *cmdline, String8 test_exemplars_path, String8 test_artifacts_path, TestResult *result_out, String8List *test_out, String8 target_cmdline, String8 target_line) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List test_log_strings = {0}; + + // rjf: start debugger + Process debugger = rd_test__open_debugger(cmdline, test_artifacts_path, target_cmdline); + U64 debugger_pid = pid_from_process(debugger); + + // rjf: query state + U64 initial_stop_count = 0; + { + String8 response = rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, s("state")); + MD_Node *state = md_child_from_string(md_tree_from_string(scratch.arena, response), s("state"), 0); + MD_Node *stop_count = md_child_from_string(state, s("stop_count"), 0); + try_u64_from_str8_c_rules(stop_count->first->string, &initial_stop_count); + } + + // rjf: run to target line + rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, str8f(scratch.arena, "run_to_line %S", target_line)); + + // rjf: wait for stop, collect locals + String8List locals = {0}; + for(;;) + { + Temp scratch2 = scratch_begin(&scratch.arena, 1); + String8 response = rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, s("state")); + MD_Node *state = md_child_from_string(md_tree_from_string(scratch.arena, response), s("state"), 0); + MD_Node *stop_count = md_child_from_string(state, s("stop_count"), 0); + U64 new_stop_count = 0; + try_u64_from_str8_c_rules(stop_count->first->string, &new_stop_count); + scratch_end(scratch2); + if(new_stop_count != initial_stop_count) + { + MD_Node *locals_root = md_child_from_string(state, s("locals"), 0); + for MD_EachNode(c, locals_root->first) + { + str8_list_push(scratch.arena, &locals, c->string); + } + break; + } + } + + // rjf: evaluate all locals + String8List eval_cmd_strings = {0}; + for EachNode(n, String8Node, locals.first) + { + str8_list_pushf(scratch.arena, &eval_cmd_strings, "eval %S", n->string); + } + StringJoin join = {.sep = s(" ; ")}; + String8 eval_msg = str8_list_join(scratch.arena, &eval_cmd_strings, &join); + String8 response = rd_test__ipc_cmd(scratch.arena, cmdline, test_artifacts_path, debugger_pid, eval_msg); + MD_Node *response_tree = md_tree_from_string(scratch.arena, response); + for MD_EachNode(n, response_tree->first) + { + if(str8_match(n->string, s("eval"), 0)) + { + String8 eval_dump = md_string_from_tree(scratch.arena, n); + str8_list_push(scratch.arena, &test_log_strings, eval_dump); + } + } + + // rjf: end exemplar test + rd_test__exemplar_test_finish(arena, test_log_strings, test_exemplars_path, test_artifacts_path, result_out, test_out); + + // rjf: close debugger + rd_test__close_debugger(debugger); + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Tests + +#if 0 +Test(mule_main_step_regressions_0) +{ + String8 exe_path = {0}; + rd_test__stepping_regressions(arena, cmdline, test_exemplars_path, test_artifacts_path, result_out, test_out, rd_test__mule_main_path(arena), s("mule_main"), 0); +} + +Test(mule_main_step_regressions_1) +{ + rd_test__stepping_regressions(arena, cmdline, test_exemplars_path, test_artifacts_path, result_out, test_out, rd_test__mule_main_path(arena), s("mule_main"), 1); +} + +Test(control_flow_step_regressions) +{ + rd_test__stepping_regressions(arena, cmdline, test_exemplars_path, test_artifacts_path, result_out, test_out, rd_test__mule_main_path(arena), s("mule_main"), 0); +} + +SkippedTest(type_coverage_eval_regressions) +{ + // TODO(rjf): see @eval_regressions + rd_test__eval_regressions(arena, cmdline, test_exemplars_path, test_artifacts_path, result_out, test_out, rd_test__mule_main_path(arena), s("C:/devel/raddebugger/src/mule/mule_main.cpp:723")); +} +#endif + #if 0 //////////////////////////////// // IPC Controller diff --git a/src/torture/torture.c b/src/torture/torture.c index d1949fd1..57769d19 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -4,6 +4,7 @@ // command line global String8 g_stdout_file_name = str8_lit_comp("torture.out"); global String8 g_wdir; +global String8 g_exemplar_dir; global String8 g_out = str8_lit_comp("torture_artifacts"); global B32 g_verbose; global B32 g_redirect_stdout = 1; @@ -168,7 +169,7 @@ t_run_caller(void *raw_ctx) if (ctx->test->skip) { ctx->result.status = TestStatus_Skip; } else { - ctx->test->test_fn(scratch.arena, ctx->cmdline, g_wdir, &ctx->result, &test_out); + ctx->test->test_fn(scratch.arena, ctx->cmdline, g_exemplar_dir, g_wdir, &ctx->result, &test_out); } if (ctx->result.status == TestStatus_Fail || ctx->result.status == TestStatus_Crash) { @@ -1273,6 +1274,13 @@ t_entry_point(CmdLine *cmdline) fprintf(stdout, " %.*s ", (int)dots_count, dots); fflush(stdout); + // rjf: find exemplar data directory + { + String8 binary_dir_path = get_process_info()->binary_path; + String8 root_dir_path = str8_chop_last_slash(binary_dir_path); + g_exemplar_dir = str8f(scratch.arena, "%S/data/test_exemplars/%S", root_dir_path, test->label); + } + // setup output directory g_wdir = str8f(scratch.arena, "%S/%S", g_out, test->label); g_wdir = full_path_from_path(scratch.arena, g_wdir); diff --git a/src/x64/disasm/x64_disasm.c b/src/x64/disasm/x64_disasm.c index f60eef86..2f71876f 100644 --- a/src/x64/disasm/x64_disasm.c +++ b/src/x64/disasm/x64_disasm.c @@ -7,7 +7,7 @@ #endif internal DASM_Inst -x64_dasm_inst_from_code(Arena *arena, U64 vaddr, String8 code, DASM_Syntax syntax) +x64_dasm_inst_from_code(Arena *arena, U64 vaddr, String8 code, DASM_Syntax syntax) { DASM_Inst inst = {0};