From 8564884d76d908c322510d311c92fb3aef708123 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 26 May 2026 10:29:53 -0700 Subject: [PATCH] raddbg: stepping regression tests --- project.raddbg | 5 +- src/base/base_processes.h | 1 + src/base/base_strings.c | 15 +++++ src/base/base_strings.h | 1 + src/base/base_test.h | 2 +- src/linux/base/linux_base.c | 7 +++ src/raddbg/raddbg_core.c | 29 ++++++++-- src/raddbg/tests/raddbg_tests.c | 97 ++++++++++++++++++++++++++++++++- src/torture/torture.c | 8 +-- src/torture/torture.h | 7 ++- src/win32/base/win32_base.c | 8 +++ 11 files changed, 162 insertions(+), 18 deletions(-) diff --git a/project.raddbg b/project.raddbg index 9d5d96f4..eccd0b71 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: build arguments: "--user:raddbg_test.user --project:raddbg_test.project" debug_subprocesses: 0 - enabled: 1 } target: { @@ -41,5 +40,7 @@ target: target: { executable: "build/torture.exe" - working_directory: "build/" + working_directory: build + enabled: 1 + arguments: "raddbg/* --gui" } diff --git a/src/base/base_processes.h b/src/base/base_processes.h index c1250fc1..78fc7969 100644 --- a/src/base/base_processes.h +++ b/src/base/base_processes.h @@ -82,6 +82,7 @@ internal U32 get_process_start_time_unix(void); //~ rjf: @per_os_impl Child Processes internal Process process_launch(ProcessLaunchParams *params); +internal U64 pid_from_process(Process process); internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out); internal void process_detach(Process process); internal B32 process_kill(Process process); diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 45c8a7a3..27af8e7e 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -1491,6 +1491,21 @@ program_data_folder_prefix_from_os(OperatingSystem os) return result; } +internal String8 +program_ext_postfix_from_os(OperatingSystem os) +{ + String8 result = {0}; + switch(os) + { + default:{}break; + case OperatingSystem_Windows: + { + result = s(".exe"); + }break; + } + return result; +} + internal String8 str8_chop_last_slash(String8 string) { diff --git a/src/base/base_strings.h b/src/base/base_strings.h index cbe9ad53..4d26ce59 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -324,6 +324,7 @@ g_path_style_map[] = }; internal String8 program_data_folder_prefix_from_os(OperatingSystem os); +internal String8 program_ext_postfix_from_os(OperatingSystem os); internal String8 str8_chop_last_slash(String8 string); internal String8 str8_skip_last_slash(String8 string); diff --git a/src/base/base_test.h b/src/base/base_test.h index c988062c..d9350812 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, TestResult *result_out, String8List *test_out) +#define TEST_FUNCTION_SIG(name) void name(Arena *arena, CmdLine *cmdline, 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); diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index 0dc09903..aaaf71bf 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -1299,6 +1299,13 @@ process_launch(ProcessLaunchParams *params) return handle; } +internal U64 +pid_from_process(Process process) +{ + U64 result = process.u64[0]; + return result; +} + internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out) { diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 5a098328..df12f7be 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -12382,12 +12382,6 @@ rd_frame(void) // rjf: request frame rd_request_frame(); - // rjf: record 'ack' for this command - if(kind != RD_CmdKind_Null) - { - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "ack:{cmd:%S}\n", cmd->name); - } - // rjf: process command CFG_Node *cfg = &cfg_nil_node; String8 dst_path = {0}; @@ -12649,6 +12643,29 @@ 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"); } + { + 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); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, voff); + str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " lines:\n {\n"); + for EachNode(n, D_LineNode, lines.first) + { + D_Line line = n->v; + 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, " 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, " }\n"); + } + 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, " threads:\n {\n"); D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); diff --git a/src/raddbg/tests/raddbg_tests.c b/src/raddbg/tests/raddbg_tests.c index 0c1ab525..2a3a607d 100644 --- a/src/raddbg/tests/raddbg_tests.c +++ b/src/raddbg/tests/raddbg_tests.c @@ -1,9 +1,102 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -Test(stepping_regressions) +internal String8 +rd_test__raddbg_path(Arena *arena, CmdLine *cmdline) { - + String8 raddbg_exe_name = cmd_line_has_flag(cmdline, s("gui")) ? s("raddbg") : s("raddbg_non_graphical"); + String8 raddbg_exe_filename = str8f(arena, "%S/%S%S", get_process_info()->binary_path, raddbg_exe_name, program_ext_postfix_from_os(OperatingSystem_CURRENT)); + return raddbg_exe_filename; +} + +internal Process +rd_test__open_debugger(CmdLine *cmdline, String8 test_artifacts_path, String8 target_cmd_line) +{ + 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);\ + 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); + scratch_end(scratch); + return process; +} + +internal void +rd_test__close_debugger(Process process) +{ + process_kill(process); +} + +internal String8 +rd_test__ipc_cmd(Arena *arena, CmdLine *cmdline, String8 test_artifacts_path, U64 debugger_pid, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 raddbg_path = rd_test__raddbg_path(scratch.arena, cmdline); + String8 stdout_path = str8f(scratch.arena, "%S/ipc_output", test_artifacts_path); + Process process = launch_cmd_linef("%S --ipc --pid:%I64u %S > %S", raddbg_path, debugger_pid, string, stdout_path); + process_join(process, max_U64, 0); + String8 response = data_from_file_path(arena, stdout_path); + delete_file_at_path(stdout_path); + scratch_end(scratch); + return response; +} + +Test(stepping) +{ + 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 + { + String8 cmdline; + String8 start_symbol; + B32 step_over_on_even; + } + test_cases[] = + { + { 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) + { + String8 step_cmd = s("step_into"); + 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(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); + 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) + { + next_step_cmd = s("step_out"); + } + if(cmd_idx >= 100) + { + break; + } + } + rd_test__close_debugger(debugger); + } + scratch_end(scratch); } #if 0 diff --git a/src/torture/torture.c b/src/torture/torture.c index e4d75236..d1949fd1 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -168,7 +168,7 @@ t_run_caller(void *raw_ctx) if (ctx->test->skip) { ctx->result.status = TestStatus_Skip; } else { - ctx->test->test_fn(scratch.arena, &ctx->result, &test_out); + ctx->test->test_fn(scratch.arena, ctx->cmdline, g_wdir, &ctx->result, &test_out); } if (ctx->result.status == TestStatus_Fail || ctx->result.status == TestStatus_Crash) { @@ -187,9 +187,9 @@ t_run_caller(void *raw_ctx) } internal TestResult -t_run(TestInfo *test, String8 user_data) +t_run(CmdLine *cmdline, TestInfo *test, String8 user_data) { - T_RunCtx ctx = { .test = test, .user_data = user_data, .result.status = TestStatus_Fail }; + T_RunCtx ctx = { .test = test, .cmdline = cmdline, .user_data = user_data, .result.status = TestStatus_Fail }; t_run_caller(&ctx); if (ctx.result.status == TestStatus_Fail || ctx.result.status == TestStatus_Crash) { @@ -1290,7 +1290,7 @@ t_entry_point(CmdLine *cmdline) // run test U64 run_start_time = now_time_us(); - TestResult result = t_run(test, str8_zero()); + TestResult result = t_run(cmdline, test, str8_zero()); U64 run_end_time = now_time_us(); // update diff --git a/src/torture/torture.h b/src/torture/torture.h index 22e7a5c3..97cfd597 100644 --- a/src/torture/torture.h +++ b/src/torture/torture.h @@ -13,8 +13,9 @@ typedef struct { - TestInfo *test; - String8 user_data; + TestInfo *test; + CmdLine *cmdline; + String8 user_data; TestResult result; } T_RunCtx; @@ -47,7 +48,7 @@ internal String8 t_make_file_path(Arena *arena, String8 name); // test runner internal void t_run_caller(void *raw_ctx); internal void t_run_fail_handler(void *raw_ctx); -internal TestResult t_run(TestInfo *test, String8 user_data); +internal TestResult t_run(CmdLine *cmdline, TestInfo *test, String8 user_data); // tools internal String8 t_radbin_path(void); diff --git a/src/win32/base/win32_base.c b/src/win32/base/win32_base.c index a7bf6c0f..1506a3e7 100644 --- a/src/win32/base/win32_base.c +++ b/src/win32/base/win32_base.c @@ -1421,6 +1421,14 @@ process_launch(ProcessLaunchParams *params) return result; } +internal U64 +pid_from_process(Process process) +{ + HANDLE process_handle = (HANDLE)process.u64[0]; + U64 result = (U64)GetProcessId(process_handle); + return result; +} + internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out) {