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.
This commit is contained in:
Nikita Smith
2026-07-27 14:47:14 -07:00
committed by Ryan Fleury
parent 35722f2a01
commit b77c958b5f
5 changed files with 824 additions and 62 deletions
+46 -23
View File
@@ -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); 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 // link inputer
lnk_link_inputs(tp, arena, config, inputer, symtab, link); 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"); ProfBegin("Push Linker Symbols");
String8 linker_symbols_obj = lnk_make_linker_obj(arena->v[0], config); 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); Temp temp = temp_begin(scratch.arena);
// was section defined? // 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 = 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); LNK_SectionDefinition *sect_defn = hash_table_search_string_raw(sect_defn_ht, sect_name_with_flags);
// push new section definition // push new section definition
@@ -3434,7 +3437,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task)
sect_defn->name = sect_name; sect_defn->name = sect_name;
sect_defn->obj = obj; sect_defn->obj = obj;
sect_defn->obj_sect_idx = sect_idx; 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); 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); 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; LNK_SectionContribChunk *sc_chunk = 0;
{ {
Temp temp = temp_begin(scratch.arena); Temp temp = temp_begin(scratch.arena);
String8 sect_name = coff_name_from_section_header(string_table, sect_header); COFF_SectionFlags sect_flags_clean = sect_flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved);
String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, sect_flags & ~COFF_SectionFlags_LnkFlags); String8 sect_name = coff_name_from_section_header(string_table, sect_header);
sc_chunk = hash_table_search_string_raw(task->contribs_ht, sect_name_with_flags); 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); temp_end(temp);
} }
@@ -5005,6 +5010,22 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config
return result; 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 internal LNK_ImageContext
lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) 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 // init section table
// //
LNK_SectionTable *sectab = lnk_section_table_alloc(); LNK_SectionTable *sectab = lnk_section_table_alloc();
lnk_section_table_push(sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS ); lnk_image_section_table_push(config, sectab, str8_lit(".text" ), PE_TEXT_SECTION_FLAGS );
lnk_section_table_push(sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS); lnk_image_section_table_push(config, sectab, str8_lit(".rdata"), PE_RDATA_SECTION_FLAGS);
lnk_section_table_push(sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS ); lnk_image_section_table_push(config, sectab, str8_lit(".data" ), PE_DATA_SECTION_FLAGS );
lnk_section_table_push(sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS ); lnk_image_section_table_push(config, sectab, str8_lit(".bss" ), PE_BSS_SECTION_FLAGS );
lnk_section_table_push(sectab, str8_lit(".pdata"), PE_PDATA_SECTION_FLAGS); lnk_image_section_table_push(config, 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_Section *common_block_sect = lnk_image_section_table_search(config, sectab, str8_lit(".bss"), PE_BSS_SECTION_FLAGS);
LNK_BuildImageTask task = { LNK_BuildImageTask task = {
.config = config,
.symtab = symtab, .symtab = symtab,
.sectab = sectab, .sectab = sectab,
.objs_count = objs_count, .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) { if (~config->flags & LNK_ConfigFlag_Fixed) {
String8 base_relocs_data = lnk_build_base_relocs(tp, arena, config, objs_count, objs); String8 base_relocs_data = lnk_build_base_relocs(tp, arena, config, objs_count, objs);
if (base_relocs_data.size) { 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_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); LNK_SectionContrib *sc = lnk_section_contrib_chunk_push(first_sc_chunk, 1);
sc->first_data_node.string = base_relocs_data; 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; U32 load_config_size = 0;
if (sizeof(load_config_size) <= load_config_data.size) { 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); 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_off = lnk_voff_from_symbol(image_section_table, load_config_symbol);
load_config_dir->virt_size = load_config_size; 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 // 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) { if (pdata_sect) {
String8 raw_pdata = str8_substr(image_data, rng_1u64(pdata_sect->foff, pdata_sect->foff + pdata_sect->vsize)); 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); 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 // 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) { if (edata_sect) {
PE_DataDirectory *export_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_EXPORT); 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); 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 // 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) { if (reloc_sect) {
PE_DataDirectory *reloc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_BASE_RELOC); 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); 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 // 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_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)); 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) { 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 // 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 *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)); 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) { 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 // find max align in .tls
U64 tls_align = 0; 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 (LNK_SectionContribChunk *sc_chunk = tls_sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) {
for EachIndex (sc_idx, sc_chunk->count) { for EachIndex (sc_idx, sc_chunk->count) {
Assert(IsPow2(sc_chunk->v[sc_idx]->align)); 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 // 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) { if (debug_dir_sect) {
// patch directory // patch directory
PE_DataDirectory *debug_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_DEBUG); 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 // 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) { if (rsrc_sect) {
PE_DataDirectory *rsrc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_RESOURCES); 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); rsrc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, rsrc_sect);
+1
View File
@@ -261,6 +261,7 @@ typedef struct LNK_ImageFillNode
typedef struct typedef struct
{ {
LNK_Config *config;
LNK_SymbolTable *symtab; LNK_SymbolTable *symtab;
LNK_SectionTable *sectab; LNK_SectionTable *sectab;
U64 objs_count; U64 objs_count;
+550 -39
View File
@@ -14,6 +14,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] =
{ LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." }, { LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." },
{ LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, { LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." },
{ LNK_CmdSwitch_DefaultLib, 1, "DEFAULTLIB", ":LIBNAME", "Set default library." }, { 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_Delay, 0, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." },
{ LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." }, { LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." },
{ LNK_CmdSwitch_Dll, 0, "DLL", "", "Link to a 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_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." },
{ LNK_CmdSwitch_Force, 0, "FORCE", "", "Force image output despite errors." }, { LNK_CmdSwitch_Force, 0, "FORCE", "", "Force image output despite errors." },
{ LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, { 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_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_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." },
{ LNK_CmdSwitch_Ignore, 0, "IGNORE", ":#", "Ignore a warning." }, { 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_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_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_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_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_SubSystem, 1, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." },
{ LNK_CmdSwitch_TsAware, 0, "TSAWARE", "[:NO]", "Image is terminal server aware." }, { 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) lnk_text_file_string_from_data(Arena *arena, String8 data)
{ {
String8 result = data; String8 result = data;
if (data.size >= 2 && data.str[0] == 0xff && data.str[1] == 0xfe) { 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))); 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) { } 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); result = str8_skip(data, 3);
} }
return result; 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 internal B32
lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) 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; 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 * internal LNK_AltNameNode *
lnk_alt_name_list_push(Arena *arena, LNK_AltNameList *list, LNK_AltName v) 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; 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, &section_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 internal String8
lnk_get_image_name(LNK_Config *config) 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 internal void
lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List value_strings, LNK_Obj *obj) 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; } 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: { case LNK_CmdSwitch_Delay: {
if (value_strings.node_count == 0 || value_strings.node_count > 1) { if (value_strings.node_count == 0 || value_strings.node_count > 1) {
lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); 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: { case LNK_CmdSwitch_Export: {
PE_ExportParse export_parse = {0}; PE_ExportParse export_parse = {0};
if (lnk_parse_export_directive_ex(config->arena, value_strings, obj, &export_parse)) { if (lnk_parse_export_directive_ex(config->arena, value_strings, obj, &export_parse)) {
String8 export_name = pe_name_from_export_parse(&export_parse); lnk_push_export_to_config(config, obj, 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);
}
}
} }
} break; } 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; config->do_function_pad_min = LNK_SwitchState_Yes;
} break; } 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: { case LNK_CmdSwitch_Heap: {
Rng1U64 reserve_commit; Rng1U64 reserve_commit;
reserve_commit.v[0] = config->heap_reserve; 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; } 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: { case LNK_CmdSwitch_Stack: {
Rng1U64 reserve_commit; Rng1U64 reserve_commit;
reserve_commit.v[0] = config->stack_reserve; 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); 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, &section_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, &section_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 internal void
lnk_config_pushf(LNK_Config *config, char *fmt, ...) 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; 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) { if (config->guard_flags != LNK_Guard_None) {
config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF; // config->dll_characteristics |= PE_DllCharacteristic_GUARD_CF;
} }
// handle empty /OUT // 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 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"); 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); config->out_path = path_replace_file_extension(scratch.arena, name, ext);
+27
View File
@@ -47,6 +47,7 @@ typedef enum
LNK_CmdSwitch_Brepro, LNK_CmdSwitch_Brepro,
LNK_CmdSwitch_Debug, LNK_CmdSwitch_Debug,
LNK_CmdSwitch_DefaultLib, LNK_CmdSwitch_DefaultLib,
LNK_CmdSwitch_Def,
LNK_CmdSwitch_Delay, LNK_CmdSwitch_Delay,
LNK_CmdSwitch_DelayLoad, LNK_CmdSwitch_DelayLoad,
LNK_CmdSwitch_DisallowLib, LNK_CmdSwitch_DisallowLib,
@@ -59,6 +60,8 @@ typedef enum
LNK_CmdSwitch_Fixed, LNK_CmdSwitch_Fixed,
LNK_CmdSwitch_Force, LNK_CmdSwitch_Force,
LNK_CmdSwitch_FunctionPadMin, LNK_CmdSwitch_FunctionPadMin,
LNK_CmdSwitch_Guard,
LNK_CmdSwitch_GuardSym,
LNK_CmdSwitch_Heap, LNK_CmdSwitch_Heap,
LNK_CmdSwitch_HighEntropyVa, LNK_CmdSwitch_HighEntropyVa,
LNK_CmdSwitch_Ignore, LNK_CmdSwitch_Ignore,
@@ -88,6 +91,7 @@ typedef enum
LNK_CmdSwitch_PdbPageSize, LNK_CmdSwitch_PdbPageSize,
LNK_CmdSwitch_PdbStripped, LNK_CmdSwitch_PdbStripped,
LNK_CmdSwitch_Release, LNK_CmdSwitch_Release,
LNK_CmdSwitch_Section,
LNK_CmdSwitch_Stack, LNK_CmdSwitch_Stack,
LNK_CmdSwitch_SubSystem, LNK_CmdSwitch_SubSystem,
LNK_CmdSwitch_Time, LNK_CmdSwitch_Time,
@@ -267,6 +271,26 @@ typedef struct LNK_MergeDirectiveList
LNK_MergeDirectiveNode *last; LNK_MergeDirectiveNode *last;
} LNK_MergeDirectiveList; } 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 typedef enum
{ {
LNK_DebugInfoGuid_Null, LNK_DebugInfoGuid_Null,
@@ -363,6 +387,7 @@ typedef struct LNK_Config
LNK_IncludeSymbolList include_symbol_list; LNK_IncludeSymbolList include_symbol_list;
LNK_AltNameList alt_name_list; LNK_AltNameList alt_name_list;
LNK_MergeDirectiveList merge_list; LNK_MergeDirectiveList merge_list;
LNK_SectionDirectiveList section_list;
U64 data_dir_count; U64 data_dir_count;
B32 build_imp_lib; B32 build_imp_lib;
B32 build_exp; 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_section_removed (LNK_Config *config, String8 section_name);
internal B32 lnk_is_dll_delay_load (LNK_Config *config, String8 dll_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 String8 lnk_get_lib_name (String8 path);
internal void lnk_push_disallow_lib(LNK_Config *config, String8 path); internal void lnk_push_disallow_lib(LNK_Config *config, String8 path);
internal B32 lnk_is_lib_disallowed(LNK_Config *config, String8 path); internal B32 lnk_is_lib_disallowed(LNK_Config *config, String8 path);
+200
View File
@@ -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) TEST(link_undef)
{ {
T_Ok(t_write_def_obj("undef.obj", (T_COFF_DefObj){ 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); 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) TEST(section_sort)
{ {
COFF_SectionFlags data_flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align1Bytes; 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 // 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) TEST(utf16_rsp)
{ {
T_Ok(t_write_entry_obj()); T_Ok(t_write_entry_obj());