From b77c958b5f490e5561dcf7ae3c8dc5beea671f8f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 01:51:12 -0700 Subject: [PATCH] add DEF, GUARD, and SECTION config support Parse /DEF, /GUARD, and /SECTION switches, including DEF handling for NAME/LIBRARY, BASE, VERSION, HEAPSIZE, STACKSIZE, EXPORTS, and SECTIONS. Store section directives in linker config and apply them consistently when gathering object sections when pushing/searching image sections. Pull load-config symbols for /GUARD without setting GUARD_CF until guard tables are emitted. Add coverage for DEF file parity, section directives, and guard load-config handling. --- src/linker/lnk.c | 69 ++-- src/linker/lnk.h | 1 + src/linker/lnk_config.c | 589 +++++++++++++++++++++++++++++--- src/linker/lnk_config.h | 27 ++ src/linker/tests/linker_tests.c | 200 +++++++++++ 5 files changed, 824 insertions(+), 62 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 59213926..00e255ac 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2008,12 +2008,13 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_CmdLine, (*link->last_cmd_lib)->string); } + if (config->guard_flags != LNK_Guard_None) { + lnk_include_symbol(config, str8_lit(MSCRT_LOAD_CONFIG_SYMBOL_NAME), 0); + } + // link inputer lnk_link_inputs(tp, arena, config, inputer, symtab, link); - // TODO: need to figure out under what condition to include load config - //lnk_include_symbol(config, str8_lit(MSCRT_LOAD_CONFIG_SYMBOL_NAME), 0); - { ProfBegin("Push Linker Symbols"); String8 linker_symbols_obj = lnk_make_linker_obj(arena->v[0], config); @@ -3424,8 +3425,10 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) Temp temp = temp_begin(scratch.arena); // was section defined? + COFF_SectionFlags image_sect_flags = sect_flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); String8 sect_name = coff_name_from_section_header(string_table, sect_header); - String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags & ~COFF_SectionFlags_LnkFlags); + image_sect_flags = lnk_apply_section_directives_to_flags(task->config, sect_name, image_sect_flags); + String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, image_sect_flags); LNK_SectionDefinition *sect_defn = hash_table_search_string_raw(sect_defn_ht, sect_name_with_flags); // push new section definition @@ -3434,7 +3437,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) sect_defn->name = sect_name; sect_defn->obj = obj; sect_defn->obj_sect_idx = sect_idx; - sect_defn->flags = sect_flags & ~COFF_SectionFlags_LnkFlags; + sect_defn->flags = image_sect_flags; sect_name_with_flags = push_str8_copy(arena, sect_name_with_flags); hash_table_push_string_raw(arena, sect_defn_ht, sect_name_with_flags, sect_defn); @@ -3471,9 +3474,11 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_contribs_task) LNK_SectionContribChunk *sc_chunk = 0; { Temp temp = temp_begin(scratch.arena); - String8 sect_name = coff_name_from_section_header(string_table, sect_header); - String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags & ~COFF_SectionFlags_LnkFlags); - sc_chunk = hash_table_search_string_raw(task->contribs_ht, sect_name_with_flags); + COFF_SectionFlags sect_flags_clean = sect_flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + String8 sect_name = coff_name_from_section_header(string_table, sect_header); + sect_flags_clean = lnk_apply_section_directives_to_flags(task->config, sect_name, sect_flags_clean); + String8 sect_key = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags_clean); + sc_chunk = hash_table_search_string_raw(task->contribs_ht, sect_key); temp_end(temp); } @@ -5005,6 +5010,22 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config return result; } +internal LNK_Section * +lnk_image_section_table_push(LNK_Config *config, LNK_SectionTable *sectab, String8 name, COFF_SectionFlags flags) +{ + flags = lnk_apply_section_directives_to_flags(config, name, flags); + LNK_Section *sect = lnk_section_table_push(sectab, name, flags); + return sect; +} + +internal LNK_Section * +lnk_image_section_table_search(LNK_Config *config, LNK_SectionTable *sectab, String8 name, COFF_SectionFlags flags) +{ + flags = lnk_apply_section_directives_to_flags(config, name, flags); + LNK_Section *sect = lnk_section_table_search(sectab, name, flags); + return sect; +} + internal LNK_ImageContext lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) { @@ -5017,14 +5038,15 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // init section table // LNK_SectionTable *sectab = lnk_section_table_alloc(); - lnk_section_table_push(sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS); - lnk_section_table_push(sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS ); - lnk_section_table_push(sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); - LNK_Section *common_block_sect = lnk_section_table_search(sectab, str8_lit(".bss"), PE_BSS_SECTION_FLAGS); + lnk_image_section_table_push(config, sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS); + lnk_image_section_table_push(config, sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS ); + lnk_image_section_table_push(config, sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); + LNK_Section *common_block_sect = lnk_image_section_table_search(config, sectab, str8_lit(".bss"), PE_BSS_SECTION_FLAGS); LNK_BuildImageTask task = { + .config = config, .symtab = symtab, .sectab = sectab, .objs_count = objs_count, @@ -5322,7 +5344,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT if (~config->flags & LNK_ConfigFlag_Fixed) { String8 base_relocs_data = lnk_build_base_relocs(tp, arena, config, objs_count, objs); if (base_relocs_data.size) { - LNK_Section *reloc = lnk_section_table_push(sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); + LNK_Section *reloc = lnk_image_section_table_push(config, sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); LNK_SectionContribChunk *first_sc_chunk = lnk_section_contrib_chunk_list_push_chunk(sectab->arena, &reloc->contribs, 1, str8_zero()); LNK_SectionContrib *sc = lnk_section_contrib_chunk_push(first_sc_chunk, 1); sc->first_data_node.string = base_relocs_data; @@ -5454,6 +5476,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT U32 load_config_size = 0; if (sizeof(load_config_size) <= load_config_data.size) { + MemoryCopyStruct(&load_config_size, load_config_data.str); // TODO: load config PE_DataDirectory *load_config_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_LOAD_CONFIG); load_config_dir->virt_off = lnk_voff_from_symbol(image_section_table, load_config_symbol); load_config_dir->virt_size = load_config_size; @@ -5465,7 +5488,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch exceptions { - LNK_Section *pdata_sect = lnk_section_table_search(sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); + LNK_Section *pdata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); if (pdata_sect) { String8 raw_pdata = str8_substr(image_data, rng_1u64(pdata_sect->foff, pdata_sect->foff + pdata_sect->vsize)); pe_pdata_sort(config->machine, raw_pdata); @@ -5478,7 +5501,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch export { - LNK_Section *edata_sect = lnk_section_table_search(sectab, str8_lit(".edata"), PE_EDATA_SECTION_FLAGS); + LNK_Section *edata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".edata"), PE_EDATA_SECTION_FLAGS); if (edata_sect) { PE_DataDirectory *export_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_EXPORT); LNK_SectionContrib *edata_first_contrib = lnk_get_first_section_contrib(edata_sect); @@ -5490,7 +5513,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch base relocs { - LNK_Section *reloc_sect = lnk_section_table_search(sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); + LNK_Section *reloc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); if (reloc_sect) { PE_DataDirectory *reloc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_BASE_RELOC); reloc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, reloc_sect); @@ -5500,7 +5523,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch import and import addr { - LNK_Section *idata_sect = lnk_section_table_search(sectab, str8_lit(".idata"), PE_IDATA_SECTION_FLAGS); + LNK_Section *idata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".idata"), PE_IDATA_SECTION_FLAGS); LNK_Symbol *null_import_desc = lnk_symbol_table_searchf(symtab, "__NULL_IMPORT_DESCRIPTOR"); LNK_Symbol *null_thunk_data = lnk_symbol_table_searchf(symtab, "\x7f%S_NULL_THUNK_DATA", lnk_get_image_name(config)); if (idata_sect && null_import_desc && null_thunk_data) { @@ -5522,7 +5545,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch delay imports { - LNK_Section *didat_sect = lnk_section_table_search(sectab, str8_lit(".didat"), PE_IDATA_SECTION_FLAGS); + LNK_Section *didat_sect = lnk_image_section_table_search(config, sectab, str8_lit(".didat"), PE_IDATA_SECTION_FLAGS); LNK_Symbol *null_import_desc = lnk_symbol_table_search(symtab, str8_lit("__NULL_DELAY_IMPORT_DESCRIPTOR")); LNK_Symbol *last_null_thunk = lnk_symbol_table_searchf(symtab,"\x7f%S_NULL_THUNK_DATA_DLA", lnk_get_image_name(config)); if (didat_sect && null_import_desc && last_null_thunk) { @@ -5542,7 +5565,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // find max align in .tls U64 tls_align = 0; - LNK_Section *tls_sect = lnk_section_table_search(sectab, str8_lit(".tls"), PE_TLS_SECTION_FLAGS); + LNK_Section *tls_sect = lnk_image_section_table_search(config, sectab, str8_lit(".tls"), PE_TLS_SECTION_FLAGS); for (LNK_SectionContribChunk *sc_chunk = tls_sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { for EachIndex (sc_idx, sc_chunk->count) { Assert(IsPow2(sc_chunk->v[sc_idx]->align)); @@ -5572,7 +5595,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch debug { - LNK_Section *debug_dir_sect = lnk_section_table_search(sectab, str8_lit(".RAD_LINK_PE_DEBUG_DIR"), PE_RDATA_SECTION_FLAGS); + LNK_Section *debug_dir_sect = lnk_image_section_table_search(config, sectab, str8_lit(".RAD_LINK_PE_DEBUG_DIR"), PE_RDATA_SECTION_FLAGS); if (debug_dir_sect) { // patch directory PE_DataDirectory *debug_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_DEBUG); @@ -5599,7 +5622,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // patch resources { - LNK_Section *rsrc_sect = lnk_section_table_search(sectab, str8_lit(".rsrc"), PE_RSRC_SECTION_FLAGS); + LNK_Section *rsrc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".rsrc"), PE_RSRC_SECTION_FLAGS); if (rsrc_sect) { PE_DataDirectory *rsrc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_RESOURCES); rsrc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, rsrc_sect); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index b6a154ca..e238e26f 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -261,6 +261,7 @@ typedef struct LNK_ImageFillNode typedef struct { + LNK_Config *config; LNK_SymbolTable *symtab; LNK_SectionTable *sectab; U64 objs_count; diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 16132546..d783784c 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -14,6 +14,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { 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_Def, 1, "DEF", ":FILENAME", "Read exports from a module-definition file." }, { 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." }, @@ -26,6 +27,8 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." }, { LNK_CmdSwitch_Force, 0, "FORCE", "", "Force image output despite errors." }, { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, + { LNK_CmdSwitch_Guard, 0, "GUARD", ":{CF|NO|LONGJMP|EHCONT}", "Controls Control Flow Guard metadata." }, + { LNK_CmdSwitch_GuardSym, 1, "GUARDSYM", ":SYMBOL,S", "MSVC guard symbol directive." }, { 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." }, @@ -56,6 +59,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_PdbPageSize, 0, "PDBPAGESIZE", ":#", "Page size must be power of two." }, { LNK_CmdSwitch_PdbStripped, 0, "PDBSTRIPPED", ":FILENAME", "Create a stripped PDB containing public symbols, a section map, and a list of object files." }, { LNK_CmdSwitch_Release, 1, "RELEASE", "", "Write image checksum." }, + { LNK_CmdSwitch_Section, 1, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, { 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." }, @@ -715,14 +719,51 @@ internal String8 lnk_text_file_string_from_data(Arena *arena, String8 data) { String8 result = data; + if (data.size >= 2 && data.str[0] == 0xff && data.str[1] == 0xfe) { + // decode UTF-16LE BOM result = str8_from_16(arena, str16((U16 *)(data.str + 2), (data.size - 2) / sizeof(U16))); } else if (data.size >= 3 && data.str[0] == 0xef && data.str[1] == 0xbb && data.str[2] == 0xbf) { + // strip UTF-8 BOM result = str8_skip(data, 3); } + return result; } +internal void +lnk_push_export_to_config(LNK_Config *config, LNK_Obj *obj, PE_ExportParse export_parse) +{ + // lookup existing export + String8 export_name = pe_name_from_export_parse(&export_parse); + PE_ExportParseNode *exp_n = hash_map_search_string_raw(&config->export_ht, export_name); + + if (exp_n == 0) { + // push new export + if (!export_parse.is_forwarder) { + lnk_include_symbol(config, export_parse.name, 0); + } + + exp_n = pe_export_parse_list_push(config->arena, &config->export_symbol_list, export_parse); + hash_map_push_string_raw(config->arena, &config->export_ht, export_name, exp_n); + } else { + // merge duplicate export + PE_ExportParse *extant_export = &exp_n->data; + B32 alias_conflict = extant_export->alias.size && + export_parse.alias.size && + !str8_match(extant_export->alias, export_parse.alias, 0); + B32 ordinal_conflict = extant_export->ordinal != export_parse.ordinal; + + if (alias_conflict || ordinal_conflict) { + lnk_error_obj(LNK_Error_IllExport, obj, "ambiguous symbol export %S", export_parse.name); + } + + if (!alias_conflict && !ordinal_conflict && extant_export->alias.size == 0 && export_parse.alias.size != 0) { + extant_export->alias = export_parse.alias; + } + } +} + internal B32 lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) { @@ -740,6 +781,43 @@ lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) return is_parse_ok; } +typedef struct LNK_SectionDirectiveAttr +{ + U8 code; + COFF_SectionFlags flag; + B32 is_mem_attr; + B32 negated_sets; +} LNK_SectionDirectiveAttr; + +global read_only LNK_SectionDirectiveAttr g_section_directive_attr_map[] = +{ + { 'D', COFF_SectionFlag_MemDiscardable, 0, 0 }, + { 'E', COFF_SectionFlag_MemExecute, 1, 0 }, + { 'K', COFF_SectionFlag_MemNotCached, 0, 1 }, + { 'P', COFF_SectionFlag_MemNotPaged, 0, 1 }, + { 'R', COFF_SectionFlag_MemRead, 1, 0 }, + { 'S', COFF_SectionFlag_MemShared, 0, 0 }, + { 'W', COFF_SectionFlag_MemWrite, 1, 0 }, +}; + +typedef struct LNK_GuardOption +{ + String8 name; + LNK_GuardFlags set_flags; + LNK_GuardFlags clear_flags; +} LNK_GuardOption; + +global read_only LNK_GuardOption g_guard_option_table[] = +{ + { str8_lit_comp("cf"), LNK_Guard_Cf, 0 }, + { str8_lit_comp("nocf"), 0, LNK_Guard_Cf }, + { str8_lit_comp("longjmp"), LNK_Guard_LongJmp, 0 }, + { str8_lit_comp("nolongjmp"), 0, LNK_Guard_LongJmp }, + { str8_lit_comp("ehcont"), LNK_Guard_EhCont, 0 }, + { str8_lit_comp("noehcont"), 0, LNK_Guard_EhCont }, + { str8_lit_comp("no"), 0, LNK_Guard_All }, +}; + internal LNK_AltNameNode * lnk_alt_name_list_push(Arena *arena, LNK_AltNameList *list, LNK_AltName v) { @@ -760,6 +838,24 @@ lnk_merge_directive_list_push(Arena *arena, LNK_MergeDirectiveList *list, LNK_Me return node; } +internal COFF_SectionFlags +lnk_apply_section_directives_to_flags(LNK_Config *config, String8 full_section_name, COFF_SectionFlags flags) +{ + String8 section_name = {0}; + String8 sort_idx = {0}; + coff_parse_section_name(full_section_name, §ion_name, &sort_idx); + + for EachNode(dir_n, LNK_SectionDirectiveNode, config->section_list.first) { + LNK_SectionDirective *dir = &dir_n->v; + if (str8_match(dir->name, section_name, 0)) { + flags &= ~dir->clear_flags; + flags |= dir->set_flags; + } + } + + return flags; +} + internal String8 lnk_get_image_name(LNK_Config *config) { @@ -1148,6 +1244,8 @@ lnk_apply_write_temp_files(Arena *arena, LNK_Config *config) } } +internal void lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj); + internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List value_strings, LNK_Obj *obj) { @@ -1266,6 +1364,13 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } } break; + case LNK_CmdSwitch_Def: { + String8 path = {0}; + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &path)) { + lnk_apply_def_file_to_config(config, path, obj); + } + } break; + case LNK_CmdSwitch_Delay: { if (value_strings.node_count == 0 || value_strings.node_count > 1) { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -1313,42 +1418,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Export: { PE_ExportParse export_parse = {0}; if (lnk_parse_export_directive_ex(config->arena, value_strings, obj, &export_parse)) { - String8 export_name = pe_name_from_export_parse(&export_parse); - PE_ExportParseNode *exp_n = hash_map_search_string_raw(&config->export_ht, export_name); - - if (exp_n == 0) { - // make sure export is defined - if (!export_parse.is_forwarder) { - lnk_include_symbol(config, export_parse.name, 0); - } - - // push new export - exp_n = pe_export_parse_list_push(config->arena, &config->export_symbol_list, export_parse); - - hash_map_push_string_raw(config->arena, &config->export_ht, export_name, exp_n); - } else { - B32 is_ambiguous = 1; - PE_ExportParse *extant_export = &exp_n->data; - - if (extant_export->alias.size && export_parse.alias.size && !str8_match(extant_export->alias, export_parse.alias, 0)) { - goto report; - } - - if (extant_export->ordinal != export_parse.ordinal) { - goto report; - } - - is_ambiguous = 0; - - if (extant_export->alias.size == 0 && export_parse.alias.size != 0) { - extant_export->alias = export_parse.alias; - } - - report:; - if (is_ambiguous) { - lnk_error_obj(LNK_Error_IllExport, obj, "ambiguous symbol export %S", export_parse.name); - } - } + lnk_push_export_to_config(config, obj, export_parse); } } break; @@ -1400,6 +1470,25 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List config->do_function_pad_min = LNK_SwitchState_Yes; } break; + case LNK_CmdSwitch_Guard: { + for EachNode(n, String8Node, value_strings.first) { + LNK_GuardOption *option = 0; + for EachElement(i, g_guard_option_table) { + if (str8_matchi(g_guard_option_table[i].name, n->string)) { + option = &g_guard_option_table[i]; + break; + } + } + + if (option != 0) { + config->guard_flags &= ~option->clear_flags; + config->guard_flags |= option->set_flags; + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown option \"%S\"", n->string); + } + } + } break; + case LNK_CmdSwitch_Heap: { Rng1U64 reserve_commit; reserve_commit.v[0] = config->heap_reserve; @@ -1744,6 +1833,101 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } } break; + case LNK_CmdSwitch_Section: { + LNK_SectionDirective section_dir = {0}; + B32 is_parse_ok = 1; + + if (value_strings.node_count < 2) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "expected section name and attributes"); + is_parse_ok = 0; + } else { + section_dir.name = value_strings.first->string; + + B32 has_attr = 0; + B32 has_mem_attr = 0; + for (String8Node *param_n = value_strings.first->next; param_n != 0; param_n = param_n->next) { + String8 param = param_n->string; + + if (str8_match_lit("ALIGN=", param, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy)) { + String8 align_string = str8_skip(param, sizeof("ALIGN=") - 1); + U64 align = 0; + if (try_u64_from_str8_c_rules(align_string, &align)) { + COFF_SectionFlags align_flag = coff_section_flag_from_align_size(align); + if (align_flag) { + COFF_SectionFlags align_mask = (COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); + section_dir.clear_flags |= align_mask; + section_dir.set_flags &= ~align_mask; + section_dir.set_flags |= align_flag; + section_dir.clear_flags &= ~align_flag; + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "invalid alignment \"%S\"", align_string); + is_parse_ok = 0; + } + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "unable to parse alignment \"%S\"", align_string); + is_parse_ok = 0; + } + continue; + } + + B32 negate = 0; + for EachIndex(i, param.size) { + U8 c = upper_from_char(param.str[i]); + if (c == '!') { + negate = 1; + continue; + } + + LNK_SectionDirectiveAttr *attr = 0; + for EachElement(attr_idx, g_section_directive_attr_map) { + if (g_section_directive_attr_map[attr_idx].code == c) { + attr = &g_section_directive_attr_map[attr_idx]; + break; + } + } + + if (attr == 0) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "unknown section attribute '%c' in \"%S\"", c, param); + is_parse_ok = 0; + } else { + has_attr = 1; + has_mem_attr = has_mem_attr || attr->is_mem_attr; + + COFF_SectionFlags flags = (negate == attr->negated_sets) ? attr->flag : 0; + section_dir.clear_flags |= attr->flag; + section_dir.set_flags &= ~attr->flag; + section_dir.set_flags |= flags; + section_dir.clear_flags &= ~flags; + } + + negate = 0; + } + + if (negate) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "dangling '!' in \"%S\"", param); + is_parse_ok = 0; + } + } + + if (has_attr) { + section_dir.clear_flags |= COFF_SectionFlag_MemDiscardable|COFF_SectionFlag_MemNotCached|COFF_SectionFlag_MemNotPaged|COFF_SectionFlag_MemShared; + if (has_mem_attr) { + section_dir.clear_flags |= COFF_SectionFlag_MemExecute|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemWrite; + } + section_dir.clear_flags &= ~section_dir.set_flags; + } + } + + if (is_parse_ok) { + section_dir.name = push_str8_copy(config->arena, section_dir.name); + + LNK_SectionDirectiveNode *node = push_array_no_zero(config->arena, LNK_SectionDirectiveNode, 1); + node->v = section_dir; + SLLQueuePush(config->section_list.first, config->section_list.last, node); + config->section_list.count += 1; + } + } break; + case LNK_CmdSwitch_Stack: { Rng1U64 reserve_commit; reserve_commit.v[0] = config->stack_reserve; @@ -2204,6 +2388,331 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List scratch_end(scratch); } +typedef enum LNK_DefFileStmt +{ + LNK_DefFileStmt_Null, + LNK_DefFileStmt_Description, + LNK_DefFileStmt_Exports, + LNK_DefFileStmt_HeapSize, + LNK_DefFileStmt_Imports, + LNK_DefFileStmt_Library, + LNK_DefFileStmt_Name, + LNK_DefFileStmt_Sections, + LNK_DefFileStmt_Segments, + LNK_DefFileStmt_StackSize, + LNK_DefFileStmt_Stub, + LNK_DefFileStmt_Version, +} LNK_DefFileStmt; + +global read_only struct +{ + String8 name; + LNK_DefFileStmt stmt; +} g_def_file_stmt_map[] = +{ + { str8_lit_comp("DESCRIPTION"), LNK_DefFileStmt_Description }, + { str8_lit_comp("EXPORTS"), LNK_DefFileStmt_Exports }, + { str8_lit_comp("HEAPSIZE"), LNK_DefFileStmt_HeapSize }, + { str8_lit_comp("IMPORTS"), LNK_DefFileStmt_Imports }, + { str8_lit_comp("LIBRARY"), LNK_DefFileStmt_Library }, + { str8_lit_comp("NAME"), LNK_DefFileStmt_Name }, + { str8_lit_comp("SECTIONS"), LNK_DefFileStmt_Sections }, + { str8_lit_comp("SEGMENTS"), LNK_DefFileStmt_Segments }, + { str8_lit_comp("STACKSIZE"), LNK_DefFileStmt_StackSize }, + { str8_lit_comp("STUB"), LNK_DefFileStmt_Stub }, + { str8_lit_comp("VERSION"), LNK_DefFileStmt_Version }, +}; + +typedef struct LNK_DefFileLineNode LNK_DefFileLineNode; +struct LNK_DefFileLineNode +{ + LNK_DefFileStmt stmt; + String8 line; + B32 is_keyword; + LNK_DefFileLineNode *next; +}; + +typedef struct LNK_DefFileLineList LNK_DefFileLineList; +struct LNK_DefFileLineList +{ + U64 count; + LNK_DefFileLineNode *first; + LNK_DefFileLineNode *last; +}; + +internal String8List +lnk_def_file_tokenize(Arena *arena, String8 line) +{ + // tokenize with windows quoting rules + line = push_str8_copy(arena, line); + for EachIndex(i, line.size) { + if (line.str[i] == '\t') { + line.str[i] = ' '; + } + } + + String8List tokens = lnk_arg_list_parse_windows_rules(arena, line); + + String8List result = {0}; + for (String8Node *token_n = tokens.first; token_n != 0; token_n = token_n->next) { + String8 token = token_n->string; + String8Node *next_n = token_n->next; + if (token.size == 1 && token.str[0] == '@' && next_n != 0) { + str8_list_push(arena, &result, str8_cat(arena, token, next_n->string)); + token_n = next_n; + } else { + str8_list_push(arena, &result, token); + } + } + return result; +} + +internal void +lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) +{ + Temp scratch = scratch_begin(&config->arena, 1); + + // load DEF file + String8 raw_file = lnk_read_data_from_file_path(scratch.arena, config->io_flags, path); + String8 file = lnk_text_file_string_from_data(scratch.arena, raw_file); + + // parse & collect normalized DEF lines + LNK_DefFileLineList def_lines = {0}; + LNK_DefFileStmt active_stmt = LNK_DefFileStmt_Null; + String8 rest = file; + while (rest.size != 0) { + String8 line = str8_chop_line(&rest); + + // strip comments outside quotes + B32 in_quote = 0; + for EachIndex(i, line.size) { + U8 c = line.str[i]; + if (in_quote && c == '\\') { + i += 1; + continue; + } + if (c == '"') { + in_quote = !in_quote; + } else if (c == ';' && !in_quote) { + line = str8_prefix(line, i); + break; + } + } + + line = str8_skip_chop_whitespace(line); + if (line.size == 0) { continue; } + + // parse statement keyword + String8 tail = line; + LNK_DefFileStmt stmt = LNK_DefFileStmt_Null; + B32 is_keyword = 0; + if (line.size != 0 && line.str[0] != '"') { + U64 keyword_opl = 0; + for (; keyword_opl < line.size; keyword_opl += 1) { + U8 c = line.str[keyword_opl]; + if (char_is_space(c) || c == ':' || c == '=') { + break; + } + } + + String8 keyword = str8_prefix(line, keyword_opl); + for EachElement(i, g_def_file_stmt_map) { + if (str8_matchi(g_def_file_stmt_map[i].name, keyword)) { + stmt = g_def_file_stmt_map[i].stmt; + break; + } + } + + if (stmt != LNK_DefFileStmt_Null) { + U64 tail_pos = keyword_opl; + while (tail_pos < line.size && char_is_space(line.str[tail_pos])) { + tail_pos += 1; + } + if (tail_pos < line.size && (line.str[tail_pos] == ':' || line.str[tail_pos] == '=')) { + tail_pos += 1; + } + tail = str8_skip_chop_whitespace(str8_skip(line, tail_pos)); + } + } + + if (stmt != LNK_DefFileStmt_Null) { + B32 is_multiline = stmt == LNK_DefFileStmt_Exports || + stmt == LNK_DefFileStmt_Sections || + stmt == LNK_DefFileStmt_Imports || + stmt == LNK_DefFileStmt_Segments; + active_stmt = is_multiline ? stmt : LNK_DefFileStmt_Null; + is_keyword = 1; + line = tail; + } else { + stmt = active_stmt; + } + + LNK_DefFileLineNode *node = push_array(scratch.arena, LNK_DefFileLineNode, 1); + node->stmt = stmt; + node->line = line; + node->is_keyword = is_keyword; + SLLQueuePush(def_lines.first, def_lines.last, node); + def_lines.count += 1; + } + + // apply collected DEF lines to config + for EachNode(def_line, LNK_DefFileLineNode, def_lines.first) { + String8 line = def_line->line; + LNK_DefFileStmt stmt = def_line->stmt; + + switch (stmt) { + case LNK_DefFileStmt_Description: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "DESCRIPTION is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Imports: { + if (def_line->is_keyword) { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "IMPORTS is ignored in DEF files"); + } else { + lnk_log(LNK_Log_Debug, "%S: unsupported old-style import specifications", path); + } + } break; + + case LNK_DefFileStmt_Stub: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "STUB is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Segments: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "SEGMENTS is ignored in DEF files"); + } break; + + case LNK_DefFileStmt_Exports: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + if (tokens.node_count != 0) { + PE_ExportParse export_parse = {0}; + if (lnk_parse_export_directive_ex(config->arena, tokens, obj, &export_parse)) { + export_parse.obj_path = path; + lnk_push_export_to_config(config, obj, export_parse); + } + } + } break; + + case LNK_DefFileStmt_Library: + case LNK_DefFileStmt_Name: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + String8 name = {0}; + + for EachNode(token_n, String8Node, tokens.first) { + String8 token = token_n->string; + String8 base_value = {0}; + B32 has_base = 0; + + U64 sep_pos = str8_find_needle(token, 0, str8_lit("="), 0); + if (sep_pos < token.size) { + String8 key = str8_prefix(token, sep_pos); + if (str8_matchi(key, str8_lit("BASE"))) { + base_value = str8_skip(token, sep_pos + 1); + has_base = 1; + } + } else if (str8_matchi(token, str8_lit("BASE")) || + str8_match_lit("BASE:", token, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy)) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Def, "syntax error in DEF file BASE specification"); + } + + if (has_base) { + String8List values = str8_split_by_string_chars(scratch.arena, base_value, str8_lit(","), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("base"), values, obj); + } else if (name.size == 0) { + name = token; + } else { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "ignoring unexpected DEF token \"%S\"", token); + } + } + + if (stmt == LNK_DefFileStmt_Library) { + config->file_characteristics |= PE_ImageFileCharacteristic_FILE_DLL; + } + + if (name.size != 0) { + String8 image_name = name; + if (stmt == LNK_DefFileStmt_Library) { + // set DLL import name + String8 file_name = str8_skip_last_slash(name); + B32 has_ext = (str8_skip_last_dot(file_name).size != file_name.size); + if (!has_ext) { + image_name = path_replace_file_extension(scratch.arena, name, str8_lit("dll")); + } + if (config->image_alt_path.size == 0) { + config->image_alt_path = push_str8_copy(config->arena, image_name); + } + } + + if (config->out_path.size == 0) { + config->out_path = push_str8_copy(config->arena, image_name); + } + } + } break; + + case LNK_DefFileStmt_Sections: { + String8List tokens = lnk_def_file_tokenize(scratch.arena, line); + if (tokens.node_count != 0) { + B32 is_parse_ok = 1; + String8List section_values = {0}; + str8_list_push(scratch.arena, §ion_values, tokens.first->string); + + for (String8Node *token_n = tokens.first->next; token_n != 0; token_n = token_n->next) { + String8 token = token_n->string; + if (str8_matchi(token, str8_lit("CLASS"))) { + if (token_n->next != 0) { + token_n = token_n->next; + } + continue; + } + + String8 attr = {0}; + if (str8_matchi(token, str8_lit("EXECUTE"))) { + attr = str8_lit("E"); + } else if (str8_matchi(token, str8_lit("READ"))) { + attr = str8_lit("R"); + } else if (str8_matchi(token, str8_lit("SHARED"))) { + attr = str8_lit("S"); + } else if (str8_matchi(token, str8_lit("WRITE"))) { + attr = str8_lit("W"); + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Def, "unknown DEF SECTIONS specifier \"%S\"", token); + is_parse_ok = 0; + } + + if (attr.size != 0) { + str8_list_push(scratch.arena, §ion_values, attr); + } + } + + if (is_parse_ok) { + lnk_apply_cmd_option_to_config(config, str8_lit("section"), section_values, obj); + } + } + } break; + + case LNK_DefFileStmt_StackSize: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("stack"), values, obj); + } break; + + case LNK_DefFileStmt_Version: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("version"), values, obj); + } break; + + case LNK_DefFileStmt_HeapSize: { + String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); + lnk_apply_cmd_option_to_config(config, str8_lit("heap"), values, obj); + } break; + + default: { + lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, LNK_CmdSwitch_Def, "ignoring unrecognized DEF statement \"%S\"", line); + } break; + } + } + + scratch_end(scratch); +} + internal void lnk_config_pushf(LNK_Config *config, char *fmt, ...) { @@ -2354,13 +2863,15 @@ lnk_config_init(LNK_CmdLine cmd_line) config->dll_characteristics |= PE_DllCharacteristic_DYNAMIC_BASE; } - // set flag for /guard + // TODO: Set GUARD_CF only after emitting the Guard CF function tables. + // Marking the image without valid load-config tables makes CFG-enabled + // executables crash on indirect calls. if (config->guard_flags != LNK_Guard_None) { - config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF; + // config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF; } // handle empty /OUT - if (!lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Out)) { + if (!config->out_path.size) { String8 name = str8_list_first(&config->input_list[LNK_Input_Obj]); String8 ext = (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) ? str8_lit("dll") : str8_lit("exe"); config->out_path = path_replace_file_extension(scratch.arena, name, ext); diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 67ae4c3f..d469a413 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -47,6 +47,7 @@ typedef enum LNK_CmdSwitch_Brepro, LNK_CmdSwitch_Debug, LNK_CmdSwitch_DefaultLib, + LNK_CmdSwitch_Def, LNK_CmdSwitch_Delay, LNK_CmdSwitch_DelayLoad, LNK_CmdSwitch_DisallowLib, @@ -59,6 +60,8 @@ typedef enum LNK_CmdSwitch_Fixed, LNK_CmdSwitch_Force, LNK_CmdSwitch_FunctionPadMin, + LNK_CmdSwitch_Guard, + LNK_CmdSwitch_GuardSym, LNK_CmdSwitch_Heap, LNK_CmdSwitch_HighEntropyVa, LNK_CmdSwitch_Ignore, @@ -88,6 +91,7 @@ typedef enum LNK_CmdSwitch_PdbPageSize, LNK_CmdSwitch_PdbStripped, LNK_CmdSwitch_Release, + LNK_CmdSwitch_Section, LNK_CmdSwitch_Stack, LNK_CmdSwitch_SubSystem, LNK_CmdSwitch_Time, @@ -267,6 +271,26 @@ typedef struct LNK_MergeDirectiveList LNK_MergeDirectiveNode *last; } LNK_MergeDirectiveList; +typedef struct LNK_SectionDirective +{ + String8 name; + COFF_SectionFlags set_flags; + COFF_SectionFlags clear_flags; +} LNK_SectionDirective; + +typedef struct LNK_SectionDirectiveNode +{ + struct LNK_SectionDirectiveNode *next; + LNK_SectionDirective v; +} LNK_SectionDirectiveNode; + +typedef struct LNK_SectionDirectiveList +{ + U64 count; + LNK_SectionDirectiveNode *first; + LNK_SectionDirectiveNode *last; +} LNK_SectionDirectiveList; + typedef enum { LNK_DebugInfoGuid_Null, @@ -363,6 +387,7 @@ typedef struct LNK_Config LNK_IncludeSymbolList include_symbol_list; LNK_AltNameList alt_name_list; LNK_MergeDirectiveList merge_list; + LNK_SectionDirectiveList section_list; U64 data_dir_count; B32 build_imp_lib; B32 build_exp; @@ -575,6 +600,8 @@ internal B32 lnk_is_thread_pool_shared(LNK_Config *config); internal B32 lnk_is_section_removed (LNK_Config *config, String8 section_name); internal B32 lnk_is_dll_delay_load (LNK_Config *config, String8 dll_name); +internal COFF_SectionFlags lnk_apply_section_directives_to_flags(LNK_Config *config, String8 full_section_name, COFF_SectionFlags flags); + internal String8 lnk_get_lib_name (String8 path); internal void lnk_push_disallow_lib(LNK_Config *config, String8 path); internal B32 lnk_is_lib_disallowed(LNK_Config *config, String8 path); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 80f4d52a..ea749f8f 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -710,6 +710,58 @@ TEST(merge) } } +TEST(section_directive_read_only_grouped_section) +{ + T_Ok(t_write_entry_obj()); + + T_Ok(t_write_def_obj("prot.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "prot_a", "prot$a", str8_lit_comp("A"), .flags = "rw:data@1" }, + { "prot_mem", "prot$mem", str8_lit_comp("mem"), .flags = "rw:data@1" }, + { "prot_z", "prot$z", str8_lit_comp("Z"), .flags = "rw:data@1" }, + {0} + }, + .directives = (char *[]){ "/SECTION:prot,R", 0 }, + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe entry.obj prot.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit("prot")); + T_Ok(sect != 0); + T_Ok(sect->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead)); +} + +TEST(section_directive_align_grouped_section) +{ + if (t_id_linker() != Linker_radlink) { return; } + + T_Ok(t_write_entry_obj()); + + T_Ok(t_write_def_obj("prot.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "prot_mem", "prot$mem", str8_lit_comp("mem"), .flags = "rw:data@1" }, + {0} + }, + .directives = (char *[]){ "/SECTION:prot,R,ALIGN=8192", 0 }, + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe entry.obj prot.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit("prot")); + T_Ok(sect != 0); + T_Ok(sect->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align8192Bytes)); +} + TEST(link_undef) { T_Ok(t_write_def_obj("undef.obj", (T_COFF_DefObj){ @@ -2153,6 +2205,48 @@ TEST(find_merged_pdata) T_Ok(dim_1u64(pe.data_dir_franges[PE_DataDirectoryIndex_EXCEPTIONS]) == 0xC); } +TEST(guard_cf_pulls_load_config) +{ + U8 load_config_data[0x40] = {0}; + U32 load_config_size = sizeof(load_config_data); + MemoryCopy(load_config_data, &load_config_size, sizeof(load_config_size)); + + T_Ok(t_write_entry_obj()); + T_Ok(t_write_def_lib("loadcfg.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Obj, + .obj = { + .path = str8_lit("loadcfg.obj"), + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "loadcfg", ".rdata", str8_array_fixed(load_config_data), .flags = "r:data@8" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("_load_config_used", "loadcfg", 0), + {0} + } + } + }, + {0} + } + })); + + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /guard:cf entry.obj loadcfg.lib"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + T_Ok(dim_1u64(pe.data_dir_franges[PE_DataDirectoryIndex_LOAD_CONFIG]) == load_config_size); + + PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); + if (t_id_linker() == Linker_radlink) { + T_Ok(!(opt->dll_characteristics & PE_DllCharacteristic_GUARD_CF)); + } +} + TEST(section_sort) { COFF_SectionFlags data_flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align1Bytes; @@ -2792,6 +2886,112 @@ TEST(import_export) // TODO: check import table } +TEST(def_file_full) +{ + if (t_id_linker() == Linker_lld) { return; } + + T_Ok(t_write_def_obj("def_full.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".rdata", str8_lit("test"), .flags = "rw:data" }, + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("entry", "text", 0), + T_COFF_DefSymbol_Extern("foo", "data", 0), + {0} + }, + })); + + T_Ok(t_write_file(str8_lit("full.def"), str8_lit( + "; leading comment\n" + "NAME \"def full.exe\" BASE=0x140020000\n" + "VERSION 7.8\n" + "HEAPSIZE 0x30000, 0x4000\n" + "STACKSIZE 0x50000,0x6000\n" + "SECTIONS .rdata READ\n" + "EXPORTS foo @ 2 DATA\n"))); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:full.def def_full.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("def full.exe")); + PE_BinInfo pe = pe_bin_info_from_data(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); + PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); + + T_Ok(opt->image_base == 0x140020000); + T_Ok(opt->major_img_ver == 7); + T_Ok(opt->minor_img_ver == 8); + T_Ok(opt->sizeof_heap_reserve == 0x30000); + T_Ok(opt->sizeof_heap_commit == 0x4000); + T_Ok(opt->sizeof_stack_reserve == 0x50000); + T_Ok(opt->sizeof_stack_commit == 0x6000); + + COFF_SectionHeader *rdata = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + T_Ok(rdata != 0); + T_Ok(rdata->flags == (COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead)); + + PE_ParsedExportTable export_table = pe_exports_from_data(arena, pe.section_count, section_table, exe, pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); + T_Ok(export_table.export_count == 1); + T_Ok(str8_match(export_table.exports[0].name, str8_lit("foo"), 0)); + T_Ok(export_table.exports[0].ordinal == 2); + + T_Ok(t_write_file(str8_lit("bad_base_space.def"), str8_lit( + "NAME bad_base_space BASE = 0x140020000\n" + "EXPORTS foo @2 DATA\n"))); + T_Ok(t_write_file(str8_lit("bad_base_colon.def"), str8_lit( + "NAME bad_base_colon BASE:0x140020000\n" + "EXPORTS foo @2 DATA\n"))); + T_Ok(t_write_file(str8_lit("bad_section_align.def"), str8_lit( + "NAME bad_section_align\n" + "SECTIONS .rdata READ ALIGN=8192\n" + "EXPORTS foo @2 DATA\n"))); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_base_space.def /out:bad_base_space.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_base_colon.def /out:bad_base_colon.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + t_invoke_linkerf("/subsystem:console /entry:entry /def:bad_section_align.def /out:bad_section_align.exe def_full.obj"); + T_Ok(g_last_exit_code != 0); + + T_Ok(t_write_def_obj("def_full_dll.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".data", str8_lit("test"), .flags = "rw:data" }, + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("_DllMainCRTStartup", "text", 0), + T_COFF_DefSymbol_Extern("dll_foo", "data", 0), + {0} + }, + })); + + T_Ok(t_write_file(str8_lit("full_dll.def"), str8_lit( + "LIBRARY folded BASE=0x180020000\n" + "EXPORTS\n" + " dll_foo DATA\n"))); + + t_invoke_linkerf("/dll /subsystem:console /def:full_dll.def def_full_dll.obj"); + T_Ok(g_last_exit_code == 0); + + String8 dll = t_read_file(arena, str8_lit("folded.dll")); + PE_BinInfo dll_pe = pe_bin_info_from_data(arena, dll); + COFF_SectionHeader *dll_section_table = (COFF_SectionHeader *)str8_substr(dll, dll_pe.section_table_range).str; + PE_OptionalHeader32Plus *dll_opt = str8_deserial_get_raw_ptr(dll, dll_pe.optional_header_off, sizeof(*dll_opt)); + PE_ParsedExportTable dll_export_table = pe_exports_from_data(arena, dll_pe.section_count, dll_section_table, dll, dll_pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], dll_pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); + + T_Ok(dll_opt->image_base == 0x180020000); + T_Ok(dll_export_table.export_count == 1); + T_Ok(str8_match(dll_export_table.exports[0].name, str8_lit("dll_foo"), 0)); +} + TEST(utf16_rsp) { T_Ok(t_write_entry_obj());