// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Generated Code #include "radbin/generated/radbin.meta.c" //////////////////////////////// //~ rjf: Top-Level Entry Points internal void rb_entry_point(CmdLine *cmdline) { Temp scratch = scratch_begin(0, 0); U64 threads_count = os_get_system_info()->logical_processor_count; String8 threads_count_from_cmdline_string = cmd_line_string(cmdline, str8_lit("thread_count")); if(threads_count_from_cmdline_string.size != 0) { U64 threads_count_from_cmdline = 0; if(try_u64_from_str8_c_rules(threads_count_from_cmdline_string, &threads_count_from_cmdline)) { threads_count = threads_count_from_cmdline; } } Thread *threads = push_array(scratch.arena, Thread, threads_count); RB_ThreadParams *threads_params = push_array(scratch.arena, RB_ThreadParams, threads_count); Barrier barrier = barrier_alloc(threads_count); U64 broadcast_val = 0; for EachIndex(idx, threads_count) { threads_params[idx].cmdline = cmdline; threads_params[idx].lane_ctx.lane_idx = idx; threads_params[idx].lane_ctx.lane_count = threads_count; threads_params[idx].lane_ctx.barrier = barrier; threads_params[idx].lane_ctx.broadcast_memory = &broadcast_val; threads[idx] = thread_launch(rb_thread_entry_point, &threads_params[idx]); } for EachIndex(idx, threads_count) { thread_join(threads[idx], max_U64); } scratch_end(scratch); } internal void rb_thread_entry_point(void *p) { RB_ThreadParams *params = (RB_ThreadParams *)p; CmdLine *cmdline = params->cmdline; LaneCtx lctx = params->lane_ctx; ThreadNameF("radbin_thread_%I64u", lctx.lane_idx); lane_ctx(lctx); Arena *arena = arena_alloc(); Log *log = log_alloc(); log_select(log); log_scope_begin(); ////////////////////////////// //- rjf: set up shared state // if(lane_idx() == 0) { rb_shared = push_array(arena, RB_Shared, 1); } lane_sync(); ////////////////////////////// //- rjf: analyze & load command line input files // ProfScope("analyze & load command line input files") if(lane_idx() == 0) { String8 working_directory = os_get_current_path(arena); String8List input_file_path_tasks = str8_list_copy(arena, &cmdline->inputs); for(String8Node *n = input_file_path_tasks.first; n != 0; n = n->next) { ////////////////////////// //- rjf: possibly relative -> absolute path // String8 input_file_path = n->string; { PathStyle path_style = path_style_from_str8(n->string); if(path_style == PathStyle_Relative) { input_file_path = path_normalized_from_string(arena, str8f(arena, "%S/%S", working_directory, n->string)); } } ////////////////////////// //- rjf: do thin analysis of file // RB_FileFormat file_format = RB_FileFormat_Null; RB_FileFormatFlags file_format_flags = 0; ProfScope("do thin analysis of file") { OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, input_file_path); FileProperties props = os_properties_from_file(file); //- rjf: PDB magic -> PDB input if(file_format == RB_FileFormat_Null) { U8 msf20_magic_maybe[sizeof(msf_msf20_magic)] = {0}; os_file_read(file, r1u64(0, sizeof(msf20_magic_maybe)), msf20_magic_maybe); if(MemoryMatch(msf20_magic_maybe, msf_msf20_magic, sizeof(msf20_magic_maybe))) { file_format = RB_FileFormat_PDB; } } if(file_format == RB_FileFormat_Null) { U8 msf70_magic_maybe[sizeof(msf_msf70_magic)] = {0}; os_file_read(file, r1u64(0, sizeof(msf70_magic_maybe)), msf70_magic_maybe); if(MemoryMatch(msf70_magic_maybe, msf_msf70_magic, sizeof(msf70_magic_maybe))) { file_format = RB_FileFormat_PDB; } } //- rjf: PE magic -> PE input if(file_format == RB_FileFormat_Null) { PE_DosHeader dos_header_maybe = {0}; os_file_read_struct(file, 0, &dos_header_maybe); if(dos_header_maybe.magic == PE_DOS_MAGIC) { U32 pe_magic_maybe = 0; os_file_read_struct(file, dos_header_maybe.coff_file_offset, &pe_magic_maybe); if(pe_magic_maybe == PE_MAGIC) { file_format = RB_FileFormat_PE; } } } //- rjf: COFF archive magic -> COFF archive input if(file_format == RB_FileFormat_Null) { U8 coff_archive_sig_maybe[sizeof(g_coff_archive_sig)] = {0}; os_file_read(file, r1u64(0, sizeof(coff_archive_sig_maybe)), coff_archive_sig_maybe); if(MemoryMatch(coff_archive_sig_maybe, g_coff_archive_sig, sizeof(g_coff_archive_sig))) { file_format = RB_FileFormat_COFF_Archive; } } if(file_format == RB_FileFormat_Null) { U8 coff_thin_archive_sig_maybe[sizeof(g_coff_thin_archive_sig)] = {0}; os_file_read(file, r1u64(0, sizeof(coff_thin_archive_sig_maybe)), coff_thin_archive_sig_maybe); if(MemoryMatch(coff_thin_archive_sig_maybe, g_coff_thin_archive_sig, sizeof(g_coff_thin_archive_sig))) { file_format = RB_FileFormat_COFF_ThinArchive; } } //- rjf: COFF obj magic -> COFF obj input if(file_format == RB_FileFormat_Null) { COFF_BigObjHeader header_maybe = {0}; os_file_read_struct(file, 0, &header_maybe); if(header_maybe.sig1 == COFF_MachineType_Unknown && header_maybe.sig2 == max_U16 && header_maybe.version >= 2 && MemoryMatch(header_maybe.magic, g_coff_big_header_magic, sizeof(header_maybe.magic))) { file_format = RB_FileFormat_COFF_BigOBJ; } } if(file_format == RB_FileFormat_Null) { Temp scratch = scratch_begin(&arena, 1); COFF_FileHeader header_maybe = {0}; os_file_read_struct(file, 0, &header_maybe); U64 section_count = header_maybe.section_count; U64 section_hdr_opl_off = sizeof(header_maybe) + section_count*sizeof(COFF_SectionHeader); // rjf: check if machine type is valid B32 machine_type_is_valid = 0; switch(header_maybe.machine) { case COFF_MachineType_Unknown: case COFF_MachineType_X86: case COFF_MachineType_X64: case COFF_MachineType_Am33: case COFF_MachineType_Arm: case COFF_MachineType_Arm64: case COFF_MachineType_ArmNt: case COFF_MachineType_Ebc: case COFF_MachineType_Ia64: case COFF_MachineType_M32R: case COFF_MachineType_Mips16: case COFF_MachineType_MipsFpu:case COFF_MachineType_MipsFpu16: case COFF_MachineType_PowerPc:case COFF_MachineType_PowerPcFp: case COFF_MachineType_R4000: case COFF_MachineType_RiscV32: case COFF_MachineType_RiscV64:case COFF_MachineType_RiscV128: case COFF_MachineType_Sh3: case COFF_MachineType_Sh3Dsp: case COFF_MachineType_Sh4: case COFF_MachineType_Sh5: case COFF_MachineType_Thumb: case COFF_MachineType_WceMipsV2: { machine_type_is_valid = 1; }break; } // rjf: check if sections are valid B32 sections_are_valid = 0; if(machine_type_is_valid) { if(props.size >= section_hdr_opl_off) { COFF_SectionHeader *section_hdrs = push_array(scratch.arena, COFF_SectionHeader, section_count); os_file_read(file, r1u64(sizeof(header_maybe), sizeof(header_maybe) + section_count*sizeof(COFF_SectionHeader)), section_hdrs); B32 section_ranges_valid = 1; for EachIndex(section_hdr_idx, section_count) { COFF_SectionHeader *hdr = §ion_hdrs[section_hdr_idx]; if(!(hdr->flags & COFF_SectionFlag_CntUninitializedData)) { U64 min = hdr->foff; U64 max = min + hdr->fsize; if(hdr->fsize > 0 && !(section_hdr_opl_off <= min && min <= max && max <= props.size)) { section_ranges_valid = 0; break; } } } sections_are_valid = section_ranges_valid; } } // rjf: check if symbol table is valid B32 symbol_table_is_valid = 0; if(sections_are_valid) { U64 symbol_table_off = header_maybe.symbol_table_foff; U64 symbol_table_size = sizeof(COFF_Symbol16)*header_maybe.symbol_count; U64 symbol_table_opl_off = symbol_table_off+symbol_table_size; if(symbol_table_off == 0 && symbol_table_size == 0) { symbol_table_off = section_hdr_opl_off; symbol_table_opl_off = section_hdr_opl_off; } symbol_table_is_valid = (section_hdr_opl_off <= symbol_table_off && symbol_table_off <= symbol_table_opl_off && symbol_table_opl_off <= props.size); } // rjf: symbol table is valid -> is COFF OBJ if(symbol_table_is_valid) { file_format = RB_FileFormat_COFF_OBJ; } scratch_end(scratch); } //- rjf: ELF magic -> ELF input if(file_format == RB_FileFormat_Null) { U8 identifier_maybe[ELF_Identifier_Max] = {0}; os_file_read(file, r1u64(0, sizeof(identifier_maybe)), identifier_maybe); B32 is_elf_magic = (identifier_maybe[ELF_Identifier_Mag0] == 0x7f && identifier_maybe[ELF_Identifier_Mag1] == 'E' && identifier_maybe[ELF_Identifier_Mag2] == 'L' && identifier_maybe[ELF_Identifier_Mag3] == 'F'); if(is_elf_magic) { file_format = ELF_HdrIs64Bit(identifier_maybe) ? RB_FileFormat_ELF64 : RB_FileFormat_ELF32; } } //- rjf: RDI magic -> RDI input if(file_format == RB_FileFormat_Null) { RDI_Header rdi_header_maybe = {0}; os_file_read_struct(file, 0, &rdi_header_maybe); if(rdi_header_maybe.magic == RDI_MAGIC_CONSTANT) { file_format = RB_FileFormat_RDI; } } os_file_close(file); } ////////////////////////// //- rjf: log file recognition // if(file_format != RB_FileFormat_Null) { log_infof("%S recognized as %S\n", n->string, rb_file_format_display_name_table[file_format]); } else { log_infof("%S was not recognized as a supported format.\n", n->string); } ////////////////////////// //- rjf: load recognized files // String8 file_data = {0}; if(file_format != RB_FileFormat_Null) ProfScope("load recognized file") { file_data = os_data_from_file_path(arena, input_file_path); } ////////////////////////// //- rjf: PE format => generate new implicit path tasks for PDBs // if(file_format == RB_FileFormat_PE) ProfScope("PE file => generate task for PDB") { Temp scratch = scratch_begin(&arena, 1); String8 file_path = input_file_path; PE_BinInfo pe_bin_info = pe_bin_info_from_data(scratch.arena, file_data); String8 raw_debug_dir = str8_substr(file_data, pe_bin_info.data_dir_franges[PE_DataDirectoryIndex_DEBUG]); PE_DebugInfoList debug_dir = pe_debug_info_list_from_raw_debug_dir(scratch.arena, file_data, raw_debug_dir); for(PE_DebugInfoNode *n = debug_dir.first; n != 0; n = n->next) { if(n->v.path.size != 0) { log_infof("Found reference to separate debug info file in %S (%S) at %S\n", file_path, rb_file_format_display_name_table[file_format], n->v.path); str8_list_push(arena, &input_file_path_tasks, n->v.path); } } scratch_end(scratch); } ////////////////////////// //- rjf: ELF format => generate new implicit path tasks for debug files // if(file_format == RB_FileFormat_ELF32 || file_format == RB_FileFormat_ELF64) { Temp scratch = scratch_begin(&arena, 1); ELF_Bin bin = elf_bin_from_data(scratch.arena, file_data); ELF_GnuDebugLink debug_link = elf_gnu_debug_link_from_bin(file_data, &bin); if(debug_link.path.size != 0) { log_infof("Found reference to separate debug info file in %S (%S) at %S\n", n->string, rb_file_format_display_name_table[file_format], debug_link.path); str8_list_push(arena, &input_file_path_tasks, debug_link.path); } scratch_end(scratch); } ////////////////////////// //- rjf: PE => check if contains DWARF // if(file_format == RB_FileFormat_PE) { Temp scratch = scratch_begin(&arena, 1); PE_BinInfo pe_bin_info = pe_bin_info_from_data(scratch.arena, file_data); String8 raw_section_table = str8_substr(file_data, pe_bin_info.section_table_range); String8 string_table = str8_substr(file_data, pe_bin_info.string_table_range); U64 section_count = raw_section_table.size / sizeof(COFF_SectionHeader); COFF_SectionHeader *section_table = (COFF_SectionHeader *)raw_section_table.str; if(dw_is_dwarf_present_coff_section_table(string_table, section_count, section_table)) { log_infof("DWARF data detected in %S (%S)\n", n->string, rb_file_format_display_name_table[file_format]); file_format_flags |= RB_FileFormatFlag_HasDWARF; } scratch_end(scratch); } if(file_format == RB_FileFormat_COFF_OBJ || file_format == RB_FileFormat_COFF_BigOBJ) { Temp scratch = scratch_begin(&arena, 1); COFF_FileHeaderInfo coff_obj = coff_file_header_info_from_data(file_data); String8 string_table = str8_substr(file_data, coff_obj.string_table_range); U64 section_count = coff_obj.section_count_no_null; COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(file_data, coff_obj.section_table_range).str; if(dw_is_dwarf_present_coff_section_table(string_table, section_count, section_table)) { log_infof("DWARF data detected in %S (%S)\n", n->string, rb_file_format_display_name_table[file_format]); file_format_flags |= RB_FileFormatFlag_HasDWARF; } scratch_end(scratch); } ////////////////////////// //- rjf: ELF => check if contains DWARF // if(file_format == RB_FileFormat_ELF32 || file_format == RB_FileFormat_ELF64) { Temp scratch = scratch_begin(&arena, 1); ELF_Bin elf_bin = elf_bin_from_data(scratch.arena, file_data); if(dw_is_dwarf_present_from_elf_bin(file_data, &elf_bin)) { log_infof("DWARF data detected in %S (%S)\n", n->string, rb_file_format_display_name_table[file_format]); file_format_flags |= RB_FileFormatFlag_HasDWARF; } scratch_end(scratch); } ////////////////////////// //- rjf: push to list // { RB_File *f = push_array(arena, RB_File, 1); f->format = file_format; f->format_flags = file_format_flags; f->path = input_file_path; f->data = file_data; RB_FileNode *file_n = push_array(arena, RB_FileNode, 1); file_n->v = f; SLLQueuePush(rb_shared->input_files.first, rb_shared->input_files.last, file_n); rb_shared->input_files.count += 1; } } } lane_sync(); RB_FileList input_files = rb_shared->input_files; ////////////////////////////// //- rjf: bucket input files by format // ProfScope("bucket input files by format") if(lane_idx() == 0) { for(RB_FileNode *n = input_files.first; n != 0; n = n->next) { RB_FileNode *file_n = push_array(arena, RB_FileNode, 1); file_n->v = n->v; SLLQueuePush(rb_shared->input_files_from_format_table[n->v->format].first, rb_shared->input_files_from_format_table[n->v->format].last, file_n); rb_shared->input_files_from_format_table[n->v->format].count += 1; } } lane_sync(); RB_FileList *input_files_from_format_table = rb_shared->input_files_from_format_table; ////////////////////////////// //- rjf: unpack which kind of output we're producing, and to where // typedef enum OutputKind { OutputKind_Null, OutputKind_RDI, OutputKind_Dump, OutputKind_Breakpad, OutputKind_VOff2Line, OutputKind_COUNT } OutputKind; struct { String8 arg; String8 title; } 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("voff2line"), str8_lit_comp("Virtual Offset -> Line Mapping")}, }; OutputKind output_kind = OutputKind_Null; String8 output_path = cmd_line_string(cmdline, str8_lit("out")); { //- rjf: user manually specified output kind if(output_kind == OutputKind_Null) { for EachNonZeroEnumVal(OutputKind, k) { if(cmd_line_has_flag(cmdline, output_kind_info[k].arg)) { log_infof("User specified --%S; performing `%S`\n", output_kind_info[k].arg, output_kind_info[k].title); output_kind = k; break; } } } //- rjf: we can infer from the user-specified output path if(str8_match(str8_skip_last_dot(output_path), str8_lit("rdi"), StringMatchFlag_CaseInsensitive)) { output_kind = OutputKind_RDI; log_infof("Output path has .rdi extension; performing `%S`\n", output_kind_info[output_kind].title); } else if(str8_match(str8_skip_last_dot(output_path), str8_lit("dump"), StringMatchFlag_CaseInsensitive) || str8_match(str8_skip_last_dot(output_path), str8_lit("txt"), StringMatchFlag_CaseInsensitive)) { output_kind = OutputKind_Dump; log_infof("Output path has .dump or .txt extension; performing `%S`\n", output_kind_info[output_kind].title); } else if(str8_match(str8_skip_last_dot(output_path), str8_lit("psym"), StringMatchFlag_CaseInsensitive) || str8_match(str8_skip_last_dot(output_path), str8_lit("psyms"), StringMatchFlag_CaseInsensitive)) { output_kind = OutputKind_Breakpad; log_infof("Output path has .psym or .psyms extension; performing `%S`\n", output_kind_info[output_kind].title); } } ////////////////////////////// //- rjf: print help preamble // if(lane_idx() == 0) { if(output_kind == OutputKind_Null || cmdline->inputs.node_count == 0) { fprintf(stderr, "%s\n", BUILD_TITLE); fprintf(stderr, "%s\n\n", BUILD_VERSION_STRING_LITERAL); if(output_kind != OutputKind_Null) { fprintf(stderr, "%.*s Help\n", str8_varg(output_kind_info[output_kind].title)); fprintf(stderr, "To see top-level options for radbin, run the binary with no arguments.\n\n"); } fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); } } lane_sync(); ////////////////////////////// //- rjf: perform operation based on output kind // String8List output_blobs = {0}; switch(output_kind) { //////////////////////////// //- rjf: no output kind -> nothing to do. output help information to user // default: case OutputKind_Null: if(lane_idx() == 0) { fprintf(stderr, "USAGE EXAMPLES\n\n"); fprintf(stderr, "radbin --rdi program.exe\n"); fprintf(stderr, "Find the debug info for `program.exe`, if any, and converts it to RDI.\n\n"); fprintf(stderr, "radbin program.pdb --out:program.rdi\n"); fprintf(stderr, "Converts `program.pdb` to RDI, and stores it in `program.rdi`.\n\n"); fprintf(stderr, "radbin --dump program.rdi\n"); fprintf(stderr, "Outputs the textual dump of the debug information stored in `program.rdi`.\n\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "DESCRIPTION\n\n"); fprintf(stderr, "This utility provides a number of operations relating to executable image and\n"); fprintf(stderr, "debug information formats. It can convert debug information to the RAD Debug\n"); fprintf(stderr, "Info (.rdi) format. It can also parse and dump textualized contents of several\n"); fprintf(stderr, "formats, including PDB, PE, ELF, and RDI. It also supports converting debug\n"); fprintf(stderr, "information to the textual Breakpad format.\n\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "ARGUMENTS\n\n"); fprintf(stderr, "--rdi Specifies that the utility should convert debug information\n"); fprintf(stderr, " data to the RAD Debug Info (.rdi) format.\n\n"); fprintf(stderr, "--dump Specifies that the utility should dump textualized contents of\n"); fprintf(stderr, " all input files.\n\n"); 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 a virtual offset to a\n"); fprintf(stderr, " 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"); fprintf(stderr, " textual contents, the utility will write to `stdout`. If\n"); fprintf(stderr, " converting to another format, the utility will form a path by\n"); fprintf(stderr, " changing the extension of input files accordingly.\n\n"); fprintf(stderr, "--deterministic Turns off all sources of non-determinism in generated output,\n"); fprintf(stderr, " like build names, versions, and dates.\n\n"); fprintf(stderr, "--verbose Outputs all log information collected during execution.\n\n"); fprintf(stderr, "There are also operation-specific arguments. To see them, run this binary with\n"); fprintf(stderr, "the operation selected, with no additional inputs (e.g. `radbin --rdi`).\n\n"); }break; //////////////////////////// //- rjf: RDI, Breakpad, Debug Info Operations -> conversion based on inputs // case OutputKind_RDI: case OutputKind_Breakpad: case OutputKind_VOff2Line: { //- rjf: no inputs => help if(lane_idx() == 0 && cmdline->inputs.node_count == 0) switch(output_kind) { default: case OutputKind_RDI: { fprintf(stderr, "ARGUMENTS\n\n"); fprintf(stderr, "--compress Compresses the RDI file's contents.\n"); fprintf(stderr, "\n"); fprintf(stderr, "--only: Specifies that only the named subsets of debug\n"); fprintf(stderr, " information should be generated. See below for\n"); fprintf(stderr, " a list of valid debug info subset names.\n"); fprintf(stderr, "\n"); fprintf(stderr, "--omit: Specifies that the named subsets of debug\n"); fprintf(stderr, " information should not be generated. See below\n"); fprintf(stderr, " for a list of valid debug info subset names.\n"); fprintf(stderr, "\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "RAD DEBUG INFO SUBSET NAMES\n\n"); #define X(name, name_lower) fprintf(stderr, " - " #name_lower "\n"); #define Y(name, name_lower) X(name, name_lower) RDIM_Subset_XList #undef X #undef Y }break; case OutputKind_Breakpad: { 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; case OutputKind_VOff2Line: { fprintf(stderr, "ARGUMENTS\n\n"); fprintf(stderr, "--voff: Specifies the virtual offset to map to a source line.\n"); fprintf(stderr, "\n"); }break; } //- rjf: unpack subset flags RDIM_SubsetFlags subset_flags = 0xffffffff; switch(output_kind) { default:{}break; case OutputKind_RDI: { String8List only_names = cmd_line_strings(cmdline, str8_lit("only")); if(only_names.node_count != 0) { subset_flags = 0; } for(String8Node *n = only_names.first; n != 0; n = n->next) { if(0){} #define X(name, name_lower) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { subset_flags |= RDIM_SubsetFlag_##name; } #define Y(name, name_lower) X(name, name_lower) RDIM_Subset_XList #undef X #undef Y } String8List omit_names = cmd_line_strings(cmdline, str8_lit("omit")); for(String8Node *n = omit_names.first; n != 0; n = n->next) { if(0){} #define X(name, name_lower) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { subset_flags &= ~RDIM_SubsetFlag_##name; } #define Y(name, name_lower) X(name, name_lower) RDIM_Subset_XList #undef X #undef Y } }break; case OutputKind_Breakpad: { subset_flags = (RDIM_SubsetFlag_Units|RDIM_SubsetFlag_Procedures|RDIM_SubsetFlag_Scopes|RDIM_SubsetFlag_LineInfo|RDIM_SubsetFlag_InlineLineInfo); }break; case OutputKind_VOff2Line: { subset_flags = (RDIM_SubsetFlag_Units|RDIM_SubsetFlag_LineInfo|RDIM_SubsetFlag_InlineLineInfo|RDIM_SubsetFlag_Procedures); }break; } //- rjf: convert inputs to RDI info B32 convert_done = 0; RDIM_BakeParams pdb_bake_params = {0}; RDIM_BakeParams dwarf_bake_params = {0}; RDIM_BakeParams dwarf_bake_params_2 = {0}; typedef struct RDIM_BakeParamsNode RDIM_BakeParamsNode; struct RDIM_BakeParamsNode { RDIM_BakeParamsNode *next; RDIM_BakeParams v; }; RDIM_BakeParamsNode *first_rdi_bake_params = 0; RDIM_BakeParamsNode *last_rdi_bake_params = 0; { //- rjf: PE inputs w/ DWARF, or ELF inputs => DWARF -> RDI conversion B32 pe_w_dwarf = (input_files_from_format_table[RB_FileFormat_PE].count != 0 && input_files_from_format_table[RB_FileFormat_PE].first->v->format_flags & RB_FileFormatFlag_HasDWARF); B32 elf_w_dwarf = (input_files_from_format_table[RB_FileFormat_ELF32].count != 0 || input_files_from_format_table[RB_FileFormat_ELF64].count != 0); if(pe_w_dwarf || elf_w_dwarf) { convert_done = 1; if(0){} else if(pe_w_dwarf) { log_infof("PEs w/ DWARF specified; converting DWARF data to RDI\n"); } else if(elf_w_dwarf) { log_infof("ELFs specified; converting DWARF data to RDI\n"); } // rjf: convert D2R_ConvertParams convert_params = {0}; { B32 got_exe = 0; B32 got_dbg = 0; if(!got_exe && !got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_PE].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_exe = 1; got_dbg = 1; convert_params.dbg_name = n->v->path; convert_params.dbg_data = n->v->data; convert_params.exe_name = n->v->path; convert_params.exe_data = n->v->data; convert_params.exe_kind = ExecutableImageKind_CoffPe; break; } } } if(!got_exe) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF32].first; n != 0; n = n->next) { got_exe = 1; convert_params.exe_name = n->v->path; convert_params.exe_data = n->v->data; convert_params.exe_kind = ExecutableImageKind_Elf32; if(!(n->v->format_flags & RB_FileFormatFlag_HasDWARF)) { break; } } } if(!got_exe) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF64].first; n != 0; n = n->next) { got_exe = 1; convert_params.exe_name = n->v->path; convert_params.exe_data = n->v->data; convert_params.exe_kind = ExecutableImageKind_Elf64; if(!(n->v->format_flags & RB_FileFormatFlag_HasDWARF)) { break; } } } if(!got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF32].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_dbg = 1; convert_params.dbg_name = n->v->path; convert_params.dbg_data = n->v->data; break; } } } if(!got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF64].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_dbg = 1; convert_params.dbg_name = n->v->path; convert_params.dbg_data = n->v->data; break; } } } convert_params.subset_flags = subset_flags; convert_params.deterministic = cmd_line_has_flag(cmdline, str8_lit("deterministic")); convert_params.is_parse_relaxed = 1; // TODO: switch } ProfScope("convert") dwarf_bake_params = d2r_convert(arena, &convert_params); // rjf: convert [2] D2R2_ConvertParams convert_params_2 = {0}; { B32 got_exe = 0; B32 got_dbg = 0; String8 exe_name = {0}; String8 exe_data = {0}; ExecutableImageKind exe_kind = ExecutableImageKind_Null; String8 dbg_name = {0}; String8 dbg_data = {0}; if(!got_exe && !got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_PE].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_exe = 1; got_dbg = 1; dbg_name = n->v->path; dbg_data = n->v->data; exe_name = n->v->path; exe_data = n->v->data; exe_kind = ExecutableImageKind_CoffPe; break; } } } if(!got_exe) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF32].first; n != 0; n = n->next) { got_exe = 1; exe_name = n->v->path; exe_data = n->v->data; exe_kind = ExecutableImageKind_Elf32; if(!(n->v->format_flags & RB_FileFormatFlag_HasDWARF)) { break; } } } if(!got_exe) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF64].first; n != 0; n = n->next) { got_exe = 1; exe_name = n->v->path; exe_data = n->v->data; exe_kind = ExecutableImageKind_Elf64; if(!(n->v->format_flags & RB_FileFormatFlag_HasDWARF)) { break; } } } if(!got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF32].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_dbg = 1; dbg_name = n->v->path; dbg_data = n->v->data; break; } } } if(!got_dbg) { for(RB_FileNode *n = input_files_from_format_table[RB_FileFormat_ELF64].first; n != 0; n = n->next) { if(n->v->format_flags & RB_FileFormatFlag_HasDWARF) { got_dbg = 1; dbg_name = n->v->path; dbg_data = n->v->data; break; } } } switch(exe_kind) { default:{}break; case ExecutableImageKind_CoffPe: { Temp scratch = scratch_begin(&arena, 1); PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, exe_data); String8 raw_sections = str8_substr(exe_data, pe.section_table_range); COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(raw_sections, 0, sizeof(COFF_SectionHeader) * pe.section_count); String8 string_table = str8_substr(exe_data, pe.string_table_range); convert_params_2.arch = pe.arch; convert_params_2.base_vaddr = pe.image_base; convert_params_2.raw = dw_input_from_coff_section_table(scratch.arena, exe_data, string_table, pe.section_count, section_table); convert_params_2.path_style = PathStyle_WindowsAbsolute; convert_params_2.binary_sections = c2r_rdi_binary_sections_from_coff_sections(arena, exe_data, string_table, pe.section_count, section_table); scratch_end(scratch); }break; case ExecutableImageKind_Elf32: case ExecutableImageKind_Elf64: { Temp scratch = scratch_begin(&arena, 1); ELF_Bin bin = elf_bin_from_data(scratch.arena, dbg_data); convert_params_2.arch = arch_from_elf_machine(bin.hdr.e_machine); convert_params_2.base_vaddr = elf_base_addr_from_bin(&bin); convert_params_2.raw = dw_input_from_elf_bin(scratch.arena, dbg_data, &bin); convert_params_2.path_style = PathStyle_UnixAbsolute; convert_params_2.binary_sections = e2r_rdi_binary_sections_from_elf_section_table(arena, dbg_data, &bin, &bin.shdrs); scratch_end(scratch); }break; } convert_params_2.exe_name = exe_name; convert_params_2.exe_data = exe_data; convert_params_2.subset_flags = subset_flags; convert_params_2.deterministic = cmd_line_has_flag(cmdline, str8_lit("deterministic")); } ProfScope("convert [2]") dwarf_bake_params_2 = d2r2_convert(arena, &convert_params_2); } //- rjf: PDB inputs => PDB -> RDI conversion if(input_files_from_format_table[RB_FileFormat_PDB].count != 0) { convert_done = 1; log_infof("PDBs specified; converting PDB data to RDI\n"); // rjf: get EXE/PDB file data RB_File *exe_file = rb_file_list_first(&input_files_from_format_table[RB_FileFormat_PE]); RB_File *pdb_file = rb_file_list_first(&input_files_from_format_table[RB_FileFormat_PDB]); String8 exe_path = exe_file->path; String8 pdb_path = pdb_file->path; String8 exe_data = exe_file->data; String8 pdb_data = pdb_file->data; // rjf: convert P2R_ConvertParams convert_params = {0}; { convert_params.input_pdb_name = pdb_path; convert_params.input_exe_name = exe_path; convert_params.input_pdb_data = pdb_data; convert_params.input_exe_data = exe_data; convert_params.subset_flags = subset_flags; convert_params.deterministic = cmd_line_has_flag(cmdline, str8_lit("deterministic")); } ProfScope("convert") pdb_bake_params = p2r_convert(arena, &convert_params); } //- rjf: RDI inputs => RDI joining if(input_files_from_format_table[RB_FileFormat_RDI].count > 1) { convert_done = 1; log_infof("RDIs specified; joining RDIs\n"); // rjf: produce bake params for each RDI for EachNode(n, RB_FileNode, input_files_from_format_table[RB_FileFormat_RDI].first) { RB_File *f = n->v; // rjf: decompress RDI RDI_Parsed *rdi = 0; if(lane_idx() == 0) { rdi = push_array(arena, RDI_Parsed, 1); RDI_ParseStatus rdi_status = rdi_parse(f->data.str, f->data.size, rdi); U64 decompressed_size = rdi_decompressed_size_from_parsed(rdi); if(decompressed_size > rdi->raw_data_size) { U8 *decompressed_data = push_array_no_zero(arena, U8, decompressed_size); rdi_decompress_parsed(decompressed_data, decompressed_size, rdi); rdi_status = rdi_parse(decompressed_data, decompressed_size, rdi); } } lane_sync_u64(&rdi, 0); // rjf: RDI -> loose RDIM_BakeParams rdi_loose = rdim_loose_from_rdi(arena, subset_flags, rdi); // rjf: add RDIM_BakeParamsNode *n = push_array(arena, RDIM_BakeParamsNode, 1); n->v = rdi_loose; SLLQueuePush(first_rdi_bake_params, last_rdi_bake_params, n); } } } lane_sync(); //- rjf: join conversion artifacts RDIM_BakeParams *bake_params = 0; if(lane_idx() == 0) { bake_params = push_array(arena, RDIM_BakeParams, 1); rdim_bake_params_concat_in_place(bake_params, &pdb_bake_params); // rdim_bake_params_concat_in_place(bake_params, &dwarf_bake_params); rdim_bake_params_concat_in_place(bake_params, &dwarf_bake_params_2); for EachNode(n, RDIM_BakeParamsNode, first_rdi_bake_params) { rdim_bake_params_concat_in_place(bake_params, &n->v); } } lane_sync_u64(&bake_params, 0); //- rjf: no output path? -> pick one based on input files if(output_path.size == 0) { String8 output_path__noext = {0}; if(output_path__noext.size == 0) { output_path__noext = str8_chop_last_dot(rb_file_list_first(&input_files_from_format_table[RB_FileFormat_PDB])->path); } if(output_path__noext.size == 0) { output_path__noext = str8_chop_last_dot(rb_file_list_first(&input_files_from_format_table[RB_FileFormat_PE])->path); } if(output_path__noext.size == 0) { output_path__noext = str8_chop_last_dot(rb_file_list_first(&input_files_from_format_table[RB_FileFormat_ELF64])->path); } if(output_path__noext.size == 0) { output_path__noext = str8_chop_last_dot(rb_file_list_first(&input_files_from_format_table[RB_FileFormat_ELF32])->path); } if(output_path__noext.size == 0) { output_path__noext = str8_chop_last_dot(rb_file_list_first(&input_files_from_format_table[RB_FileFormat_RDI])->path); } switch(output_kind) { default:{}break; case OutputKind_RDI: { output_path = str8f(arena, "%S.rdi", output_path__noext); }break; case OutputKind_Breakpad: { output_path = str8f(arena, "%S.psym", output_path__noext); }break; } } //- rjf: special case: only a single RDI file passed? all conversion is trivially done, just // package up RDI data as serialized section bundle, and let the rest of the paths use it. B32 noop_conversion = 0; if(input_files.count == 1 && rb_file_list_first(&input_files)->format == RB_FileFormat_RDI) { noop_conversion = 1; convert_done = 1; log_infof("Single RDI specified; passing through (skipping conversion)"); } //- rjf: no viable input paths if(!convert_done && cmdline->inputs.node_count != 0) { log_user_errorf("Could not load debug info from the specified inputs. You must provide a valid PDB file, an executable image (PE, ELF) file with DWARF debug info, or RDI file(s)."); } //- rjf: bake RDIM_BakeResults bake_results = {0}; ProfScope("bake") { bake_results = rdim_bake(arena, bake_params); } //- rjf: serialize RDIM_SerializedSectionBundle *serialized_section_bundle = 0; ProfScope("serialize") if(lane_idx() == 0) { serialized_section_bundle = push_array(arena, RDIM_SerializedSectionBundle, 1); serialized_section_bundle[0] = bake_results.section_bundle; } lane_sync_u64(&serialized_section_bundle, 0); //- rjf: special case: no-op conversion (single RDI file) if(lane_idx() == 0 && noop_conversion) { RB_File *f = rb_file_list_first(&input_files); RDI_Parsed rdi = {0}; RDI_ParseStatus rdi_status = rdi_parse(f->data.str, f->data.size, &rdi); U64 decompressed_size = rdi_decompressed_size_from_parsed(&rdi); if(decompressed_size > rdi.raw_data_size) { U8 *decompressed_data = push_array_no_zero(arena, U8, decompressed_size); rdi_decompress_parsed(decompressed_data, decompressed_size, &rdi); rdi_status = rdi_parse(decompressed_data, decompressed_size, &rdi); } for EachIndex(idx, rdi.sections_count) { if(idx < RDI_SectionKind_COUNT) { serialized_section_bundle->sections[idx].data = rdi.raw_data + rdi.sections[idx].off; serialized_section_bundle->sections[idx].encoded_size = rdi.sections[idx].encoded_size; serialized_section_bundle->sections[idx].unpacked_size = rdi.sections[idx].unpacked_size; serialized_section_bundle->sections[idx].encoding = rdi.sections[idx].encoding; } } } lane_sync(); //- rjf: convert done => generate output if(convert_done) switch(output_kind) { default:{}break; //- rjf: generate RDI blobs case OutputKind_RDI: { // rjf: compress RDIM_SerializedSectionBundle serialized_section_bundle__compressed = serialized_section_bundle[0]; if(cmd_line_has_flag(cmdline, str8_lit("compress"))) ProfScope("compress") { serialized_section_bundle__compressed = rdim_compress(arena, serialized_section_bundle); } // rjf: serialize String8List blobs = rdim_file_blobs_from_section_bundle(arena, &serialized_section_bundle__compressed); str8_list_concat_in_place(&output_blobs, &blobs); }break; //- rjf: generate breakpad text case OutputKind_Breakpad: { //- rjf: flatten to RDI data String8List rdi_blobs = rdim_file_blobs_from_section_bundle(arena, serialized_section_bundle); String8 rdi_data = str8_list_join(arena, &rdi_blobs, 0); RDI_Parsed rdi_ = {0}; RDI_Parsed *rdi = &rdi_; RDI_ParseStatus rdi_status = rdi_parse(rdi_data.str, rdi_data.size, rdi); //- rjf: set up shared state typedef struct P2B_Shared P2B_Shared; struct P2B_Shared { String8List dump; String8List *lane_file_dumps; String8List *lane_func_dumps; }; P2B_Shared *p2b_shared = 0; if(lane_idx() == 0) { p2b_shared = push_array(arena, P2B_Shared, 1); p2b_shared->lane_file_dumps = push_array(arena, String8List, lane_count()); p2b_shared->lane_func_dumps = push_array(arena, String8List, lane_count()); } lane_sync_u64(&p2b_shared, 0); //- rjf: dump MODULE record if(lane_idx() == 0) { // rjf: pick name to identify module RDI_TopLevelInfo *tli = rdi_element_from_name_idx(rdi, TopLevelInfo, 0); String8 module_name_string = str8_from_rdi_string_idx(rdi, tli->exe_name_string_idx); if(module_name_string.size == 0 && input_files.first != 0) { module_name_string = input_files.first->v->path; } // rjf: pick string for unique code String8 unique_identifier_string = {0}; if(unique_identifier_string.size == 0 && tli->exe_hash != 0) { unique_identifier_string = str8f(arena, "%I64x", tli->exe_hash); } if(unique_identifier_string.size == 0) { Temp scratch = scratch_begin(&arena, 1); String8 msf_data = input_files.first->v->data; MSF_RawStreamTable *st = msf_raw_stream_table_from_data(scratch.arena, msf_data); String8 info_data = msf_data_from_stream_number(scratch.arena, msf_data, st, PDB_FixedStream_Info); PDB_Info *info = pdb_info_from_data(scratch.arena, info_data); if(info != 0 && info_data.size >= sizeof(PDB_InfoHeader)) { PDB_InfoHeader *info_header = (PDB_InfoHeader *)info_data.str; Guid guid = info->auth_guid; unique_identifier_string = str8f(arena, "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%u", guid.data1, guid.data2, guid.data3, guid.data4[0], guid.data4[1], guid.data4[2], guid.data4[3], guid.data4[4], guid.data4[5], guid.data4[6], guid.data4[7], info_header->age); } scratch_end(scratch); } // rjf: push record str8_list_pushf(arena, &p2b_shared->dump, "MODULE windows x86_64 %S %S\n", unique_identifier_string, module_name_string); } //- rjf: dump FILE records ProfScope("dump FILE records") { U64 count = 0; RDI_SourceFile *v = rdi_table_from_name(rdi, SourceFiles, &count); Rng1U64 range = lane_range(count); for EachInRange(idx, range) { String8List *out = &p2b_shared->lane_file_dumps[lane_idx()]; Temp scratch = scratch_begin(&arena, 1); String8 src_path = str8_from_rdi_path_node_idx(scratch.arena, rdi, PathStyle_Relative, v[idx].file_path_node_idx); str8_list_pushf(arena, out, "FILE %I64u %S\n", idx, src_path); scratch_end(scratch); } } //- rjf: dump FUNC records ProfScope("dump FUNC records") { U64 count = 0; RDI_Symbol *v = rdi_table_from_name(rdi, Procedures, &count); Rng1U64 range = lane_range(count); for EachInRange(idx, range) { // NOTE(rjf): breakpad does not support multiple voff ranges per procedure. String8List *out = &p2b_shared->lane_func_dumps[lane_idx()]; RDI_Symbol *proc = &v[idx]; RDI_Scope *root_scope = rdi_element_from_name_idx(rdi, Scopes, proc->root_scope_idx); if(root_scope->voff_range_opl > root_scope->voff_range_first) { // rjf: dump function record RDIM_Rng1U64 voff_range = { *rdi_element_from_name_idx(rdi, ScopeVOffData, root_scope->voff_range_first), *rdi_element_from_name_idx(rdi, ScopeVOffData, root_scope->voff_range_opl - 1), }; str8_list_pushf(arena, out, "FUNC %I64x %I64x %I64x %S\n", voff_range.min, voff_range.max-voff_range.min, 0ull, str8_from_rdi_string_idx(rdi, proc->name_string_idx)); // rjf: dump function lines U64 unit_vmap_count = 0; RDI_VMapEntry *unit_vmap = rdi_table_from_name(rdi, UnitVMap, &unit_vmap_count); RDI_Unit *unit = rdi_unit_from_voff(rdi, voff_range.min); RDI_LineTable *line_table = rdi_line_table_from_unit(rdi, unit); RDI_ParsedLineTable line_info = {0}; rdi_parsed_from_line_table(rdi, line_table, &line_info); for(U64 voff = voff_range.min, last_voff = 0; voff < voff_range.max && voff > last_voff;) { RDI_U64 line_info_idx = rdi_line_info_idx_from_voff(&line_info, voff); if(line_info_idx < line_info.count) { RDI_Line *line = &line_info.lines[line_info_idx]; U64 line_voff_min = line_info.voffs[line_info_idx]; U64 line_voff_opl = line_info.voffs[line_info_idx+1]; if(line->file_idx != 0) { str8_list_pushf(arena, out, "%I64x %I64x %I64u %I64u\n", line_voff_min, line_voff_opl-line_voff_min, (U64)line->line_num, (U64)line->file_idx); } last_voff = voff; voff = line_voff_opl; } else { break; } } } } } //- rjf: join lane_sync(); if(lane_idx() == 0) { for EachIndex(ln_idx, lane_count()) { str8_list_concat_in_place(&p2b_shared->dump, &p2b_shared->lane_file_dumps[ln_idx]); } for EachIndex(ln_idx, lane_count()) { str8_list_concat_in_place(&p2b_shared->dump, &p2b_shared->lane_func_dumps[ln_idx]); } } lane_sync(); output_blobs = p2b_shared->dump; }break; //- rjf: generate voff -> line results case OutputKind_VOff2Line: { //- rjf: unpack voff arg U64 voff = 0; { String8 voff_str = cmd_line_string(cmdline, str8_lit("voff")); try_u64_from_str8_c_rules(voff_str, &voff); log_infof("User specified \"%S\" as virtual offset; parsed as 0x%I64x", voff_str, voff); } //- rjf: flatten to RDI data String8List rdi_blobs = rdim_file_blobs_from_section_bundle(arena, serialized_section_bundle); String8 rdi_data = str8_list_join(arena, &rdi_blobs, 0); RDI_Parsed rdi_ = {0}; RDI_Parsed *rdi = &rdi_; RDI_ParseStatus rdi_status = rdi_parse(rdi_data.str, rdi_data.size, rdi); //- rjf: voff -> line RDI_Line line = rdi_line_from_voff(rdi, voff); RDI_SourceFile *src_file = rdi_element_from_name_idx(rdi, SourceFiles, line.file_idx); //- rjf: dump line info { Temp scratch = scratch_begin(0, 0); 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); str8_list_pushf(arena, &output_blobs, "[inlined] %S %S:%u\n", inline_name, path, inline_line.line_num); } } } } String8 path = str8_from_rdi_path_node_idx(scratch.arena, rdi, PathStyle_SystemAbsolute, src_file->file_path_node_idx); str8_list_pushf(arena, &output_blobs, "%S:%u\n", path, line.line_num); scratch_end(scratch); } }break; } }break; //////////////////////////// //- rjf: dump -> textual dump of inputs // case OutputKind_Dump: { B32 deterministic = cmd_line_has_flag(cmdline, str8_lit("deterministic")); //- rjf: no inputs => help if(lane_idx() == 0 && cmdline->inputs.node_count == 0) { fprintf(stderr, "All input files specified on the command line will be dumped. Currently, only\n"); fprintf(stderr, "RDI files are supported.\n\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "ARGUMENTS\n\n"); fprintf(stderr, "--only: Specifies that only the named subsets should\n"); fprintf(stderr, " be dumped. See below for a list of valid\n"); fprintf(stderr, " subset names.\n"); fprintf(stderr, "\n"); fprintf(stderr, "--omit: Specifies that the named subsets should not\n"); fprintf(stderr, " be dumped. See below for a list of valid\n"); fprintf(stderr, " subset names.\n"); fprintf(stderr, "\n"); fprintf(stderr, "--verbose dump more format details\n"); fprintf(stderr, "\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "RDI Sections:\n"); #define X(name, name_lower, title) fprintf(stderr, " - " #name_lower "\n"); RDI_DumpSubset_XList #undef X fprintf(stderr, "\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n\n"); fprintf(stderr, "DWARF Sections:\n"); #define X(_N, _L, ...) fprintf(stderr, _L "\n"); DW_SectionKind_XList #undef X fprintf(stderr, "\n"); } B32 verbose = cmd_line_has_flag(cmdline, str8_lit("verbose")); //- rjf: unpack dump subset flags RDI_DumpSubsetFlags rdi_dump_subset_flags = RDI_DumpSubsetFlag_All; DW_SectionFlags dw_dump_subset_flags = DW_SectionFlag_All; EH_DumpSubsetFlags eh_dump_subset_flags = EH_DumpSubsetFlag_All; ELF_DumpSubsetFlags elf_dump_subset_flags = ELF_DumpSubsetFlag_All; { String8List only_names = cmd_line_strings(cmdline, str8_lit("only")); if(only_names.node_count != 0) { rdi_dump_subset_flags = 0; dw_dump_subset_flags = 0; eh_dump_subset_flags = 0; } for(String8Node *n = only_names.first; n != 0; n = n->next) { if(0){} #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { rdi_dump_subset_flags |= RDI_DumpSubsetFlag_##name; } RDI_DumpSubset_XList #undef X #define X(_N, _L, ...) else if(str8_match(n->string, str8_skip(str8_lit(_L), 1), 0)) { dw_dump_subset_flags |= DW_SectionFlag_##_N; } DW_SectionKind_XList #undef X #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { eh_dump_subset_flags |= EH_DumpSubsetFlag_##name; } EH_DumpSubset_XList #undef X #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { elf_dump_subset_flags |= ELF_DumpSubsetFlag_##name; } ELF_DumpSubset_XList #undef X } String8List omit_names = cmd_line_strings(cmdline, str8_lit("omit")); for(String8Node *n = omit_names.first; n != 0; n = n->next) { if(0){} #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { rdi_dump_subset_flags &= ~RDI_DumpSubsetFlag_##name; } RDI_DumpSubset_XList #undef X #define X(_N, _L, ...) else if(str8_match(n->string, str8_skip(str8_lit(_L), 1), 0)) { dw_dump_subset_flags &= ~DW_SectionFlag_##_N; } DW_SectionKind_XList #undef X #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { eh_dump_subset_flags &= ~EH_DumpSubsetFlag_##name; } EH_DumpSubset_XList #undef X #define X(name, name_lower, title) else if(str8_match(n->string, str8_lit(#name_lower), 0)) { elf_dump_subset_flags &= ~ELF_DumpSubsetFlag_##name; } ELF_DumpSubset_XList #undef X } } //- rjf: dump input files in ordere for(RB_FileNode *n = input_files.first; n != 0; n = n->next) { RB_File *f = n->v; if(lane_idx() == 0) { if(!deterministic) { str8_list_pushf(arena, &output_blobs, "// %S (%S)\n\n", f->path, f->format ? rb_file_format_display_name_table[f->format] : str8_lit("Unsupported format")); } } lane_sync(); //- rjf: unpack file parses Arch arch = Arch_Null; PE_BinInfo pe = {0}; ELF_Bin elf = {0}; COFF_FileHeaderInfo coff_obj = {0}; DW_Raw dw = {0}; U64 eh_frame_hdr_vaddr = 0; U64 eh_frame_vaddr = 0; String8 eh_frame_hdr = {0}; String8 eh_frame = {0}; { if(f->format == RB_FileFormat_PE) { pe = pe_bin_info_from_data(arena, f->data); arch = pe.arch; } else if(f->format == RB_FileFormat_COFF_OBJ || f->format == RB_FileFormat_COFF_BigOBJ) { coff_obj = coff_file_header_info_from_data(f->data); String8 string_table = str8_substr(f->data, coff_obj.string_table_range); U64 section_count = coff_obj.section_count_no_null; COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(f->data, coff_obj.section_table_range).str; dw = dw_input_from_coff_section_table(arena, f->data, string_table, section_count, section_table); } else if(f->format == RB_FileFormat_ELF32 || f->format == RB_FileFormat_ELF64) { elf = elf_bin_from_data(arena, f->data); arch = arch_from_elf_machine(elf.hdr.e_machine); for EachIndex(sect_idx, elf.hdr.e_shnum) { ELF_Shdr64 *shdr = &elf.shdrs.v[sect_idx]; String8 name = elf_name_from_shdr64(f->data, &elf, shdr); if (str8_match(name, str8_lit(".eh_frame_hdr"), 0)) { eh_frame_hdr = str8_substr(f->data, r1u64(shdr->sh_offset, shdr->sh_offset + shdr->sh_size)); eh_frame_hdr_vaddr = shdr->sh_addr; } else if(str8_match(name, str8_lit(".eh_frame"), 0)) { eh_frame = str8_substr(f->data, r1u64(shdr->sh_offset, shdr->sh_offset + shdr->sh_size)); eh_frame_vaddr = shdr->sh_addr; } } } if(f->format_flags & RB_FileFormatFlag_HasDWARF) { if(f->format == RB_FileFormat_PE) { String8 raw_sections = str8_substr(f->data, pe.section_table_range); U64 section_count = raw_sections.size / sizeof(COFF_SectionHeader); COFF_SectionHeader *section_table = (COFF_SectionHeader *)raw_sections.str; String8 string_table = str8_substr(f->data, pe.string_table_range); dw = dw_input_from_coff_section_table(arena, f->data, string_table, section_count, section_table); } else if(f->format == RB_FileFormat_ELF32 || f->format == RB_FileFormat_ELF64) { dw = dw_input_from_elf_bin(arena, f->data, &elf); } } } //- rjf: dump file info based on format switch(f->format) { default:{}break; //- rjf: PDB case RB_FileFormat_PDB: { // TODO(rjf) }break; //- rjf: PE case RB_FileFormat_PE: { // TODO(rjf) }break; //- rjf: COFF OBJ case RB_FileFormat_COFF_OBJ: { // TODO(rjf) }break; //- rjf: COFF big OBJ case RB_FileFormat_COFF_BigOBJ: { // TODO(rjf) }break; //- rjf: COFF archive case RB_FileFormat_COFF_Archive: case RB_FileFormat_COFF_ThinArchive: { // TODO(rjf) }break; //- rjf: ELF case RB_FileFormat_ELF32: case RB_FileFormat_ELF64: { String8List elf_strings = elf_dump(arena, f->data, elf_dump_subset_flags); str8_list_concat_in_place(&output_blobs, &elf_strings); }break; //- rjf: RDI file case RB_FileFormat_RDI: { RDI_Parsed rdi = {0}; RDI_ParseStatus rdi_status = rdi_parse(f->data.str, f->data.size, &rdi); U64 decompressed_size = rdi_decompressed_size_from_parsed(&rdi); if(decompressed_size > rdi.raw_data_size) { U8 *decompressed_data = push_array_no_zero(arena, U8, decompressed_size); rdi_decompress_parsed(decompressed_data, decompressed_size, &rdi); rdi_status = rdi_parse(decompressed_data, decompressed_size, &rdi); } switch(rdi_status) { default:{}break; case RDI_ParseStatus_HeaderDoesNotMatch: {log_user_errorf("RDI parse failure: header does not match\n");}break; case RDI_ParseStatus_UnsupportedVersionNumber:{log_user_errorf("RDI parse failure: unsupported version\n");}break; case RDI_ParseStatus_InvalidDataSecionLayout: {log_user_errorf("RDI parse failure: invalid data section layout\n");}break; case RDI_ParseStatus_Good: { String8List dump = rdi_dump_list_from_parsed(arena, &rdi, rdi_dump_subset_flags); if(lane_idx() == 0) { str8_list_concat_in_place(&output_blobs, &dump); } }break; } }break; } //- rjf: dump file extension info if(f->format_flags & RB_FileFormatFlag_HasDWARF) { if(lane_idx() == 0) { str8_list_pushf(arena, &output_blobs, "// %S (%S) (DWARF)\n\n", deterministic ? str8_skip_last_slash(f->path) : f->path, f->format ? rb_file_format_display_name_table[f->format] : str8_lit("Unsupported format")); String8List dump = dw_dump_list_from_sections(arena, &dw, arch, dw_dump_subset_flags, verbose); str8_list_concat_in_place(&output_blobs, &dump); } lane_sync(); } if(eh_dump_subset_flags) { if(lane_idx() == 0) { String8List dump = eh_dump_list_from_data(arena, arch, eh_frame_hdr_vaddr, eh_frame_vaddr, eh_frame_hdr, eh_frame, eh_dump_subset_flags); str8_list_concat_in_place(&output_blobs, &dump); } lane_sync(); } } }break; } ////////////////////////////// //- rjf: write outputs // if(lane_idx() == 0) { if(output_path.size != 0) ProfScope("write outputs [file]") { B32 is_written = os_write_data_list_to_file_path(output_path, output_blobs); if(is_written) { log_infof("Results written to %S", output_path); } else { log_user_errorf("ERROR: failed to write file %S\n", output_path); } } else ProfScope("write outputs [stdout]") { for(String8Node *n = output_blobs.first; n != 0; n = n->next) { for(U64 off = 0; off < n->string.size;) { U64 size_to_write = Min(n->string.size - off, GB(2)); fwrite(n->string.str + off, size_to_write, 1, stdout); off += size_to_write; } } log_info(str8_lit("Results written to stdout")); } } lane_sync(); ////////////////////////////// //- rjf: write info & errors // LogScopeResult log_scope = log_scope_end(arena); if(lane_idx() == 0) { if(cmd_line_has_flag(cmdline, str8_lit("verbose")) && log_scope.strings[LogMsgKind_Info].size != 0) { String8List lines = wrapped_lines_from_string(arena, log_scope.strings[LogMsgKind_Info], 80, 80, 0); for(String8Node *n = lines.first; n != 0; n = n->next) { fprintf(stderr, "%.*s\n", str8_varg(n->string)); } } if(log_scope.strings[LogMsgKind_UserError].size != 0) { String8List lines = wrapped_lines_from_string(arena, log_scope.strings[LogMsgKind_UserError], 80, 80, 0); for(String8Node *n = lines.first; n != 0; n = n->next) { fprintf(stderr, "%.*s\n", str8_varg(n->string)); } } } }