From 3d2b1e19e35f7f335597090857870afd33bd4cbf Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 29 May 2025 16:19:44 -0700 Subject: [PATCH] handle communal var vs regular non-comdat symbol --- src/linker/lnk_symbol_table.c | 60 +++++++++++++++-------------- src/torture/torture.c | 72 ++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 223e6cb0..e9e4339b 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -181,6 +181,7 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) B32 can_replace = 0; + // lib vs lib if (dst->type == LNK_Symbol_Lib && src->type == LNK_Symbol_Lib) { // link.exe picks symbol from lib that is discovered first @@ -234,52 +235,60 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) else if (dst_interp == COFF_SymbolValueInterp_Abs && src_interp == COFF_SymbolValueInterp_Weak) { can_replace = 0; } - // weak vs regular,common,abs + // weak vs (regular, common, abs) else if (dst_interp == COFF_SymbolValueInterp_Weak && (src_interp == COFF_SymbolValueInterp_Regular || src_interp == COFF_SymbolValueInterp_Common || src_interp == COFF_SymbolValueInterp_Abs)) { can_replace = 1; } - // regular,common vs regular,common + // (regular, common) vs (regular, common) else if ((dst_interp == COFF_SymbolValueInterp_Regular || dst_interp == COFF_SymbolValueInterp_Common) && (src_interp == COFF_SymbolValueInterp_Regular || src_interp == COFF_SymbolValueInterp_Common)) { - B32 is_dst_single_defn = 0; - B32 is_src_single_defn = 0; - + B32 dst_is_comdat = 0; COFF_ComdatSelectType dst_select; - U32 dst_section_length; - U32 dst_check_sum; + U32 dst_section_length; + U32 dst_check_sum; if (dst_interp == COFF_SymbolValueInterp_Regular) { U32 dst_comdat_symbol_idx = dst_obj->comdats[dst_parsed.section_number-1]; if (dst_comdat_symbol_idx != max_U32) { COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx(dst_obj, dst_comdat_symbol_idx); coff_parse_secdef(secdef, dst_obj->header.is_big_obj, &dst_select, 0, &dst_section_length, &dst_check_sum); - } else { - is_dst_single_defn = 1; + dst_is_comdat = 1; } } else if (dst_interp == COFF_SymbolValueInterp_Common) { - dst_select = COFF_ComdatSelect_Largest; + dst_select = COFF_ComdatSelect_Largest; dst_section_length = dst_parsed.value; - dst_check_sum = 0; + dst_check_sum = 0; + dst_is_comdat = 1; } + B32 src_is_comdat = 0; COFF_ComdatSelectType src_select; - U32 src_section_length; - U32 src_check_sum; + U32 src_section_length; + U32 src_check_sum; if (src_interp == COFF_SymbolValueInterp_Regular) { U32 src_comdat_symbol_idx = src_obj->comdats[src_parsed.section_number-1]; if (src_comdat_symbol_idx != max_U32) { COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx(src_obj, src_comdat_symbol_idx); coff_parse_secdef(secdef, src_obj->header.is_big_obj, &src_select, 0, &src_section_length, &src_check_sum); - } else { - is_src_single_defn = 1; + src_is_comdat = 1; } } else if (src_interp == COFF_SymbolValueInterp_Common) { - src_select = COFF_ComdatSelect_Largest; + src_select = COFF_ComdatSelect_Largest; src_section_length = src_parsed.value; - src_check_sum = 0; + src_check_sum = 0; + src_is_comdat = 1; } - if (is_dst_single_defn || is_src_single_defn) { - lnk_error_multiply_defined_symbol(dst, src); - } else { + // regular non-comdat vs communal + if (dst_interp == COFF_SymbolValueInterp_Regular && !dst_is_comdat && + src_interp == COFF_SymbolValueInterp_Common) { + can_replace = 0; + } + // communal vs regular non-comdat + else if (dst_interp == COFF_SymbolValueInterp_Common && + src_interp == COFF_SymbolValueInterp_Regular && !src_is_comdat) { + can_replace = 1; + } + // handle COMDATs + else if (dst_is_comdat && src_is_comdat) { // handle objs compiled with /GR- and /GR if ((src_select == COFF_ComdatSelect_Any && dst_select == COFF_ComdatSelect_Largest)) { src_select = COFF_ComdatSelect_Largest; @@ -331,14 +340,7 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) } break; case COFF_ComdatSelect_Largest: { if (dst_section_length == src_section_length) { - if (dst_interp == COFF_SymbolValueInterp_Common) { - // handle communal variable - // - // MSVC CRT relies on this behaviour (e.g. __scrt_ucrt_dll_is_in_use in ucrt_detection.c) - can_replace = 1; - } else { - can_replace = src_obj->input_idx < dst_obj->input_idx; - } + can_replace = src_obj->input_idx < dst_obj->input_idx; } else { can_replace = dst_section_length < src_section_length; } @@ -355,6 +357,8 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) "%S: COMDAT selection conflict detected, current selection %S, leader selection %S from %S", src->name, src_select_str, dst_select_str, dst_obj); } + } else { + lnk_error_multiply_defined_symbol(dst, src); } } else { lnk_error(LNK_Error_InvalidPath, "unable to find a suitable replacement logic for symbol combination"); diff --git a/src/torture/torture.c b/src/torture/torture.c index 3d29b5aa..f1150d33 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -2953,7 +2953,74 @@ t_include(void) } internal T_Result -t_communal_var(void) +t_communal_var_vs_regular(void) +{ + Temp scratch = scratch_begin(0,0); + T_Result result = T_Result_Fail; + + { + COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_symbol_common(obj_writer, str8_lit("TEST"), 1); + String8 obj = coff_obj_writer_serialize(scratch.arena, obj_writer); + coff_obj_writer_release(&obj_writer); + if (!t_write_file(str8_lit("communal.obj"), obj)) { goto exit; } + } + + { + COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(0, COFF_MachineType_X64); + COFF_ObjSection *sect = t_push_data_section(obj_writer, str8_lit("test")); + coff_obj_writer_push_symbol_extern(obj_writer, str8_lit("TEST"), 0, sect); + String8 obj = coff_obj_writer_serialize(scratch.arena, obj_writer); + coff_obj_writer_release(&obj_writer); + if (!t_write_file(str8_lit("defn.obj"), obj)) { goto exit; } + } + + { + COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(0, COFF_MachineType_X64); + U8 text[] = { + 0x48, 0xC7, 0xC0, 0x00, 0x00, 0x00, 0x00, // mov rax, $imm + 0xC3 // ret + }; + COFF_ObjSection *sect = coff_obj_writer_push_section(obj_writer, str8_lit(".text"), PE_TEXT_SECTION_FLAGS, str8_array_fixed(text)); + coff_obj_writer_push_symbol_extern(obj_writer, str8_lit("entry"), 0, sect); + COFF_ObjSymbol *symbol = coff_obj_writer_push_symbol_undef(obj_writer, str8_lit("TEST")); + coff_obj_writer_section_push_reloc_voff(obj_writer, sect, 0, symbol); + String8 obj = coff_obj_writer_serialize(scratch.arena, obj_writer); + coff_obj_writer_release(&obj_writer); + if (!t_write_file(str8_lit("entry.obj"), obj)) { + goto exit; + } + } + + // linker should replace communal TEST with .data TEST + int linker_exit_code; + + linker_exit_code = t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe communal.obj defn.obj entry.obj"); + if (linker_exit_code != 0) { goto exit; } + + linker_exit_code = t_invoke_linkerf("/subsystem:console /entry:entry /out:b.exe defn.obj communal.obj entry.obj"); + if (linker_exit_code != 0) { goto exit; } + + char *exes[] = { "a.exe", "b.exe" }; + for (U64 i = 0; i < ArrayCount(exes); i += 1) { + String8 exe = t_read_file(scratch.arena, str8_cstring(exes[i])); + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + String8 string_table = str8_substr(exe, pe.string_table_range); + COFF_SectionHeader *data_sect = t_coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data")); + if (!data_sect) { goto exit; } + String8 data = str8_substr(exe, rng_1u64(data_sect->foff, data_sect->foff + data_sect->vsize)); + if (!str8_match(data, str8_lit("test"), 0)) { goto exit; } + } + + result = T_Result_Pass; +exit:; + scratch_end(scratch); + return result; +} + +internal T_Result +t_communal_var_vs_regular_comdat(void) { Temp scratch = scratch_begin(0,0); T_Result result = T_Result_Fail; @@ -3187,7 +3254,8 @@ entry_point(CmdLine *cmdline) { "comdat_associative_out_of_bounds", t_comdat_associative_out_of_bounds }, { "alt_name", t_alt_name }, { "include", t_include }, - { "communal_var", t_communal_var }, + { "communal_var_vs_regular_comdat", t_communal_var_vs_regular_comdat }, + { "communal_var_vs_regular", t_communal_var_vs_regular }, { "import_kernel32", t_import_kernel32 }, { "delay_import_user32", t_delay_import_user32 }, //{ "import_export", t_import_export },