Skip to content

Commit 000997d

Browse files
radlink: implement COFF_ImportBy_Undecorate
1 parent 3e7b11f commit 000997d

3 files changed

Lines changed: 157 additions & 4 deletions

File tree

‎src/linker/tests/linker_tests.c‎

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4909,6 +4909,146 @@ TEST(communal_var_vs_regular_comdat)
49094909
}
49104910
}
49114911

4912+
TEST(import_undecorate)
4913+
{
4914+
// Keep the decorated COFF symbols, but use the undecorated DLL export name
4915+
// in the hint/name table. An empty ILT/IAT still links, but calls through its
4916+
// jump thunk reach address zero at runtime.
4917+
struct {
4918+
char *symbol_name;
4919+
char *export_name;
4920+
} cases[] = {
4921+
{ "?CppImport@@YAHXZ", "CppImport" },
4922+
{ "_StdcallImport@8", "StdcallImport" },
4923+
{ "@FastcallImport@8", "FastcallImport" },
4924+
{ "PlainImport", "PlainImport" },
4925+
};
4926+
struct {
4927+
char *args;
4928+
B32 delayed;
4929+
} opts[] = {
4930+
{ "/opt:ref", 0 },
4931+
{ "/opt:noref", 0 },
4932+
{ "/opt:ref /delayload:undecorate.dll", 1 },
4933+
{ "/opt:noref /delayload:undecorate.dll", 1 },
4934+
};
4935+
4936+
for EachElement(case_idx, cases) {
4937+
String8 symbol_name = str8_cstring(cases[case_idx].symbol_name);
4938+
String8 iat_name = str8f(arena, "__imp_%S", symbol_name);
4939+
U16 hint = 17;
4940+
4941+
T_Ok(t_write_def_lib("undecorate.lib", (T_COFF_DefLib){
4942+
.emit_second_member = 1,
4943+
.members = (T_COFF_DefLibMember[]){
4944+
{
4945+
.type = T_COFF_DefLibMember_Import,
4946+
.import = { "undecorate.dll", cases[case_idx].symbol_name,
4947+
COFF_ImportBy_Undecorate, COFF_ImportHeader_Code,
4948+
.hit_or_ordinal = hint },
4949+
},
4950+
{0},
4951+
},
4952+
}));
4953+
T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){
4954+
.sections = (T_COFF_DefSection[]){
4955+
{
4956+
"text", ".text",
4957+
str8_lit_comp(
4958+
"\x48\x83\xEC\x28" // sub rsp,28h
4959+
"\xE8\0\0\0\0" // call decorated function's jump thunk
4960+
"\xFF\x15\0\0\0\0" // call [__imp_decorated_function]
4961+
"\x48\x83\xC4\x28\xC3" // add rsp,28h; ret
4962+
),
4963+
.flags = "rx:code",
4964+
.relocs = (T_COFF_DefReloc[]){
4965+
T_COFF_DefReloc(X64_Rel32, 5, cases[case_idx].symbol_name),
4966+
T_COFF_DefReloc(X64_Rel32, 11, (char *)iat_name.str),
4967+
{0},
4968+
},
4969+
},
4970+
{0},
4971+
},
4972+
.symbols = (T_COFF_DefSymbol[]){
4973+
T_COFF_DefSymbol_ExternFunc("entry", "text", 0),
4974+
// Link-only test: the delay helper only needs to resolve to a ret.
4975+
T_COFF_DefSymbol_ExternFunc("__delayLoadHelper2", "text", 19),
4976+
T_COFF_DefSymbol_UndefFunc(cases[case_idx].symbol_name),
4977+
T_COFF_DefSymbol_Undef((char *)iat_name.str),
4978+
{0},
4979+
},
4980+
}));
4981+
4982+
for EachElement(opt_idx, opts) {
4983+
t_infof("Undecorate import %S -> %s (%s)\n", symbol_name, cases[case_idx].export_name, opts[opt_idx].args);
4984+
t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:undecorate.exe %s entry.obj undecorate.lib", opts[opt_idx].args);
4985+
T_Ok(g_last_exit_code == 0);
4986+
4987+
String8 image = t_read_file(arena, str8_lit("undecorate.exe"));
4988+
PE_BinInfo bin = pe_bin_info_from_data(arena, image);
4989+
COFF_SectionHeader *sections = (COFF_SectionHeader *)(image.str + bin.section_table_range.min);
4990+
PE_ParsedImport *import = 0;
4991+
U64 iat_voff = 0, ilt_voff = 0;
4992+
4993+
if (opts[opt_idx].delayed) {
4994+
T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_DELAY_IMPORT);
4995+
PE_ParsedDelayImportTable imports = pe_delay_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_DELAY_IMPORT]);
4996+
T_Ok(imports.count == 1);
4997+
T_Ok(str8_match(imports.v[0].name, str8_lit("undecorate.dll"), 0));
4998+
T_Ok(imports.v[0].import_count == 1);
4999+
import = &imports.v[0].imports[0];
5000+
iat_voff = imports.v[0].iat_voff;
5001+
ilt_voff = imports.v[0].name_table_voff;
5002+
} else {
5003+
T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_IMPORT);
5004+
PE_ParsedStaticImportTable imports = pe_static_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_IMPORT]);
5005+
T_Ok(imports.count == 1);
5006+
T_Ok(str8_match(imports.v[0].name, str8_lit("undecorate.dll"), 0));
5007+
T_Ok(imports.v[0].import_count == 1);
5008+
import = &imports.v[0].imports[0];
5009+
iat_voff = imports.v[0].import_address_table_voff;
5010+
ilt_voff = imports.v[0].import_name_table_voff;
5011+
}
5012+
T_Ok(import->type == PE_ParsedImport_Name);
5013+
5014+
// Delay-load linkers may replace the advisory import hint with zero.
5015+
if (!opts[opt_idx].delayed) {
5016+
T_Ok(import->u.name.hint == hint);
5017+
}
5018+
T_Ok(str8_match(import->u.name.string, str8_cstring(cases[case_idx].export_name), 0));
5019+
5020+
// Both call forms must resolve to the same populated IAT slot.
5021+
U64 entry_off = pe_foff_from_voff(image, &bin, bin.entry_point);
5022+
S32 call_disp = 0, iat_disp = 0, thunk_disp = 0;
5023+
T_Ok(str8_deserial_read_struct(image, entry_off + 5, &call_disp) == sizeof(call_disp));
5024+
T_Ok(str8_deserial_read_struct(image, entry_off + 11, &iat_disp) == sizeof(iat_disp));
5025+
5026+
U64 thunk_voff = (U64)((S64)bin.entry_point + 9 + call_disp);
5027+
U64 thunk_off = pe_foff_from_voff(image, &bin, thunk_voff);
5028+
T_Ok(str8_match(str8_substr(image, rng_1u64(thunk_off, thunk_off + 2)), str8_lit("\xFF\x25"), 0));
5029+
T_Ok(str8_deserial_read_struct(image, thunk_off + 2, &thunk_disp) == sizeof(thunk_disp));
5030+
T_Ok((U64)((S64)bin.entry_point + 15 + iat_disp) == iat_voff);
5031+
T_Ok((U64)((S64)thunk_voff + 6 + thunk_disp) == iat_voff);
5032+
5033+
U64 iat_off = pe_foff_from_voff(image, &bin, iat_voff);
5034+
U64 ilt_off = pe_foff_from_voff(image, &bin, ilt_voff);
5035+
U64 iat_entry = 0, iat_end = 1, ilt_entry = 0;
5036+
T_Ok(str8_deserial_read_struct(image, iat_off, &iat_entry) == sizeof(iat_entry));
5037+
T_Ok(str8_deserial_read_struct(image, iat_off + 8, &iat_end) == sizeof(iat_end));
5038+
T_Ok(str8_deserial_read_struct(image, ilt_off, &ilt_entry) == sizeof(ilt_entry));
5039+
T_Ok(iat_entry != 0 && iat_end == 0);
5040+
5041+
if (opts[opt_idx].delayed) {
5042+
// Delay-load IAT entries start at the load thunk, rather than the name.
5043+
T_Ok(iat_entry >= bin.image_base);
5044+
T_Ok(pe_foff_from_voff(image, &bin, iat_entry - bin.image_base) != 0);
5045+
} else {
5046+
T_Ok(iat_entry == ilt_entry);
5047+
}
5048+
}
5049+
}
5050+
}
5051+
49125052
TEST(link_large_import_object)
49135053
{
49145054
// Each named import creates three sections in the synthesized DLL object.

‎src/pe/pe_make_import_table.c‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeIm
1717
return node;
1818
}
1919

20+
internal String8
21+
pe_undecorate_import_name(String8 name)
22+
{
23+
// COFF IMPORT_NAME_UNDECORATE strips one prefix character and the suffix starting at the first '@'
24+
if (name.size && (name.str[0] == '?' || name.str[0] == '@' || name.str[0] == '_')) {
25+
name = str8_skip(name, 1);
26+
}
27+
return str8_prefix(name, str8_find_needle(name, 0, str8_lit("@"), 0));
28+
}
29+
2030
internal COFF_ObjSymbol *
2131
pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *iat_symbol, String8 thunk_name)
2232
{
@@ -286,11 +296,13 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach
286296
str8_list_push(obj_writer->arena, &ilt_sect->data, ordinal_data);
287297
str8_list_push(obj_writer->arena, &iat_sect->data, ordinal_data);
288298
} break;
299+
case COFF_ImportBy_Undecorate:
289300
case COFF_ImportBy_Name: {
290301
COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes|comdat_flags, str8_zero());
291302
coff_obj_writer_push_symbol_associative(obj_writer, int_sect, iat_sect);
292303
COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, 0, int_sect);
293-
String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name);
304+
String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name;
305+
String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name);
294306
str8_list_push(obj_writer->arena, &int_sect->data, int_data);
295307
str8_list_push_aligner(obj_writer->arena, &int_sect->data, 0, 2);
296308

@@ -302,7 +314,6 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach
302314
str8_list_push(obj_writer->arena, &ilt_sect->data, str8_array(import_entry, import_size));
303315
str8_list_push(obj_writer->arena, &iat_sect->data, str8_array(import_entry, import_size));
304316
} break;
305-
case COFF_ImportBy_Undecorate: { NotImplemented; } break;
306317
case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break;
307318
default: { InvalidPath; } break;
308319
}
@@ -461,9 +472,11 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac
461472
coff_obj_writer_section_push_reloc_addr(obj_writer, uiat_sect, uiat_offset, load_thunk_symbol);
462473
}
463474
} break;
475+
case COFF_ImportBy_Undecorate:
464476
case COFF_ImportBy_Name: {
465477
// put together name look up entry
466-
String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name);
478+
String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name;
479+
String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name);
467480
U64 int_data_offset = int_sect->data.total_size;
468481
str8_list_push(obj_writer->arena, &int_sect->data, int_data);
469482

@@ -503,7 +516,6 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac
503516
coff_obj_writer_section_push_reloc_addr(obj_writer, uiat_sect, uiat_data_offset, load_thunk_symbol);
504517
}
505518
} break;
506-
case COFF_ImportBy_Undecorate: { NotImplemented; } break;
507519
case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break;
508520
}
509521
}

‎src/pe/pe_make_import_table.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ typedef struct PE_MakeImportList
2727

2828
internal void pe_make_import_header_list_push_node(PE_MakeImportList *list, PE_MakeImportNode *node);
2929
internal PE_MakeImportNode * pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeImport header);
30+
internal String8 pe_undecorate_import_name(String8 name);
3031

3132
internal COFF_ObjSymbol * pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *iat_symbol, String8 thunk_name);
3233
internal COFF_ObjSymbol * pe_make_load_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *imp_addr_ptr, COFF_ObjSymbol *tail_merge, String8 func_name);

0 commit comments

Comments
 (0)