pe-mixed-archive.c (12501B)
1 /* Mixed-member archive ingestion test. 2 * 3 * Verifies that a single archive containing BOTH a short-import member 4 * and a long-form COFF object with a real defined symbol satisfies 5 * references from both shapes in one pass — the same composition 6 * llvm-mingw's libucrt.a uses (api-ms-win-crt-*.dll short imports 7 * alongside lib64_libucrt_extra_a-*.o helpers). 8 * 9 * Composition: 10 * Member A: short-import record (Sig1=0/Sig2=0xFFFF) for `ImportedFn` 11 * living in `FOO.dll`. 12 * Member B: a normal COFF object (emit_coff'd from a tiny ObjBuilder) 13 * defining `g_helper_value` in `.data`. 14 * 15 * The program references both via R_PC32 from .text. After link_resolve 16 * we assert: 17 * - ImportedFn surfaces as imported (dso_input_id != 0). 18 * - g_helper_value resolves to a real defined LinkSym. 19 * No external tools required. */ 20 21 #include <kit/archive.h> 22 #include <kit/core.h> 23 #include <kit/link.h> 24 #include <kit/object.h> 25 #include <setjmp.h> 26 #include <stdarg.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 31 #include "core/core.h" 32 #include "core/pool.h" 33 #include "link/link.h" 34 #include "obj/obj.h" 35 36 /* ---- short-import wire constants (mirror pe-import-smoke.c). ---- */ 37 #define SHIM_HEADER_SIZE 20u 38 #define SHIM_SYM_CSTR "ImportedFn" 39 #define SHIM_DLL_CSTR "FOO.dll" 40 #define SHIM_SYM_NUL_LEN 11u /* "ImportedFn\0" */ 41 #define SHIM_DLL_NUL_LEN 8u /* "FOO.dll\0" */ 42 #define SHIM_DATA_LEN (SHIM_SYM_NUL_LEN + SHIM_DLL_NUL_LEN) /* 19 */ 43 #define SHIM_TOTAL_LEN (SHIM_HEADER_SIZE + SHIM_DATA_LEN) /* 39 */ 44 #define COFF_MACHINE_AMD64 0x8664u 45 #define COFF_SHIMP_SIG2 0xFFFFu 46 #define COFF_SHIMP_TYPEFLAGS 0x0004u /* Type=CODE | NameType=NAME */ 47 48 #define HELPER_SYM_CSTR "g_helper_value" 49 50 /* Program text: two `call disp32` instructions plus `ret`. Each call's 51 * disp32 is patched by the linker via R_PC32 against an undef target. */ 52 static const uint8_t PROG_TEXT_X64[11] = { 53 0xe8, 0, 0, 54 0, 0, /* call ImportedFn */ 55 0xe8, 0, 0, 56 0, 0, /* call g_helper_value (target treated as PC-rel 57 reference; data symbols can be referenced the 58 same way for the purposes of this test — the 59 linker just resolves the symbol address) */ 60 0xc3, /* ret */ 61 }; 62 63 /* ---- env vtables --------------------------------------------------- */ 64 65 static void* heap_alloc(KitHeap* h, size_t n, size_t a) { 66 (void)h; 67 (void)a; 68 return n ? malloc(n) : NULL; 69 } 70 static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) { 71 (void)h; 72 (void)o; 73 (void)a; 74 return realloc(p, n); 75 } 76 static void heap_free(KitHeap* h, void* p, size_t n) { 77 (void)h; 78 (void)n; 79 free(p); 80 } 81 static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL}; 82 83 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, 84 const char* fmt, va_list ap) { 85 static const char* names[] = {"note", "warning", "error", "fatal"}; 86 (void)s; 87 (void)loc; 88 fprintf(stderr, "%s: ", names[k]); 89 vfprintf(stderr, fmt, ap); 90 fputc('\n', stderr); 91 } 92 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0}; 93 94 static int g_failures; 95 #define EXPECT(cond, ...) \ 96 do { \ 97 if (!(cond)) { \ 98 fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ 99 fprintf(stderr, __VA_ARGS__); \ 100 fputc('\n', stderr); \ 101 g_failures++; \ 102 } \ 103 } while (0) 104 105 /* ---- compiler / target -------------------------------------------- */ 106 107 static KitContext g_ctx; 108 109 static void target_x64_windows(KitTargetSpec* t) { 110 memset(t, 0, sizeof *t); 111 t->arch = KIT_ARCH_X86_64; 112 t->os = KIT_OS_WINDOWS; 113 t->obj = KIT_OBJ_COFF; 114 t->ptr_size = 8; 115 t->ptr_align = 8; 116 t->big_endian = false; 117 t->pic = KIT_PIC_PIE; 118 t->code_model = KIT_CM_SMALL; 119 } 120 121 static Compiler* make_compiler(const KitTargetSpec* t) { 122 memset(&g_ctx, 0, sizeof g_ctx); 123 g_ctx.heap = &g_heap; 124 g_ctx.diag = &g_diag; 125 g_ctx.now = -1; 126 KitTargetOptions opts; 127 memset(&opts, 0, sizeof opts); 128 opts.spec = *t; 129 KitTarget* target = NULL; 130 if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL; 131 KitCompiler* cc = NULL; 132 if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) { 133 kit_target_free(target); 134 return NULL; 135 } 136 return (Compiler*)cc; 137 } 138 139 static void free_compiler(Compiler* c) { 140 if (!c) return; 141 const KitTarget* target = kit_compiler_target((KitCompiler*)c); 142 kit_compiler_free((KitCompiler*)c); 143 kit_target_free((KitTarget*)target); 144 } 145 146 /* ---- builders ----------------------------------------------------- */ 147 148 static void build_short_import_amd64(uint8_t buf[SHIM_TOTAL_LEN]) { 149 memset(buf, 0, SHIM_TOTAL_LEN); 150 /* Sig1=0, Sig2=0xFFFF. */ 151 buf[2] = (uint8_t)(COFF_SHIMP_SIG2 & 0xFF); 152 buf[3] = (uint8_t)((COFF_SHIMP_SIG2 >> 8) & 0xFF); 153 /* Machine. */ 154 buf[6] = (uint8_t)(COFF_MACHINE_AMD64 & 0xFF); 155 buf[7] = (uint8_t)((COFF_MACHINE_AMD64 >> 8) & 0xFF); 156 /* SizeOfData. */ 157 buf[12] = (uint8_t)(SHIM_DATA_LEN & 0xFFu); 158 buf[13] = (uint8_t)((SHIM_DATA_LEN >> 8) & 0xFFu); 159 /* TypeFlags = CODE | NAME. */ 160 buf[18] = (uint8_t)(COFF_SHIMP_TYPEFLAGS & 0xFF); 161 buf[19] = (uint8_t)((COFF_SHIMP_TYPEFLAGS >> 8) & 0xFF); 162 memcpy(buf + SHIM_HEADER_SIZE, SHIM_SYM_CSTR, SHIM_SYM_NUL_LEN); 163 memcpy(buf + SHIM_HEADER_SIZE + SHIM_SYM_NUL_LEN, SHIM_DLL_CSTR, 164 SHIM_DLL_NUL_LEN); 165 } 166 167 /* Build a long-form COFF object that defines `g_helper_value` in .data. */ 168 static uint8_t* build_helper_object(Compiler* c, size_t* len_out) { 169 ObjBuilder* ob = obj_new(c); 170 Pool* p = c->global; 171 Sym data_name = pool_intern_slice(p, SLICE_LIT(".data")); 172 Sym helper_name = pool_intern_slice(p, SLICE_LIT(HELPER_SYM_CSTR)); 173 ObjSecId data = obj_section(ob, data_name, SEC_DATA, SF_ALLOC | SF_WRITE, 4); 174 static const uint8_t kHelperBytes[4] = {0x2A, 0x00, 0x00, 0x00}; 175 obj_write(ob, data, kHelperBytes, sizeof kHelperBytes); 176 obj_symbol(ob, helper_name, SB_GLOBAL, SK_OBJ, data, 0, sizeof kHelperBytes); 177 obj_finalize(ob); 178 179 KitWriter* w = NULL; 180 if (kit_writer_mem(&g_heap, &w) != KIT_OK || !w) return NULL; 181 emit_coff(c, ob, w); 182 size_t n = 0; 183 const uint8_t* src = kit_writer_mem_bytes(w, &n); 184 uint8_t* copy = (uint8_t*)malloc(n); 185 if (copy && n) memcpy(copy, src, n); 186 kit_writer_close(w); 187 *len_out = n; 188 return copy; 189 } 190 191 /* Program: .text references both ImportedFn (function) and 192 * g_helper_value (data) via R_PC32 relocs. */ 193 static ObjBuilder* build_program(Compiler* c) { 194 ObjBuilder* ob = obj_new(c); 195 Pool* p = c->global; 196 Sym text_name = pool_intern_slice(p, SLICE_LIT(".text")); 197 Sym main_name = pool_intern_slice(p, SLICE_LIT("mainCRTStartup")); 198 Sym import_name = pool_intern_slice(p, SLICE_LIT(SHIM_SYM_CSTR)); 199 Sym helper_name = pool_intern_slice(p, SLICE_LIT(HELPER_SYM_CSTR)); 200 ObjSecId text = obj_section(ob, text_name, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 201 obj_write(ob, text, PROG_TEXT_X64, sizeof PROG_TEXT_X64); 202 obj_symbol(ob, main_name, SB_GLOBAL, SK_FUNC, text, 0, sizeof PROG_TEXT_X64); 203 ObjSymId import_sym = 204 obj_symbol(ob, import_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 205 ObjSymId helper_sym = 206 obj_symbol(ob, helper_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 207 obj_reloc(ob, text, 1, R_PC32, import_sym, -4); 208 obj_reloc(ob, text, 6, R_PC32, helper_sym, -4); 209 obj_finalize(ob); 210 return ob; 211 } 212 213 /* ---- main --------------------------------------------------------- */ 214 215 int main(void) { 216 KitTargetSpec t; 217 target_x64_windows(&t); 218 Compiler* c = make_compiler(&t); 219 if (!c) { 220 fprintf(stderr, "FAIL: compiler_new\n"); 221 return 1; 222 } 223 if (setjmp(c->panic)) { 224 fprintf(stderr, "FAIL: panic during pe-mixed-archive\n"); 225 compiler_run_cleanups(c); 226 free_compiler(c); 227 return 1; 228 } 229 230 ObjBuilder* prog = build_program(c); 231 232 /* Member A: short-import shim. */ 233 uint8_t shim[SHIM_TOTAL_LEN]; 234 build_short_import_amd64(shim); 235 236 /* Member B: long-form COFF object defining g_helper_value. */ 237 size_t helper_len = 0; 238 uint8_t* helper_bytes = build_helper_object(c, &helper_len); 239 EXPECT(helper_bytes != NULL && helper_len > 0, 240 "build_helper_object produced %zu bytes", helper_len); 241 if (!helper_bytes) { 242 free_compiler(c); 243 return 1; 244 } 245 246 /* Assemble both into an archive. kit's archive ingestion walks 247 * every member regardless of the symbol index, so symbol_index=0 is 248 * sufficient — the linker rediscovers each member's exports during 249 * scan_presence_before. */ 250 KitArInput members[2]; 251 members[0].name = KIT_SLICE_LIT("importfn.o"); 252 members[0].bytes.data = shim; 253 members[0].bytes.len = SHIM_TOTAL_LEN; 254 members[1].name = KIT_SLICE_LIT("helper.o"); 255 members[1].bytes.data = helper_bytes; 256 members[1].bytes.len = helper_len; 257 258 KitWriter* aw = NULL; 259 if (kit_writer_mem(&g_heap, &aw) != KIT_OK || !aw) { 260 fprintf(stderr, "FAIL: writer_mem for archive\n"); 261 free(helper_bytes); 262 free_compiler(c); 263 return 1; 264 } 265 KitArWriteOptions opts; 266 memset(&opts, 0, sizeof opts); 267 KitStatus arst = kit_ar_write(aw, members, 2, &opts); 268 EXPECT(arst == KIT_OK, "kit_ar_write rc=%d", (int)arst); 269 270 size_t ar_len = 0; 271 const uint8_t* ar_view = kit_writer_mem_bytes(aw, &ar_len); 272 uint8_t* ar_bytes = (uint8_t*)malloc(ar_len); 273 if (ar_bytes && ar_len) memcpy(ar_bytes, ar_view, ar_len); 274 kit_writer_close(aw); 275 EXPECT(ar_bytes != NULL && ar_len > 0, 276 "archive empty after kit_ar_write (len=%zu)", ar_len); 277 if (!ar_bytes) { 278 free(helper_bytes); 279 free_compiler(c); 280 return 1; 281 } 282 283 /* Drive the linker. Name the archive `libmixed.a` so 284 * derive_dll_name_from_archive_path treats it as a potential import 285 * archive (archive_dll_name="mixed.dll") — the short-import member 286 * carries its own DLL name (FOO.dll) and overrides this fallback, and 287 * the long-form COFF object is classified COFF_AR_KEEP and read as a 288 * normal object regardless. */ 289 Linker* l = link_new(c); 290 EXPECT(l != NULL, "link_new returned NULL"); 291 link_add_obj(l, prog); 292 LinkInputId ar_id = link_add_archive_bytes(l, "libmixed.a", ar_bytes, ar_len, 293 /*whole_archive=*/0, 294 /*link_mode=*/0, 295 /*group_id=*/0); 296 EXPECT(ar_id != LINK_INPUT_NONE, 297 "link_add_archive_bytes returned LINK_INPUT_NONE"); 298 link_set_entry(l, KIT_SLICE_LIT("mainCRTStartup")); 299 link_set_pie(l, 1); 300 link_set_emit_static_exe(l, 1); 301 302 LinkImage* img = link_resolve(l); 303 EXPECT(img != NULL, "link_resolve returned NULL"); 304 if (img) { 305 Sym import_name = pool_intern_slice(c->global, SLICE_LIT(SHIM_SYM_CSTR)); 306 Sym helper_name = pool_intern_slice(c->global, SLICE_LIT(HELPER_SYM_CSTR)); 307 const LinkSymbol* importf = NULL; 308 const LinkSymbol* helper = NULL; 309 for (LinkSymId i = 1;; ++i) { 310 const LinkSymbol* s = link_symbol(img, i); 311 if (!s) break; 312 if (s->name == import_name) 313 importf = s; 314 else if (s->name == helper_name) 315 helper = s; 316 } 317 EXPECT(importf != NULL, 318 "ImportedFn LinkSymbol not present after link_resolve"); 319 if (importf) { 320 EXPECT(importf->imported, 321 "ImportedFn.imported=0 (expected 1; short-import member " 322 "should be pulled in and routed as DSO)"); 323 EXPECT(importf->dso_input_id != 0, 324 "ImportedFn.dso_input_id=0 (expected nonzero — short-import " 325 "member should be present as a DSO input)"); 326 } 327 EXPECT(helper != NULL, 328 "g_helper_value LinkSymbol not present after link_resolve"); 329 if (helper) { 330 EXPECT(!helper->imported, 331 "g_helper_value.imported=1 (expected 0; helper.o is a real " 332 "COFF object, not an import shim)"); 333 EXPECT(helper->section_id != LINK_SEC_NONE, 334 "g_helper_value.section_id=NONE (expected a real .data " 335 "section after long-form COFF ingestion)"); 336 } 337 link_image_free(img); 338 } 339 link_free(l); 340 341 free(ar_bytes); 342 free(helper_bytes); 343 free_compiler(c); 344 345 if (g_failures) { 346 fprintf(stderr, "FAILED %d assertion(s)\n", g_failures); 347 return 1; 348 } 349 fprintf(stderr, "OK pe-mixed-archive\n"); 350 return 0; 351 }