@@ -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+
49125052TEST (link_large_import_object )
49135053{
49145054 // Each named import creates three sections in the synthesized DLL object.
0 commit comments