pe-dso-forwarder.c (11596B)
1 /* read_coff_dso forwarder-export contract test. 2 * 3 * Synthesizes a minimal PE32+ DLL with two named exports — one direct 4 * (EAT RVA outside the export directory's range) and one forwarder 5 * (EAT RVA inside the export directory's range, contents 6 * "OTHERDLL.OtherSym") — and asserts that read_coff_dso surfaces both 7 * as OBJ_SEC_NONE globals on the returned ObjBuilder. kit's linker 8 * does not follow forwarder chains: the symbols just need to be 9 * defined so import resolution succeeds, and the OS loader follows 10 * the chain at runtime. This test locks in that contract. */ 11 12 #include <kit/core.h> 13 #include <kit/object.h> 14 #include <setjmp.h> 15 #include <stdarg.h> 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <string.h> 19 20 #include "core/core.h" 21 #include "core/pool.h" 22 #include "obj/coff/coff.h" 23 #include "obj/obj.h" 24 25 /* ---- env vtables --------------------------------------------------- */ 26 27 static void* heap_alloc(KitHeap* h, size_t n, size_t a) { 28 (void)h; 29 (void)a; 30 return n ? malloc(n) : NULL; 31 } 32 static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) { 33 (void)h; 34 (void)o; 35 (void)a; 36 return realloc(p, n); 37 } 38 static void heap_free(KitHeap* h, void* p, size_t n) { 39 (void)h; 40 (void)n; 41 free(p); 42 } 43 static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL}; 44 45 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, 46 const char* fmt, va_list ap) { 47 static const char* names[] = {"note", "warning", "error", "fatal"}; 48 (void)s; 49 (void)loc; 50 fprintf(stderr, "%s: ", names[k]); 51 vfprintf(stderr, fmt, ap); 52 fputc('\n', stderr); 53 } 54 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0}; 55 56 static int g_failures; 57 #define EXPECT(cond, ...) \ 58 do { \ 59 if (!(cond)) { \ 60 fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ 61 fprintf(stderr, __VA_ARGS__); \ 62 fputc('\n', stderr); \ 63 g_failures++; \ 64 } \ 65 } while (0) 66 67 /* ---- compiler ----------------------------------------------------- */ 68 69 static KitContext g_ctx; 70 71 static void target_x64_windows(KitTargetSpec* t) { 72 memset(t, 0, sizeof *t); 73 t->arch = KIT_ARCH_X86_64; 74 t->os = KIT_OS_WINDOWS; 75 t->obj = KIT_OBJ_COFF; 76 t->ptr_size = 8; 77 t->ptr_align = 8; 78 t->big_endian = false; 79 t->pic = KIT_PIC_PIE; 80 t->code_model = KIT_CM_SMALL; 81 } 82 83 static Compiler* make_compiler(const KitTargetSpec* t) { 84 memset(&g_ctx, 0, sizeof g_ctx); 85 g_ctx.heap = &g_heap; 86 g_ctx.diag = &g_diag; 87 g_ctx.now = -1; 88 KitTargetOptions opts; 89 memset(&opts, 0, sizeof opts); 90 opts.spec = *t; 91 KitTarget* target = NULL; 92 if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL; 93 KitCompiler* cc = NULL; 94 if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) { 95 kit_target_free(target); 96 return NULL; 97 } 98 return (Compiler*)cc; 99 } 100 101 static void free_compiler(Compiler* c) { 102 if (!c) return; 103 const KitTarget* target = kit_compiler_target((KitCompiler*)c); 104 kit_compiler_free((KitCompiler*)c); 105 kit_target_free((KitTarget*)target); 106 } 107 108 /* ---- little-endian writers ---------------------------------------- */ 109 110 static void wr_u16(uint8_t* p, uint16_t v) { 111 p[0] = (uint8_t)(v & 0xFF); 112 p[1] = (uint8_t)((v >> 8) & 0xFF); 113 } 114 static void wr_u32(uint8_t* p, uint32_t v) { 115 p[0] = (uint8_t)(v & 0xFF); 116 p[1] = (uint8_t)((v >> 8) & 0xFF); 117 p[2] = (uint8_t)((v >> 16) & 0xFF); 118 p[3] = (uint8_t)((v >> 24) & 0xFF); 119 } 120 121 /* ---- synthetic PE32+ DLL builder ---------------------------------- */ 122 123 /* Layout (file offsets): 124 * 0x000 .. 0x03F DOS header (e_lfanew = 0x40) 125 * 0x040 .. 0x043 "PE\0\0" 126 * 0x044 .. 0x057 IMAGE_FILE_HEADER (20 bytes) 127 * 0x058 .. 0x147 IMAGE_OPTIONAL_HEADER64 (240 bytes) 128 * 0x148 .. 0x16F one IMAGE_SECTION_HEADER (40 bytes) 129 * 0x170 .. 0x36F section raw data (RVA 0x1000, 0x200 bytes) 130 * 131 * The single section ".edata" at RVA 0x1000 carries the export 132 * directory plus its tables and strings. The export DataDirectory 133 * record points at the start of that section and covers everything 134 * including the forwarder target string so the reader classifies 135 * "OTHERDLL.OtherSym" EAT entries as forwarders. */ 136 137 #define E_LFANEW 0x40u 138 #define FH_OFF (E_LFANEW + 4u) 139 #define OH_OFF (FH_OFF + COFF_FILE_HEADER_SIZE) 140 #define SH_OFF (OH_OFF + COFF_OPT_HDR64_SIZE) 141 #define RAW_OFF 0x170u 142 #define SEC_VA 0x1000u 143 #define SEC_RAW_SZ 0x200u 144 #define FILE_SIZE (RAW_OFF + SEC_RAW_SZ) 145 146 /* In-section offsets (relative to RAW_OFF / RVA = SEC_VA + off). */ 147 #define EXP_DIR_OFF 0u 148 #define EAT_OFF (EXP_DIR_OFF + COFF_EXPORT_DIR_SIZE) /* +40 */ 149 #define EAT_COUNT 2u 150 #define ENT_OFF (EAT_OFF + EAT_COUNT * 4u) /* +48 */ 151 #define ENT_COUNT 2u 152 #define ORD_OFF (ENT_OFF + ENT_COUNT * 4u) /* +56 */ 153 #define DLLNAME_OFF (ORD_OFF + ENT_COUNT * 2u) /* +60 */ 154 155 static const char kDllName[] = "TestDll.dll"; 156 static const char kDirect[] = "DirectFn"; 157 static const char kForwarded[] = "ForwardedFn"; 158 static const char kForwardTarget[] = "OTHERDLL.OtherSym"; 159 160 #define DIRECT_NAME_OFF (DLLNAME_OFF + (uint32_t)sizeof kDllName) 161 #define FORWARDED_NAME_OFF (DIRECT_NAME_OFF + (uint32_t)sizeof kDirect) 162 #define FORWARD_TGT_OFF (FORWARDED_NAME_OFF + (uint32_t)sizeof kForwarded) 163 #define EXP_DIR_END (FORWARD_TGT_OFF + (uint32_t)sizeof kForwardTarget) 164 165 /* Some RVA outside the export directory range — interpreted as a 166 * direct export pointing into the (notional) code section. */ 167 #define DIRECT_FN_RVA 0x2000u 168 169 static void build_dso(uint8_t* buf) { 170 memset(buf, 0, FILE_SIZE); 171 172 /* DOS header. */ 173 wr_u16(buf + 0, IMAGE_DOS_SIGNATURE); 174 wr_u32(buf + 60, E_LFANEW); 175 176 /* PE signature. */ 177 wr_u32(buf + E_LFANEW, IMAGE_NT_SIGNATURE); 178 179 /* IMAGE_FILE_HEADER. */ 180 wr_u16(buf + FH_OFF + 0, IMAGE_FILE_MACHINE_AMD64); 181 wr_u16(buf + FH_OFF + 2, 1); /* NumberOfSections */ 182 wr_u32(buf + FH_OFF + 4, 0); /* TimeDateStamp */ 183 wr_u32(buf + FH_OFF + 8, 0); /* PointerToSymbolTable */ 184 wr_u32(buf + FH_OFF + 12, 0); /* NumberOfSymbols */ 185 wr_u16(buf + FH_OFF + 16, COFF_OPT_HDR64_SIZE); 186 wr_u16(buf + FH_OFF + 18, IMAGE_FILE_DLL); 187 188 /* IMAGE_OPTIONAL_HEADER64. Only the fields the reader inspects 189 * matter: Magic, and the export DataDirectory at index 0. */ 190 wr_u16(buf + OH_OFF + 0, IMAGE_NT_OPTIONAL_HDR64_MAGIC); 191 /* Data directories live at the tail of the optional header. */ 192 uint32_t dd_off = OH_OFF + COFF_OPT_HDR64_SIZE - 193 COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE; 194 uint32_t exp_rva = SEC_VA + EXP_DIR_OFF; 195 uint32_t exp_size = EXP_DIR_END; 196 wr_u32(buf + dd_off + IMAGE_DIRECTORY_ENTRY_EXPORT * 8u + 0, exp_rva); 197 wr_u32(buf + dd_off + IMAGE_DIRECTORY_ENTRY_EXPORT * 8u + 4, exp_size); 198 199 /* One section header: ".edata". */ 200 memcpy(buf + SH_OFF + 0, ".edata\0\0", 8); 201 wr_u32(buf + SH_OFF + 8, exp_size); /* VirtualSize */ 202 wr_u32(buf + SH_OFF + 12, SEC_VA); /* VirtualAddress */ 203 wr_u32(buf + SH_OFF + 16, SEC_RAW_SZ); /* SizeOfRawData */ 204 wr_u32(buf + SH_OFF + 20, RAW_OFF); /* PointerToRawData */ 205 wr_u32(buf + SH_OFF + 24, 0); /* PtrToRelocations */ 206 wr_u32(buf + SH_OFF + 28, 0); /* PtrToLinenumbers */ 207 wr_u16(buf + SH_OFF + 32, 0); /* NumberOfRelocations */ 208 wr_u16(buf + SH_OFF + 34, 0); /* NumberOfLinenumbers */ 209 wr_u32(buf + SH_OFF + 36, 0x40000040u); /* Characteristics: 210 INITIALIZED_DATA | 211 MEM_READ */ 212 213 /* Section raw data — written via RAW_OFF + off. */ 214 uint8_t* sec = buf + RAW_OFF; 215 216 /* Export Directory header. */ 217 wr_u32(sec + EXP_DIR_OFF + 0, 0); /* Characteristics */ 218 wr_u32(sec + EXP_DIR_OFF + 4, 0); /* TimeDateStamp */ 219 wr_u16(sec + EXP_DIR_OFF + 8, 0); /* MajorVersion */ 220 wr_u16(sec + EXP_DIR_OFF + 10, 0); /* MinorVersion */ 221 wr_u32(sec + EXP_DIR_OFF + 12, SEC_VA + DLLNAME_OFF); /* Name */ 222 wr_u32(sec + EXP_DIR_OFF + 16, 1); /* Base */ 223 wr_u32(sec + EXP_DIR_OFF + 20, EAT_COUNT); /* NumberOfFunctions */ 224 wr_u32(sec + EXP_DIR_OFF + 24, ENT_COUNT); /* NumberOfNames */ 225 wr_u32(sec + EXP_DIR_OFF + 28, SEC_VA + EAT_OFF); /* AddressOfFunctions */ 226 wr_u32(sec + EXP_DIR_OFF + 32, SEC_VA + ENT_OFF); /* AddressOfNames */ 227 wr_u32(sec + EXP_DIR_OFF + 36, SEC_VA + ORD_OFF); /* AddressOfNameOrds */ 228 229 /* EAT: index 0 = direct (outside export-dir range); 230 * index 1 = forwarder (inside export-dir range, pointing at 231 * the OTHERDLL.OtherSym string). */ 232 wr_u32(sec + EAT_OFF + 0u, DIRECT_FN_RVA); 233 wr_u32(sec + EAT_OFF + 4u, SEC_VA + FORWARD_TGT_OFF); 234 235 /* ENT: RVAs of the two name strings, in alphabetical-ish order. 236 * The reader walks ENT[i] -> Ord[i] -> EAT[Ord[i]]. */ 237 wr_u32(sec + ENT_OFF + 0u, SEC_VA + DIRECT_NAME_OFF); 238 wr_u32(sec + ENT_OFF + 4u, SEC_VA + FORWARDED_NAME_OFF); 239 240 /* Ordinal table: index into the EAT. */ 241 wr_u16(sec + ORD_OFF + 0u, 0); 242 wr_u16(sec + ORD_OFF + 2u, 1); 243 244 /* Strings. */ 245 memcpy(sec + DLLNAME_OFF, kDllName, sizeof kDllName); 246 memcpy(sec + DIRECT_NAME_OFF, kDirect, sizeof kDirect); 247 memcpy(sec + FORWARDED_NAME_OFF, kForwarded, sizeof kForwarded); 248 memcpy(sec + FORWARD_TGT_OFF, kForwardTarget, sizeof kForwardTarget); 249 } 250 251 /* ---- main --------------------------------------------------------- */ 252 253 static int has_sym(const ObjBuilder* ob, Pool* p, const char* name) { 254 Sym needle = pool_intern_slice(p, slice_from_cstr(name)); 255 ObjSymIter* it = obj_symiter_new(ob); 256 ObjSymEntry e; 257 int found = 0; 258 while (obj_symiter_next(it, &e)) { 259 if (e.sym && !e.sym->removed && e.sym->name == needle && 260 e.sym->section_id == OBJ_SEC_NONE && e.sym->bind == SB_GLOBAL) { 261 found = 1; 262 break; 263 } 264 } 265 obj_symiter_free(it); 266 return found; 267 } 268 269 int main(void) { 270 KitTargetSpec t; 271 target_x64_windows(&t); 272 Compiler* c = make_compiler(&t); 273 if (!c) { 274 fprintf(stderr, "FAIL: compiler_new\n"); 275 return 1; 276 } 277 if (setjmp(c->panic)) { 278 fprintf(stderr, "FAIL: panic during pe-dso-forwarder\n"); 279 compiler_run_cleanups(c); 280 free_compiler(c); 281 return 1; 282 } 283 284 uint8_t* buf = (uint8_t*)malloc(FILE_SIZE); 285 EXPECT(buf != NULL, "malloc PE buffer"); 286 if (!buf) { 287 free_compiler(c); 288 return 1; 289 } 290 build_dso(buf); 291 292 Sym soname = 0; 293 ObjBuilder* ob = read_coff_dso(c, "TestDll.dll", buf, FILE_SIZE, &soname); 294 EXPECT(ob != NULL, "read_coff_dso returned NULL"); 295 296 /* soname propagated from the Export Directory's Name field. */ 297 Sym expected_soname = pool_intern_slice(c->global, slice_from_cstr(kDllName)); 298 EXPECT(soname == expected_soname, 299 "soname mismatch (expected interned \"%s\")", kDllName); 300 301 /* Both exports must surface — direct and forwarder treated the same 302 * way by read_coff_dso (the OS loader chases the forwarder chain at 303 * runtime; the linker just needs the name defined). */ 304 EXPECT(has_sym(ob, c->global, kDirect), 305 "direct export \"%s\" missing from ObjBuilder", kDirect); 306 EXPECT(has_sym(ob, c->global, kForwarded), 307 "forwarded export \"%s\" missing from ObjBuilder", kForwarded); 308 309 free(buf); 310 free_compiler(c); 311 312 if (g_failures) { 313 fprintf(stderr, "FAILED %d assertion(s)\n", g_failures); 314 return 1; 315 } 316 fprintf(stderr, "OK pe-dso-forwarder\n"); 317 return 0; 318 }