From c7f7e154342052ef542c0c27c08fa476250d6922 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 24 Feb 2026 18:00:48 -0800 Subject: [PATCH] WIP DWARF line table conversion tester --- src/radbin/radbin.c | 114 +++++++++++++++++++++++++++++++-- src/rdi/rdi_local.c | 18 ++++++ src/rdi/rdi_local.h | 1 + src/torture/torture.c | 131 ++++++++++++++++++++++++++++---------- src/torture/torture_d2r.c | 58 ++++++++++++++++- 5 files changed, 282 insertions(+), 40 deletions(-) diff --git a/src/radbin/radbin.c b/src/radbin/radbin.c index 99bcd7be..2711b9ae 100644 --- a/src/radbin/radbin.c +++ b/src/radbin/radbin.c @@ -417,6 +417,7 @@ rb_thread_entry_point(void *p) OutputKind_RDI, OutputKind_Dump, OutputKind_Breakpad, + OutputKind_VOff2Line, OutputKind_COUNT } OutputKind; @@ -427,10 +428,11 @@ rb_thread_entry_point(void *p) } output_kind_info[] = { - {str8_lit_comp(""), str8_lit_comp("")}, - {str8_lit_comp("rdi"), str8_lit_comp("RAD Debug Info (.rdi) Conversion")}, - {str8_lit_comp("dump"), str8_lit_comp("Textual Dumping")}, - {str8_lit_comp("breakpad"), str8_lit_comp("Breakpad Debug Info Conversion")}, + {str8_lit_comp(""), str8_lit_comp("")}, + {str8_lit_comp("rdi"), str8_lit_comp("RAD Debug Info (.rdi) Conversion")}, + {str8_lit_comp("dump"), str8_lit_comp("Textual Dumping")}, + {str8_lit_comp("breakpad"), str8_lit_comp("Breakpad Debug Info Conversion")}, + {str8_lit_comp("voff2line"), str8_lit_comp("Map virtual offset to a source line")}, }; OutputKind output_kind = OutputKind_Null; String8 output_path = cmd_line_string(cmdline, str8_lit("out")); @@ -533,6 +535,8 @@ rb_thread_entry_point(void *p) fprintf(stderr, "--breakpad Specifies that the utility should convert debug information\n"); fprintf(stderr, " data to the textual Breakpad format.\n\n"); + + fprintf(stderr, "--VOff2Line Specifies that the utility should map virtual offset to source line.\n\n"); fprintf(stderr, "--out: Specifies the path to which output data should be written. If\n"); fprintf(stderr, " not specified, the utility will choose a fallback. If dumping\n"); @@ -587,7 +591,7 @@ rb_thread_entry_point(void *p) { fprintf(stderr, "All input files specified on the command line will be dumped. The following\n"); fprintf(stderr, "formats are currently supported: PE, COFF, RDI, and ELF\n\n"); - } + }break; } //- rjf: unpack subset flags @@ -1276,6 +1280,106 @@ rb_thread_entry_point(void *p) } } }break; + + case OutputKind_VOff2Line: + { + if(lane_idx() != 0) { break; } + + if(cmdline->inputs.node_count == 0) + { + fprintf(stderr, "ARGUMENTS\n\n"); + fprintf(stderr, "--voff:OFFSET Map specified virtual offset to a source line.\n"); + break; + } + + if(cmdline->inputs.node_count > 1) + { + fprintf(stderr, "ERROR: too many input files!\n"); + break; + } + + if(!cmd_line_has_argument(cmdline, str8_lit("voff"))) { + fprintf(stderr, "ERROR: missing -voff\n"); + break; + } + + String8 voff_str = cmd_line_string(cmdline, str8_lit("voff")); + if(voff_str.size == 0) + { + fprintf(stderr, "ERROR: missing argument for -voff\n"); + break; + } + + U64 voff = 0; + if(!try_u64_from_str8_c_rules(voff_str, &voff)) + { + fprintf(stderr, "ERROR: invalid argument for -voff\n"); + break; + } + + RB_File *f = input_files.first->v; + if(f->format != RB_FileFormat_RDI) + { + fprintf(stderr, "ERROR: input file must be RDI.\n"); + break; + } + + RDI_Parsed rdi = {0}; + RDI_ParseStatus rdi_parse_status = rdi_parse(f->data.str, f->data.size, &rdi); + if(rdi_parse_status != RDI_ParseStatus_Good) + { + fprintf(stderr, "ERROR: failed to parse RDI with code %d\n", rdi_parse_status); + break; + } + + RDI_Line line = rdi_line_from_voff(&rdi, voff); + if(line.file_idx == 0 || line.line_num == 0) + { + fprintf(stderr, "ERROR: failed to find mapping for virtual offset 0x%llx.\n", voff); + break; + } + + RDI_SourceFile *src_file = rdi_element_from_name_idx(&rdi, SourceFiles, line.file_idx); + if(src_file == 0) + { + fprintf(stderr, "ERROR: failed to find source file with index %u.\n", line.file_idx); + break; + } + + Temp scratch = scratch_begin(0, 0); + + // format inline site stack + { + RDI_Scope *voff_scope = rdi_scope_from_voff(&rdi, voff); + for(RDI_Scope *scope = voff_scope, *null_scope = rdi_element_from_name_idx(&rdi, Scopes, 0); scope != 0 && scope != null_scope; scope = rdi_parent_from_scope(&rdi, scope)) + { + RDI_InlineSite *inline_site = rdi_inline_site_from_scope(&rdi, scope); + RDI_InlineSite *null_inline_site = rdi_element_from_name_idx(&rdi, InlineSites, 0); + if(inline_site && inline_site != null_inline_site) + { + RDI_LineTable *inline_line_table = rdi_element_from_name_idx(&rdi, LineTables, inline_site->line_table_idx); + RDI_LineTable *null_inline_line_table = rdi_element_from_name_idx(&rdi, LineTables, 0); + if(inline_line_table && inline_line_table != null_inline_line_table) + { + String8 inline_name = str8_from_rdi_string_idx(&rdi, inline_site->name_string_idx); + RDI_Line inline_line = rdi_line_from_line_table_voff(&rdi, inline_line_table, voff); + RDI_SourceFile *inline_src_file = rdi_element_from_name_idx(&rdi, SourceFiles, inline_line.file_idx); + RDI_SourceFile *null_inline_src_file = rdi_element_from_name_idx(&rdi, SourceFiles, 0); + if(inline_src_file && inline_src_file != null_inline_src_file) + { + String8 path = str8_from_rdi_path_node_idx(scratch.arena, &rdi, PathStyle_SystemAbsolute, src_file->file_path_node_idx); + fprintf(stdout, "[inlined] %.*s %.*s:%u\n", str8_varg(inline_name), str8_varg(path), inline_line.line_num); + } + } + } + } + } + + String8 path = str8_from_rdi_path_node_idx(scratch.arena, &rdi, PathStyle_SystemAbsolute, src_file->file_path_node_idx); + fprintf(stdout, "%.*s:%u\n", str8_varg(path), line.line_num); + + scratch_end(scratch); + }break; } ////////////////////////////// diff --git a/src/rdi/rdi_local.c b/src/rdi/rdi_local.c index c03f6f66..c012fe9a 100644 --- a/src/rdi/rdi_local.c +++ b/src/rdi/rdi_local.c @@ -30,6 +30,24 @@ str8_from_rdi_string_idx(RDI_Parsed *rdi, U32 idx) return result; } +internal String8 +str8_from_rdi_path_node_idx(Arena *arena, RDI_Parsed *rdi, PathStyle path_style, U32 path_node_idx) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List path_parts = {0}; + for(RDI_FilePathNode *fpn = rdi_element_from_name_idx(rdi, FilePathNodes, path_node_idx); + fpn != rdi_element_from_name_idx(rdi, FilePathNodes, 0); + fpn = rdi_element_from_name_idx(rdi, FilePathNodes, fpn->parent_path_node)) + { + String8 path_part = {0}; + path_part.str = rdi_string_from_idx(rdi, fpn->name_string_idx, &path_part.size); + str8_list_push_front(scratch.arena, &path_parts, path_part); + } + String8 path = str8_path_list_join_by_style(arena, &path_parts, path_style); + scratch_end(scratch); + return path; +} + //////////////////////////////// //~ rjf: String <=> Enum diff --git a/src/rdi/rdi_local.h b/src/rdi/rdi_local.h index ea69cc8b..85261d10 100644 --- a/src/rdi/rdi_local.h +++ b/src/rdi/rdi_local.h @@ -73,6 +73,7 @@ internal Arch arch_from_rdi_arch(RDI_Arch arch); //~ rjf: Lookup Helpers internal String8 str8_from_rdi_string_idx(RDI_Parsed *rdi, U32 idx); +internal String8 str8_from_rdi_path_node_idx(Arena *arean, RDI_Parsed *rdi, PathStyle path_style, U32 path_node_idx); //////////////////////////////// //~ rjf: String <=> Enum diff --git a/src/torture/torture.c b/src/torture/torture.c index b4744af6..42832dd3 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -91,25 +91,73 @@ t_run(T_Run run) return ctx.result; } -internal B32 -t_invoke(String8 exe_path, String8 cmdline, U64 timeout) +internal String8 +t_radbin_path(void) { - Temp scratch = scratch_begin(0,0); + local_persist String8 path = {0}; + if (path.size == 0) { + local_persist U8 buffer[4096]; + ArenaParams params = { .reserve_size = sizeof(buffer), .commit_size = sizeof(buffer), .optional_backing_buffer = buffer }; + Arena *arena = arena_alloc_(¶ms); +#if OS_WINDOWS + path = os_full_path_from_path(arena, str8_lit("radbin.exe")); +#else + path = os_full_path_from_path(arena, str8_lit("radbin")); +#endif + AssertAlways(path.size); + } + return path; +} + +internal String8 +t_radlink_path(void) +{ + local_persist String8 path = {0}; + if (path.size == 0) { + local_persist U8 buffer[4096]; + ArenaParams params = { .reserve_size = sizeof(buffer), .commit_size = sizeof(buffer), .optional_backing_buffer = buffer }; + Arena *arena = arena_alloc_(¶ms); +#if OS_WINDOWS + path = os_full_path_from_path(arena, str8_lit("radlink.exe")); +#else + path = os_full_path_from_path(arena, str8_lit("radlink")); +#endif + AssertAlways(path.size); + } + return path; +} + +internal B32 +t_invoke_(String8 exe_path, String8 cmdline, U64 timeout, Arena *output_arena, String8 *output_out) +{ + Temp scratch = scratch_begin(&output_arena,1); B32 is_ok = 0; - OS_Handle output_redirect = {0}; - if (g_redirect_stdout) { - output_redirect = os_file_open(OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, g_stdout_file_name); + B32 capture_output = output_out || g_redirect_stdout; + + g_last_exit_code = -1; + + OS_Handle read_pipe_handle = {0}, write_pipe_handle = {0}; + if (capture_output) { + HANDLE read_pipe, write_pipe; + SECURITY_ATTRIBUTES at = { .nLength = sizeof(at), .bInheritHandle = 1 }; + if (!CreatePipe(&read_pipe, &write_pipe, &at, 0)) { + AssertAlways(0 && "failed to create a pipe"); + } + read_pipe_handle = (OS_Handle){ .u64[0] = (U64)read_pipe }; + write_pipe_handle = (OS_Handle){ .u64[0] = (U64)write_pipe }; } // Build Launch Options - OS_ProcessLaunchParams launch_opts = { .path = g_wdir, .inherit_env = 1, .stdout_file = output_redirect, .stderr_file = output_redirect }; - str8_list_push(scratch.arena, &launch_opts.cmd_line, exe_path); - { - String8List parsed_cmdline = lnk_arg_list_parse_windows_rules(scratch.arena, cmdline); - str8_list_concat_in_place(&launch_opts.cmd_line, &parsed_cmdline); - } + OS_ProcessLaunchParams launch_opts = { + .path = g_wdir, + .inherit_env = 1, + .stdout_file = write_pipe_handle, + .stderr_file = write_pipe_handle, + .cmd_line = lnk_arg_list_parse_windows_rules(scratch.arena, cmdline), + }; + str8_list_push_front(scratch.arena, &launch_opts.cmd_line, exe_path); if (g_verbose) { String8 full_cmd_line = str8_list_join(scratch.arena, &launch_opts.cmd_line, &(StringJoin){ .sep = str8_lit(" ") }); @@ -117,37 +165,54 @@ t_invoke(String8 exe_path, String8 cmdline, U64 timeout) fprintf(stdout, "Working Dir: %.*s\n", str8_varg(g_wdir)); } - // Invoke Exe - int exit_code = -1; - { - OS_Handle process_handle = os_process_launch(&launch_opts); - if (!os_handle_match(process_handle, os_handle_zero())) { - U64 exit_code_u64 = 0; - is_ok = os_process_join(process_handle, timeout, &exit_code_u64); - exit_code = (int)exit_code_u64; - if (!is_ok) { - os_process_kill(process_handle); + // invoke exe + OS_Handle process_handle = os_process_launch(&launch_opts); + + // close handle so last to ReadFile does not block + os_file_close(write_pipe_handle); + + if ( ! os_handle_match(process_handle, os_handle_zero())) { + if (capture_output) { + // capture process output + String8List output = {0}; + for (;;) { + String8 string = os_file_read_cstring(scratch.arena, read_pipe_handle, 0); + if (string.size == 0) { break; } + str8_list_push(scratch.arena, &output, string); + } + os_file_close(read_pipe_handle); + + if (output_out) { + *output_out = str8_list_join(output_arena, &output, 0); + } + + // write to the output file + if (g_redirect_stdout) { + os_write_data_list_to_file_path(g_stdout_file_name, output); } - os_process_detach(process_handle); } - } - // close handles - if (g_redirect_stdout) { - os_file_close(output_redirect); - } + U64 exit_code_u64 = 0; + if (os_process_join(process_handle, timeout, &exit_code_u64)) { + g_last_exit_code = (int)exit_code_u64; + is_ok = 1; + } else { + os_process_kill(process_handle); + } - // update global exit code - if (is_ok) { - g_last_exit_code = exit_code; - } else { - g_last_exit_code = -1; + os_process_detach(process_handle); } scratch_end(scratch); return is_ok; } +internal B32 +t_invoke(String8 exe_path, String8 cmdline, U64 timeout) +{ + return t_invoke_(exe_path, cmdline, timeout, 0, 0); +} + internal int t_test_is_before(void *raw_a, void *raw_b) { diff --git a/src/torture/torture_d2r.c b/src/torture/torture_d2r.c index c0f5ee47..7833c2f5 100644 --- a/src/torture/torture_d2r.c +++ b/src/torture/torture_d2r.c @@ -20,7 +20,7 @@ d2r_rdi_from_dwarf_writer(Arena *arena, DW_Writer *writer) } Assert(t_write_file(str8_lit("a.obj"), raw_coff)); - t_invoke(str8_lit("radlink.exe"), str8_lit("/subsystem:console /out:a.exe /entry:main /DEBUG:FULL /opt:noref /opt:noicf a.obj"), max_U64); + t_invoke(t_radlink_path(), str8_lit("/subsystem:console /out:a.exe /entry:main /DEBUG:FULL /opt:noref /opt:noicf a.obj"), max_U64); Assert(g_last_exit_code == 0); String8 exe = t_read_file(scratch.arena, str8_lit("a.exe")); Assert(exe.size > 0); @@ -28,7 +28,7 @@ d2r_rdi_from_dwarf_writer(Arena *arena, DW_Writer *writer) B32 was_pdb_deleted = os_delete_file_at_path(t_make_file_path(scratch.arena, str8_lit("a.pdb"))); Assert(was_pdb_deleted); - t_invoke(str8_lit("radbin.exe"), str8_lit("-rdi a.exe"), max_U64); + t_invoke_(t_radbin_path(), str8_lit("-rdi a.exe -out:a.rdi"), max_U64, 0, 0); Assert(g_last_exit_code == 0); String8 raw_rdi = t_read_file(arena, str8_lit("a.rdi")); @@ -222,6 +222,60 @@ T_BeginTest(d2r_types) } T_EndTest; +#if 0 +T_BeginTest(d2r_line_table) +{ + DW_Writer *writer = dw_writer_begin(DW_Format_32Bit, DW_Version_5, DW_CompUnitKind_Compile, Arch_x64); + String8 comp_dir = str8_lit("c:/devel/"); + String8 comp_name = str8_lit("test.c"); + + DW_WriterFile *file = dw_writer_new_file(writer, comp_name); + file->md5 = *(U128 *)&(U8[]){ 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc }; + + struct { + DW_WriterFile *file; U64 ln; U64 line_size; U64 voff; + } test_table[] = { + { file, 6, 1 }, + { file, 4, 2 }, + { file, 2, 3 }, + { file, 1, 4 }, + { file, 8, 5 }, + }; + + U64 exe_base = coff_default_exe_base_from_machine(COFF_MachineType_X64); + U64 voff = 0x1000; + for EachElement(i, test_table) { + test_table[i].voff = voff; + dw_writer_line_emit(writer, test_table[i].file, test_table[i].ln, 0, exe_base + voff); + voff += test_table[i].line_size; + } + + dw_writer_tag_begin(writer, DW_TagKind_CompileUnit); + dw_writer_push_attrib_string(writer, DW_AttribKind_Producer, str8_lit("RAD DWARF WRITER")); + dw_writer_push_attrib_string(writer, DW_AttribKind_CompDir, comp_dir); + dw_writer_push_attrib_string(writer, DW_AttribKind_Name, comp_name); + dw_writer_push_attrib_address(writer, DW_AttribKind_LowPc, exe_base); + dw_writer_push_attrib_address(writer, DW_AttribKind_HighPc, exe_base + voff); + dw_writer_push_attrib_line_ptr(writer, DW_AttribKind_StmtList, 0); + dw_writer_tag_end(writer); + + d2r_rdi_from_dwarf_writer(scratch.arena, writer); + + for EachElement(i, test_table) { + for EachIndex(k, test_table[i].line_size) { + String8 cmd_line = str8f(scratch.arena, "-voff2line -voff:%llx, a.rdi", test_table[i].voff + k); + String8 output = {0}; + t_invoke_(str8_lit("radbin.exe"), cmd_line, max_U64, scratch.arena, &output); + T_Ok(g_last_exit_code == 0); + T_MatchLinef(&output, "%S:/%llu", test_table[i].file->path, test_table[i].ln); + } + } + + dw_writer_end(&writer); +} +T_EndTest; +#endif + T_BeginTest(d2r_general) { DW_Writer *writer = dw_writer_begin(DW_Format_32Bit, DW_Version_5, DW_CompUnitKind_Compile, Arch_x64);