// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Basic Enum Functions internal U32 pe_slot_count_from_unwind_op_code(PE_UnwindOpCode opcode) { U32 result = 0; switch(opcode) { case PE_UnwindOpCode_PUSH_NONVOL: result = 1; break; case PE_UnwindOpCode_ALLOC_LARGE: result = 2; break; case PE_UnwindOpCode_ALLOC_SMALL: result = 1; break; case PE_UnwindOpCode_SET_FPREG: result = 1; break; case PE_UnwindOpCode_SAVE_NONVOL: result = 2; break; case PE_UnwindOpCode_SAVE_NONVOL_FAR: result = 3; break; case PE_UnwindOpCode_EPILOG: result = 2; break; case PE_UnwindOpCode_SPARE_CODE: result = 3; break; case PE_UnwindOpCode_SAVE_XMM128: result = 2; break; case PE_UnwindOpCode_SAVE_XMM128_FAR: result = 3; break; case PE_UnwindOpCode_PUSH_MACHFRAME: result = 1; break; } return result; } global read_only struct { String8 string; PE_WindowsSubsystem type; } g_pe_subsystem_map[] = { { str8_lit_comp(""), PE_WindowsSubsystem_UNKNOWN }, { str8_lit_comp("native"), PE_WindowsSubsystem_NATIVE }, { str8_lit_comp("windows"), PE_WindowsSubsystem_WINDOWS_GUI }, { str8_lit_comp("console"), PE_WindowsSubsystem_WINDOWS_CUI }, { str8_lit_comp("os2_cui"), PE_WindowsSubsystem_OS2_CUI }, { str8_lit_comp("posix"), PE_WindowsSubsystem_POSIX_CUI }, { str8_lit_comp("native_windows"), PE_WindowsSubsystem_NATIVE_WINDOWS }, { str8_lit_comp("windows_ce_gui"), PE_WindowsSubsystem_WINDOWS_CE_GUI }, { str8_lit_comp("efi_application"), PE_WindowsSubsystem_EFI_APPLICATION }, { str8_lit_comp("efi_boot_service_driver"), PE_WindowsSubsystem_EFI_BOOT_SERVICE_DRIVER }, { str8_lit_comp("efi_runtime_driver"), PE_WindowsSubsystem_EFI_RUNTIME_DRIVER }, { str8_lit_comp("efi_rom"), PE_WindowsSubsystem_EFI_ROM }, { str8_lit_comp("xbox"), PE_WindowsSubsystem_XBOX }, { str8_lit_comp("windows_boot_application"), PE_WindowsSubsystem_WINDOWS_BOOT_APPLICATION }, }; StaticAssert(ArrayCount(g_pe_subsystem_map) == PE_WindowsSubsystem_COUNT, g_pe_subsystem_map_count_check); internal String8 pe_string_from_subsystem(PE_WindowsSubsystem subsystem) { for (U64 i = 0; i < ArrayCount(g_pe_subsystem_map); i += 1) { if (g_pe_subsystem_map[i].type == subsystem) { return g_pe_subsystem_map[i].string; } } return str8(0,0); } internal String8 pe_string_from_unwind_gpr_x64(PE_UnwindGprRegX64 x) { switch (x) { case PE_UnwindGprRegX64_RAX: return str8_lit("RCX"); case PE_UnwindGprRegX64_RCX: return str8_lit("RCX"); case PE_UnwindGprRegX64_RDX: return str8_lit("RDX"); case PE_UnwindGprRegX64_RBX: return str8_lit("RBX"); case PE_UnwindGprRegX64_RSP: return str8_lit("RSP"); case PE_UnwindGprRegX64_RBP: return str8_lit("RBP"); case PE_UnwindGprRegX64_RSI: return str8_lit("RSI"); case PE_UnwindGprRegX64_RDI: return str8_lit("RDI"); case PE_UnwindGprRegX64_R8: return str8_lit("R8"); case PE_UnwindGprRegX64_R9: return str8_lit("R9"); case PE_UnwindGprRegX64_R10: return str8_lit("R10"); case PE_UnwindGprRegX64_R11: return str8_lit("R11"); case PE_UnwindGprRegX64_R12: return str8_lit("R12"); case PE_UnwindGprRegX64_R13: return str8_lit("R13"); case PE_UnwindGprRegX64_R14: return str8_lit("R14"); case PE_UnwindGprRegX64_R15: return str8_lit("R15"); default: InvalidPath; } return str8_zero(); } internal String8 pe_string_from_data_directory_index(PE_DataDirectoryIndex x) { switch (x) { case PE_DataDirectoryIndex_EXPORT: return str8_lit("Export"); case PE_DataDirectoryIndex_IMPORT: return str8_lit("Import"); case PE_DataDirectoryIndex_RESOURCES: return str8_lit("Resources"); case PE_DataDirectoryIndex_EXCEPTIONS: return str8_lit("Exceptions"); case PE_DataDirectoryIndex_CERT: return str8_lit("Cert"); case PE_DataDirectoryIndex_BASE_RELOC: return str8_lit("BaseReloc"); case PE_DataDirectoryIndex_DEBUG: return str8_lit("Debug"); case PE_DataDirectoryIndex_ARCH: return str8_lit("Arch"); case PE_DataDirectoryIndex_GLOBAL_PTR: return str8_lit("GlobalPtr"); case PE_DataDirectoryIndex_TLS: return str8_lit("TLS"); case PE_DataDirectoryIndex_LOAD_CONFIG: return str8_lit("LoadConfig"); case PE_DataDirectoryIndex_BOUND_IMPORT: return str8_lit("BoundImport"); case PE_DataDirectoryIndex_IMPORT_ADDR: return str8_lit("ImportAddr"); case PE_DataDirectoryIndex_DELAY_IMPORT: return str8_lit("DelayImport"); case PE_DataDirectoryIndex_COM_DESCRIPTOR: return str8_lit("COM Descriptor"); case PE_DataDirectoryIndex_RESERVED: return str8_lit("Reserved"); default: InvalidPath; } return str8_zero(); } internal String8 pe_string_from_debug_directory_type(PE_DebugDirectoryType x) { switch (x) { case PE_DebugDirectoryType_UNKNOWN: return str8_lit("UNKNOWN"); case PE_DebugDirectoryType_COFF: return str8_lit("COFF"); case PE_DebugDirectoryType_CODEVIEW: return str8_lit("CODEVIEW"); case PE_DebugDirectoryType_FPO: return str8_lit("FPO"); case PE_DebugDirectoryType_MISC: return str8_lit("MISC"); case PE_DebugDirectoryType_EXCEPTION: return str8_lit("EXCEPTION"); case PE_DebugDirectoryType_FIXUP: return str8_lit("FIXUP"); case PE_DebugDirectoryType_OMAP_TO_SRC: return str8_lit("OMAP_TO_SRC"); case PE_DebugDirectoryType_OMAP_FROM_SRC: return str8_lit("OMAP_FROM_SRC"); case PE_DebugDirectoryType_BORLAND: return str8_lit("BORLAND"); case PE_DebugDirectoryType_RESERVED10: return str8_lit("RESERVED10"); case PE_DebugDirectoryType_CLSID: return str8_lit("CLSID"); case PE_DebugDirectoryType_VC_FEATURE: return str8_lit("VC_FEATURE"); case PE_DebugDirectoryType_COFF_GROUP: return str8_lit("COFF_GROUP"); case PE_DebugDirectoryType_ILTCG: return str8_lit("ILTCG"); case PE_DebugDirectoryType_MPX: return str8_lit("MPX"); case PE_DebugDirectoryType_REPRO: return str8_lit("REPRO"); case PE_DebugDirectoryType_EX_DLLCHARACTERISTICS: return str8_lit("EX_DLLCHARACTERISTICS"); case PE_DebugDirectoryType_COUNT: return str8_lit("COUNT"); } return str8_zero(); } internal String8 pe_string_from_fpo_type(PE_FPOType x) { switch (x) { case PE_FPOType_FPO: return str8_lit("FPO"); case PE_FPOType_TRAP: return str8_lit("TRAP"); case PE_FPOType_TSS: return str8_lit("TSS"); case PE_FPOType_NOFPO: return str8_lit("NOFPO"); } return str8_zero(); } internal String8 pe_string_from_misc_type(PE_DebugMiscType x) { switch (x) { case PE_DebugMiscType_NULL: break; case PE_DebugMiscType_EXE_NAME: return str8_lit("EXE_NAME"); } return str8_zero(); } internal String8 pe_resource_kind_to_string(PE_ResourceKind x) { String8 result = str8_zero(); switch (x) { case PE_ResourceKind_ACCELERATOR: result = str8_lit("Accelerator"); break; case PE_ResourceKind_ANICURSOR: result = str8_lit("Animated Cursor"); break; case PE_ResourceKind_ANIICON: result = str8_lit("Animated Icon"); break; case PE_ResourceKind_BITMAP: result = str8_lit("Bitmap"); break; case PE_ResourceKind_CURSOR: result = str8_lit("Cursor"); break; case PE_ResourceKind_DIALOG: result = str8_lit("Dialog"); break; case PE_ResourceKind_FONT: result = str8_lit("Font"); break; case PE_ResourceKind_FONTDIR: result = str8_lit("Font Directory"); break; case PE_ResourceKind_GROUP_CURSOR: result = str8_lit("Cursor Group"); break; case PE_ResourceKind_GROUP_ICON: result = str8_lit("Icon Group"); break; case PE_ResourceKind_HTML: result = str8_lit("HTML"); break; case PE_ResourceKind_ICON: result = str8_lit("Icon"); break; case PE_ResourceKind_MANIFEST: result = str8_lit("Manifest"); break; case PE_ResourceKind_MENU: result = str8_lit("Menu"); break; case PE_ResourceKind_MESSAGETABLE: result = str8_lit("Message Table"); break; case PE_ResourceKind_PLUGPLAY: result = str8_lit("Plug Play"); break; case PE_ResourceKind_RCDATA: result = str8_lit("RC Data"); break; case PE_ResourceKind_STRING: result = str8_lit("String"); break; case PE_ResourceKind_VERSION: result = str8_lit("Version Info"); break; case PE_ResourceKind_VXD: result = str8_lit("VXD"); break; } return result; } internal String8 pe_string_from_fpo_flags(Arena *arena, PE_FPOFlags flags) { Temp scratch = scratch_begin(&arena, 1); String8List l = {0}; if (flags & PE_FPOFlags_HAS_SEH) { str8_list_pushf(scratch.arena, &l, "HAS_SEH"); } if (flags & PE_FPOFlags_USE_BP_REG) { str8_list_pushf(scratch.arena, &l, "USE_BP_REG"); } if (flags & PE_FPOFlags_RESERVED) { str8_list_pushf(scratch.arena, &l, "RESERVED"); } String8 result = str8_list_join(arena, &l, &(StringJoin){.sep=str8_lit(" ")}); scratch_end(scratch); return result; } internal String8 pe_string_from_global_flags(Arena *arena, PE_GlobalFlags flags) { Temp scratch = scratch_begin(&arena, 1); String8List l = {0}; if (flags & PE_GlobalFlags_STOP_ON_EXCEPTION) { str8_list_pushf(scratch.arena, &l, "STOP_ON_EXCEPTION"); } if (flags & PE_GlobalFlags_SHOW_LDR_SNAPS) { str8_list_pushf(scratch.arena, &l, "SHOW_LDR_SNAPS"); } if (flags & PE_GlobalFlags_DEBUG_INITIAL_COMMAND) { str8_list_pushf(scratch.arena, &l, "DEBUG_INITIAL_COMMAND"); } if (flags & PE_GlobalFlags_STOP_ON_HUNG_GUI) { str8_list_pushf(scratch.arena, &l, "STOP_ON_HUNG_GUI"); } if (flags & PE_GlobalFlags_HEAP_ENABLE_TAIL_CHECK) { str8_list_pushf(scratch.arena, &l, "HEAP_ENABLE_TAIL_CHECK"); } if (flags & PE_GlobalFlags_HEAP_ENABLE_FREE_CHECK) { str8_list_pushf(scratch.arena, &l, "HEAP_ENABLE_FREE_CHECK"); } if (flags & PE_GlobalFlags_HEAP_VALIDATE_PARAMETERS) { str8_list_pushf(scratch.arena, &l, "HEAP_VALIDATE_PARAMETERS"); } if (flags & PE_GlobalFlags_HEAP_VALIDATE_ALL) { str8_list_pushf(scratch.arena, &l, "HEAP_VALIDATE_ALL"); } if (flags & PE_GlobalFlags_APPLICATION_VERIFIER) { str8_list_pushf(scratch.arena, &l, "APPLICATION_VERIFIER"); } if (flags & PE_GlobalFlags_POOL_ENABLE_TAGGING) { str8_list_pushf(scratch.arena, &l, "POOL_ENABLE_TAGGING"); } if (flags & PE_GlobalFlags_HEAP_ENABLE_TAGGING) { str8_list_pushf(scratch.arena, &l, "HEAP_ENABLE_TAGGING"); } if (flags & PE_GlobalFlags_STACK_TRACE_DB) { str8_list_pushf(scratch.arena, &l, "STACK_TRACE_DB"); } if (flags & PE_GlobalFlags_KERNEL_STACK_TRACE_DB) { str8_list_pushf(scratch.arena, &l, "KERNEL_STACK_TRACE_DB"); } if (flags & PE_GlobalFlags_MAINTAIN_OBJECT_TYPELIST) { str8_list_pushf(scratch.arena, &l, "MAINTAIN_OBJECT_TYPELIST"); } if (flags & PE_GlobalFlags_HEAP_ENABLE_TAG_BY_DLL) { str8_list_pushf(scratch.arena, &l, "HEAP_ENABLE_TAG_BY_DLL"); } if (flags & PE_GlobalFlags_DISABLE_STACK_EXTENSION) { str8_list_pushf(scratch.arena, &l, "DISABLE_STACK_EXTENSION"); } if (flags & PE_GlobalFlags_ENABLE_CSRDEBUG) { str8_list_pushf(scratch.arena, &l, "ENABLE_CSRDEBUG"); } if (flags & PE_GlobalFlags_ENABLE_KDEBUG_SYMBOL_LOAD) { str8_list_pushf(scratch.arena, &l, "ENABLE_KDEBUG_SYMBOL_LOAD"); } if (flags & PE_GlobalFlags_DISABLE_PAGE_KERNEL_STACKS) { str8_list_pushf(scratch.arena, &l, "DISABLE_PAGE_KERNEL_STACKS"); } if (flags & PE_GlobalFlags_ENABLE_SYSTEM_CRIT_BREAKS) { str8_list_pushf(scratch.arena, &l, "ENABLE_SYSTEM_CRIT_BREAKS"); } if (flags & PE_GlobalFlags_HEAP_DISABLE_COALESCING) { str8_list_pushf(scratch.arena, &l, "HEAP_DISABLE_COALESCING"); } if (flags & PE_GlobalFlags_ENABLE_CLOSE_EXCEPTIONS) { str8_list_pushf(scratch.arena, &l, "ENABLE_CLOSE_EXCEPTIONS"); } if (flags & PE_GlobalFlags_ENABLE_EXCEPTION_LOGGING) { str8_list_pushf(scratch.arena, &l, "ENABLE_EXCEPTION_LOGGING"); } if (flags & PE_GlobalFlags_ENABLE_HANDLE_TYPE_TAGGING) { str8_list_pushf(scratch.arena, &l, "ENABLE_HANDLE_TYPE_TAGGING"); } if (flags & PE_GlobalFlags_HEAP_PAGE_ALLOCS) { str8_list_pushf(scratch.arena, &l, "HEAP_PAGE_ALLOCS"); } if (flags & PE_GlobalFlags_DEBUG_INITIAL_COMMAND_EX) { str8_list_pushf(scratch.arena, &l, "DEBUG_INITIAL_COMMAND_EX"); } if (flags & PE_GlobalFlags_DISABLE_DBGPRINT) { str8_list_pushf(scratch.arena, &l, "DISABLE_DBGPRINT"); } if (flags & PE_GlobalFlags_CRITSEC_EVENT_CREATION) { str8_list_pushf(scratch.arena, &l, "CRITSEC_EVENT_CREATION"); } if (flags & PE_GlobalFlags_LDR_TOP_DOWN) { str8_list_pushf(scratch.arena, &l, "LDR_TOP_DOWN"); } if (flags & PE_GlobalFlags_ENABLE_HANDLE_EXCEPTIONS) { str8_list_pushf(scratch.arena, &l, "ENABLE_HANDLE_EXCEPTIONS"); } if (flags & PE_GlobalFlags_DISABLE_PROTDLLS) { str8_list_pushf(scratch.arena, &l, "DISABLE_PROTDLLS"); } String8 result = str8_list_join(arena, &l, &(StringJoin){.sep=str8_lit(" ")}); return result; } internal String8 pe_string_from_load_config_guard_flags(Arena *arena, PE_LoadConfigGuardFlags flags) { Temp scratch = scratch_begin(&arena, 1); String8List l = {0}; if (flags & PE_LoadConfigGuardFlags_CF_INSTRUMENTED) { str8_list_pushf(scratch.arena, &l, "CF_INSTRUMENTED"); } if (flags & PE_LoadConfigGuardFlags_CFW_INSTRUMENTED) { str8_list_pushf(scratch.arena, &l, "CFW_INSTRUMENTED"); } if (flags & PE_LoadConfigGuardFlags_CF_FUNCTION_TABLE_PRESENT) { str8_list_pushf(scratch.arena, &l, "CF_FUNCTION_TABLE_PRESENT"); } if (flags & PE_LoadConfigGuardFlags_SECURITY_COOKIE_UNUSED) { str8_list_pushf(scratch.arena, &l, "SECURITY_COOKIE_UNUSED"); } if (flags & PE_LoadConfigGuardFlags_PROTECT_DELAYLOAD_IAT) { str8_list_pushf(scratch.arena, &l, "PROTECT_DELAYLOAD_IAT"); } if (flags & PE_LoadConfigGuardFlags_DELAYLOAD_IAT_IN_ITS_OWN_SECTION) { str8_list_pushf(scratch.arena, &l, "DELAYLOAD_IAT_IN_ITS_OWN_SECTION"); } if (flags & PE_LoadConfigGuardFlags_CF_EXPORT_SUPPRESSION_INFO_PRESENT) { str8_list_pushf(scratch.arena, &l, "CF_EXPORT_SUPPRESSION_INFO_PRESENT"); } if (flags & PE_LoadConfigGuardFlags_CF_ENABLE_EXPORT_SUPPRESSION) { str8_list_pushf(scratch.arena, &l, "CF_ENABLE_EXPORT_SUPPRESSION"); } if (flags & PE_LoadConfigGuardFlags_CF_LONGJUMP_TABLE_PRESENT) { str8_list_pushf(scratch.arena, &l, "CF_LONGJUMP_TABLE_PRESENT"); } if (flags & PE_LoadConfigGuardFlags_EH_CONTINUATION_TABLE_PRESENT) { str8_list_pushf(scratch.arena, &l, "EH_CONTINUATION_TABLE_PRESENT"); } String8 result = str8_list_join(arena, &l, &(StringJoin){.sep = str8_lit(" ")}); scratch_end(scratch); return result; } internal String8 pe_string_from_dll_characteristics(Arena *arena, PE_DllCharacteristics dll_chars) { Temp scratch = scratch_begin(&arena, 1); String8List l = {0}; if (dll_chars & PE_DllCharacteristic_HIGH_ENTROPY_VA) { str8_list_pushf(scratch.arena, &l, "High Entropy Virtual Address"); } if (dll_chars & PE_DllCharacteristic_DYNAMIC_BASE) { str8_list_pushf(scratch.arena, &l, "Dynamic Base"); } if (dll_chars & PE_DllCharacteristic_FORCE_INTEGRITY) { str8_list_pushf(scratch.arena, &l, "Force Integrity"); } if (dll_chars & PE_DllCharacteristic_NX_COMPAT) { str8_list_pushf(scratch.arena, &l, "NX Compatible"); } if (dll_chars & PE_DllCharacteristic_NO_ISOLATION) { str8_list_pushf(scratch.arena, &l, "No Isolation"); } if (dll_chars & PE_DllCharacteristic_NO_SEH) { str8_list_pushf(scratch.arena, &l, "No SEH"); } if (dll_chars & PE_DllCharacteristic_NO_BIND) { str8_list_pushf(scratch.arena, &l, "No Bind"); } if (dll_chars & PE_DllCharacteristic_APPCONTAINER) { str8_list_pushf(scratch.arena, &l, "App Container"); } if (dll_chars & PE_DllCharacteristic_WDM_DRIVER) { str8_list_pushf(scratch.arena, &l, "WDM Driver"); } if (dll_chars & PE_DllCharacteristic_GUARD_CF) { str8_list_pushf(scratch.arena, &l, "GuardCF"); } if (dll_chars & PE_DllCharacteristic_TERMINAL_SERVER_AWARE) { str8_list_pushf(scratch.arena, &l, "Terminal Server Aware"); } String8 result = str8_list_join(arena, &l, &(StringJoin){.sep=str8_lit(", ")}); scratch_end(scratch); return result; } internal PE_WindowsSubsystem pe_subsystem_from_string(String8 string) { for (U64 i = 0; i < ArrayCount(g_pe_subsystem_map); i += 1) { if (str8_match(g_pe_subsystem_map[i].string, string, StringMatchFlag_CaseInsensitive)) { return g_pe_subsystem_map[i].type; } } return PE_WindowsSubsystem_UNKNOWN; } //////////////////////////////// //~ rjf: Parser Functions internal B32 pe_check_magic(String8 data) { B32 is_pe = 0; PE_DosHeader dos_header = {0}; str8_deserial_read_struct(data, 0, &dos_header); if (dos_header.magic == PE_DOS_MAGIC) { U32 pe_magic = 0; str8_deserial_read_struct(data, dos_header.coff_file_offset, &pe_magic); is_pe= pe_magic == PE_MAGIC; } return is_pe; } internal PE_BinInfo pe_bin_info_from_data(Arena *arena, String8 data) { PE_BinInfo info = {0}; B32 valid = 1; // rjf: read dos header PE_DosHeader dos_header = {0}; str8_deserial_read_struct(data, 0, &dos_header); // rjf: bad dos magic -> bad if(dos_header.magic != PE_DOS_MAGIC) { valid = 0; } // rjf: read pe magic U32 pe_magic = 0; if(valid) { str8_deserial_read_struct(data, dos_header.coff_file_offset, &pe_magic); } // rjf: bad pe magic -> abort if(pe_magic != PE_MAGIC) { valid = 0; } // rjf: read coff header U32 file_header_off = dos_header.coff_file_offset + sizeof(pe_magic); COFF_FileHeader file_header = {0}; if(valid) { str8_deserial_read_struct(data, file_header_off, &file_header); } // rjf: range of optional extension header ("optional" for short) U32 optional_size = file_header.optional_header_size; U64 after_file_header_off = file_header_off + sizeof(COFF_FileHeader); U64 after_optional_header_off = after_file_header_off + optional_size; Rng1U64 optional_range = {0}; if(valid) { optional_range.min = ClampTop(after_file_header_off, data.size); optional_range.max = ClampTop(after_optional_header_off, data.size); } // rjf: get sections U64 sec_array_off = optional_range.max; U64 sec_array_raw_opl = sec_array_off + file_header.section_count*sizeof(COFF_SectionHeader); U64 sec_array_opl = ClampTop(sec_array_raw_opl, data.size); U64 clamped_sec_count = (sec_array_opl - sec_array_off)/sizeof(COFF_SectionHeader); COFF_SectionHeader *sections = (COFF_SectionHeader*)(data.str + sec_array_off); // rjf: get symbols U64 symbol_array_off = file_header.symbol_table_foff; U64 symbol_count = file_header.symbol_count; // rjf: get string table U64 string_table_off = symbol_array_off + sizeof(COFF_Symbol16) * symbol_count; // rjf: read optional header U16 optional_magic = 0; U64 image_base = 0; U64 entry_point = 0; PE_WindowsSubsystem subsystem = 0; U32 *check_sum = 0; U32 data_dir_count = 0; U64 virt_section_align = 0; U64 file_section_align = 0; Rng1U64 data_dir_range = {0}; Rng1U64 *data_dir_franges = 0; Rng1U64 *data_dir_vranges = 0; if(valid && optional_size > 0) { // rjf: read magic number str8_deserial_read_struct(data, optional_range.min, &optional_magic); // rjf: read info U32 reported_data_dir_offset = 0; U32 reported_data_dir_count = 0; switch(optional_magic) { case PE_PE32_MAGIC: { PE_OptionalHeader32 *pe_optional = str8_deserial_get_raw_ptr(data, optional_range.min, sizeof(*pe_optional)); if(pe_optional) { image_base = pe_optional->image_base; entry_point = pe_optional->entry_point_va; subsystem = pe_optional->subsystem; check_sum = &pe_optional->check_sum; virt_section_align = pe_optional->section_alignment; file_section_align = pe_optional->file_alignment; reported_data_dir_offset = sizeof(*pe_optional); reported_data_dir_count = pe_optional->data_dir_count; } else { Assert(!"unable to read PE Optional Header"); } }break; case PE_PE32PLUS_MAGIC: { PE_OptionalHeader32Plus *pe_optional = str8_deserial_get_raw_ptr(data, optional_range.min, sizeof(*pe_optional)); if(pe_optional) { image_base = pe_optional->image_base; entry_point = pe_optional->entry_point_va; subsystem = pe_optional->subsystem; check_sum = &pe_optional->check_sum; virt_section_align = pe_optional->section_alignment; file_section_align = pe_optional->file_alignment; reported_data_dir_offset = sizeof(*pe_optional); reported_data_dir_count = pe_optional->data_dir_count; } else { Assert(!"unable to read PE Optional Plus Header"); } }break; } // rjf: find file ranges of data directories U32 data_dir_max = (optional_size - reported_data_dir_offset) / sizeof(PE_DataDirectory); data_dir_count = ClampTop(reported_data_dir_count, data_dir_max); // rjf: convert PE directories to ranges data_dir_franges = push_array(arena, Rng1U64, Max(data_dir_count, PE_DataDirectoryIndex_COUNT)); for(U32 dir_idx = 0; dir_idx < data_dir_count; dir_idx += 1) { U64 dir_offset = optional_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*dir_idx; PE_DataDirectory dir = {0}; if(str8_deserial_read_struct(data, dir_offset, &dir) == sizeof(dir)) { U64 file_off = coff_foff_from_voff(sections, clamped_sec_count, dir.virt_off); data_dir_franges[dir_idx] = r1u64(file_off, file_off+dir.virt_size); } else { Assert(!"unable to read data directory"); } } // export virtual directory ranges data_dir_vranges = push_array(arena, Rng1U64, data_dir_count); for(U32 dir_idx = 0; dir_idx < data_dir_count; dir_idx += 1) { U64 dir_offset = optional_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*dir_idx; PE_DataDirectory dir = {0}; if(str8_deserial_read_struct(data, dir_offset, &dir) == sizeof(dir)) { data_dir_vranges[dir_idx] = r1u64(dir.virt_off, dir.virt_off+dir.virt_size); } else { Assert(!"unable to read data directory"); } } // export directory range data_dir_range = rng_1u64(optional_range.min + reported_data_dir_offset, optional_range.min + reported_data_dir_offset + data_dir_count * sizeof(PE_DataDirectory)); } // rjf: extract tls header PE_TLSHeader64 tls_header = {0}; if(valid && PE_DataDirectoryIndex_TLS < data_dir_count) { Rng1U64 tls_header_frng = data_dir_franges[PE_DataDirectoryIndex_TLS]; switch(file_header.machine) { default:{ NotImplemented; }break; case COFF_MachineType_Unknown: break; case COFF_MachineType_X86: { PE_TLSHeader32 tls_header32 = {0}; if(str8_deserial_read_struct(data, tls_header_frng.min, &tls_header32) == sizeof(tls_header32)) { tls_header.raw_data_start = (U64)tls_header32.raw_data_start; tls_header.raw_data_end = (U64)tls_header32.raw_data_end; tls_header.index_address = (U64)tls_header32.index_address; tls_header.callbacks_address = (U64)tls_header32.callbacks_address; tls_header.zero_fill_size = (U64)tls_header32.zero_fill_size; tls_header.characteristics = (U64)tls_header32.characteristics; } else { Assert(!"unable to read TLS Header 32"); } }break; case COFF_MachineType_X64: { if(str8_deserial_read_struct(data, tls_header_frng.min, &tls_header) != sizeof(tls_header)) { MemoryZeroStruct(&tls_header); Assert(!"unable to read TLS Header 64"); } }break; } } // rjf: fill info if(valid) { info.arch = arch_from_coff_machine(file_header.machine); info.image_base = image_base; info.entry_point = entry_point; info.is_pe32 = (optional_magic == PE_PE32_MAGIC); info.subsystem = subsystem; info.check_sum = check_sum; info.virt_section_align = virt_section_align; info.file_section_align = file_section_align; info.section_count = clamped_sec_count; info.symbol_count = symbol_count; info.optional_header_off = optional_range.min; info.section_table_range = rng_1u64(sec_array_off, sec_array_off + sizeof(COFF_SectionHeader) * clamped_sec_count); info.symbol_table_range = rng_1u64(symbol_array_off, symbol_array_off + sizeof(COFF_Symbol16) * symbol_count); info.string_table_range = rng_1u64(string_table_off, data.size); info.data_dir_range = data_dir_range; info.data_dir_franges = data_dir_franges; info.data_dir_vranges = data_dir_vranges; info.data_dir_count = data_dir_count; info.tls_header = tls_header; } return info; } internal PE_DataDirectory * pe_data_directory_from_idx(String8 file_data, PE_BinInfo pe, PE_DataDirectoryIndex dir_idx) { PE_DataDirectory *result = 0; if (dir_idx < pe.data_dir_count) { result = str8_deserial_get_raw_ptr(file_data, pe.data_dir_range.min + sizeof(*result)*dir_idx, sizeof(*result)); } return result; } internal PE_DebugInfoList pe_debug_info_list_from_raw_debug_dir(Arena *arena, String8 raw_image, String8 raw_debug_dir) { PE_DebugInfoList result = {0}; PE_DebugDirectory *debug_entry = str8_deserial_get_raw_ptr(raw_debug_dir, 0, sizeof(*debug_entry)); PE_DebugDirectory *debug_entry_opl = debug_entry + raw_debug_dir.size/sizeof(*debug_entry_opl); for(PE_DebugDirectory *entry = debug_entry; entry < debug_entry_opl; entry += 1) { PE_DebugInfoNode *n = push_array(arena, PE_DebugInfoNode, 1); SLLQueuePush(result.first, result.last, n); result.count += 1; n->v.header = *entry; switch(entry->type) { default:{}break; case PE_DebugDirectoryType_CODEVIEW: { str8_deserial_read_struct(raw_image, entry->foff, &n->v.cv_magic); switch(n->v.cv_magic) { case PE_CODEVIEW_PDB20_MAGIC: { PE_CvHeaderPDB20 cv = {0}; U64 cv_read_size = str8_deserial_read_struct(raw_image, entry->foff, &cv); if(cv_read_size == sizeof(cv)) { String8 path = {0}; str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_pdb20_header = cv; n->v.path = path; } }break; case PE_CODEVIEW_PDB70_MAGIC: { PE_CvHeaderPDB70 cv = {0}; U64 cv_read_size = str8_deserial_read_struct(raw_image, entry->foff, &cv); if(cv_read_size == sizeof(cv)) { String8 path = {0}; str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_pdb70_header = cv; n->v.path = path; } }break; case PE_CODEVIEW_RDI_MAGIC: { PE_CvHeaderRDI cv = {0}; U64 cv_read_size = str8_deserial_read_struct(raw_image, entry->foff, &cv); if(cv_read_size == sizeof(cv)) { String8 path = {0}; str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_rdi_header = cv; n->v.path = path; } }break; default:{}break; } }break; } } return result; } //////////////////////////////// //~ rjf: Helpers internal U64 pe_pdata_off_from_voff__binary_search_x8664(String8 raw_pdata, U64 voff) { U64 result = 0; if(raw_pdata.size >= sizeof(PE_IntelPdata)) { U64 pdata_count = raw_pdata.size/sizeof(PE_IntelPdata); PE_IntelPdata *pdata_array = (PE_IntelPdata*)raw_pdata.str; if(voff >= pdata_array[0].voff_first) { // binary search: // find max index s.t. pdata_array[index].voff_first <= voff // we assume (i < j) -> (pdata_array[i].voff_first < pdata_array[j].voff_first) U64 index = pdata_count; U64 min = 0; U64 opl = pdata_count; for(;;) { U64 mid = (min + opl)/2; PE_IntelPdata *pdata = pdata_array + mid; if(voff < pdata->voff_first) { opl = mid; } else if(pdata->voff_first < voff) { min = mid; } else { index = mid; break; } if(min + 1 >= opl) { index = min; break; } } // if we are in range fill result { PE_IntelPdata *pdata = pdata_array + index; if(pdata->voff_first <= voff && voff < pdata->voff_one_past_last) { result = index*sizeof(PE_IntelPdata); } } } } return result; } internal U64 pe_foff_from_voff(String8 data, PE_BinInfo *bin, U64 voff) { U64 foff = 0; String8 raw_section_table = str8_substr(data, bin->section_table_range); COFF_SectionHeader *section_table = (COFF_SectionHeader *)raw_section_table.str; for(U64 sect_idx = 0; sect_idx < bin->section_count; sect_idx += 1) { COFF_SectionHeader *sect = §ion_table[sect_idx]; if(sect->voff <= voff && voff < sect->voff + sect->vsize) { if(!(sect->flags & COFF_SectionFlag_CntUninitializedData)) { foff = sect->foff + (voff - sect->voff); } break; } } return foff; } internal PE_BaseRelocBlockList pe_base_reloc_block_list_from_data(Arena *arena, String8 raw_base_relocs) { PE_BaseRelocBlockList list = {0}; for(U64 off = 0; off < raw_base_relocs.size;) { // rjf: read next entry U32 page_virt_off = 0; U32 block_size = 0; off += str8_deserial_read_struct(raw_base_relocs, off, &page_virt_off); off += str8_deserial_read_struct(raw_base_relocs, off, &block_size); // rjf: break on sentinel if(block_size == 0) { break; } // rjf: add node PE_BaseRelocBlockNode *node = push_array(arena, PE_BaseRelocBlockNode, 1); SLLQueuePush(list.first, list.last, node); list.count += 1; U64 entries_size = block_size - (sizeof(block_size) + sizeof(page_virt_off)); // rjf: fill block PE_BaseRelocBlock *block = &node->v; block->page_virt_off = page_virt_off; block->entry_count = entries_size / sizeof(U16); block->entries = push_array(arena, U16, block->entry_count); U64 entry_read_size = str8_deserial_read_array(raw_base_relocs, off, &block->entries[0], block->entry_count); Assert(entry_read_size == sizeof(block->entries[0]) * block->entry_count); off += entry_read_size; } return list; } internal Rng1U64 pe_tls_rng_from_bin_base_vaddr(String8 data, PE_BinInfo *bin, U64 base_vaddr) { U64 result_addr = (bin->tls_header.index_address - bin->image_base); U64 result_size = sizeof(U32); if(bin->arch != Arch_Null) { U64 addr_size = bit_size_from_arch(bin->arch)/8; Temp scratch = scratch_begin(0, 0); String8 raw_relocs = str8_substr(data, bin->data_dir_franges[PE_DataDirectoryIndex_BASE_RELOC]); PE_BaseRelocBlockList relocs = pe_base_reloc_block_list_from_data(scratch.arena, raw_relocs); for(PE_BaseRelocBlockNode *n = relocs.first; n != 0; n = n->next) { PE_BaseRelocBlock *block = &n->v; for(U64 ientry = 0; ientry < block->entry_count;) { U32 reloc = block->entries[ientry]; U16 kind = PE_BaseRelocKindFromEntry(reloc); U16 offset = PE_BaseRelocOffsetFromEntry(reloc); U64 apply_to_voff = block->page_virt_off + offset; U64 apply_to_foff = pe_foff_from_voff(data, bin, apply_to_voff); U64 apply_to = 0; str8_deserial_read(data, apply_to_foff, &apply_to, addr_size, 1); if(apply_to == bin->tls_header.index_address) { U64 base_diff = base_vaddr-bin->image_base; switch(kind) { default: { // NOTE(rjf): these relocs are arm/mips/riscv specific which aren't supported at the moment }break; case PE_BaseRelocKind_ABSOLUTE:{}break; case PE_BaseRelocKind_HIGH: { // rjf: relocate high 16-bits. U64 high_bits = (apply_to & max_U16) << 16; result_addr = (high_bits + ((base_diff & max_U32) >> 16)) & max_U16; }break; case PE_BaseRelocKind_LOW: { // rjf: relocate low 16-bits. U64 low_bits = apply_to & max_U16; result_addr = (low_bits + (base_diff & max_U32)) & max_U16; }break; case PE_BaseRelocKind_HIGHLOW: { // rjf: relocate 32-bits. result_addr = (apply_to & max_U32) + (base_diff & max_U32); }break; case PE_BaseRelocKind_HIGHADJ: { if(ientry + 1 >= block->entry_count) { // NOTE(rjf): malformed relocation, expected two 16-bit entries break; } // rjf: relocate high bits and adjust sign bit on lower half. U16 adj_offset = PE_BaseRelocOffsetFromEntry(block->entries[ientry + 1]); result_addr = (apply_to & max_U16) << 16; result_addr += adj_offset; result_addr += (base_diff & max_U32); result_addr += 0x8000; result_addr = (result_addr >> 16) & max_U16; }break; case PE_BaseRelocKind_DIR64: { // rjf: image base relocation. result_addr = apply_to + base_diff; }break; } goto dbl_break; } U32 advance = (kind == PE_BaseRelocKind_HIGHADJ) ? 2 : 1; ientry += advance; } } dbl_break:; scratch_end(scratch); } Rng1U64 result = r1u64(result_addr, result_addr+result_size); return result; } internal String8Array pe_get_entry_point_names(COFF_MachineType machine, PE_WindowsSubsystem subsystem, PE_ImageFileCharacteristics file_characteristics) { String8Array entry_point_names = {0}; if (file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) { if (machine == COFF_MachineType_X86) { read_only static String8 dll_entry_point_arr[] = { str8_lit_comp("__DllMainCRTStartup@12"), }; entry_point_names.v = &dll_entry_point_arr[0]; entry_point_names.count = ArrayCount(dll_entry_point_arr); } else { read_only static String8 dll_entry_point_arr[] = { str8_lit_comp("_DllMainCRTStartup"), }; entry_point_names.v = &dll_entry_point_arr[0]; entry_point_names.count = ArrayCount(dll_entry_point_arr); } } else { switch (subsystem) { case PE_WindowsSubsystem_UNKNOWN: break; case PE_WindowsSubsystem_WINDOWS_GUI: { read_only static String8 gui_entry_point_arr[] = { str8_lit_comp("WinMain"), str8_lit_comp("wWinMain"), str8_lit_comp("WinMainCRTStartup"), str8_lit_comp("wWinMainCRTStartup"), }; entry_point_names.v = &gui_entry_point_arr[0]; entry_point_names.count = ArrayCount(gui_entry_point_arr); } break; case PE_WindowsSubsystem_WINDOWS_CUI: { read_only static String8 cui_entry_point_arr[] = { str8_lit_comp("main"), str8_lit_comp("wmain"), str8_lit_comp("mainCRTStartup"), str8_lit_comp("wmainCRTStartup"), }; entry_point_names.v = &cui_entry_point_arr[0]; entry_point_names.count = ArrayCount(cui_entry_point_arr); } break; case PE_WindowsSubsystem_NATIVE: case PE_WindowsSubsystem_OS2_CUI: case PE_WindowsSubsystem_POSIX_CUI: case PE_WindowsSubsystem_NATIVE_WINDOWS: case PE_WindowsSubsystem_WINDOWS_CE_GUI: case PE_WindowsSubsystem_EFI_APPLICATION: case PE_WindowsSubsystem_EFI_BOOT_SERVICE_DRIVER: case PE_WindowsSubsystem_EFI_RUNTIME_DRIVER: case PE_WindowsSubsystem_EFI_ROM: case PE_WindowsSubsystem_XBOX: case PE_WindowsSubsystem_WINDOWS_BOOT_APPLICATION: { // TODO } break; } } return entry_point_names; } //////////////////////////////// internal PE_ParsedImport * pe_parsed_imports_from_data(Arena *arena, B32 is_pe32, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, U64 name_table_voff, U64 *import_count_out) { PE_ParsedImport *imports = 0; U64 import_count = 0; U64 name_table_foff = coff_foff_from_voff(sections, section_count, name_table_voff); String8 entries = str8_substr(raw_data, rng_1u64(name_table_foff, raw_data.size)); if (is_pe32) { import_count = index_of_zero_u32((U32 *)entries.str, entries.size/sizeof(U32)); if (import_count == max_U64) { import_count = 0; } imports = push_array(arena, PE_ParsedImport, import_count); for (U64 imp_idx = 0; imp_idx < import_count; imp_idx += 1) { U32 raw_entry = 0; str8_deserial_read_struct(entries, imp_idx*sizeof(raw_entry), &raw_entry); B32 is_ordinal = ExtractBit(raw_entry, 31); if (is_ordinal) { // fill out ordinal import PE_ParsedImport *imp = imports+imp_idx; imp->type = PE_ParsedImport_Ordinal; imp->u.ordinal = Extract16(raw_entry, 0); } else { // map voff -> foff U64 off = coff_foff_from_voff(sections, section_count, raw_entry); // read hint & name U16 hint = 0; String8 name = str8_zero(); str8_deserial_read_struct(raw_data, off, &hint); str8_deserial_read_cstr(raw_data, off+sizeof(hint), &name); // fill out named import PE_ParsedImport *imp = imports+imp_idx; imp->type = PE_ParsedImport_Name; imp->u.name.hint = hint; imp->u.name.string = name; } } } else { import_count = index_of_zero_u64((U64 *)entries.str, entries.size/sizeof(U64)); if (import_count == max_U64) { import_count = 0; } imports = push_array(arena, PE_ParsedImport, import_count); for (U64 imp_idx = 0; imp_idx < import_count; imp_idx += 1) { U64 raw_entry = 0; str8_deserial_read_struct(entries, imp_idx*sizeof(raw_entry), &raw_entry); B32 is_ordinal = ExtractBit(raw_entry, 63); if (is_ordinal) { // fill out ordinal import PE_ParsedImport *imp = imports+imp_idx; imp->type = PE_ParsedImport_Ordinal; imp->u.ordinal = Extract16(raw_entry, 0); } else { // map voff -> foff U64 off = coff_foff_from_voff(sections, section_count, raw_entry); // read hint & name U16 hint = 0; String8 name = str8_zero(); str8_deserial_read_struct(raw_data, off, &hint); str8_deserial_read_cstr(raw_data, off + sizeof(hint), &name); // fill out named import PE_ParsedImport *imp = imports+imp_idx; imp->type = PE_ParsedImport_Name; imp->u.name.hint = hint; imp->u.name.string = name; } } } *import_count_out = import_count; return imports; } internal U64 * pe_array_from_null_term_addr(Arena *arena, B32 is_pe32, String8 raw_data, Rng1U64 range, U64 *count_out) { U64 *result = 0; *count_out = 0; if (is_pe32) { U32 *src = (U32 *)(raw_data.str + range.min); U32 *opl = (U32 *)(raw_data.str + AlignDownPow2(range.max, sizeof(*opl))); // count items U32 *ptr; for (ptr = src; ptr < opl && *ptr != 0; ++ptr); // push output array *count_out = (U64)(ptr - src); result = push_array(arena, U64, *count_out); // convert & copy for (U64 i = 0; i < *count_out; ++i) { result[i] = (U64)src[i]; } } else { U64 *src = (U64 *)(raw_data.str + range.min); U64 *opl = (U64 *)(raw_data.str + AlignDownPow2(range.max, sizeof(*opl))); // count items U64 *ptr; for (ptr = src; ptr < opl && *ptr != 0; ++ptr); // push output array *count_out = (U64)(ptr - src); result = push_array(arena, U64, *count_out); // copy MemoryCopyTyped(result, src, *count_out); } return result; } internal PE_ParsedStaticImportTable pe_static_imports_from_data(Arena *arena, B32 is_pe32, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, Rng1U64 dir_file_range) { // count imports U64 max_dll_count = dim_1u64(dir_file_range) / sizeof (PE_ImportEntry); U64 dll_count = max_dll_count; for (U64 i = 0; i < max_dll_count; ++i) { PE_ImportEntry *imp = str8_deserial_get_raw_ptr(raw_data, dir_file_range.min+(i*sizeof(*imp)), sizeof(*imp)); if (memory_is_zero(imp, sizeof(*imp))) { dll_count = i; break; } } PE_ParsedStaticDLLImport *dlls = push_array(arena, PE_ParsedStaticDLLImport, dll_count); for (U64 dll_idx = 0; dll_idx < dll_count; ++dll_idx) { PE_ImportEntry *raw_dll = str8_deserial_get_raw_ptr(raw_data, dir_file_range.min+(dll_idx*sizeof(*raw_dll)), sizeof(*raw_dll)); // get name U64 name_off = coff_foff_from_voff(sections, section_count, raw_dll->name_voff); String8 name = str8_zero(); str8_deserial_read_cstr(raw_data, name_off, &name); U64 import_count = 0; PE_ParsedImport *imports = pe_parsed_imports_from_data(arena, is_pe32, section_count, sections, raw_data, raw_dll->lookup_table_voff, &import_count); PE_ParsedStaticDLLImport *dll = dlls+dll_idx; dll->name = name; dll->import_address_table_voff = raw_dll->import_addr_table_voff; dll->import_name_table_voff = raw_dll->lookup_table_voff; dll->time_stamp = raw_dll->time_stamp; dll->forwarder_chain = raw_dll->forwarder_chain; dll->import_count = import_count; dll->imports = imports; } PE_ParsedStaticImportTable imptab = {0}; imptab.count = dll_count; imptab.v = dlls; return imptab; } internal PE_ParsedDelayImportTable pe_delay_imports_from_data(Arena *arena, B32 is_pe32, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, Rng1U64 dir_file_range) { // count imports U64 max_dll_count = dim_1u64(dir_file_range) / sizeof(PE_DelayedImportEntry); U64 dll_count = 0; for (; dll_count < max_dll_count; ++dll_count) { PE_DelayedImportEntry *raw_dll = str8_deserial_get_raw_ptr(raw_data, dir_file_range.min+(dll_count*sizeof(*raw_dll)), sizeof(*raw_dll)); if (memory_is_zero(raw_dll, sizeof(*raw_dll))) { break; } } // parse dll imports PE_ParsedDelayDLLImport *dlls = push_array(arena, PE_ParsedDelayDLLImport, dll_count); for (U64 dll_idx = 0; dll_idx < dll_count; ++dll_idx) { PE_DelayedImportEntry *raw_dll = str8_deserial_get_raw_ptr(raw_data, dir_file_range.min+(dll_idx*sizeof(*raw_dll)), sizeof(*raw_dll)); U64 name_off = coff_foff_from_voff(sections, section_count, raw_dll->name_voff); String8 name = str8_zero(); str8_deserial_read_cstr(raw_data, name_off, &name); // parse import table U64 import_count = 0; PE_ParsedImport *imports = pe_parsed_imports_from_data(arena, is_pe32, section_count, sections, raw_data, raw_dll->name_table_voff, &import_count); // parse bound table Rng1U64 bound_table_range = {0}; if (raw_dll->bound_table_voff) { U64 bound_table_foff = coff_foff_from_voff(sections, section_count, raw_dll->bound_table_voff); bound_table_range = rng_1u64(bound_table_foff, raw_data.size); } U64 bound_table_count; U64 *bound_table = pe_array_from_null_term_addr(arena, is_pe32, raw_data, bound_table_range, &bound_table_count); // parse unload table Rng1U64 unload_table_range = {0}; if (raw_dll->unload_table_voff) { U64 unload_table_foff = coff_foff_from_voff(sections, section_count, raw_dll->unload_table_voff); unload_table_range = rng_1u64(unload_table_foff, raw_data.size); } U64 unload_table_count; U64 *unload_table = pe_array_from_null_term_addr(arena, is_pe32, raw_data, unload_table_range, &unload_table_count); // fill out DLL PE_ParsedDelayDLLImport *dll = dlls+dll_idx; dll->attributes = raw_dll->attributes; dll->name = name; dll->module_handle_voff = raw_dll->module_handle_voff; dll->iat_voff = raw_dll->iat_voff; dll->name_table_voff = raw_dll->name_table_voff; dll->bound_table_voff = raw_dll->bound_table_voff; dll->unload_table_voff = raw_dll->unload_table_voff; dll->time_stamp = raw_dll->time_stamp; dll->bound_table_count = bound_table_count; dll->bound_table = bound_table; dll->unload_table_count = unload_table_count; dll->unload_table = unload_table; dll->import_count = import_count; dll->imports = imports; } // fill out result PE_ParsedDelayImportTable imptab = {0}; imptab.count = dll_count; imptab.v = dlls; return imptab; } internal PE_ParsedExportTable pe_exports_from_data(Arena *arena, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, Rng1U64 dir_file_range, Rng1U64 dir_virt_range) { Temp scratch = scratch_begin(&arena, 1); PE_ParsedExportTable exptab = {0}; String8 raw_dir = str8_substr(raw_data, dir_file_range); PE_ExportTableHeader *header = str8_deserial_get_raw_ptr(raw_dir, 0, sizeof(*header)); if (header) { U64 name_table_off = coff_foff_from_voff(sections, section_count, header->name_pointer_table_voff); U64 export_table_off = coff_foff_from_voff(sections, section_count, header->export_address_table_voff); U64 ordinal_table_off = coff_foff_from_voff(sections, section_count, header->ordinal_table_voff); U32 *name_table = str8_deserial_get_raw_ptr(raw_data, name_table_off, sizeof(*name_table )*header->name_pointer_table_count); U32 *export_table = str8_deserial_get_raw_ptr(raw_data, export_table_off, sizeof(*export_table )*header->export_address_table_count); U16 *ordinal_table = str8_deserial_get_raw_ptr(raw_data, ordinal_table_off, sizeof(*ordinal_table)*header->name_pointer_table_count); if (name_table && export_table && ordinal_table) { // Scan export address table to get accruate count of ordinals. // We can't rely on "name_pointer_table_count" becuase it is possible // to define an export without a name through NONAME attribute in DEF file U64 ordinal_count = 0; for (U64 voff_idx = 0; voff_idx < header->export_address_table_count; ++voff_idx) { if (export_table[voff_idx] != 0) { ++ordinal_count; } } U64 ordinal_max = header->export_address_table_count; B32 *is_ordinal_used = push_array(scratch.arena, B32, ordinal_max); PE_ParsedExport *exports = push_array(arena, PE_ParsedExport, ordinal_count); PE_ParsedExport *curr_exp = exports; // parse exports with name for (U64 i = 0; i < header->name_pointer_table_count; ++i) { // get name U32 name_voff = name_table[i]; U64 name_foff = coff_foff_from_voff(sections, section_count, name_voff); String8 name = str8_cstring_capped(raw_data.str+name_foff, raw_data.str+raw_data.size); // get ordinal U16 ordinal_nb = ordinal_table[i]; // mark ordinal Assert(ordinal_nb < ordinal_max); is_ordinal_used[ordinal_nb] = 1; // get voff U32 export_voff = 0; if (ordinal_nb < header->export_address_table_count) { export_voff = export_table[ordinal_nb]; } // make ordinal U16 ordinal = header->ordinal_base + ordinal_nb; String8 forwarder = str8_zero(); { B32 is_forwarder = dir_virt_range.min <= export_voff && export_voff < dir_virt_range.max; if (is_forwarder) { U64 fwd_name_off = coff_foff_from_voff(sections, section_count, export_voff); str8_deserial_read_cstr(raw_data, fwd_name_off, &forwarder); } } curr_exp->forwarder = forwarder; curr_exp->name = name; curr_exp->voff = export_voff; curr_exp->ordinal = ordinal; ++curr_exp; } // parse exports with ordinal for (U64 ordinal_nb = 0; ordinal_nb < header->export_address_table_count; ++ordinal_nb) { U32 voff = export_table[ordinal_nb]; B32 is_voff_taken = (voff != 0); B32 is_ordinal_free = !is_ordinal_used[ordinal_nb]; if (is_voff_taken && is_ordinal_free) { curr_exp->name = str8_zero(); curr_exp->voff = voff; curr_exp->ordinal = header->ordinal_base; ++curr_exp; } } // fill out result exptab.flags = header->flags; exptab.time_stamp = header->time_stamp; exptab.major_ver = header->major_ver; exptab.minor_ver = header->minor_ver; exptab.ordinal_base = header->ordinal_base; exptab.export_count = ordinal_count; exptab.exports = exports; } } scratch_end(scratch); return exptab; } internal PE_ParsedTLS pe_tls_from_data(Arena *arena, COFF_MachineType machine, U64 image_base, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, Rng1U64 tls_frange) { String8 raw_tls = str8_substr(raw_data, tls_frange); PE_TLSHeader64 header64 = {0}; U64 callback_count = 0; U64 *callback_addrs = 0; switch (machine) { case COFF_MachineType_Unknown: break; case COFF_MachineType_X86: { PE_TLSHeader32 header32 = {0}; str8_deserial_read_struct(raw_tls, 0, &header32); header64.raw_data_start = header32.raw_data_start; header64.raw_data_end = header32.raw_data_end; header64.index_address = header32.index_address; header64.callbacks_address = header32.callbacks_address; header64.zero_fill_size = header32.zero_fill_size; header64.characteristics = header32.characteristics; U64 callbacks_voff = header32.callbacks_address - image_base; U64 callbacks_foff = coff_foff_from_voff(sections, section_count, callbacks_voff); U32 *src = (U32 *)(raw_data.str + callbacks_foff); U32 *opl = (U32 *)(raw_data.str + raw_data.size); U32 *ptr = src; for (; ptr < opl && *ptr != 0; ++ptr); callback_count = (U64)(ptr-src); callback_addrs = push_array(arena, U64, callback_count); for (U64 i = 0; i < callback_count; ++i) { callback_addrs[i] = (U64)src[i]; } } break; case COFF_MachineType_X64: { str8_deserial_read_struct(raw_tls, 0, &header64); U64 callbacks_voff = header64.callbacks_address - image_base; U64 callbacks_foff = coff_foff_from_voff(sections, section_count, callbacks_voff); U64 *src = (U64 *)(raw_data.str + callbacks_foff); U64 *opl = (U64 *)(raw_data.str + raw_data.size); U64 *ptr = src; for (; ptr < opl && *ptr != 0; ++ptr); callback_count = (U64)(ptr-src); callback_addrs = push_array(arena, U64, callback_count); MemoryCopyTyped(callback_addrs, src, callback_count); } break; default: NotImplemented; } PE_ParsedTLS result = {0}; result.header = header64; result.callback_count = callback_count; result.callback_addrs = callback_addrs; return result; } //////////////////////////////// internal B32 pe_is_res(String8 data) { U8 magic[sizeof(PE_RES_MAGIC)]; MemoryZeroStruct(&magic); str8_deserial_read_struct(data, 0, &magic); B32 is_res = MemoryCompare(&PE_RES_MAGIC, &magic, sizeof(magic)) == 0; return is_res; } internal PE_ResourceNode * pe_resource_dir_push_dir_node(Arena *arena, PE_ResourceDir *dir, COFF_ResourceID id, U32 characteristics, COFF_TimeStamp time_stamp, U16 major_version, U16 minor_version) { PE_ResourceList *list = 0; switch (id.type) { default: case COFF_ResourceIDType_Null: break; case COFF_ResourceIDType_String: list = &dir->named_list; break; case COFF_ResourceIDType_Number: list = &dir->id_list; break; } PE_ResourceNode *res_node = push_array(arena, PE_ResourceNode, 1); SLLQueuePush(list->first, list->last, res_node); list->count += 1; PE_ResourceDir *sub_dir = push_array(arena, PE_ResourceDir, 1); sub_dir->characteristics = characteristics; sub_dir->time_stamp = time_stamp; sub_dir->major_version = major_version; sub_dir->minor_version = minor_version; PE_Resource *res = &res_node->data; res->id = id; res->kind = PE_ResDataKind_DIR; res->u.dir = sub_dir; return res_node; } internal PE_ResourceNode * pe_resource_dir_push_entry_node(Arena *arena, PE_ResourceDir *dir, COFF_ResourceID id, COFF_ResourceID type, U32 data_version, U32 version, COFF_ResourceMemoryFlags memory_flags, String8 data) { PE_ResourceList *list = NULL; switch (id.type) { default: case COFF_ResourceIDType_Null: break; case COFF_ResourceIDType_String: list = &dir->named_list; break; case COFF_ResourceIDType_Number: list = &dir->id_list; break; } PE_ResourceNode *res_node = push_array(arena, PE_ResourceNode, 1); SLLQueuePush(list->first, list->last, res_node); list->count += 1; PE_Resource *res = &res_node->data; res->id = id; res->kind = PE_ResDataKind_COFF_RESOURCE; res->u.coff_res.type = type; res->u.coff_res.data_version = data_version; res->u.coff_res.version = version; res->u.coff_res.memory_flags = memory_flags; res->u.coff_res.data = data; return res_node; } internal PE_Resource * pe_resource_dir_push_entry(Arena *arena, PE_ResourceDir *dir, COFF_ResourceID id, COFF_ResourceID type, U32 data_version, U32 version, COFF_ResourceMemoryFlags memory_flags, String8 data) { PE_ResourceNode *node = pe_resource_dir_push_entry_node(arena, dir, id, type, data_version, version, memory_flags, data); return &node->data; } internal PE_Resource * pe_resource_dir_push_dir(Arena *arena, PE_ResourceDir *dir, COFF_ResourceID id, U32 characteristics, COFF_TimeStamp time_stamp, U16 major_version, U16 minor_version) { PE_ResourceNode *dir_node = pe_resource_dir_push_dir_node(arena, dir, id, characteristics, time_stamp, major_version, minor_version); return &dir_node->data; } internal PE_ResourceNode * pe_resource_dir_search_node(PE_ResourceDir *dir, COFF_ResourceID id) { for (PE_ResourceNode *i = dir->id_list.first; i != 0; i = i->next) { if (coff_resource_id_compar(&i->data.id, &id) == 0) { return i; } } return NULL; } internal PE_Resource * pe_resource_dir_search(PE_ResourceDir *dir, COFF_ResourceID id) { PE_ResourceNode *node = pe_resource_dir_search_node(dir, id); return node ? &node->data : NULL; } internal PE_ResourceArray pe_resource_list_to_array(Arena *arena, PE_ResourceList *list) { PE_ResourceArray result; result.count = 0; result.v = push_array(arena, PE_Resource, list->count); for (PE_ResourceNode *n = list->first; n != NULL; n = n->next) { result.v[result.count++] = n->data; } return result; } internal void pe_resource_dir_push_res_file(Arena *arena, PE_ResourceDir *root_dir, String8 res_file) { // parse file into resource list String8 res_data = str8_substr(res_file, rng_1u64(sizeof(PE_RES_MAGIC), res_file.size)); COFF_ParsedResourceList list = coff_resource_list_from_data(arena, res_data); // move resources to directories based on type for (COFF_ParsedResourceNode *res_node = list.first; res_node != NULL; res_node = res_node->next) { COFF_ParsedResource *res = &res_node->data; // search existing directories PE_Resource *dir_res = pe_resource_dir_search(root_dir, res->type); // create new directory if (dir_res == NULL) { dir_res = pe_resource_dir_push_dir(arena, root_dir, res->type, 0, 0, 0, 0); } PE_ResourceDir *dir = dir_res->u.dir; // check for name collisions PE_Resource *check_res = pe_resource_dir_search(dir, res->name); if (check_res != NULL) { // TODO: how do we handle name conflicts? Assert(!"name collision"); continue; } // push entry PE_Resource *sub_dir_res = pe_resource_dir_push_dir(arena, dir, res->name, 0, 0, 0, 0); COFF_ResourceID id; id.type = COFF_ResourceIDType_Number; id.u.number = res->language_id; pe_resource_dir_push_entry(arena, sub_dir_res->u.dir, id, res->type, res->data_version, res->version, res->memory_flags, res->data); } } internal PE_ResourceDir * pe_resource_table_from_directory_data(Arena *arena, String8 data) { struct stack_s { struct stack_s *next; U64 table_offset; U64 name_base_offset; U64 id_base_offset; PE_ResourceDir *table; PE_ResourceDir **directory_ptr; U64 name_ientry; U64 id_ientry; U64 name_entry_count; U64 id_entry_count; }; Temp scratch = scratch_begin(&arena,1); struct stack_s *bottom_frame = push_array(scratch.arena, struct stack_s, 1); struct stack_s *stack = bottom_frame; while (stack) { if (stack->table == NULL) { COFF_ResourceDirTable coff_table = {0}; str8_deserial_read_struct(data, stack->table_offset, &coff_table); PE_ResourceDir *table = push_array(arena, PE_ResourceDir, 1); table->characteristics = coff_table.characteristics; table->time_stamp = coff_table.time_stamp; table->major_version = coff_table.major_version; table->minor_version = coff_table.minor_version; stack->table = table; stack->name_base_offset = stack->table_offset + sizeof(COFF_ResourceDirTable); stack->id_base_offset = stack->table_offset + sizeof(COFF_ResourceDirTable) + sizeof(COFF_ResourceDirEntry) * coff_table.name_entry_count; stack->name_entry_count = coff_table.name_entry_count; stack->id_entry_count = coff_table.id_entry_count; if (stack->directory_ptr) { *stack->directory_ptr = table; } } while (stack->name_ientry < stack->name_entry_count) { U64 entry_offset = stack->name_base_offset + stack->name_ientry * sizeof(COFF_ResourceDirEntry); ++stack->name_ientry; PE_ResourceNode *named_node = push_array(arena, PE_ResourceNode, 1); SLLQueuePush(stack->table->named_list.first, stack->table->named_list.last, named_node); ++stack->table->named_list.count; PE_Resource *entry = &named_node->data; COFF_ResourceDirEntry coff_entry = {0}; str8_deserial_read_struct(data, entry_offset, &coff_entry); // NOTE: this is not documented on MSDN but high bit here is set for some reason U32 name_offset = coff_entry.name.offset & ~COFF_Resource_SubDirFlag; U16 name_size = 0; str8_deserial_read_struct(data, name_offset, &name_size); String8 name_block; str8_deserial_read_block(data, name_offset + sizeof(name_size), name_size*sizeof(U16), &name_block); String16 name16 = str16((U16*)name_block.str, name_size); B32 is_dir = !!(coff_entry.id.data_entry_offset & COFF_Resource_SubDirFlag); entry->id.type = COFF_ResourceIDType_String; entry->id.u.string = str8_from_16(arena, name16); entry->kind = is_dir ? PE_ResDataKind_DIR : PE_ResDataKind_COFF_LEAF; if (is_dir) { struct stack_s *frame = push_array(scratch.arena, struct stack_s, 1); frame->table_offset = coff_entry.id.sub_dir_offset & ~COFF_Resource_SubDirFlag; frame->directory_ptr = &entry->u.dir; SLLStackPush(stack, frame); goto yeild; } else { str8_deserial_read_struct(data, coff_entry.id.data_entry_offset, &entry->u.leaf); } } while (stack->id_ientry < stack->id_entry_count) { U64 entry_offset = stack->id_base_offset + stack->id_ientry * sizeof(COFF_ResourceDirEntry); ++stack->id_ientry; PE_ResourceNode *id_node = push_array(arena, PE_ResourceNode, 1); SLLQueuePush(stack->table->id_list.first, stack->table->id_list.last, id_node); ++stack->table->id_list.count; PE_Resource *entry = &id_node->data; COFF_ResourceDirEntry coff_entry = {0}; str8_deserial_read_struct(data, entry_offset, &coff_entry); B32 is_dir = !!(coff_entry.id.sub_dir_offset & COFF_Resource_SubDirFlag); entry->id.type = COFF_ResourceIDType_Number; entry->id.u.number = coff_entry.name.id; entry->kind = is_dir ? PE_ResDataKind_DIR : PE_ResDataKind_COFF_LEAF; if (is_dir) { struct stack_s *frame = push_array(scratch.arena, struct stack_s, 1); frame->table_offset = coff_entry.id.sub_dir_offset & ~COFF_Resource_SubDirFlag; frame->directory_ptr = &entry->u.dir; SLLStackPush(stack, frame); goto yeild; } else { str8_deserial_read_struct(data, coff_entry.id.sub_dir_offset, &entry->u.leaf); } } SLLStackPop(stack); yeild:; } scratch_end(scratch); return bottom_frame->table; } internal String8 pe_make_manifest_resource(Arena *arena, U32 resource_id, String8 manifest_data) { COFF_ResourceID type = {0}; type.type = COFF_ResourceIDType_Number; type.u.number = PE_ResourceKind_MANIFEST; COFF_ResourceID id = {0}; id.type = COFF_ResourceIDType_Number; id.u.number = resource_id; String8 res = coff_write_resource(arena, type, id, 1, 0, 1033, 0, 0, manifest_data); return res; } //////////////////////////////// //~ Debug Directory internal String8 pe_make_debug_header_pdb70(Arena *arena, Guid guid, U32 age, String8 pdb_path) { Temp scratch = scratch_begin(&arena, 1); PE_CvHeaderPDB70 header = {0}; header.magic = PE_CODEVIEW_PDB70_MAGIC; header.guid = guid; header.age = age; String8List cv_list = {0}; str8_serial_begin(scratch.arena, &cv_list); str8_serial_push_struct(scratch.arena, &cv_list, &header); str8_serial_push_cstr(scratch.arena, &cv_list, pdb_path); String8 cv_data = str8_serial_end(arena, &cv_list); scratch_end(scratch); return cv_data; } internal String8 pe_make_debug_header_rdi(Arena *arena, Guid guid, String8 rdi_path) { Temp scratch = scratch_begin(&arena,1); PE_CvHeaderRDI header = {0}; header.magic = PE_CODEVIEW_RDI_MAGIC; header.guid = guid; String8List list = {0}; str8_serial_begin(scratch.arena, &list); str8_serial_push_struct(scratch.arena, &list, &header); str8_serial_push_cstr(scratch.arena, &list, rdi_path); String8 cv_data = str8_serial_end(arena, &list); scratch_end(scratch); return cv_data; } //////////////////////////////// //~ Image Checksum internal U32 pe_compute_checksum(U8 *buffer, U64 buffer_size) { // https://bytepointer.com/resources/microsoft_pe_checksum_algo_distilled.htm U32 hash = 0; for (U16 *ptr16 = (U16*)buffer, *opl16 = (U16*)(buffer + buffer_size); ptr16 < opl16; ptr16 += 1) { hash += *ptr16; hash = (hash >> 16) + (hash & 0xffff); } hash = (U16)(((hash >> 16) + hash) & 0xffff); hash += buffer_size; return hash; } //////////////////////////////// internal B32 pe_has_plus_header(COFF_MachineType machine) { B32 has_plus_header = 0; switch (machine) { case COFF_MachineType_X86: { has_plus_header = 0; } break; case COFF_MachineType_X64: { has_plus_header = 1; } break; } return has_plus_header; } //////////////////////////////// internal int pe_pdata_is_before_x86_64(void *raw_a, void *raw_b) { PE_IntelPdata *a = raw_a, *b = raw_b; return a->voff_first < b->voff_first; } internal void pe_pdata_sort(COFF_MachineType machine, String8 raw_pdata) { ProfBeginFunction(); switch (machine) { case COFF_MachineType_Unknown: break; case COFF_MachineType_X86: case COFF_MachineType_X64: { U64 count = raw_pdata.size / sizeof(PE_IntelPdata); radsort((PE_IntelPdata *)raw_pdata.str, count, pe_pdata_is_before_x86_64); } break; default: { NotImplemented; } break; } ProfEnd(); }