mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-11-15 10:40:12 -08:00
radlink: resolve alternate-name fallbacks through prevailing weak symbols
Follow prevailing weak definitions instead of synthetic anti-dependency placeholders. Resolve all weak symbols before publishing results so anti-dependency chaining rules are independent of worker order.
This commit is contained in:
1 parent
64c2216ae6
commit
2afb78e498
3 files changed
+387
-122
No files matched your search
+12
-11
@@ -1154,14 +1154,14 @@ internal LNK_Link *
|
||||
lnk_link_init(TP_Arena *arena, LNK_Config *config)
|
||||
{
|
||||
LNK_Link *link = push_array(arena->v[0], LNK_Link, 1);
|
||||
link->arena = arena_alloc(.name = "LINK");
|
||||
link->last_symbol_input = &link->objs.first;
|
||||
link->last_include = &config->include_symbol_list.first;
|
||||
link->arena = arena_alloc(.name = "LINK");
|
||||
link->last_symbol_input = &link->objs.first;
|
||||
link->last_include = &config->include_symbol_list.first;
|
||||
link->last_func_override_alt_name = &config->alt_name_list.first;
|
||||
link->last_default_lib = &config->input_default_lib_list.first;
|
||||
link->last_obj_lib = &config->input_obj_lib_list.first;
|
||||
link->last_cmd_lib = &config->input_list[LNK_Input_Lib].first;
|
||||
link->try_to_resolve_entry_point = 1;
|
||||
link->last_default_lib = &config->input_default_lib_list.first;
|
||||
link->last_obj_lib = &config->input_obj_lib_list.first;
|
||||
link->last_cmd_lib = &config->input_list[LNK_Input_Lib].first;
|
||||
link->try_to_resolve_entry_point = 1;
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -1924,14 +1924,13 @@ lnk_link_inputs(TP_Context *tp,
|
||||
search_anti_deps = 0;
|
||||
|
||||
// replace undefined symbols that have an alternate name with a weak symbol
|
||||
for (LNK_AltNameNode *alt_name_n = config->alt_name_list.first; alt_name_n != 0; alt_name_n = alt_name_n->next) {
|
||||
for EachNode(alt_name_n, LNK_AltNameNode, config->alt_name_list.first) {
|
||||
LNK_SymbolHashTrie *symbol_ht = lnk_symbol_table_search_(symtab, alt_name_n->v.from);
|
||||
if (symbol_ht) {
|
||||
COFF_SymbolValueInterpType interp = lnk_interp_from_symbol(symbol_ht->symbol);
|
||||
if (interp == COFF_SymbolValueInterp_Undefined) {
|
||||
// clear out slot so weak symbol can replace undefined symbol (general rule is
|
||||
// weak symbol is not allowed to replace undefined)
|
||||
LNK_Symbol *undef_symbol = symbol_ht->symbol;
|
||||
// /ALTERNATENAME explicitly installs a fallback for an unresolved name.
|
||||
// Normally, an undefined external prevails over a SERACH_LIBRARY weak symbol.
|
||||
symbol_ht->symbol = 0;
|
||||
|
||||
// make obj with alternamte name symbol
|
||||
@@ -1945,10 +1944,12 @@ lnk_link_inputs(TP_Context *tp,
|
||||
coff_obj_writer_release(&obj_writer);
|
||||
}
|
||||
|
||||
// input synthetic object
|
||||
LNK_Obj *obj_with_alt_name = alt_name_n->v.obj;
|
||||
String8 obj_with_alt_name_path = obj_with_alt_name ? obj_with_alt_name->path : str8_lit("RADLINK");
|
||||
lnk_inputer_push_obj_linkgen(inputer, obj_with_alt_name ? obj_with_alt_name->link_member : 0, obj_with_alt_name_path, alt_name_obj_data);
|
||||
|
||||
// request another round of anti-dependency symbols search
|
||||
search_anti_deps = 1;
|
||||
}
|
||||
}
|
||||
|
||||
+101
-46
@@ -37,6 +37,12 @@ lnk_make_symbol(Arena *arena, String8 name, LNK_Obj *obj, U32 symbol_idx, LNK_Sy
|
||||
return symbol;
|
||||
}
|
||||
|
||||
internal B32
|
||||
lnk_obj_symbol_ref_match(LNK_ObjSymbolRef a, LNK_ObjSymbolRef b)
|
||||
{
|
||||
return MemoryMatchStruct(&a, &b);
|
||||
}
|
||||
|
||||
internal LNK_ObjSymbolRefNode *
|
||||
lnk_last_ref_from_symbol(LNK_Symbol *symbol)
|
||||
{
|
||||
@@ -717,69 +723,92 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob
|
||||
{
|
||||
Temp scratch = scratch_begin(0,0);
|
||||
|
||||
B32 is_resolved = 0;
|
||||
|
||||
struct S { struct S *next; LNK_ObjSymbolRef symbol; B32 is_anti_dep; };
|
||||
struct S { struct S *next; LNK_ObjSymbolRef symbol; };
|
||||
struct S *sf = 0, *sl = 0;
|
||||
|
||||
LNK_ObjSymbolRef current_symbol = symbol;
|
||||
LNK_ObjSymbolRef current_symbol = symbol;
|
||||
B32 followed_fallback = 0;
|
||||
B32 is_resolved = 0;
|
||||
for (;;) {
|
||||
// guard against self-referencing weak symbols
|
||||
struct S *was_visited = 0;
|
||||
for (struct S *s = sf; s != 0; s = s->next) {
|
||||
if (MemoryCompare(&s->symbol, ¤t_symbol, sizeof(LNK_ObjSymbolRef)) == 0) { was_visited = s; break; }
|
||||
}
|
||||
if (was_visited) {
|
||||
String8List chain = {0};
|
||||
for (struct S *s = sf; s != 0; s = s->next) {
|
||||
COFF_ParsedSymbol s_parsed = lnk_parsed_symbol_from_coff_symbol_idx(s->symbol.obj, s->symbol.symbol_idx);
|
||||
str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x) =>", s->symbol.obj->path, s_parsed.name, s->symbol.symbol_idx);
|
||||
// guard against cyclic weak symbols
|
||||
{
|
||||
struct S *was_visited = 0;
|
||||
for EachNode(s, struct S, sf) {
|
||||
if (s->symbol.obj == current_symbol.obj && s->symbol.symbol_idx == current_symbol.symbol_idx) { was_visited = s; break; }
|
||||
}
|
||||
COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx);
|
||||
str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x)", sf->symbol.obj->path, symbol_parsed.name, sf->symbol.symbol_idx);
|
||||
if (was_visited) {
|
||||
String8List chain = {0};
|
||||
for EachNode(s, struct S, sf) {
|
||||
COFF_ParsedSymbol s_parsed = lnk_parsed_symbol_from_coff_symbol_idx(s->symbol.obj, s->symbol.symbol_idx);
|
||||
str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x) =>", s->symbol.obj->path, s_parsed.name, s->symbol.symbol_idx);
|
||||
}
|
||||
COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx);
|
||||
str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x)", sf->symbol.obj->path, symbol_parsed.name, sf->symbol.symbol_idx);
|
||||
|
||||
String8 chain_string = str8_list_join(scratch.arena, &chain, &(StringJoin){ .sep = str8_lit("\n") });
|
||||
lnk_error_obj(LNK_Error_WeakCycle, symbol.obj, "unable to resolve cyclic symbol %S; ref chain:\n%S", symbol_parsed.name, chain_string);
|
||||
String8 chain_string = str8_list_join(scratch.arena, &chain, &(StringJoin){ .sep = str8_lit("\n") });
|
||||
lnk_error_obj(LNK_Error_WeakCycle, symbol.obj, "unable to resolve cyclic symbol %S; ref chain:\n%S", symbol_parsed.name, chain_string);
|
||||
|
||||
goto exit;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
COFF_ParsedSymbol current_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, current_symbol.symbol_idx);
|
||||
COFF_SymbolValueInterpType current_interp = coff_interp_symbol(current_parsed.section_number, current_parsed.value, current_parsed.storage_class);
|
||||
|
||||
if (current_interp == COFF_SymbolValueInterp_Weak) {
|
||||
// record visited symbol
|
||||
struct S *s = push_array(scratch.arena, struct S, 1);
|
||||
s->symbol = current_symbol;
|
||||
SLLQueuePush(sf, sl, s);
|
||||
{
|
||||
struct S *s = push_array(scratch.arena, struct S, 1);
|
||||
s->symbol = current_symbol;
|
||||
SLLQueuePush(sf, sl, s);
|
||||
}
|
||||
|
||||
// does weak symbol have a definition?
|
||||
LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx));
|
||||
COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn_symbol);
|
||||
COFF_SymbolValueInterpType defn_interp = coff_interp_symbol(defn_parsed.section_number, defn_parsed.value, defn_parsed.storage_class);
|
||||
if (defn_interp != COFF_SymbolValueInterp_Weak) {
|
||||
current_symbol = lnk_ref_from_symbol(defn_symbol);
|
||||
break;
|
||||
// follow the leader symbol even when it is another weak symbol
|
||||
{
|
||||
LNK_Symbol *leader_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx));
|
||||
if (leader_symbol) {
|
||||
LNK_ObjSymbolRef leader_ref = lnk_ref_from_symbol(leader_symbol);
|
||||
if ( ! lnk_obj_symbol_ref_match(leader_ref, current_symbol)) {
|
||||
current_symbol = leader_ref;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(current_parsed, current_symbol.obj->coff.header.is_big_obj);
|
||||
|
||||
//
|
||||
// An anti-dependency can resolve its own direct fallback, but cannot be
|
||||
// used as a link in another alias's chain. Test the prevailing record's
|
||||
// kind, before any weak symbols have been flattened to their defaults.
|
||||
//
|
||||
// Given this chain:
|
||||
// foo [WeakAlias] -> bar [AntiDependency] -> qwe [Defined]
|
||||
//
|
||||
// * A relocation referencing 'foo' cannot follow through 'bar'; 'foo' remains unresolved.
|
||||
// * A relocation referencing 'bar' directly resolves to 'qwe'. The relocation reference itself is not an alias-chain step.
|
||||
//
|
||||
if (followed_fallback && weak_ext->characteristics == COFF_WeakExt_AntiDependency) { break; }
|
||||
|
||||
// no definition -- fallback to default symbol
|
||||
COFF_ParsedSymbol tag_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, weak_ext->tag_index);
|
||||
COFF_SymbolValueInterpType tag_interp = coff_interp_symbol(tag_parsed.section_number, tag_parsed.value, tag_parsed.storage_class);
|
||||
current_symbol = (LNK_ObjSymbolRef){ .obj = current_symbol.obj, .symbol_idx = weak_ext->tag_index };
|
||||
followed_fallback = 1;
|
||||
|
||||
if (weak_ext->characteristics == COFF_WeakExt_AntiDependency) {
|
||||
if (tag_interp == COFF_SymbolValueInterp_Undefined || tag_interp == COFF_SymbolValueInterp_Weak) {
|
||||
LNK_Symbol *dep_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, weak_ext->tag_index));
|
||||
tag_interp = lnk_interp_from_symbol(dep_symbol);
|
||||
if (dep_symbol) { tag_interp = lnk_interp_from_symbol(dep_symbol); }
|
||||
}
|
||||
if (tag_interp == COFF_SymbolValueInterp_Weak) { break; }
|
||||
}
|
||||
} else if (current_interp == COFF_SymbolValueInterp_Undefined) {
|
||||
LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx));
|
||||
COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn_symbol);
|
||||
LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx));
|
||||
if (defn_symbol == 0) { break; }
|
||||
|
||||
// unresolved undefined symbol
|
||||
// unresolved symbol
|
||||
COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn_symbol);
|
||||
if (defn_interp == COFF_SymbolValueInterp_Undefined) { break; }
|
||||
|
||||
// follow symbol definition
|
||||
@@ -846,6 +875,19 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb
|
||||
internal
|
||||
THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task)
|
||||
{
|
||||
Temp scratch = scratch_begin(0,0);
|
||||
|
||||
struct Resolution {
|
||||
struct Resolution *next;
|
||||
LNK_Symbol *symbol;
|
||||
LNK_ObjSymbolRef target;
|
||||
B32 resolved;
|
||||
};
|
||||
struct Resolution *first = 0, *last = 0;
|
||||
|
||||
// Resolve against a stable symbol table. Updating the symbol table in this loop
|
||||
// would make alias/anti-dependency semantics depend on traversal order and
|
||||
// allow other workers to read symbol records while they are being rewritten.
|
||||
LNK_SymbolTable *symtab = raw_task;
|
||||
for EachNode(c, LNK_SymbolHashTrieChunk, symtab->search_chunks[task_id].first) {
|
||||
for EachIndex(i, c->count) {
|
||||
@@ -853,25 +895,38 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task)
|
||||
LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol);
|
||||
COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symbol_ref.obj, symbol_ref.symbol_idx);
|
||||
COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol_parsed);
|
||||
|
||||
if (symbol_interp == COFF_SymbolValueInterp_Weak) {
|
||||
LNK_ObjSymbolRef resolve = {0};
|
||||
if (lnk_resolve_weak_symbol(symtab, symbol_ref, &resolve)) {
|
||||
COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(resolve.obj, resolve.symbol_idx);
|
||||
struct Resolution *r = push_array(scratch.arena, struct Resolution, 1);
|
||||
r->symbol = symbol;
|
||||
SLLQueuePush(first, last, r);
|
||||
|
||||
if (lnk_resolve_weak_symbol(symtab, symbol_ref, &r->target)) {
|
||||
COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(r->target.obj, r->target.symbol_idx);
|
||||
COFF_SymbolValueInterpType resolve_interp = coff_interp_from_parsed_symbol(resolve_parsed);
|
||||
if (resolve_interp == COFF_SymbolValueInterp_Weak) {
|
||||
U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx);
|
||||
symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = COFF_Symbol_UndefinedSection;
|
||||
symbol_ref.obj->coff.symbols.values [primary_idx] = 0;
|
||||
symbol_ref.obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_External;
|
||||
lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined);
|
||||
} else {
|
||||
symbol->first_ref->v = resolve;
|
||||
lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Null);
|
||||
}
|
||||
r->resolved = resolve_interp != COFF_SymbolValueInterp_Weak && resolve_interp != COFF_SymbolValueInterp_Undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier_wait(tp->barrier);
|
||||
|
||||
for EachNode(r, struct Resolution, first) {
|
||||
LNK_Symbol *symbol = r->symbol;
|
||||
if (r->resolved) {
|
||||
symbol->first_ref->v = r->target;
|
||||
lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Null);
|
||||
} else {
|
||||
LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol);
|
||||
U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx);
|
||||
symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = COFF_Symbol_UndefinedSection;
|
||||
symbol_ref.obj->coff.symbols.values [primary_idx] = 0;
|
||||
symbol_ref.obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_External;
|
||||
lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined);
|
||||
}
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
internal void
|
||||
|
||||
+274
-65
@@ -4786,99 +4786,308 @@ TEST(sect_align)
|
||||
|
||||
TEST(alt_name)
|
||||
{
|
||||
T_Ok(t_write_def_obj("test.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
// Inspect an RVA relocation instead of merely accepting a successful link.
|
||||
// Separate sections and nonzero symbol offsets distinguish the selected target
|
||||
// without depending on a particular linker's section ordering or image layout.
|
||||
T_COFF_DefObj target = {
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.path = str8_lit("target.obj"),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{ "data", ".data", str8_lit("test"), .flags = "rw:data" },
|
||||
{ "data", ".target", str8_lit("pad!TEST"), .flags = "rw:data@4" },
|
||||
{0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Extern("test", "data", 0),
|
||||
T_COFF_DefSymbol_Extern("test", "data", 4),
|
||||
{0}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
T_Ok(t_write_def_obj("foo.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
T_COFF_DefObj primary = {
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.path = str8_lit("foo.obj"),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{ "data", ".data", str8_lit("foo"), .flags = "rw:data" },
|
||||
{ "data", ".primary", str8_lit("pad!REAL"), .flags = "rw:data@4" },
|
||||
{0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Extern("foo", "data", 0),
|
||||
T_COFF_DefSymbol_Extern("foo", "data", 4),
|
||||
{0}
|
||||
}
|
||||
};
|
||||
|
||||
T_COFF_DefObj weak = {
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.path = str8_lit("weak.obj"),
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Undef("test"),
|
||||
T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "test"),
|
||||
T_COFF_DefSymbol_AbsExtern("alias_anchor", 1),
|
||||
{0}
|
||||
}
|
||||
};
|
||||
|
||||
T_Ok(t_write_def_obj("target.obj", target));
|
||||
T_Ok(t_write_def_obj("foo.obj", primary));
|
||||
T_Ok(t_write_def_obj("weak.obj", weak));
|
||||
T_Ok(t_write_entry_obj());
|
||||
T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{ "probe", ".probe", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4",
|
||||
.relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "foo"), {0} } },
|
||||
{0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Undef("foo"), {0} }
|
||||
}));
|
||||
|
||||
// Use ADDR32 for the absolute target: LINK rejects absolute ADDR32NB fixups.
|
||||
T_Ok(t_write_def_obj("ref_absolute.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{ "probe", ".probe", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4",
|
||||
.relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32, 0, "foo"), {0} } },
|
||||
{0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Undef("foo"), {0} }
|
||||
}));
|
||||
|
||||
// Same-object weak target, as in LLVM's alternatename-alias.s.
|
||||
T_Ok(t_write_def_obj("weak_local.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = target.sections,
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Extern("test", "data", 4),
|
||||
T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "test"),
|
||||
{0}
|
||||
}
|
||||
}));
|
||||
|
||||
T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
// A local default is identified by its object-local tag, not its spelling.
|
||||
T_Ok(t_write_def_obj("weak_static.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = target.sections,
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Static("local_target", "data", 4),
|
||||
T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "local_target"),
|
||||
{0}
|
||||
}
|
||||
}));
|
||||
T_Ok(t_write_def_obj("global_target.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = primary.sections,
|
||||
.symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Extern("local_target", "data", 4), {0} }
|
||||
}));
|
||||
T_Ok(t_write_def_obj("override.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{
|
||||
"text", ".text",
|
||||
str8_lit_comp(
|
||||
"\x48\xC7\xC0\x00\x00\x00\x00" // mov rax, $imm
|
||||
"\xC3"
|
||||
), // ret
|
||||
.flags = "rx:code",
|
||||
.relocs = (T_COFF_DefReloc[]){
|
||||
T_COFF_DefReloc(X64_Addr32Nb, 0, "foo"),
|
||||
{0}
|
||||
}
|
||||
{ "data", ".chosen", str8_lit("pad!OVER"), .flags = "rw:data@4" }, {0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Extern("altsym", "data", 4), {0} }
|
||||
}));
|
||||
T_Ok(t_write_def_obj("absolute.obj", (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_AbsExtern("absolute", 0x1234), {0} }
|
||||
}));
|
||||
|
||||
struct {
|
||||
char *path;
|
||||
char *name;
|
||||
char *tag;
|
||||
COFF_WeakExtType type;
|
||||
} aliases[] = {
|
||||
{ "weak_chain.obj", "altsym", "middle", COFF_WeakExt_SearchAlias },
|
||||
{ "weak_middle.obj", "middle", "test", COFF_WeakExt_SearchAlias },
|
||||
{ "anti.obj", "altsym", "test", COFF_WeakExt_AntiDependency },
|
||||
{ "anti_middle.obj", "middle", "test", COFF_WeakExt_AntiDependency },
|
||||
{ "missing.obj", "altsym", "missing", COFF_WeakExt_SearchAlias },
|
||||
{ "cycle_tail.obj", "middle", "altsym", COFF_WeakExt_SearchAlias },
|
||||
{ "source_weak.obj", "foo", "test", COFF_WeakExt_SearchAlias },
|
||||
{ "weak_nolib.obj", "altsym", "test", COFF_WeakExt_NoLibrary },
|
||||
{ "weak_lib.obj", "altsym", "test", COFF_WeakExt_SearchLibrary },
|
||||
};
|
||||
for EachElement(i, aliases) {
|
||||
T_Ok(t_write_def_obj(aliases[i].path, (T_COFF_DefObj){
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Undef(aliases[i].tag),
|
||||
T_COFF_DefSymbol_Weak(aliases[i].name, aliases[i].type, aliases[i].tag),
|
||||
{0}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
T_Ok(t_write_def_obj("directive.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=test", 0 } }));
|
||||
T_Ok(t_write_def_obj("weak_directive.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=altsym", 0 } }));
|
||||
T_Ok(t_write_def_obj("conflict.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=other", 0 } }));
|
||||
T_Ok(t_write_file(str8_lit("alternate.rsp"), str8_lit("/alternatename:foo=test")));
|
||||
|
||||
T_COFF_DefObj lib_objs[] = { target, primary, weak };
|
||||
char *lib_paths[] = { "target.lib", "foo.lib", "weak.lib" };
|
||||
for EachElement(i, lib_objs) {
|
||||
T_Ok(t_write_def_lib(lib_paths[i], (T_COFF_DefLib){
|
||||
.emit_second_member = 1,
|
||||
.members = (T_COFF_DefLibMember[]){ { .type = T_COFF_DefLibMember_Obj, .obj = lib_objs[i] }, {0} }
|
||||
}));
|
||||
}
|
||||
|
||||
// Extracting the fallback unnecessarily must fail, even if its data is unused.
|
||||
T_Ok(t_write_def_lib("poison.lib", (T_COFF_DefLib){
|
||||
.emit_second_member = 1,
|
||||
.members = (T_COFF_DefLibMember[]){ { .type = T_COFF_DefLibMember_Obj, .obj = {
|
||||
.path = str8_lit("poison.obj"),
|
||||
.machine = T_COFF_DefSetMachine(X64),
|
||||
.sections = (T_COFF_DefSection[]){
|
||||
{ "data", ".poison", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4",
|
||||
.relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "poison_missing"), {0} } },
|
||||
{0}
|
||||
},
|
||||
{0}
|
||||
},
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Extern("entry", "text", 0),
|
||||
T_COFF_DefSymbol_Undef("foo"),
|
||||
{0}
|
||||
}
|
||||
.symbols = (T_COFF_DefSymbol[]){
|
||||
T_COFF_DefSymbol_Extern("test", "data", 0),
|
||||
T_COFF_DefSymbol_Undef("poison_missing"),
|
||||
{0}
|
||||
}
|
||||
} }, {0} }
|
||||
}));
|
||||
|
||||
// basic alternate name test
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo=test test.obj entry.obj");
|
||||
T_Ok(g_last_exit_code == 0);
|
||||
struct {
|
||||
char *name;
|
||||
char *options;
|
||||
char *inputs;
|
||||
B32 success;
|
||||
char *section; // NULL on success means an absolute symbol value.
|
||||
U32 offset;
|
||||
LNK_ErrorCode error; // Defaults to UnresolvedSymbol for negative cases.
|
||||
} cases[] = {
|
||||
{ "direct", "/alternatename:foo=test", "target.obj", 1, ".target", 4 },
|
||||
{ "identical_duplicate", "/alternatename:foo=test /alternatename:foo=test", "target.obj", 1, ".target", 4 },
|
||||
{ "absolute", "/alternatename:foo=absolute", "absolute.obj", 1, 0, 0x1234 },
|
||||
{ "primary_before", "/alternatename:foo=test", "foo.obj target.obj", 1, ".primary", 4 },
|
||||
{ "primary_after", "/alternatename:foo=test", "target.obj foo.obj", 1, ".primary", 4 },
|
||||
{ "primary_missing_alt", "/alternatename:foo=missing", "foo.obj", 1, ".primary", 4 },
|
||||
{ "primary_self", "/alternatename:foo=foo", "foo.obj", 1, ".primary", 4 },
|
||||
{ "unused_mapping", "/alternatename:unused=missing", "foo.obj", 1, ".primary", 4 },
|
||||
{ "unused_cycle", "/alternatename:unused=other /alternatename:other=unused", "foo.obj", 1, ".primary", 4 },
|
||||
|
||||
// linker should not chase alt name links
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:b.exe /alternatename:foo=bar /alternatename:bar=test test.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// Object-file and response-file directives have the same fallback semantics.
|
||||
{ "directive_before", "", "directive.obj target.obj", 1, ".target", 4 },
|
||||
{ "directive_after", "", "target.obj directive.obj", 1, ".target", 4 },
|
||||
{ "directive_duplicate", "/alternatename:foo=test", "directive.obj target.obj", 1, ".target", 4 },
|
||||
{ "response_file", "@alternate.rsp", "target.obj", 1, ".target", 4 },
|
||||
|
||||
// alt name conflict
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:c.exe /alternatename:foo=test /alternatename:foo=qwe test.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// Archive search must prefer the primary name and avoid unused fallbacks.
|
||||
{ "archive_target", "/alternatename:foo=test", "target.lib", 1, ".target", 4 },
|
||||
{ "archive_primary", "/alternatename:foo=test", "target.obj foo.lib", 1, ".primary", 4 },
|
||||
{ "archives_before", "/alternatename:foo=test", "foo.lib target.lib", 1, ".primary", 4 },
|
||||
{ "archives_after", "/alternatename:foo=test", "target.lib foo.lib", 1, ".primary", 4 },
|
||||
{ "unused_fallback_lib", "/alternatename:foo=test", "foo.obj poison.lib", 1, ".primary", 4 },
|
||||
{ "unused_mapping_lib", "/alternatename:unused=test", "foo.obj poison.lib", 1, ".primary", 4 },
|
||||
{ "required_poison_lib", "/alternatename:foo=test", "poison.lib", 0 },
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:d.exe /alternatename:foo foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:e.exe /alternatename:foo-oof foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// An ordinary weak alias is a valid target; its prevailing definition wins.
|
||||
{ "weak_local", "/alternatename:foo=altsym", "weak_local.obj", 1, ".target", 4 },
|
||||
{ "weak_before", "/alternatename:foo=altsym", "weak.obj target.obj", 1, ".target", 4 },
|
||||
{ "weak_after", "/alternatename:foo=altsym", "target.obj weak.obj", 1, ".target", 4 },
|
||||
{ "weak_chain_before", "/alternatename:foo=altsym", "weak_chain.obj weak_middle.obj target.obj", 1, ".target", 4 },
|
||||
{ "weak_chain_after", "/alternatename:foo=altsym", "target.obj weak_middle.obj weak_chain.obj", 1, ".target", 4 },
|
||||
{ "weak_override_first", "/alternatename:foo=altsym", "override.obj weak.obj target.obj", 1, ".chosen", 4 },
|
||||
{ "weak_override_last", "/alternatename:foo=altsym", "weak.obj target.obj override.obj", 1, ".chosen", 4 },
|
||||
{ "weak_source", "/alternatename:foo=missing", "source_weak.obj target.obj", 1, ".target", 4 },
|
||||
{ "weak_directive", "", "weak_directive.obj weak.obj target.obj", 1, ".target", 4 },
|
||||
{ "weak_archive", "/alternatename:foo=altsym /include:alias_anchor", "weak.lib target.obj", 1, ".target", 4 },
|
||||
{ "weak_target_archive", "/alternatename:foo=altsym", "weak.obj target.lib", 1, ".target", 4 },
|
||||
{ "weak_nolib_target", "/alternatename:foo=altsym", "weak_nolib.obj target.obj", 1, ".target", 4 },
|
||||
{ "weak_lib_target", "/alternatename:foo=altsym", "weak_lib.obj target.obj", 1, ".target", 4 },
|
||||
// LINK rejects a section-defined static weak default; RAD and LLD support it.
|
||||
{ "weak_static_target", "/alternatename:foo=altsym", "weak_static.obj global_target.obj", t_id_linker() != Linker_msvc, ".target", 4 },
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo=test=bar foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// altsym is discovered only after both synthetic fallback pairs exist.
|
||||
{ "late_weak_archive", "/alternatename:foo=altsym /alternatename:trigger=alias_anchor /include:trigger", "weak.lib target.obj", 1, ".target", 4 },
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo= foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// Unlike an ordinary alias, an anti-dependency cannot be chained through.
|
||||
{ "anti_before", "/alternatename:foo=altsym", "anti.obj target.obj", 0 },
|
||||
{ "anti_after", "/alternatename:foo=altsym", "target.obj anti.obj", 0 },
|
||||
{ "nested_anti", "/alternatename:foo=altsym", "weak_chain.obj anti_middle.obj target.obj", 0 },
|
||||
{ "anti_override_first", "/alternatename:foo=altsym", "override.obj anti.obj target.obj", 1, ".chosen", 4 },
|
||||
{ "anti_override_last", "/alternatename:foo=altsym", "anti.obj target.obj override.obj", 1, ".chosen", 4 },
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:= foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// Unreferenced intermediate command-line alternate names are not alias chains.
|
||||
{ "command_chain", "/alternatename:foo=bar /alternatename:bar=test", "target.obj", 0 },
|
||||
{ "command_chain_order", "/alternatename:bar=test /alternatename:foo=bar", "target.obj", 0 },
|
||||
{ "missing_target", "/alternatename:foo=missing", "target.obj", 0 },
|
||||
{ "case_sensitive_from", "/alternatename:Foo=test", "target.obj", 0 },
|
||||
{ "case_sensitive_to", "/alternatename:foo=TEST", "target.obj", 0 },
|
||||
{ "missing_weak_target", "/alternatename:foo=altsym", "missing.obj", 0 },
|
||||
{ "undefined_self", "/alternatename:foo=foo", "target.obj", 0 },
|
||||
{ "command_cycle", "/alternatename:foo=bar /alternatename:bar=foo", "target.obj", 0 },
|
||||
{ "weak_cycle", "/alternatename:foo=altsym", "weak_chain.obj cycle_tail.obj", 0 },
|
||||
|
||||
// syntax error
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename: foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code != 0);
|
||||
// RAD and LLD reject conflicts even for defined sources; LINK keeps the first mapping.
|
||||
{ "conflict", "/alternatename:foo=test /alternatename:foo=other", "target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict },
|
||||
{ "defined_conflict", "/alternatename:foo=test /alternatename:foo=other", "foo.obj", t_id_linker() == Linker_msvc, ".primary", 4, LNK_Error_AlternateNameConflict },
|
||||
{ "directive_conflict", "/alternatename:foo=test", "conflict.obj target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict },
|
||||
{ "two_directives", "", "directive.obj conflict.obj target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict },
|
||||
{ "missing_equals", "/alternatename:foo", "foo.obj", 0, 0, 0, LNK_Error_Cmdl },
|
||||
{ "wrong_separator", "/alternatename:foo-oof", "foo.obj", 0, 0, 0, LNK_Error_Cmdl },
|
||||
{ "empty_target", "/alternatename:foo=", "foo.obj", 0, 0, 0, LNK_Error_Cmdl },
|
||||
|
||||
// TODO: check that RAD Linker prints these warnings
|
||||
// LINK accepts an empty (unused) source; RAD and LLD diagnose it.
|
||||
{ "empty_source", "/alternatename:=test", "foo.obj", t_id_linker() == Linker_msvc, ".primary", 4, LNK_Error_Cmdl },
|
||||
{ "empty_both", "/alternatename:=", "foo.obj", 0, 0, 0, LNK_Error_Cmdl },
|
||||
{ "empty_option", "/alternatename:", "foo.obj", 0, 0, 0, LNK_Error_Cmdl },
|
||||
|
||||
// LLD and LINK accept the extra '=' with a defined source; RAD rejects it.
|
||||
{ "extra_equals", "/alternatename:foo=test=bar", "foo.obj", t_id_linker() != Linker_radlink, ".primary", 4, LNK_Error_Cmdl },
|
||||
};
|
||||
|
||||
// warn about alt name to self alt name?
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:f.exe /alternatename:foo=foo foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code == 0);
|
||||
B32 all_ok = 1;
|
||||
U32 worker_counts[] = { 1, 4 };
|
||||
U64 pass_count = t_id_linker() == Linker_radlink ? ArrayCount(worker_counts) : 1;
|
||||
for EachIndex(pass_idx, pass_count) {
|
||||
for EachElement(case_idx, cases) {
|
||||
String8 out_name = str8f(arena, "alt_%s_%u.exe", cases[case_idx].name, worker_counts[pass_idx]);
|
||||
String8 workers = t_id_linker() == Linker_radlink ? str8f(arena, "/rad_workers:%u", worker_counts[pass_idx]) : str8_zero();
|
||||
char *ref_obj = cases[case_idx].success && !cases[case_idx].section ? "ref_absolute.obj" : "ref.obj";
|
||||
String8 cmdline = str8f(arena, "/subsystem:console /entry:entry /nodefaultlib /opt:noref,noicf /out:%S %S %s entry.obj %s %s",
|
||||
out_name, workers, cases[case_idx].options, ref_obj, cases[case_idx].inputs);
|
||||
test_outf("alt_name %s (workers %u): %S\n", cases[case_idx].name, worker_counts[pass_idx], cmdline);
|
||||
|
||||
// warn about alt name to unknown symbol?
|
||||
t_invoke_linkerf("/subsystem:console /entry:entry /out:g.exe /alternatename:qwe=ewq foo.obj entry.obj");
|
||||
T_Ok(g_last_exit_code == 0);
|
||||
// Bound cycles require a normal exit: a crash/timeout is not an expected failure.
|
||||
B32 invoked = t_invoke(t_linker_path(), cmdline, 10 * 1000 * 1000);
|
||||
B32 normal_exit = invoked && g_last_exit_code < 0x80000000 && g_last_exit_code != 999;
|
||||
B32 case_ok = normal_exit && ((g_last_exit_code == 0) == cases[case_idx].success);
|
||||
if (case_ok && !cases[case_idx].success && t_id_linker() == Linker_radlink) {
|
||||
LNK_ErrorCode expected_error = cases[case_idx].error ? cases[case_idx].error : LNK_Error_UnresolvedSymbol;
|
||||
case_ok = g_last_exit_code == expected_error;
|
||||
if (!case_ok) { test_outf(" expected error code %u\n", expected_error); }
|
||||
}
|
||||
|
||||
if (normal_exit && g_last_exit_code == 0 && cases[case_idx].success) {
|
||||
String8 image = t_read_file(arena, out_name);
|
||||
PE_BinInfo pe = pe_bin_info_from_data(arena, image);
|
||||
COFF_SectionHeader *sections = (COFF_SectionHeader *)str8_substr(image, pe.section_table_range).str;
|
||||
String8 strings = str8_substr(image, pe.string_table_range);
|
||||
COFF_SectionHeader *probe = coff_section_header_from_name(strings, sections, pe.section_count, str8_lit(".probe"));
|
||||
U64 expected_rva = cases[case_idx].offset;
|
||||
|
||||
case_ok = pe.arch == Arch_x64 && probe && probe->vsize == sizeof(U32);
|
||||
if (cases[case_idx].section) {
|
||||
COFF_SectionHeader *dest = coff_section_header_from_name(strings, sections, pe.section_count, str8_cstring(cases[case_idx].section));
|
||||
case_ok &= dest != 0 && dest->vsize >= cases[case_idx].offset + sizeof(U32);
|
||||
if (dest) { expected_rva += dest->voff; }
|
||||
}
|
||||
|
||||
if (case_ok) {
|
||||
String8 data = str8_substr(image, rng_1u64(probe->foff, (U64)probe->foff + sizeof(U32)));
|
||||
case_ok = data.size == sizeof(U32) && memory_read32(data.str) == expected_rva;
|
||||
if (!case_ok) { test_outf(" incorrect relocated RVA; expected %#llx\n", expected_rva); }
|
||||
}
|
||||
}
|
||||
|
||||
test_outf(" %s: normal exit %u, exit code %llu, expected %s\n", case_ok ? "PASS" : "FAIL", normal_exit, g_last_exit_code, cases[case_idx].success ? "success with correct RVA" : "failure");
|
||||
T_Ok(case_ok);
|
||||
}
|
||||
}
|
||||
|
||||
T_Ok(all_ok);
|
||||
}
|
||||
|
||||
TEST(include)
|
||||
|
||||
Reference in new issue
Block a user