From 3bb8983461aed16e071d2a48e670bb07e50542ff Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 6 Apr 2026 14:35:38 -0700 Subject: [PATCH] implement /WHOLEARCHIVE --- src/linker/lnk.c | 30 +++++++-- src/linker/lnk_config.c | 122 +++++++++++++++++++--------------- src/linker/lnk_config.h | 3 + src/torture/torture_radlink.c | 79 ++++++++++++++++++++++ 4 files changed, 175 insertions(+), 59 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 4eebee03..ff9b235b 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1603,14 +1603,34 @@ lnk_link_inputs(TP_Context *tp, lib_member_infos = push_array(link->arena, LNK_LibMemberInfo, lib->member_count); hash_table_push_raw_raw(link->arena, link->lib_member_infos_ht, lib, lib_member_infos); } - + + B32 link_whole_archive = config->whole_archive_all; + if ( ! link_whole_archive) { + String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); + link_whole_archive = hash_table_search_path(config->whole_archive_ht, lib_name) != 0; + } + ProfBeginV("Search %S", str8_skip_last_slash(lib->path)); do { lnk_load_inputs(tp, arena, config, inputer, symtab, link); - // search symbols in lib - MemoryZeroTyped(member_ref_lists, tp->worker_count); - tp_for_parallel(tp, arena, tp->worker_count, lnk_search_lib_task, &(LNK_SearchLibTask){ .search_anti_deps = search_anti_deps, .lib = lib, .symtab = symtab, .lib_member_infos = lib_member_infos, .member_ref_lists = member_ref_lists }); + if (link_whole_archive) { + LNK_LibMemberRef *member_refs = push_array(scratch.arena, LNK_LibMemberRef, lib->member_count); + for EachIndex(member_idx, lib->member_count) { + static LNK_Symbol *null_symbol = 0; + if (null_symbol == 0) { + null_symbol = push_array(inputer->arena, LNK_Symbol, 1); + null_symbol->refs = push_array(inputer->arena, LNK_ObjSymbolRefNode, 1); + null_symbol->refs->v.obj = &link->objs.first->data; + } + lnk_queue_lib_member(scratch.arena, &member_ref_lists[0], null_symbol, lib, lib_member_infos, member_idx); + } + } else { + // search symbols in lib + MemoryZeroTyped(member_ref_lists, tp->worker_count); + LNK_SearchLibTask search_task = { .search_anti_deps = search_anti_deps, .lib = lib, .symtab = symtab, .lib_member_infos = lib_member_infos, .member_ref_lists = member_ref_lists }; + tp_for_parallel(tp, arena, tp->worker_count, lnk_search_lib_task, &search_task); + } LNK_LibMemberRefList queued_members = {0}; lnk_lib_member_ref_list_concat_in_place_array(&queued_members, member_ref_lists, tp->worker_count); @@ -1646,7 +1666,7 @@ lnk_link_inputs(TP_Context *tp, temp_end(temp); } } - + // push inputs for lib member refs for EachIndex(i, queued_members.count) { LNK_LibMemberRef *member_ref = member_refs[i]; diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 7e72e2f1..6d5d7496 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -3,61 +3,62 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { - { LNK_CmdSwitch_Null, 0, "", "", "" }, - { LNK_CmdSwitch_NotImplemented, 0, "NOT_IMPLEMENTED", "", "" }, - { LNK_CmdSwitch_Align, 0, "ALIGN", ":#", "Set section alignment in the virtual address space." }, - { LNK_CmdSwitch_AllowBind, 0, "ALLOWBIND", "[:NO]", "Toggles bind bit in the image header." }, - { LNK_CmdSwitch_AllowIsolation, 0, "ALLOWISOLATION", "[:NO]", "Toggles isolation bit in the image header." }, - { LNK_CmdSwitch_AlternateName, 1, "ALTERNATENAME", ":FROM=TO", "Creates a symbol alias \"FROM=TO\"." }, - { LNK_CmdSwitch_AppContainer, 0, "APPCONTAINER", "[:NO]", "Toggles app container bit in the image header." }, - { LNK_CmdSwitch_Base, 0, "BASE", "{ADDRESS[,SIZE]|@FILENAME,KEY}", "Set default image base address." }, - { LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." }, - { LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, - { LNK_CmdSwitch_DefaultLib, 1, "DEFAULTLIB", ":LIBNAME", "Set default library." }, - { LNK_CmdSwitch_Delay, 0, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, - { LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." }, - { LNK_CmdSwitch_Dll, 0, "DLL", "", "Link to a DLL." }, - { LNK_CmdSwitch_DisallowLib, 1, "DISALLOWLIB", ":LIBRARY", "Prevents LIBRARY from being linked.", }, - { LNK_CmdSwitch_DynamicBase, 0, "DYNAMICBASE", "[:NO]", "Enable random base address in the linked image." }, - { LNK_CmdSwitch_Entry, 1, "ENTRY", ":FUNCTION", "Name of the entry point symbol." }, - { LNK_CmdSwitch_Export, 1, "EXPORT", ":SYMBOL", "Create an export entry for SYMBOL." }, - { LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, - { LNK_CmdSwitch_FileAlign, 0, "FILEALIGN", ":#", "Set section alignment in the file." }, - { LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." }, - { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, - { LNK_CmdSwitch_Heap, 0, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." }, - { LNK_CmdSwitch_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." }, - { LNK_CmdSwitch_Ignore, 0, "IGNORE", ":#", "Ignore a warning." }, - { LNK_CmdSwitch_ImpLib, 0, "IMPLIB", ":FILENAME", "Set file name for the import library." }, - { LNK_CmdSwitch_Include, 1, "INCLUDE", ":SYMBOL", "Force a link against SYMBOL." }, - { LNK_CmdSwitch_InferAsanLibs, 1, "INFERASANLIBS", "[:NO]", "No support." }, - { LNK_CmdSwitch_InferAsanLibsNo, 1, "INFERASANLIBSNO", "", "No support.", }, - { LNK_CmdSwitch_LargeAddressAware, 0, "LARGEADDRESSAWARE", "[:NO]", "For images that can handle addresses > 2GiB." }, - { LNK_CmdSwitch_Lib, 0, "LIB", "", "Turn linker into lib.exe." }, - { LNK_CmdSwitch_LibPath, 0, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, - { LNK_CmdSwitch_Machine, 0, "MACHINE", ":{X64|X86}", "Image target platform." }, - { LNK_CmdSwitch_Manifest, 0, "MANIFEST", "[:{EMBED[,ID=#]|NO]", "Controls whether the linker should create a side manifest." }, - { LNK_CmdSwitch_ManifestDependency, 1, "MANIFESTDEPENDENCY", ":\"manifest dependency XML string\"", "Add a manifest dependency." }, - { LNK_CmdSwitch_ManifestFile, 0, "MANIFESTFILE", ":FILENAME", "Specifies a manifest file." }, - { LNK_CmdSwitch_ManifestInput, 0, "MANIFESTINPUT", ":FILENAME", "Manifest that is embedded in the image." }, + { LNK_CmdSwitch_Null, 0, "", "", "" }, + { LNK_CmdSwitch_NotImplemented, 0, "NOT_IMPLEMENTED", "", "" }, + { LNK_CmdSwitch_Align, 0, "ALIGN", ":#", "Set section alignment in the virtual address space." }, + { LNK_CmdSwitch_AllowBind, 0, "ALLOWBIND", "[:NO]", "Toggles bind bit in the image header." }, + { LNK_CmdSwitch_AllowIsolation, 0, "ALLOWISOLATION", "[:NO]", "Toggles isolation bit in the image header." }, + { LNK_CmdSwitch_AlternateName, 1, "ALTERNATENAME", ":FROM=TO", "Creates a symbol alias \"FROM=TO\"." }, + { LNK_CmdSwitch_AppContainer, 0, "APPCONTAINER", "[:NO]", "Toggles app container bit in the image header." }, + { LNK_CmdSwitch_Base, 0, "BASE", "{ADDRESS[,SIZE]|@FILENAME,KEY}", "Set default image base address." }, + { LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." }, + { LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, + { LNK_CmdSwitch_DefaultLib, 1, "DEFAULTLIB", ":LIBNAME", "Set default library." }, + { LNK_CmdSwitch_Delay, 0, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, + { LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." }, + { LNK_CmdSwitch_Dll, 0, "DLL", "", "Link to a DLL." }, + { LNK_CmdSwitch_DisallowLib, 1, "DISALLOWLIB", ":LIBRARY", "Prevents LIBRARY from being linked.", }, + { LNK_CmdSwitch_DynamicBase, 0, "DYNAMICBASE", "[:NO]", "Enable random base address in the linked image." }, + { LNK_CmdSwitch_Entry, 1, "ENTRY", ":FUNCTION", "Name of the entry point symbol." }, + { LNK_CmdSwitch_Export, 1, "EXPORT", ":SYMBOL", "Create an export entry for SYMBOL." }, + { LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, + { LNK_CmdSwitch_FileAlign, 0, "FILEALIGN", ":#", "Set section alignment in the file." }, + { LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." }, + { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, + { LNK_CmdSwitch_Heap, 0, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." }, + { LNK_CmdSwitch_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." }, + { LNK_CmdSwitch_Ignore, 0, "IGNORE", ":#", "Ignore a warning." }, + { LNK_CmdSwitch_ImpLib, 0, "IMPLIB", ":FILENAME", "Set file name for the import library." }, + { LNK_CmdSwitch_Include, 1, "INCLUDE", ":SYMBOL", "Force a link against SYMBOL." }, + { LNK_CmdSwitch_InferAsanLibs, 1, "INFERASANLIBS", "[:NO]", "No support." }, + { LNK_CmdSwitch_InferAsanLibsNo, 1, "INFERASANLIBSNO", "", "No support.", }, + { LNK_CmdSwitch_LargeAddressAware, 0, "LARGEADDRESSAWARE", "[:NO]", "For images that can handle addresses > 2GiB." }, + { LNK_CmdSwitch_Lib, 0, "LIB", "", "Turn linker into lib.exe." }, + { LNK_CmdSwitch_LibPath, 0, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, + { LNK_CmdSwitch_Machine, 0, "MACHINE", ":{X64|X86}", "Image target platform." }, + { LNK_CmdSwitch_Manifest, 0, "MANIFEST", "[:{EMBED[,ID=#]|NO]", "Controls whether the linker should create a side manifest." }, + { LNK_CmdSwitch_ManifestDependency, 1, "MANIFESTDEPENDENCY", ":\"manifest dependency XML string\"", "Add a manifest dependency." }, + { LNK_CmdSwitch_ManifestFile, 0, "MANIFESTFILE", ":FILENAME", "Specifies a manifest file." }, + { LNK_CmdSwitch_ManifestInput, 0, "MANIFESTINPUT", ":FILENAME", "Manifest that is embedded in the image." }, { LNK_CmdSwitch_ManifestUac, 0, "MANIFESTUAC", ":{NO|{'level'={'asInvoker'|'highestAvailable'|'requireAdministrator'} ['uiAccess'={'true'|'false'}]}}", "Controls UAC information in the manifest." }, - { LNK_CmdSwitch_Merge, 1, "MERGE", ":FROM=TO", "Merges sections." }, - { LNK_CmdSwitch_Natvis, 0, "NATVIS", ":FILENAME", "NATVIS to embed in the PDB." }, - { LNK_CmdSwitch_NoDefaultLib, 1, "NODEFAULTLIB", ":LIBNAME", "Ignore a /DEFAULTLIB." }, - { LNK_CmdSwitch_NoDefaultLib, 0, "NOD", ":LIBNAME", "Alias for /NODEFAULTLIB." }, - { LNK_CmdSwitch_NoExp, 0, "NOEXP", "", "No support." }, - { LNK_CmdSwitch_NoImpLib, 0, "NOIMPLIB", "", "Do not create the import library." }, - { LNK_CmdSwitch_NxCompat, 0, "NXCOMPAT", "[:NO]", "Image is compatible with data execution prevention." }, - { LNK_CmdSwitch_Opt, 0, "OPT", "{REF|ICF}", "Optimizations." }, - { LNK_CmdSwitch_Out, 0, "OUT", ":FILENAME", "File name of the output image." }, - { LNK_CmdSwitch_Pdb, 0, "PDB", ":FILENAME", "File name of the output PDB." }, - { LNK_CmdSwitch_PdbAltPath, 0, "PDBALTPATH", ":PATH", "Alternative output path for the PDB." }, - { LNK_CmdSwitch_PdbPageSize, 0, "PDBPAGESIZE", ":#", "Page size must be power of two." }, - { LNK_CmdSwitch_Release, 1, "RELEASE", "", "Write image checksum." }, - { LNK_CmdSwitch_Stack, 1, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, - { LNK_CmdSwitch_SubSystem, 1, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, - { LNK_CmdSwitch_TsAware, 0, "TSAWARE", "[:NO]", "Image is terminal server aware." }, - { LNK_CmdSwitch_Version, 0, "VERSION", "", "Image version." }, + { LNK_CmdSwitch_Merge, 1, "MERGE", ":FROM=TO", "Merges sections." }, + { LNK_CmdSwitch_Natvis, 0, "NATVIS", ":FILENAME", "NATVIS to embed in the PDB." }, + { LNK_CmdSwitch_NoDefaultLib, 1, "NODEFAULTLIB", ":LIBNAME", "Ignore a /DEFAULTLIB." }, + { LNK_CmdSwitch_NoDefaultLib, 0, "NOD", ":LIBNAME", "Alias for /NODEFAULTLIB." }, + { LNK_CmdSwitch_NoExp, 0, "NOEXP", "", "No support." }, + { LNK_CmdSwitch_NoImpLib, 0, "NOIMPLIB", "", "Do not create the import library." }, + { LNK_CmdSwitch_NxCompat, 0, "NXCOMPAT", "[:NO]", "Image is compatible with data execution prevention." }, + { LNK_CmdSwitch_Opt, 0, "OPT", "{REF|ICF}", "Optimizations." }, + { LNK_CmdSwitch_Out, 0, "OUT", ":FILENAME", "File name of the output image." }, + { LNK_CmdSwitch_Pdb, 0, "PDB", ":FILENAME", "File name of the output PDB." }, + { LNK_CmdSwitch_PdbAltPath, 0, "PDBALTPATH", ":PATH", "Alternative output path for the PDB." }, + { LNK_CmdSwitch_PdbPageSize, 0, "PDBPAGESIZE", ":#", "Page size must be power of two." }, + { LNK_CmdSwitch_Release, 1, "RELEASE", "", "Write image checksum." }, + { LNK_CmdSwitch_Stack, 1, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, + { LNK_CmdSwitch_SubSystem, 1, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, + { LNK_CmdSwitch_TsAware, 0, "TSAWARE", "[:NO]", "Image is terminal server aware." }, + { LNK_CmdSwitch_Version, 0, "VERSION", "", "Image version." }, + { LNK_CmdSwitch_WholeArchive, 0, "WHOLEARCHIVE", "[:LIBNAME]", "Force linker to pull in all objs from the specified lib." }, { LNK_CmdSwitch_Rad_Age, 0, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, { LNK_CmdSwitch_Rad_AltPchDir, 0, "RAD_ALT_PCH_DIR", ":PATH", "Alternative directory to search for PCH object files." }, @@ -1775,6 +1776,18 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_cmd_switch_parse_version(obj, cmd_switch, value_strings, &config->image_ver); } break; + case LNK_CmdSwitch_WholeArchive: { + if (value_strings.node_count == 0) { + config->whole_archive_all = 1; + } else { + String8 lib_name; + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &lib_name)) { + lib_name = str8_chop_last_dot(str8_skip_last_slash(lib_name)); + hash_table_push_path_string(config->arena, config->whole_archive_ht, lib_name, str8_zero()); + } + } + } break; + case LNK_CmdSwitch_Rad_Age: { lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->age, 0); } break; @@ -2086,6 +2099,7 @@ lnk_config_from_cmd_line(String8List raw_cmd_line, LNK_CmdLine cmd_line) config->delay_load_ht = hash_table_init(arena, 0x100); config->disallow_lib_ht = hash_table_init(arena, 0x100); config->fail_if_mismatch_ht = hash_table_init(arena, 0x100); + config->whole_archive_ht = hash_table_init(arena, 0x100); // process command line switches for (LNK_CmdOption *cmd = cmd_line.first_option; cmd != 0; cmd = cmd->next) { diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 401a578f..70d20ffb 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -85,6 +85,7 @@ typedef enum LNK_CmdSwitch_Time, LNK_CmdSwitch_TsAware, LNK_CmdSwitch_Version, + LNK_CmdSwitch_WholeArchive, LNK_CmdSwitch_Rad_Age, LNK_CmdSwitch_Rad_AltPchDir, @@ -350,6 +351,8 @@ typedef struct LNK_Config HashTable *delay_load_ht; HashTable *disallow_lib_ht; HashTable *fail_if_mismatch_ht; + HashTable *whole_archive_ht; + B32 whole_archive_all; U64 unresolved_symbol_limit; U64 unresolved_symbol_ref_limit; LNK_SwitchState map_lines_for_unresolved_symbols; diff --git a/src/torture/torture_radlink.c b/src/torture/torture_radlink.c index b3f8506d..8bbf6846 100644 --- a/src/torture/torture_radlink.c +++ b/src/torture/torture_radlink.c @@ -4758,6 +4758,85 @@ T_BeginTest(patch_cv_symbol_tree) } T_EndTest; +T_BeginTest(whole_archive) +{ + String8 a_obj; + { + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".a"), PE_DATA_SECTION_FLAGS, str8_lit("a")); + a_obj = coff_obj_writer_serialize(scratch.arena, cow); + coff_obj_writer_release(&cow); + } + + String8 b_obj; + { + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".b"), PE_DATA_SECTION_FLAGS, str8_lit("b")); + b_obj = coff_obj_writer_serialize(scratch.arena, cow); + coff_obj_writer_release(&cow); + } + + String8 a_lib; + { + COFF_LibWriter *ciw = coff_lib_writer_alloc(); + coff_lib_writer_push_obj(ciw, str8_lit("a.obj"), a_obj); + a_lib = coff_lib_writer_serialize(scratch.arena, ciw, 0, 0, 1); + coff_lib_writer_release(&ciw); + } + + String8 b_lib; + { + COFF_LibWriter *ciw = coff_lib_writer_alloc(); + coff_lib_writer_push_obj(ciw, str8_lit("b.obj"), b_obj); + b_lib = coff_lib_writer_serialize(scratch.arena, ciw, 0, 0, 1); + coff_lib_writer_release(&ciw); + } + + T_Ok(t_write_entry_obj()); + T_Ok(t_write_file(str8_lit("a.lib"), a_lib)); + T_Ok(t_write_file(str8_lit("b.lib"), b_lib)); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:all_libs.exe entry.obj /wholearchive a.lib b.lib"); + T_Ok(g_last_exit_code == 0); + { + String8 exe = t_read_file(scratch.arena, str8_lit("all_libs.exe")); + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *a_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".a")); + COFF_SectionHeader *b_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".b")); + T_Ok(a_sect != 0); + T_Ok(b_sect != 0); + } + + t_invoke_linkerf("/subsystem:console /entry:entry /out:only_a.exe entry.obj /wholearchive:a.lib a.lib b.lib"); + T_Ok(g_last_exit_code == 0); + { + String8 exe = t_read_file(scratch.arena, str8_lit("only_a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *a_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".a")); + COFF_SectionHeader *b_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".b")); + T_Ok(a_sect != 0); + T_Ok(b_sect == 0); + } + + t_invoke_linkerf("/subsystem:console /entry:entry /out:only_b.exe /wholearchive:b.lib a.lib b.lib entry.obj"); + T_Ok(g_last_exit_code == 0); + { + String8 exe = t_read_file(scratch.arena, str8_lit("only_b.exe")); + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *a_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".a")); + COFF_SectionHeader *b_sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".b")); + T_Ok(a_sect == 0); + T_Ok(b_sect != 0); + } +} +T_EndTest; + #if 0 T_BeginTest(pdb_determ_test) {