simplify COMDAT leader symbol patching

Collapse COMDAT leader symbol patching to a single raw-symbol write path
and make the same-object versus cross-object symlink handling explicit.
Preserve public symbol offsets for ICF folds while avoiding cross-object
section number rewrites.
This commit is contained in:
Nikita Smith
2026-07-27 14:47:16 -07:00
committed by Ryan Fleury
parent 7197398f31
commit f3d2792fb8
2 changed files with 483 additions and 19 deletions
+31 -18
View File
@@ -3674,52 +3674,65 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task)
internal
THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task)
{
Temp scratch = scratch_begin(&arena, 1);
LNK_BuildImageTask *task = raw_task;
U64 obj_idx = task_id;
LNK_Obj *obj = task->objs[obj_idx];
ProfBeginV("%S", obj->path);
ProfBegin("Patch COMDAT Offsets");
ProfBeginV("Patch COMDAT Offsets in %S", obj->path);
COFF_ParsedSymbol symbol;
for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) {
symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx);
COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class);
COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol);
if (interp != COFF_SymbolValueInterp_Regular) { continue; }
LNK_ObjSymbolRef symlink_ref = {0};
if ( ! lnk_obj_get_comdat_symlink(obj, symbol.section_number, &symlink_ref)) { continue; }
COFF_ParsedSymbol parsed_symlink = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
if (symlink_ref.obj == obj && parsed_symlink.section_number == symbol.section_number) { continue; }
COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx);
if (symlink_ref.obj == obj && leader_symbol.section_number == symbol.section_number) { continue; }
B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static &&
obj->comdats[symbol.section_number-1] == symbol_idx;
B32 is_external = symbol.storage_class == COFF_SymStorageClass_External;
B32 is_same_obj = symlink_ref.obj == obj;
if (symbol.storage_class == COFF_SymStorageClass_External || is_static_comdat_leader) {
// COMDAT leader may be at a different offset, so update this symbol with leader's offset
U32 section_number = symbol.section_number;
U32 value = parsed_symlink.value;
U32 value = symbol.value;
B32 should_patch = 0;
if (is_same_obj) {
B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->comdats[symbol.section_number-1] == symbol_idx;
if (is_external || is_static_comdat_leader) {
section_number = leader_symbol.section_number;
value = leader_symbol.value;
// ICF folds sections by linking to the leader section definition; preserve
// public symbol offsets inside identical folded sections
if (is_external && leader_symbol.storage_class == COFF_SymStorageClass_Static && leader_symbol.aux_symbol_count > 0) {
value = symbol.value;
}
should_patch = 1;
}
} else {
if (is_external && str8_match(symbol.name, leader_symbol.name, 0)) {
value = leader_symbol.value;
should_patch = 1;
}
}
if (should_patch) {
if (obj->header.is_big_obj) {
COFF_Symbol32 *symbol32 = symbol.raw_symbol;
symbol32->section_number = section_number;
symbol32->value = value;
} else {
COFF_Symbol16 *symbol16 = symbol.raw_symbol;
symbol16->section_number = (U16)section_number;
symbol16->section_number = safe_cast_u16(section_number);
symbol16->value = value;
}
}
}
ProfEnd();
ProfEnd();
scratch_end(scratch);
}
internal int
+451
View File
@@ -3132,6 +3132,457 @@ TEST(comdat_any)
}
}
// MSVC vftables use COMDAT sections whose public symbol can start past the
// section definition symbol; references to a replaced copy must target the winner.
TEST(comdat_external_symbol_at_nonzero_offset)
{
U8 data[16] = {0};
U8 ptr[8] = {0};
U8 text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + foo]
0xC3
};
T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("foo", "data", 8),
{0}
}
}));
T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "foo"),
{0}
}},
{ "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "foo"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("foo", "data", 8),
T_COFF_DefSymbol_Extern("entry", "text", 0),
{0}
}
}));
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.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;
String8 string_table = str8_substr(exe, pe.string_table_range);
COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata"));
COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data"));
COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text"));
T_Ok(rdata_section != 0);
T_Ok(data_section != 0);
T_Ok(text_section != 0);
U64 actual_ptr = 0;
str8_deserial_read_struct(exe, data_section->foff, &actual_ptr);
U64 expected_ptr = pe.image_base + rdata_section->voff + 8;
T_Ok(actual_ptr == expected_ptr);
S32 lea_disp = 0;
str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp);
U64 actual_lea_target = text_section->voff + 7 + lea_disp;
U64 expected_lea_target = rdata_section->voff + 8;
T_Ok(actual_lea_target == expected_lea_target);
}
TEST(comdat_external_symbol_at_zero_offset)
{
U8 data[8] = {0};
U8 ptr[8] = {0};
U8 text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + foo]
0xC3
};
T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("foo", "data", 0),
{0}
}
}));
T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "data", ".rdata", str8_array_fixed(data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "foo"),
{0}
}},
{ "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "foo"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("data", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("foo", "data", 0),
T_COFF_DefSymbol_Extern("entry", "text", 0),
{0}
}
}));
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.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;
String8 string_table = str8_substr(exe, pe.string_table_range);
COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata"));
COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data"));
COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text"));
T_Ok(rdata_section != 0);
T_Ok(data_section != 0);
T_Ok(text_section != 0);
U64 actual_ptr = 0;
str8_deserial_read_struct(exe, data_section->foff, &actual_ptr);
U64 expected_ptr = pe.image_base + rdata_section->voff;
T_Ok(actual_ptr == expected_ptr);
S32 lea_disp = 0;
str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp);
U64 actual_lea_target = text_section->voff + 7 + lea_disp;
U64 expected_lea_target = rdata_section->voff;
T_Ok(actual_lea_target == expected_lea_target);
}
// Duplicate COMDAT sections can have identical bytes while their symbol tables
// disagree about where a same-named public symbol points inside the section.
// This mirrors MSVC vftable COMDATs: the selected copy may have leading RTTI data
// at offset 0 and the vftable symbol at offset 8, while a discarded copy's
// vftable symbol is at offset 0. Relocations against the discarded symbol must
// use the selected symbol's value, not just the selected section contribution
// plus the discarded symbol's original offset.
TEST(comdat_external_symbol_uses_leader_offset)
{
U8 leader_data[16] = {0};
U8 ref_data[16] = {0};
U8 ptr[8] = {0};
U8 text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7X]
0xC3
};
T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "vftable", ".rdata", str8_array_fixed(leader_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 8),
{0}
}
}));
T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "vftable", ".rdata", str8_array_fixed(ref_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "??_7X@@6B@"),
{0}
}},
{ "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "??_7X@@6B@"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 0),
T_COFF_DefSymbol_ExternFunc("entry", "text", 0),
{0}
}
}));
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe leader.obj ref.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;
String8 string_table = str8_substr(exe, pe.string_table_range);
COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata"));
COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data"));
COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text"));
T_Ok(rdata_section != 0);
T_Ok(data_section != 0);
T_Ok(text_section != 0);
U64 actual_ptr = 0;
str8_deserial_read_struct(exe, data_section->foff, &actual_ptr);
U64 expected_ptr = pe.image_base + rdata_section->voff + 8;
T_Ok(actual_ptr == expected_ptr);
S32 lea_disp = 0;
str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp);
U64 actual_lea_target = text_section->voff + 7 + lea_disp;
U64 expected_lea_target = rdata_section->voff + 8;
T_Ok(actual_lea_target == expected_lea_target);
}
// Chromium has duplicate vftable COMDATs where the referencing copy is an
// IMAGE_COMDAT_SELECT_ANY section with the public vftable at offset 0, while
// the selected IMAGE_COMDAT_SELECT_LARGEST copy has the same public symbol at
// offset 8. Relocations in the discarded object must resolve to the selected
// public symbol, not to the discarded section.
TEST(comdat_largest_external_symbol_uses_selected_offset)
{
U8 discarded_data[16] = {0};
U8 selected_data[24] = {0};
U8 ptr[16] = {0};
U8 selected_text[] = { 0xC3 };
U8 text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7X]
0xC3
};
T_Ok(t_write_def_obj("discarded.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "vftable", ".rdata", str8_array_fixed(discarded_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "ptr", ".data", str8_array_fixed(ptr), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "??_7X@@6B@"),
T_COFF_DefReloc(X64_Addr64, 8, "force_selected"),
{0}
}},
{ "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "??_7X@@6B@"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 0),
T_COFF_DefSymbol_Undef("force_selected"),
T_COFF_DefSymbol_ExternFunc("entry", "text", 0),
{0}
}
}));
T_Ok(t_write_def_lib("selected.lib", (T_COFF_DefLib){
.emit_second_member = 1,
.members = (T_COFF_DefLibMember[]){
{
.type = T_COFF_DefLibMember_Obj,
.obj = {
.path = str8_lit("selected.obj"),
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "vftable", ".rdata", str8_array_fixed(selected_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "force_text", ".text", str8_array_fixed(selected_text), .flags = "rx:code@1" },
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("vftable", COFF_ComdatSelect_Largest),
T_COFF_DefSymbol_Extern("??_7X@@6B@", "vftable", 8),
T_COFF_DefSymbol_ExternFunc("force_selected", "force_text", 0),
{0}
}
}
},
{0}
}
}));
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /opt:ref,noicf discarded.obj selected.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);
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 *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata"));
COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data"));
COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text"));
T_Ok(rdata_section != 0);
T_Ok(data_section != 0);
T_Ok(text_section != 0);
U64 actual_ptr = 0;
str8_deserial_read_struct(exe, data_section->foff, &actual_ptr);
U64 expected_ptr = pe.image_base + rdata_section->voff + 8;
T_Ok(actual_ptr == expected_ptr);
S32 lea_disp = 0;
str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp);
U64 actual_lea_target = text_section->voff + 7 + lea_disp;
U64 expected_lea_target = rdata_section->voff + 8;
T_Ok(actual_lea_target == expected_lea_target);
}
TEST(icf_vftable_external_symbol_at_nonzero_offset)
{
U8 entry_text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + ??_7B]
0xC3, // ret
};
U8 vftable_data[16] = {0};
U8 addresses[16] = {0};
T_Ok(t_write_def_obj("vftable.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "??_7B@@6B@"),
{0}
}},
{ "vftable_a", ".rdata$vt", str8_array_fixed(vftable_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "vftable_b", ".rdata$vt", str8_array_fixed(vftable_data), .flags = "r:data@8", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@8", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "??_7A@@6B@"),
T_COFF_DefReloc(X64_Addr64, 8, "??_7B@@6B@"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("vftable_a", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Secdef("vftable_b", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("??_R4A@@6B@", "vftable_a", 0),
T_COFF_DefSymbol_Extern("??_7A@@6B@", "vftable_a", 8),
T_COFF_DefSymbol_Extern("??_R4B@@6B@", "vftable_b", 0),
T_COFF_DefSymbol_Extern("??_7B@@6B@", "vftable_b", 8),
T_COFF_DefSymbol_ExternFunc("entry", "entry", 0),
T_COFF_DefSymbol_Extern("addresses", "addresses", 0),
{0}
}
}));
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses vftable.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;
String8 string_table = str8_substr(exe, pe.string_table_range);
COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata"));
COFF_SectionHeader *data_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".data"));
COFF_SectionHeader *text_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".text"));
T_Ok(rdata_section != 0);
T_Ok(data_section != 0);
T_Ok(text_section != 0);
U64 a_vftable_ptr = 0;
U64 b_vftable_ptr = 0;
str8_deserial_read_struct(exe, data_section->foff + 0, &a_vftable_ptr);
str8_deserial_read_struct(exe, data_section->foff + 8, &b_vftable_ptr);
T_Ok(a_vftable_ptr != 0);
T_Ok(b_vftable_ptr != 0);
T_Ok((a_vftable_ptr - pe.image_base - rdata_section->voff) % sizeof(vftable_data) == 8);
T_Ok((b_vftable_ptr - pe.image_base - rdata_section->voff) % sizeof(vftable_data) == 8);
S32 lea_disp = 0;
str8_deserial_read_struct(exe, text_section->foff + 3, &lea_disp);
U64 actual_lea_target = text_section->voff + 7 + lea_disp;
T_Ok(actual_lea_target == b_vftable_ptr - pe.image_base);
}
TEST(zero_length_comdat_referenced_by_reloc)
{
if (t_id_linker() != Linker_radlink) { return; }
U8 data[8] = {0};
T_Ok(t_write_entry_obj());
T_Ok(t_write_def_obj("leader.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "empty", ".rdata", str8_zero(), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("EMPTY", "empty", 0),
{0}
}
}));
T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "empty", ".rdata", str8_zero(), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "data", ".data", str8_array_fixed(data), .flags = "rw:data", .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Addr64, 0, "EMPTY"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_Any),
T_COFF_DefSymbol_Extern("EMPTY", "empty", 0),
{0}
}
}));
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref entry.obj leader.obj ref.obj");
T_Ok(g_last_exit_code == 0);
}
TEST(zero_length_static_comdat_referenced_by_reloc)
{
if (t_id_linker() != Linker_radlink) { return; }
U8 text[] = {
0x48, 0x8D, 0x05, 0, 0, 0, 0, // lea rax, [rip + EMPTY]
0xC3
};
T_Ok(t_write_def_obj("test.obj", (T_COFF_DefObj){
.machine = T_COFF_DefSetMachine(X64),
.sections = (T_COFF_DefSection[]){
{ "empty", ".rdata", str8_zero(), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT },
{ "text", ".text", str8_array_fixed(text), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, .relocs = (T_COFF_DefReloc[]){
T_COFF_DefReloc(X64_Rel32, 3, "EMPTY"),
{0}
}},
{0}
},
.symbols = (T_COFF_DefSymbol[]){
T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_NoDuplicates),
T_COFF_DefSymbol_Static("EMPTY", "empty", 0),
T_COFF_DefSymbol_Secdef("text", COFF_ComdatSelect_NoDuplicates),
T_COFF_DefSymbol_Extern("entry", "text", 0),
{0}
}
}));
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,noicf test.obj");
T_Ok(g_last_exit_code == 0);
}
TEST(comdat_no_duplicates)
{
T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){