kit-roundtrip-coff.c (47228B)
1 /* PE/COFF round-trip harness — peer of test/elf/unit/smoke.c and 2 * the Mach-O unit tests. Each test_*() builds an ObjBuilder, emits via 3 * emit_coff into a memory writer, reads back via read_coff, and 4 * asserts: 5 * 6 * 1. Structural equivalence — sections by name, symbols by name, 7 * relocations by (section, offset, kind, target-sym-name). 8 * Section-symbol synthesis is honored as a known asymmetry 9 * (see test/coff/CORPUS.md §10). 10 * 2. Byte stability — re-emitting the readback ObjBuilder produces 11 * the same bytes as the first emit (memcmp). 12 * 13 * Mixes public (<kit/core.h>, <kit/object.h>) and internal 14 * (src/obj/obj.h, src/core/core.h) surfaces. Compiled with -Isrc 15 * by mk/test.mk. Not a libkit consumer in the usual sense — a 16 * test binary that pokes the same private headers the writer / 17 * reader use. */ 18 19 #include <kit/core.h> 20 #include <kit/object.h> 21 #include <setjmp.h> 22 #include <stdarg.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #include "core/core.h" 28 #include "core/pool.h" 29 #include "obj/obj.h" 30 31 /* ---- env vtables --------------------------------------------------- */ 32 33 static void* heap_alloc(KitHeap* h, size_t n, size_t a) { 34 (void)h; 35 (void)a; 36 return n ? malloc(n) : NULL; 37 } 38 static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) { 39 (void)h; 40 (void)o; 41 (void)a; 42 return realloc(p, n); 43 } 44 static void heap_free(KitHeap* h, void* p, size_t n) { 45 (void)h; 46 (void)n; 47 free(p); 48 } 49 static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL}; 50 51 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, 52 const char* fmt, va_list ap) { 53 static const char* names[] = {"note", "warning", "error", "fatal"}; 54 (void)s; 55 (void)loc; 56 fprintf(stderr, "%s: ", names[k]); 57 vfprintf(stderr, fmt, ap); 58 fputc('\n', stderr); 59 } 60 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0}; 61 62 /* ---- failure tracking --------------------------------------------- */ 63 64 static int g_failures; 65 static const char* g_test_name = "?"; 66 #define EXPECT(cond, ...) \ 67 do { \ 68 if (!(cond)) { \ 69 fprintf(stderr, "FAIL [%s] %s:%d: ", g_test_name, __FILE__, __LINE__); \ 70 fprintf(stderr, __VA_ARGS__); \ 71 fputc('\n', stderr); \ 72 g_failures++; \ 73 } \ 74 } while (0) 75 76 /* ---- target builders ---------------------------------------------- */ 77 78 static void target_x64_windows(KitTargetSpec* t) { 79 memset(t, 0, sizeof *t); 80 t->arch = KIT_ARCH_X86_64; 81 t->os = KIT_OS_WINDOWS; 82 t->obj = KIT_OBJ_COFF; 83 t->ptr_size = 8; 84 t->ptr_align = 8; 85 t->big_endian = false; 86 t->pic = KIT_PIC_PIE; 87 t->code_model = KIT_CM_SMALL; 88 } 89 90 static void target_aa64_windows(KitTargetSpec* t) { 91 memset(t, 0, sizeof *t); 92 t->arch = KIT_ARCH_ARM_64; 93 t->os = KIT_OS_WINDOWS; 94 t->obj = KIT_OBJ_COFF; 95 t->ptr_size = 8; 96 t->ptr_align = 8; 97 t->big_endian = false; 98 t->pic = KIT_PIC_PIE; 99 t->code_model = KIT_CM_SMALL; 100 } 101 102 /* ---- shape helpers ------------------------------------------------- */ 103 104 static int sym_eq_str(Pool* p, Sym s, const char* want) { 105 Slice got = pool_slice(p, s); 106 size_t wlen = strlen(want); 107 return got.s && got.len == wlen && memcmp(got.s, want, got.len) == 0; 108 } 109 110 static const Section* find_section_named(const ObjBuilder* ob, Pool* p, 111 const char* want) { 112 u32 n = obj_section_count(ob); 113 for (u32 i = 1; i < n; ++i) { 114 const Section* s = obj_section_get(ob, i); 115 if (s->removed) continue; 116 if (sym_eq_str(p, s->name, want)) return s; 117 } 118 return NULL; 119 } 120 121 static ObjSecId find_section_id(const ObjBuilder* ob, Pool* p, 122 const char* want) { 123 u32 n = obj_section_count(ob); 124 for (u32 i = 1; i < n; ++i) { 125 const Section* s = obj_section_get(ob, i); 126 if (s->removed) continue; 127 if (sym_eq_str(p, s->name, want)) return i; 128 } 129 return OBJ_SEC_NONE; 130 } 131 132 static ObjSymId find_sym_named(const ObjBuilder* ob, Pool* p, 133 const char* want) { 134 ObjSymIter* it = obj_symiter_new(ob); 135 ObjSymEntry e; 136 ObjSymId found = OBJ_SYM_NONE; 137 while (obj_symiter_next(it, &e)) { 138 if (e.sym->removed) continue; 139 if (sym_eq_str(p, e.sym->name, want)) { 140 found = e.id; 141 break; 142 } 143 } 144 obj_symiter_free(it); 145 return found; 146 } 147 148 /* ---- emit / read driver ------------------------------------------- */ 149 150 /* Emit ob into a fresh malloc()ed buffer. Caller frees *out_buf. 151 * Returns 0 on success, non-zero on failure. */ 152 static int emit_to_buf(Compiler* c, ObjBuilder* ob, uint8_t** out_buf, 153 size_t* out_len) { 154 KitWriter* w = NULL; 155 if (kit_writer_mem(&g_heap, &w) != KIT_OK || !w) return -1; 156 emit_coff(c, ob, w); 157 size_t n = 0; 158 const uint8_t* data = kit_writer_mem_bytes(w, &n); 159 uint8_t* buf = (uint8_t*)malloc(n ? n : 1); 160 if (!buf) { 161 kit_writer_close(w); 162 return -1; 163 } 164 if (n) memcpy(buf, data, n); 165 kit_writer_close(w); 166 *out_buf = buf; 167 *out_len = n; 168 return 0; 169 } 170 171 /* Debug helper: dump bytes side-by-side to stderr. */ 172 static void dump_diff(const uint8_t* a, const uint8_t* b, size_t n) { 173 for (size_t i = 0; i < n; i += 16) { 174 fprintf(stderr, "%04zx ", i); 175 for (size_t j = 0; j < 16 && i + j < n; ++j) { 176 fprintf(stderr, "%02x%c", a[i + j], 177 (i + j < n && a[i + j] != b[i + j]) ? '*' : ' '); 178 } 179 fprintf(stderr, " | "); 180 for (size_t j = 0; j < 16 && i + j < n; ++j) { 181 fprintf(stderr, "%02x%c", b[i + j], 182 (i + j < n && a[i + j] != b[i + j]) ? '*' : ' '); 183 } 184 fprintf(stderr, "\n"); 185 } 186 } 187 188 /* Three-stage round-trip workflow: 189 * 190 * emit_coff(in) -> b1 191 * read_coff(b1) -> mid 192 * verify(mid) 193 * emit_coff(mid) -> b2 194 * EXPECT(b1 == b2) [skipped when expect_byte_stable == 0] 195 * 196 * `verify_fn` runs against the readback ObjBuilder (mid). */ 197 static void run_roundtrip_ex(Compiler* c, ObjBuilder* in, 198 void (*verify_fn)(const ObjBuilder*, Pool*), 199 int expect_byte_stable) { 200 uint8_t* b1 = NULL; 201 size_t n1 = 0; 202 if (emit_to_buf(c, in, &b1, &n1) != 0) { 203 EXPECT(0, "emit_to_buf #1 failed"); 204 return; 205 } 206 /* Header sanity: little-endian machine + nsections must be present. */ 207 EXPECT(n1 >= 20, "emit_coff #1 produced %zu bytes (< 20)", n1); 208 209 ObjBuilder* mid = read_coff(c, "roundtrip", b1, n1); 210 EXPECT(mid != NULL, "read_coff returned NULL"); 211 if (!mid) { 212 free(b1); 213 return; 214 } 215 216 if (verify_fn) verify_fn(mid, c->global); 217 218 uint8_t* b2 = NULL; 219 size_t n2 = 0; 220 if (emit_to_buf(c, mid, &b2, &n2) != 0) { 221 EXPECT(0, "emit_to_buf #2 failed"); 222 obj_free(mid); 223 free(b1); 224 return; 225 } 226 227 if (expect_byte_stable) { 228 EXPECT(n1 == n2, "byte-stable round-trip size mismatch: %zu vs %zu", n1, 229 n2); 230 if (n1 == n2) { 231 int differs = memcmp(b1, b2, n1) != 0; 232 EXPECT(!differs, "byte-stable round-trip differs (size %zu)", n1); 233 if (differs && getenv("KIT_COFF_DUMP_DIFF")) { 234 fprintf(stderr, "--- b1 | b2 ---\n"); 235 dump_diff(b1, b2, n1); 236 } 237 } 238 } 239 240 obj_free(mid); 241 free(b1); 242 free(b2); 243 } 244 245 static void run_roundtrip(Compiler* c, ObjBuilder* in, 246 void (*verify_fn)(const ObjBuilder*, Pool*)) { 247 run_roundtrip_ex(c, in, verify_fn, /*expect_byte_stable=*/1); 248 } 249 250 /* ---- compiler lifecycle ---------------------------------------------- 251 * KitContext must outlive the Compiler — compiler_init stashes the 252 * pointer. Use a file-scope context so make_compiler doesn't leave 253 * the compiler with a dangling ctx. */ 254 255 static KitContext g_ctx; 256 257 static Compiler* make_compiler(const KitTargetSpec* t) { 258 memset(&g_ctx, 0, sizeof g_ctx); 259 g_ctx.heap = &g_heap; 260 g_ctx.diag = &g_diag; 261 g_ctx.now = -1; 262 KitTargetOptions opts; 263 memset(&opts, 0, sizeof opts); 264 opts.spec = *t; 265 KitTarget* target = NULL; 266 if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL; 267 KitCompiler* cc = NULL; 268 if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) { 269 kit_target_free(target); 270 return NULL; 271 } 272 return (Compiler*)cc; 273 } 274 275 static void free_compiler(Compiler* c) { 276 if (!c) return; 277 const KitTarget* target = kit_compiler_target((KitCompiler*)c); 278 kit_compiler_free((KitCompiler*)c); 279 kit_target_free((KitTarget*)target); 280 } 281 282 /* ---- payload bytes ------------------------------------------------- */ 283 284 /* x64: mov eax, 42 ; ret. */ 285 static const uint8_t TEXT_X64[6] = { 286 0xb8, 0x2a, 0x00, 0x00, 0x00, 0xc3, 287 }; 288 289 /* aa64: mov w0, #42 ; ret. */ 290 static const uint8_t TEXT_AA64[8] = { 291 0x40, 0x05, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6, 292 }; 293 294 /* ---- per-test verifiers / builders -------------------------------- */ 295 296 /* test_header_minimal_x64 / _aa64: a single .text section, no 297 * relocations, no user symbols. Exercises the file/section-header 298 * encoder + the section-symbol synthesis path. */ 299 300 static void verify_header_minimal(const ObjBuilder* ob, Pool* p) { 301 const Section* text = find_section_named(ob, p, ".text"); 302 EXPECT(text != NULL, ".text not present"); 303 if (text) { 304 EXPECT(text->kind == SEC_TEXT, ".text kind=%u", text->kind); 305 EXPECT((text->flags & SF_EXEC) != 0, ".text missing SF_EXEC"); 306 EXPECT((text->flags & SF_ALLOC) != 0, ".text missing SF_ALLOC"); 307 } 308 } 309 310 static void test_header_minimal_x64(void) { 311 g_test_name = "header_minimal_x64"; 312 KitTargetSpec t; 313 target_x64_windows(&t); 314 Compiler* c = make_compiler(&t); 315 EXPECT(c != NULL, "compiler_new"); 316 if (!c) return; 317 318 if (setjmp(c->panic)) { 319 compiler_run_cleanups(c); 320 free_compiler(c); 321 EXPECT(0, "panic during test"); 322 return; 323 } 324 ObjBuilder* ob = obj_new(c); 325 Pool* p = c->global; 326 Sym text = pool_intern_slice(p, SLICE_LIT(".text")); 327 ObjSecId sec = obj_section(ob, text, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 328 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 329 obj_finalize(ob); 330 331 run_roundtrip(c, ob, verify_header_minimal); 332 333 obj_free(ob); 334 free_compiler(c); 335 } 336 337 static void test_header_minimal_aa64(void) { 338 g_test_name = "header_minimal_aa64"; 339 KitTargetSpec t; 340 target_aa64_windows(&t); 341 Compiler* c = make_compiler(&t); 342 EXPECT(c != NULL, "compiler_new"); 343 if (!c) return; 344 345 if (setjmp(c->panic)) { 346 compiler_run_cleanups(c); 347 free_compiler(c); 348 EXPECT(0, "panic during test"); 349 return; 350 } 351 ObjBuilder* ob = obj_new(c); 352 Pool* p = c->global; 353 Sym text = pool_intern_slice(p, SLICE_LIT(".text")); 354 ObjSecId sec = obj_section(ob, text, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); 355 obj_write(ob, sec, TEXT_AA64, sizeof TEXT_AA64); 356 obj_finalize(ob); 357 358 run_roundtrip(c, ob, verify_header_minimal); 359 360 obj_free(ob); 361 free_compiler(c); 362 } 363 364 /* test_text_only_x64: .text + one defined global function symbol. */ 365 366 static void verify_text_only(const ObjBuilder* ob, Pool* p) { 367 const Section* text = find_section_named(ob, p, ".text"); 368 EXPECT(text != NULL, ".text not present"); 369 ObjSymId main = find_sym_named(ob, p, "main"); 370 EXPECT(main != OBJ_SYM_NONE, "missing 'main' symbol"); 371 if (main) { 372 const ObjSym* s = obj_symbol_get(ob, main); 373 EXPECT(s->bind == SB_GLOBAL, "main bind=%u", s->bind); 374 EXPECT(s->kind == SK_FUNC, "main kind=%u", s->kind); 375 EXPECT(s->section_id != OBJ_SEC_NONE, "main has no section"); 376 } 377 } 378 379 static void test_text_only_x64(void) { 380 g_test_name = "text_only_x64"; 381 KitTargetSpec t; 382 target_x64_windows(&t); 383 Compiler* c = make_compiler(&t); 384 if (!c) { 385 EXPECT(0, "compiler_new"); 386 return; 387 } 388 if (setjmp(c->panic)) { 389 compiler_run_cleanups(c); 390 free_compiler(c); 391 EXPECT(0, "panic"); 392 return; 393 } 394 ObjBuilder* ob = obj_new(c); 395 Pool* p = c->global; 396 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 397 Sym mn = pool_intern_slice(p, SLICE_LIT("main")); 398 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 399 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 400 obj_symbol(ob, mn, SB_GLOBAL, SK_FUNC, sec, 0, sizeof TEXT_X64); 401 obj_finalize(ob); 402 403 run_roundtrip(c, ob, verify_text_only); 404 405 obj_free(ob); 406 free_compiler(c); 407 } 408 409 static void test_text_only_aa64(void) { 410 g_test_name = "text_only_aa64"; 411 KitTargetSpec t; 412 target_aa64_windows(&t); 413 Compiler* c = make_compiler(&t); 414 if (!c) { 415 EXPECT(0, "compiler_new"); 416 return; 417 } 418 if (setjmp(c->panic)) { 419 compiler_run_cleanups(c); 420 free_compiler(c); 421 EXPECT(0, "panic"); 422 return; 423 } 424 ObjBuilder* ob = obj_new(c); 425 Pool* p = c->global; 426 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 427 Sym mn = pool_intern_slice(p, SLICE_LIT("main")); 428 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); 429 obj_write(ob, sec, TEXT_AA64, sizeof TEXT_AA64); 430 obj_symbol(ob, mn, SB_GLOBAL, SK_FUNC, sec, 0, sizeof TEXT_AA64); 431 obj_finalize(ob); 432 433 run_roundtrip(c, ob, verify_text_only); 434 435 obj_free(ob); 436 free_compiler(c); 437 } 438 439 /* test_rodata: .rdata read-only data + a defined object symbol. */ 440 441 static void verify_rodata(const ObjBuilder* ob, Pool* p) { 442 const Section* rd = find_section_named(ob, p, ".rdata"); 443 EXPECT(rd != NULL, ".rdata not present"); 444 if (rd) { 445 EXPECT(rd->kind == SEC_RODATA, ".rdata kind=%u (want %u)", rd->kind, 446 SEC_RODATA); 447 EXPECT((rd->flags & SF_ALLOC) != 0, ".rdata missing SF_ALLOC"); 448 EXPECT((rd->flags & SF_WRITE) == 0, ".rdata wrongly has SF_WRITE"); 449 } 450 ObjSymId k = find_sym_named(ob, p, "kMsg"); 451 EXPECT(k != OBJ_SYM_NONE, "missing 'kMsg' symbol"); 452 } 453 454 static void test_rodata(void) { 455 g_test_name = "rodata"; 456 KitTargetSpec t; 457 target_x64_windows(&t); 458 Compiler* c = make_compiler(&t); 459 if (!c) { 460 EXPECT(0, "compiler_new"); 461 return; 462 } 463 if (setjmp(c->panic)) { 464 compiler_run_cleanups(c); 465 free_compiler(c); 466 EXPECT(0, "panic"); 467 return; 468 } 469 ObjBuilder* ob = obj_new(c); 470 Pool* p = c->global; 471 Sym rdn = pool_intern_slice(p, SLICE_LIT(".rdata")); 472 Sym kn = pool_intern_slice(p, SLICE_LIT("kMsg")); 473 ObjSecId sec = obj_section(ob, rdn, SEC_RODATA, SF_ALLOC, 8); 474 static const uint8_t MSG[12] = "hello world\0"; 475 obj_write(ob, sec, MSG, sizeof MSG); 476 obj_symbol(ob, kn, SB_GLOBAL, SK_OBJ, sec, 0, sizeof MSG); 477 obj_finalize(ob); 478 479 run_roundtrip(c, ob, verify_rodata); 480 481 obj_free(ob); 482 free_compiler(c); 483 } 484 485 /* test_bss: .bss section (NOBITS), one defined symbol, size > 0. */ 486 487 static void verify_bss(const ObjBuilder* ob, Pool* p) { 488 const Section* bss = find_section_named(ob, p, ".bss"); 489 EXPECT(bss != NULL, ".bss not present"); 490 if (bss) { 491 EXPECT(bss->kind == SEC_BSS, ".bss kind=%u (want %u)", bss->kind, SEC_BSS); 492 EXPECT(bss->bss_size >= 64, ".bss size=%u (want >= 64)", bss->bss_size); 493 } 494 } 495 496 static void test_bss(void) { 497 g_test_name = "bss"; 498 KitTargetSpec t; 499 target_x64_windows(&t); 500 Compiler* c = make_compiler(&t); 501 if (!c) { 502 EXPECT(0, "compiler_new"); 503 return; 504 } 505 if (setjmp(c->panic)) { 506 compiler_run_cleanups(c); 507 free_compiler(c); 508 EXPECT(0, "panic"); 509 return; 510 } 511 ObjBuilder* ob = obj_new(c); 512 Pool* p = c->global; 513 Sym bn = pool_intern_slice(p, SLICE_LIT(".bss")); 514 Sym vn = pool_intern_slice(p, SLICE_LIT("g_buf")); 515 ObjSecId sec = obj_section_ex(ob, bn, SEC_BSS, SSEM_NOBITS, 516 SF_ALLOC | SF_WRITE, 16, 0, 0, 0); 517 obj_reserve_bss(ob, sec, 64, 16); 518 obj_symbol(ob, vn, SB_GLOBAL, SK_OBJ, sec, 0, 64); 519 obj_finalize(ob); 520 521 run_roundtrip(c, ob, verify_bss); 522 523 obj_free(ob); 524 free_compiler(c); 525 } 526 527 /* test_data_with_reloc_abs64_x64: .data with an 8-byte slot 528 * relocated R_ABS64 against an undefined external. */ 529 530 static void verify_data_abs64(const ObjBuilder* ob, Pool* p) { 531 ObjSecId data_id = find_section_id(ob, p, ".data"); 532 EXPECT(data_id != OBJ_SEC_NONE, ".data id"); 533 ObjSymId foo = find_sym_named(ob, p, "foo_extern"); 534 EXPECT(foo != OBJ_SYM_NONE, "missing 'foo_extern'"); 535 if (foo) { 536 const ObjSym* s = obj_symbol_get(ob, foo); 537 EXPECT(s->section_id == OBJ_SEC_NONE, "foo_extern not undef"); 538 } 539 if (data_id == OBJ_SEC_NONE) return; 540 u32 nr = obj_reloc_count(ob, data_id); 541 EXPECT(nr == 1, ".data reloc count=%u (want 1)", nr); 542 u32 total = obj_reloc_total(ob); 543 const Reloc* found = NULL; 544 for (u32 i = 0; i < total; ++i) { 545 const Reloc* r = obj_reloc_at(ob, i); 546 if (r->removed) continue; 547 if (r->section_id == data_id) { 548 found = r; 549 break; 550 } 551 } 552 EXPECT(found != NULL, "no reloc on .data"); 553 if (found) { 554 EXPECT(found->kind == R_ABS64, ".data reloc kind=%u (want %u)", found->kind, 555 R_ABS64); 556 EXPECT(found->offset == 0, ".data reloc offset=%u", found->offset); 557 } 558 } 559 560 static void test_data_with_reloc_abs64_x64(void) { 561 g_test_name = "data_with_reloc_abs64_x64"; 562 KitTargetSpec t; 563 target_x64_windows(&t); 564 Compiler* c = make_compiler(&t); 565 if (!c) { 566 EXPECT(0, "compiler_new"); 567 return; 568 } 569 if (setjmp(c->panic)) { 570 compiler_run_cleanups(c); 571 free_compiler(c); 572 EXPECT(0, "panic"); 573 return; 574 } 575 ObjBuilder* ob = obj_new(c); 576 Pool* p = c->global; 577 Sym dn = pool_intern_slice(p, SLICE_LIT(".data")); 578 Sym fn = pool_intern_slice(p, SLICE_LIT("foo_extern")); 579 ObjSecId sec = obj_section(ob, dn, SEC_DATA, SF_ALLOC | SF_WRITE, 8); 580 static const uint8_t zero8[8] = {0}; 581 obj_write(ob, sec, zero8, sizeof zero8); 582 ObjSymId foo = obj_symbol(ob, fn, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 583 obj_reloc(ob, sec, 0, R_ABS64, foo, 0); 584 obj_finalize(ob); 585 586 run_roundtrip(c, ob, verify_data_abs64); 587 588 obj_free(ob); 589 free_compiler(c); 590 } 591 592 static void test_data_with_reloc_abs64_aa64(void) { 593 g_test_name = "data_with_reloc_abs64_aa64"; 594 KitTargetSpec t; 595 target_aa64_windows(&t); 596 Compiler* c = make_compiler(&t); 597 if (!c) { 598 EXPECT(0, "compiler_new"); 599 return; 600 } 601 if (setjmp(c->panic)) { 602 compiler_run_cleanups(c); 603 free_compiler(c); 604 EXPECT(0, "panic"); 605 return; 606 } 607 ObjBuilder* ob = obj_new(c); 608 Pool* p = c->global; 609 Sym dn = pool_intern_slice(p, SLICE_LIT(".data")); 610 Sym fn = pool_intern_slice(p, SLICE_LIT("foo_extern")); 611 ObjSecId sec = obj_section(ob, dn, SEC_DATA, SF_ALLOC | SF_WRITE, 8); 612 static const uint8_t zero8[8] = {0}; 613 obj_write(ob, sec, zero8, sizeof zero8); 614 ObjSymId foo = obj_symbol(ob, fn, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 615 obj_reloc(ob, sec, 0, R_ABS64, foo, 0); 616 obj_finalize(ob); 617 618 run_roundtrip(c, ob, verify_data_abs64); 619 620 obj_free(ob); 621 free_compiler(c); 622 } 623 624 /* test_data_with_reloc_rel32_x64: .text with a REL32 relocation 625 * referencing an external symbol (call thunk). */ 626 627 static void verify_rel32(const ObjBuilder* ob, Pool* p) { 628 ObjSecId text_id = find_section_id(ob, p, ".text"); 629 EXPECT(text_id != OBJ_SEC_NONE, ".text id"); 630 ObjSymId helper = find_sym_named(ob, p, "helper"); 631 EXPECT(helper != OBJ_SYM_NONE, "missing 'helper'"); 632 if (text_id == OBJ_SEC_NONE) return; 633 u32 nr = obj_reloc_count(ob, text_id); 634 EXPECT(nr == 1, ".text reloc count=%u (want 1)", nr); 635 u32 total = obj_reloc_total(ob); 636 for (u32 i = 0; i < total; ++i) { 637 const Reloc* r = obj_reloc_at(ob, i); 638 if (r->removed) continue; 639 if (r->section_id != text_id) continue; 640 EXPECT(r->kind == R_PC32, "reloc kind=%u (want R_PC32=%u)", r->kind, 641 R_PC32); 642 } 643 } 644 645 static void test_data_with_reloc_rel32_x64(void) { 646 g_test_name = "reloc_rel32_x64"; 647 KitTargetSpec t; 648 target_x64_windows(&t); 649 Compiler* c = make_compiler(&t); 650 if (!c) { 651 EXPECT(0, "compiler_new"); 652 return; 653 } 654 if (setjmp(c->panic)) { 655 compiler_run_cleanups(c); 656 free_compiler(c); 657 EXPECT(0, "panic"); 658 return; 659 } 660 ObjBuilder* ob = obj_new(c); 661 Pool* p = c->global; 662 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 663 Sym hn = pool_intern_slice(p, SLICE_LIT("helper")); 664 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 665 /* call helper ; ret — e8 disp32 c3 (disp filled by reloc). */ 666 static const uint8_t bytes[6] = {0xe8, 0, 0, 0, 0, 0xc3}; 667 obj_write(ob, sec, bytes, sizeof bytes); 668 /* Undef symbol kind: SK_UNDEF — matches what real COFF inputs carry. 669 * SK_FUNC + section_id == 0 emits Type=function but the reader collapses 670 * to SK_UNDEF on readback (no "undef function" kind in kit's model), 671 * which breaks byte stability. See CORPUS.md §10. */ 672 ObjSymId helper = obj_symbol(ob, hn, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 673 obj_reloc(ob, sec, 1, R_PC32, helper, 0); 674 obj_finalize(ob); 675 676 run_roundtrip(c, ob, verify_rel32); 677 678 obj_free(ob); 679 free_compiler(c); 680 } 681 682 static void verify_rel32_inline_addend(const ObjBuilder* ob, Pool* p) { 683 ObjSecId text_id = find_section_id(ob, p, ".text"); 684 EXPECT(text_id != OBJ_SEC_NONE, ".text id"); 685 u32 total = obj_reloc_total(ob); 686 const Reloc* found = NULL; 687 for (u32 i = 0; i < total; ++i) { 688 const Reloc* r = obj_reloc_at(ob, i); 689 if (r->removed) continue; 690 if (r->section_id != text_id) continue; 691 found = r; 692 break; 693 } 694 EXPECT(found != NULL, "no reloc on .text"); 695 if (found) { 696 EXPECT(found->kind == R_PC32, "reloc kind=%u (want R_PC32=%u)", 697 found->kind, R_PC32); 698 EXPECT(found->addend == 0x124, "reloc addend=%lld (want 0x124)", 699 (long long)found->addend); 700 EXPECT(found->has_explicit_addend == 0, "inline addend marked explicit"); 701 } 702 } 703 704 static void test_reloc_rel32_inline_addend_x64(void) { 705 g_test_name = "reloc_rel32_inline_addend_x64"; 706 KitTargetSpec t; 707 target_x64_windows(&t); 708 Compiler* c = make_compiler(&t); 709 if (!c) { 710 EXPECT(0, "compiler_new"); 711 return; 712 } 713 if (setjmp(c->panic)) { 714 compiler_run_cleanups(c); 715 free_compiler(c); 716 EXPECT(0, "panic"); 717 return; 718 } 719 ObjBuilder* ob = obj_new(c); 720 Pool* p = c->global; 721 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 722 Sym hn = pool_intern_slice(p, SLICE_LIT("helper")); 723 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 724 /* call helper+0x128; ret. COFF stores this addend inline. */ 725 static const uint8_t bytes[6] = {0xe8, 0x28, 0x01, 0, 0, 0xc3}; 726 obj_write(ob, sec, bytes, sizeof bytes); 727 ObjSymId helper = obj_symbol(ob, hn, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 728 obj_reloc_ex(ob, sec, 1, R_PC32, helper, 0, 0, 0); 729 obj_finalize(ob); 730 731 run_roundtrip(c, ob, verify_rel32_inline_addend); 732 733 obj_free(ob); 734 free_compiler(c); 735 } 736 737 /* test_aa64_branch26: .text with a BRANCH26 (R_AARCH64_CALL26) 738 * relocation against an external. */ 739 740 static void verify_aa64_branch26(const ObjBuilder* ob, Pool* p) { 741 ObjSecId text_id = find_section_id(ob, p, ".text"); 742 EXPECT(text_id != OBJ_SEC_NONE, ".text id"); 743 u32 total = obj_reloc_total(ob); 744 int seen = 0; 745 for (u32 i = 0; i < total; ++i) { 746 const Reloc* r = obj_reloc_at(ob, i); 747 if (r->removed) continue; 748 if (r->section_id != text_id) continue; 749 EXPECT(r->kind == R_AARCH64_CALL26, "branch26 reloc kind=%u (want %u)", 750 r->kind, R_AARCH64_CALL26); 751 ++seen; 752 } 753 EXPECT(seen == 1, "branch26 reloc count=%d (want 1)", seen); 754 } 755 756 static void test_aa64_branch26(void) { 757 g_test_name = "aa64_branch26"; 758 KitTargetSpec t; 759 target_aa64_windows(&t); 760 Compiler* c = make_compiler(&t); 761 if (!c) { 762 EXPECT(0, "compiler_new"); 763 return; 764 } 765 if (setjmp(c->panic)) { 766 compiler_run_cleanups(c); 767 free_compiler(c); 768 EXPECT(0, "panic"); 769 return; 770 } 771 ObjBuilder* ob = obj_new(c); 772 Pool* p = c->global; 773 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 774 Sym cn = pool_intern_slice(p, SLICE_LIT("callee")); 775 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); 776 /* bl callee ; ret — both 4 bytes; disp filled by reloc. */ 777 static const uint8_t bytes[8] = {0, 0, 0, 0x94, 0xc0, 0x03, 0x5f, 0xd6}; 778 obj_write(ob, sec, bytes, sizeof bytes); 779 /* See reloc_rel32_x64 note on SK_UNDEF for undef symbols. */ 780 ObjSymId callee = obj_symbol(ob, cn, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 781 obj_reloc(ob, sec, 0, R_AARCH64_CALL26, callee, 0); 782 obj_finalize(ob); 783 784 run_roundtrip(c, ob, verify_aa64_branch26); 785 786 obj_free(ob); 787 free_compiler(c); 788 } 789 790 /* test_aa64_pagebase_pageoffset: ADRP + ADD pair against a .rdata 791 * symbol — exercises both PAGEBASE_REL21 and PAGEOFFSET_12A. */ 792 793 static void verify_aa64_adrp_add(const ObjBuilder* ob, Pool* p) { 794 ObjSecId text_id = find_section_id(ob, p, ".text"); 795 EXPECT(text_id != OBJ_SEC_NONE, ".text id"); 796 u32 total = obj_reloc_total(ob); 797 int n_page = 0, n_off = 0; 798 for (u32 i = 0; i < total; ++i) { 799 const Reloc* r = obj_reloc_at(ob, i); 800 if (r->removed) continue; 801 if (r->section_id != text_id) continue; 802 if (r->kind == R_AARCH64_ADR_PREL_PG_HI21) ++n_page; 803 if (r->kind == R_AARCH64_ADD_ABS_LO12_NC) ++n_off; 804 } 805 EXPECT(n_page == 1, "ADRP reloc count=%d (want 1)", n_page); 806 EXPECT(n_off == 1, "ADD lo12 reloc count=%d (want 1)", n_off); 807 } 808 809 static void test_aa64_pagebase_pageoffset(void) { 810 g_test_name = "aa64_pagebase_pageoffset"; 811 KitTargetSpec t; 812 target_aa64_windows(&t); 813 Compiler* c = make_compiler(&t); 814 if (!c) { 815 EXPECT(0, "compiler_new"); 816 return; 817 } 818 if (setjmp(c->panic)) { 819 compiler_run_cleanups(c); 820 free_compiler(c); 821 EXPECT(0, "panic"); 822 return; 823 } 824 ObjBuilder* ob = obj_new(c); 825 Pool* p = c->global; 826 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 827 Sym rdn = pool_intern_slice(p, SLICE_LIT(".rdata")); 828 Sym kn = pool_intern_slice(p, SLICE_LIT("kStr")); 829 ObjSecId tsec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 4); 830 ObjSecId rsec = obj_section(ob, rdn, SEC_RODATA, SF_ALLOC, 8); 831 /* adrp x0, kStr ; add x0, x0, :lo12:kStr ; ret. */ 832 static const uint8_t txt[12] = { 833 0x00, 0, 0, 0x90, 0, 0, 0, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 834 }; 835 obj_write(ob, tsec, txt, sizeof txt); 836 static const uint8_t str[6] = "hello"; 837 obj_write(ob, rsec, str, sizeof str); 838 ObjSymId kStr = obj_symbol(ob, kn, SB_LOCAL, SK_OBJ, rsec, 0, sizeof str); 839 obj_reloc(ob, tsec, 0, R_AARCH64_ADR_PREL_PG_HI21, kStr, 0); 840 obj_reloc(ob, tsec, 4, R_AARCH64_ADD_ABS_LO12_NC, kStr, 0); 841 obj_finalize(ob); 842 843 run_roundtrip(c, ob, verify_aa64_adrp_add); 844 845 obj_free(ob); 846 free_compiler(c); 847 } 848 849 /* test_long_section_name: section whose name exceeds 8 bytes, 850 * triggering the "/N" strtab-spill encoding. */ 851 852 static void verify_long_section_name(const ObjBuilder* ob, Pool* p) { 853 const Section* s = find_section_named(ob, p, ".text$long_name_section"); 854 EXPECT(s != NULL, "long-named section not present"); 855 if (s) EXPECT(s->kind == SEC_TEXT, "long section kind=%u", s->kind); 856 } 857 858 static void test_long_section_name(void) { 859 g_test_name = "long_section_name"; 860 KitTargetSpec t; 861 target_x64_windows(&t); 862 Compiler* c = make_compiler(&t); 863 if (!c) { 864 EXPECT(0, "compiler_new"); 865 return; 866 } 867 if (setjmp(c->panic)) { 868 compiler_run_cleanups(c); 869 free_compiler(c); 870 EXPECT(0, "panic"); 871 return; 872 } 873 ObjBuilder* ob = obj_new(c); 874 Pool* p = c->global; 875 Sym nm = pool_intern_slice(p, SLICE_LIT(".text$long_name_section")); 876 ObjSecId sec = obj_section(ob, nm, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 877 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 878 obj_finalize(ob); 879 880 run_roundtrip(c, ob, verify_long_section_name); 881 882 obj_free(ob); 883 free_compiler(c); 884 } 885 886 /* test_long_symbol_name: symbol whose name exceeds 8 bytes — uses 887 * the LongName (Zeroes=0, Offset) wire form. */ 888 889 static void verify_long_symbol_name(const ObjBuilder* ob, Pool* p) { 890 ObjSymId s = find_sym_named(ob, p, "very_long_symbol_name"); 891 EXPECT(s != OBJ_SYM_NONE, "long-named symbol not present"); 892 if (s) { 893 const ObjSym* sym = obj_symbol_get(ob, s); 894 EXPECT(sym->bind == SB_GLOBAL, "long sym bind=%u", sym->bind); 895 EXPECT(sym->kind == SK_FUNC, "long sym kind=%u", sym->kind); 896 } 897 } 898 899 static void test_long_symbol_name(void) { 900 g_test_name = "long_symbol_name"; 901 KitTargetSpec t; 902 target_x64_windows(&t); 903 Compiler* c = make_compiler(&t); 904 if (!c) { 905 EXPECT(0, "compiler_new"); 906 return; 907 } 908 if (setjmp(c->panic)) { 909 compiler_run_cleanups(c); 910 free_compiler(c); 911 EXPECT(0, "panic"); 912 return; 913 } 914 ObjBuilder* ob = obj_new(c); 915 Pool* p = c->global; 916 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 917 Sym sn = pool_intern_slice(p, SLICE_LIT("very_long_symbol_name")); 918 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 919 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 920 obj_symbol(ob, sn, SB_GLOBAL, SK_FUNC, sec, 0, sizeof TEXT_X64); 921 obj_finalize(ob); 922 923 run_roundtrip(c, ob, verify_long_symbol_name); 924 925 obj_free(ob); 926 free_compiler(c); 927 } 928 929 /* test_weak_global: weak global symbol — IMAGE_SYM_CLASS_WEAK_EXTERNAL 930 * with a weak-extern aux record. */ 931 932 static void verify_weak_global(const ObjBuilder* ob, Pool* p) { 933 ObjSymId s = find_sym_named(ob, p, "weak_sym"); 934 EXPECT(s != OBJ_SYM_NONE, "weak_sym not present"); 935 if (s) { 936 const ObjSym* sym = obj_symbol_get(ob, s); 937 EXPECT(sym->bind == SB_WEAK, "weak_sym bind=%u (want SB_WEAK=%u)", 938 sym->bind, SB_WEAK); 939 } 940 } 941 942 static void test_weak_global(void) { 943 g_test_name = "weak_global"; 944 KitTargetSpec t; 945 target_x64_windows(&t); 946 Compiler* c = make_compiler(&t); 947 if (!c) { 948 EXPECT(0, "compiler_new"); 949 return; 950 } 951 if (setjmp(c->panic)) { 952 compiler_run_cleanups(c); 953 free_compiler(c); 954 EXPECT(0, "panic"); 955 return; 956 } 957 ObjBuilder* ob = obj_new(c); 958 Pool* p = c->global; 959 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 960 Sym wn = pool_intern_slice(p, SLICE_LIT("weak_sym")); 961 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 962 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 963 obj_symbol(ob, wn, SB_WEAK, SK_FUNC, sec, 0, sizeof TEXT_X64); 964 obj_finalize(ob); 965 966 run_roundtrip(c, ob, verify_weak_global); 967 968 obj_free(ob); 969 free_compiler(c); 970 } 971 972 /* test_common_symbol: COFF common — UNDEFINED section number with 973 * Value > 0 holding the size. */ 974 975 static void verify_common_symbol(const ObjBuilder* ob, Pool* p) { 976 ObjSymId s = find_sym_named(ob, p, "common_var"); 977 EXPECT(s != OBJ_SYM_NONE, "common_var not present"); 978 if (s) { 979 const ObjSym* sym = obj_symbol_get(ob, s); 980 EXPECT(sym->kind == SK_COMMON, "common_var kind=%u (want SK_COMMON=%u)", 981 sym->kind, SK_COMMON); 982 EXPECT(sym->size == 128, "common_var size=%llu (want 128)", 983 (unsigned long long)sym->size); 984 } 985 } 986 987 static void test_common_symbol(void) { 988 g_test_name = "common_symbol"; 989 KitTargetSpec t; 990 target_x64_windows(&t); 991 Compiler* c = make_compiler(&t); 992 if (!c) { 993 EXPECT(0, "compiler_new"); 994 return; 995 } 996 if (setjmp(c->panic)) { 997 compiler_run_cleanups(c); 998 free_compiler(c); 999 EXPECT(0, "panic"); 1000 return; 1001 } 1002 ObjBuilder* ob = obj_new(c); 1003 Pool* p = c->global; 1004 Sym cn = pool_intern_slice(p, SLICE_LIT("common_var")); 1005 obj_symbol_ex(ob, cn, SB_GLOBAL, SV_DEFAULT, SK_COMMON, OBJ_SEC_NONE, 0, 128, 1006 1); 1007 obj_finalize(ob); 1008 1009 run_roundtrip(c, ob, verify_common_symbol); 1010 1011 obj_free(ob); 1012 free_compiler(c); 1013 } 1014 1015 /* test_comdat_group: two sections wired into one COMDAT group. */ 1016 1017 static void verify_comdat_group(const ObjBuilder* ob, Pool* p) { 1018 const Section* tsec = find_section_named(ob, p, ".text$x"); 1019 const Section* dsec = find_section_named(ob, p, ".data$x"); 1020 EXPECT(tsec != NULL, ".text$x missing"); 1021 EXPECT(dsec != NULL, ".data$x missing"); 1022 if (tsec) EXPECT((tsec->flags & SF_GROUP) != 0, ".text$x missing SF_GROUP"); 1023 if (dsec) EXPECT((dsec->flags & SF_GROUP) != 0, ".data$x missing SF_GROUP"); 1024 1025 /* COFF encodes COMDAT per-section (each member section carries its 1026 * own section-definition aux with the selection rule); the wire 1027 * format has no SHT_GROUP-style "N-member" record. read_coff 1028 * therefore emits one ObjGroup per COMDAT section — two input 1029 * COMDAT sections => two single-section groups after round-trip. 1030 * Each carries the section's section-symbol as its signature. */ 1031 ObjGroupIter* it = obj_groupiter_new(ob); 1032 ObjGroupEntry e; 1033 int seen = 0; 1034 u32 total_member_sections = 0; 1035 while (obj_groupiter_next(it, &e)) { 1036 if (e.group->removed) continue; 1037 ++seen; 1038 total_member_sections += e.group->nsections; 1039 } 1040 obj_groupiter_free(it); 1041 EXPECT(seen == 2, "expected 2 groups after COMDAT round-trip, got %d", seen); 1042 EXPECT(total_member_sections == 2, "total COMDAT member sections=%u (want 2)", 1043 total_member_sections); 1044 } 1045 1046 static void test_comdat_group(void) { 1047 g_test_name = "comdat_group"; 1048 KitTargetSpec t; 1049 target_x64_windows(&t); 1050 Compiler* c = make_compiler(&t); 1051 if (!c) { 1052 EXPECT(0, "compiler_new"); 1053 return; 1054 } 1055 if (setjmp(c->panic)) { 1056 compiler_run_cleanups(c); 1057 free_compiler(c); 1058 EXPECT(0, "panic"); 1059 return; 1060 } 1061 ObjBuilder* ob = obj_new(c); 1062 Pool* p = c->global; 1063 /* Short section names (<= 8 bytes) — section names that overflow into 1064 * the strtab don't round-trip COMDAT detection because the section 1065 * symbol's name is truncated on emit but the reader compares the 1066 * resolved long name. See CORPUS.md §10 / src/obj/coff/read.c 1067 * is_section_sym logic. */ 1068 Sym tn = pool_intern_slice(p, SLICE_LIT(".text$x")); 1069 Sym dn = pool_intern_slice(p, SLICE_LIT(".data$x")); 1070 Sym sign = pool_intern_slice(p, SLICE_LIT("inline_fn")); 1071 1072 ObjSecId tsec = 1073 obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC | SF_GROUP, 16); 1074 ObjSecId dsec = 1075 obj_section(ob, dn, SEC_DATA, SF_ALLOC | SF_WRITE | SF_GROUP, 8); 1076 obj_write(ob, tsec, TEXT_X64, sizeof TEXT_X64); 1077 static const uint8_t z8[8] = {0}; 1078 obj_write(ob, dsec, z8, sizeof z8); 1079 1080 ObjSymId sig = 1081 obj_symbol(ob, sign, SB_WEAK, SK_FUNC, tsec, 0, sizeof TEXT_X64); 1082 ObjGroupId gid = obj_group(ob, sign, sig, KIT_OBJ_GROUP_COMDAT); 1083 obj_group_add_section(ob, gid, tsec); 1084 obj_group_add_section(ob, gid, dsec); 1085 obj_section_set_group(ob, tsec, gid); 1086 obj_section_set_group(ob, dsec, gid); 1087 1088 obj_finalize(ob); 1089 1090 run_roundtrip(c, ob, verify_comdat_group); 1091 1092 obj_free(ob); 1093 free_compiler(c); 1094 } 1095 1096 /* test_static_local_symbol: STATIC storage class — file-local symbol. */ 1097 1098 static void verify_static_local(const ObjBuilder* ob, Pool* p) { 1099 ObjSymId s = find_sym_named(ob, p, "local_fn"); 1100 EXPECT(s != OBJ_SYM_NONE, "local_fn not present"); 1101 if (s) { 1102 const ObjSym* sym = obj_symbol_get(ob, s); 1103 EXPECT(sym->bind == SB_LOCAL, "local_fn bind=%u (want SB_LOCAL=%u)", 1104 sym->bind, SB_LOCAL); 1105 } 1106 } 1107 1108 static void test_static_local_symbol(void) { 1109 g_test_name = "static_local_symbol"; 1110 KitTargetSpec t; 1111 target_x64_windows(&t); 1112 Compiler* c = make_compiler(&t); 1113 if (!c) { 1114 EXPECT(0, "compiler_new"); 1115 return; 1116 } 1117 if (setjmp(c->panic)) { 1118 compiler_run_cleanups(c); 1119 free_compiler(c); 1120 EXPECT(0, "panic"); 1121 return; 1122 } 1123 ObjBuilder* ob = obj_new(c); 1124 Pool* p = c->global; 1125 Sym tn = pool_intern_slice(p, SLICE_LIT(".text")); 1126 Sym ln = pool_intern_slice(p, SLICE_LIT("local_fn")); 1127 ObjSecId sec = obj_section(ob, tn, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 1128 obj_write(ob, sec, TEXT_X64, sizeof TEXT_X64); 1129 obj_symbol(ob, ln, SB_LOCAL, SK_FUNC, sec, 0, sizeof TEXT_X64); 1130 obj_finalize(ob); 1131 1132 run_roundtrip(c, ob, verify_static_local); 1133 1134 obj_free(ob); 1135 free_compiler(c); 1136 } 1137 1138 /* test_section_symbol_synthesis: input has no explicit SK_SECTION 1139 * symbol; readback should contain one per kept section (from the 1140 * emitter-synthesized SECTION primary + section-definition aux). */ 1141 1142 static void verify_section_symbol_synthesis(const ObjBuilder* ob, Pool* p) { 1143 ObjSymIter* it = obj_symiter_new(ob); 1144 ObjSymEntry e; 1145 int n_section_syms = 0; 1146 while (obj_symiter_next(it, &e)) { 1147 if (e.sym->removed) continue; 1148 if (e.sym->kind == SK_SECTION) ++n_section_syms; 1149 } 1150 obj_symiter_free(it); 1151 EXPECT(n_section_syms >= 1, 1152 "no SK_SECTION symbols after round-trip (expected at least one)"); 1153 /* Best-effort: text + data + bss + rdata = 4. */ 1154 EXPECT(n_section_syms == 4, 1155 "section-symbol count=%d (want 4: text/data/bss/rdata)", 1156 n_section_syms); 1157 (void)p; 1158 } 1159 1160 static void test_section_symbol_synthesis(void) { 1161 g_test_name = "section_symbol_synthesis"; 1162 KitTargetSpec t; 1163 target_x64_windows(&t); 1164 Compiler* c = make_compiler(&t); 1165 if (!c) { 1166 EXPECT(0, "compiler_new"); 1167 return; 1168 } 1169 if (setjmp(c->panic)) { 1170 compiler_run_cleanups(c); 1171 free_compiler(c); 1172 EXPECT(0, "panic"); 1173 return; 1174 } 1175 ObjBuilder* ob = obj_new(c); 1176 Pool* p = c->global; 1177 1178 ObjSecId text = obj_section(ob, pool_intern_slice(p, SLICE_LIT(".text")), 1179 SEC_TEXT, SF_ALLOC | SF_EXEC, 16); 1180 obj_write(ob, text, TEXT_X64, sizeof TEXT_X64); 1181 ObjSecId data = obj_section(ob, pool_intern_slice(p, SLICE_LIT(".data")), 1182 SEC_DATA, SF_ALLOC | SF_WRITE, 8); 1183 static const uint8_t z8[8] = {0}; 1184 obj_write(ob, data, z8, sizeof z8); 1185 ObjSecId rdata = obj_section(ob, pool_intern_slice(p, SLICE_LIT(".rdata")), 1186 SEC_RODATA, SF_ALLOC, 8); 1187 obj_write(ob, rdata, "hi\0", 3); 1188 ObjSecId bss = 1189 obj_section_ex(ob, pool_intern_slice(p, SLICE_LIT(".bss")), SEC_BSS, 1190 SSEM_NOBITS, SF_ALLOC | SF_WRITE, 8, 0, 0, 0); 1191 obj_reserve_bss(ob, bss, 16, 8); 1192 1193 obj_finalize(ob); 1194 1195 run_roundtrip(c, ob, verify_section_symbol_synthesis); 1196 1197 obj_free(ob); 1198 free_compiler(c); 1199 } 1200 1201 /* test_tls_section: ".tls$" section gets SF_TLS on readback (name- 1202 * based detection in read_coff). */ 1203 1204 static void verify_tls_section(const ObjBuilder* ob, Pool* p) { 1205 const Section* s = find_section_named(ob, p, ".tls$"); 1206 EXPECT(s != NULL, ".tls$ not present"); 1207 if (s) { 1208 EXPECT((s->flags & SF_TLS) != 0, ".tls$ missing SF_TLS (flags=0x%x)", 1209 s->flags); 1210 } 1211 } 1212 1213 static void test_tls_section(void) { 1214 g_test_name = "tls_section"; 1215 KitTargetSpec t; 1216 target_x64_windows(&t); 1217 Compiler* c = make_compiler(&t); 1218 if (!c) { 1219 EXPECT(0, "compiler_new"); 1220 return; 1221 } 1222 if (setjmp(c->panic)) { 1223 compiler_run_cleanups(c); 1224 free_compiler(c); 1225 EXPECT(0, "panic"); 1226 return; 1227 } 1228 ObjBuilder* ob = obj_new(c); 1229 Pool* p = c->global; 1230 Sym nm = pool_intern_slice(p, SLICE_LIT(".tls$")); 1231 Sym vn = pool_intern_slice(p, SLICE_LIT("tls_var")); 1232 ObjSecId sec = obj_section(ob, nm, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, 8); 1233 static const uint8_t z8[8] = {0}; 1234 obj_write(ob, sec, z8, sizeof z8); 1235 obj_symbol(ob, vn, SB_GLOBAL, SK_OBJ, sec, 0, sizeof z8); 1236 obj_finalize(ob); 1237 1238 run_roundtrip(c, ob, verify_tls_section); 1239 1240 obj_free(ob); 1241 free_compiler(c); 1242 } 1243 1244 /* test_align_nibble: section with a non-trivial alignment (4096) 1245 * round-trips via the ALIGN_4096BYTES nibble. */ 1246 1247 static void verify_align_nibble(const ObjBuilder* ob, Pool* p) { 1248 const Section* s = find_section_named(ob, p, ".rdata"); 1249 EXPECT(s != NULL, ".rdata not present"); 1250 if (s) { 1251 EXPECT(s->align == 4096, ".rdata align=%u (want 4096)", s->align); 1252 } 1253 } 1254 1255 static void test_align_nibble(void) { 1256 g_test_name = "align_nibble"; 1257 KitTargetSpec t; 1258 target_x64_windows(&t); 1259 Compiler* c = make_compiler(&t); 1260 if (!c) { 1261 EXPECT(0, "compiler_new"); 1262 return; 1263 } 1264 if (setjmp(c->panic)) { 1265 compiler_run_cleanups(c); 1266 free_compiler(c); 1267 EXPECT(0, "panic"); 1268 return; 1269 } 1270 ObjBuilder* ob = obj_new(c); 1271 Pool* p = c->global; 1272 Sym nm = pool_intern_slice(p, SLICE_LIT(".rdata")); 1273 ObjSecId sec = obj_section(ob, nm, SEC_RODATA, SF_ALLOC, 4096); 1274 static const uint8_t z[16] = {0}; 1275 obj_write(ob, sec, z, sizeof z); 1276 obj_finalize(ob); 1277 1278 run_roundtrip(c, ob, verify_align_nibble); 1279 1280 obj_free(ob); 1281 free_compiler(c); 1282 } 1283 1284 /* test_empty_obj: no sections, no symbols. Smallest valid .obj. */ 1285 1286 static void verify_empty_obj(const ObjBuilder* ob, Pool* p) { 1287 (void)p; 1288 u32 n = obj_section_count(ob); 1289 /* obj_section_count includes the id-0 placeholder. */ 1290 int real = 0; 1291 for (u32 i = 1; i < n; ++i) { 1292 const Section* s = obj_section_get(ob, i); 1293 if (!s->removed) ++real; 1294 } 1295 EXPECT(real == 0, "empty obj has %d sections after round-trip", real); 1296 } 1297 1298 static void test_empty_obj(void) { 1299 g_test_name = "empty_obj"; 1300 KitTargetSpec t; 1301 target_x64_windows(&t); 1302 Compiler* c = make_compiler(&t); 1303 if (!c) { 1304 EXPECT(0, "compiler_new"); 1305 return; 1306 } 1307 if (setjmp(c->panic)) { 1308 compiler_run_cleanups(c); 1309 free_compiler(c); 1310 EXPECT(0, "panic"); 1311 return; 1312 } 1313 ObjBuilder* ob = obj_new(c); 1314 obj_finalize(ob); 1315 1316 run_roundtrip(c, ob, verify_empty_obj); 1317 1318 obj_free(ob); 1319 free_compiler(c); 1320 } 1321 1322 /* ---- short-import (Microsoft .lib member) smoke ------------------ */ 1323 1324 /* Hand-build a 45-byte short-import record: 1325 * header (20) + "ExitProcess\0" (12) + "KERNEL32.dll\0" (13) = 45 1326 * SizeOfData = 25 (the two NUL-terminated strings). 1327 * Machine = AMD64 (0x8664). 1328 * TypeFlags = (Type=CODE) | (NameType=NAME << 2) = 0 | (1<<2) = 4. 1329 * 1330 * Verifies that read_coff dispatches to the short-import path, the 1331 * synthesized ObjBuilder has the imported symbol and its `__imp_*` 1332 * alias defined at OBJ_SEC_NONE (DSO-shape), and the providing DLL 1333 * name is recoverable via obj_get_coff_import_dll. */ 1334 static void test_short_import_amd64(void) { 1335 g_test_name = "short_import_amd64"; 1336 KitTargetSpec t; 1337 target_x64_windows(&t); 1338 Compiler* c = make_compiler(&t); 1339 if (!c) { 1340 EXPECT(0, "compiler_new"); 1341 return; 1342 } 1343 if (setjmp(c->panic)) { 1344 compiler_run_cleanups(c); 1345 free_compiler(c); 1346 EXPECT(0, "panic during test"); 1347 return; 1348 } 1349 1350 static const char kSym[] = "ExitProcess"; /* 11 chars + NUL = 12 */ 1351 static const char kDll[] = "KERNEL32.dll"; /* 12 chars + NUL = 13 */ 1352 const uint32_t kSymLen = (uint32_t)(sizeof kSym - 1); 1353 const uint32_t kDllLen = (uint32_t)(sizeof kDll - 1); 1354 const uint32_t kDataLen = sizeof kSym + sizeof kDll; /* 12 + 13 = 25 */ 1355 const size_t kTotal = 20 + kDataLen; /* 45 */ 1356 uint8_t buf[64]; 1357 EXPECT(kTotal <= sizeof buf, "buf too small"); 1358 memset(buf, 0, kTotal); 1359 /* Header. */ 1360 buf[0] = 0x00; 1361 buf[1] = 0x00; /* Sig1 = 0 */ 1362 buf[2] = 0xFF; 1363 buf[3] = 0xFF; /* Sig2 = 0xFFFF */ 1364 buf[4] = 0x00; 1365 buf[5] = 0x00; /* Version = 0 */ 1366 buf[6] = 0x64; 1367 buf[7] = 0x86; /* Machine = AMD64 (0x8664) */ 1368 /* TimeDateStamp = 0 (bytes 8..11 already 0). */ 1369 buf[12] = (uint8_t)(kDataLen & 0xFF); 1370 buf[13] = (uint8_t)((kDataLen >> 8) & 0xFF); 1371 buf[14] = (uint8_t)((kDataLen >> 16) & 0xFF); 1372 buf[15] = (uint8_t)((kDataLen >> 24) & 0xFF); 1373 /* OrdinalOrHint = 0 (16..17). */ 1374 /* TypeFlags = Type=CODE(0) | NameType=NAME(1)<<2 = 0x0004. */ 1375 buf[18] = 0x04; 1376 buf[19] = 0x00; 1377 /* Body: symbol name NUL DLL name NUL. */ 1378 memcpy(buf + 20, kSym, sizeof kSym); 1379 memcpy(buf + 20 + sizeof kSym, kDll, sizeof kDll); 1380 1381 ObjBuilder* ob = read_coff(c, "short-import", buf, kTotal); 1382 EXPECT(ob != NULL, "read_coff returned NULL on short-import"); 1383 if (!ob) { 1384 free_compiler(c); 1385 return; 1386 } 1387 1388 Pool* p = c->global; 1389 ObjSymId sid = find_sym_named(ob, p, kSym); 1390 EXPECT(sid != OBJ_SYM_NONE, "missing imported symbol"); 1391 if (sid) { 1392 const ObjSym* s = obj_symbol_get(ob, sid); 1393 EXPECT(s->bind == SB_GLOBAL, "imported sym bind=%u (want SB_GLOBAL)", 1394 s->bind); 1395 EXPECT(s->kind == SK_FUNC, "imported sym kind=%u (want SK_FUNC)", s->kind); 1396 EXPECT(s->section_id == OBJ_SEC_NONE, 1397 "imported sym section_id=%u (want OBJ_SEC_NONE)", 1398 (unsigned)s->section_id); 1399 } 1400 1401 ObjSymId imp_id = find_sym_named(ob, p, "__imp_ExitProcess"); 1402 EXPECT(imp_id != OBJ_SYM_NONE, "missing __imp_<name> alias"); 1403 if (imp_id) { 1404 const ObjSym* s = obj_symbol_get(ob, imp_id); 1405 EXPECT(s->bind == SB_GLOBAL, "__imp_ bind=%u (want SB_GLOBAL)", s->bind); 1406 EXPECT(s->section_id == OBJ_SEC_NONE, 1407 "__imp_ section_id=%u (want OBJ_SEC_NONE)", (unsigned)s->section_id); 1408 } 1409 1410 Sym dll = 0; 1411 int got = obj_get_coff_import_dll(ob, &dll); 1412 EXPECT(got, "obj_get_coff_import_dll returned 0 (annotation missing)"); 1413 if (got) EXPECT(sym_eq_str(p, dll, kDll), "DLL name mismatch"); 1414 1415 (void)kSymLen; 1416 (void)kDllLen; 1417 obj_free(ob); 1418 free_compiler(c); 1419 } 1420 1421 /* Format/target detection from raw header bytes. ARM64EC (Machine 0xA641) 1422 * must be recognized as a COFF object and resolve to plain AArch64 — the 1423 * encoding is identical to ARM64 and the linker/codegen treat it as such. */ 1424 static void test_detect_arm64ec(void) { 1425 /* Minimal COFF header prefix: Machine word then NumberOfSections. 0xA641 1426 * is little-endian {0x41, 0xA6}. */ 1427 static const uint8_t arm64ec[] = {0x41, 0xA6, 0x00, 0x00}; 1428 static const uint8_t arm64[] = {0x64, 0xAA, 0x00, 0x00}; 1429 KitTargetSpec t; 1430 EXPECT(kit_detect_fmt(arm64ec, sizeof arm64ec) == KIT_BIN_COFF, 1431 "ARM64EC machine not detected as COFF"); 1432 memset(&t, 0, sizeof t); 1433 EXPECT(kit_detect_target(arm64ec, sizeof arm64ec, &t) == KIT_OK, 1434 "kit_detect_target failed on ARM64EC"); 1435 EXPECT(t.arch == KIT_ARCH_ARM_64, "ARM64EC did not resolve to AArch64"); 1436 EXPECT(t.obj == KIT_OBJ_COFF, "ARM64EC obj fmt not COFF"); 1437 /* Sanity: plain ARM64 still resolves the same arch. */ 1438 memset(&t, 0, sizeof t); 1439 EXPECT(kit_detect_target(arm64, sizeof arm64, &t) == KIT_OK, 1440 "kit_detect_target failed on ARM64"); 1441 EXPECT(t.arch == KIT_ARCH_ARM_64, "ARM64 did not resolve to AArch64"); 1442 } 1443 1444 /* ---- driver -------------------------------------------------------- */ 1445 1446 typedef void (*TestFn)(void); 1447 1448 static const struct { 1449 const char* name; 1450 TestFn fn; 1451 } TESTS[] = { 1452 {"header_minimal_x64", test_header_minimal_x64}, 1453 {"header_minimal_aa64", test_header_minimal_aa64}, 1454 {"text_only_x64", test_text_only_x64}, 1455 {"text_only_aa64", test_text_only_aa64}, 1456 {"rodata", test_rodata}, 1457 {"bss", test_bss}, 1458 {"data_with_reloc_abs64_x64", test_data_with_reloc_abs64_x64}, 1459 {"data_with_reloc_abs64_aa64", test_data_with_reloc_abs64_aa64}, 1460 {"reloc_rel32_x64", test_data_with_reloc_rel32_x64}, 1461 {"reloc_rel32_inline_addend_x64", test_reloc_rel32_inline_addend_x64}, 1462 {"aa64_branch26", test_aa64_branch26}, 1463 {"aa64_pagebase_pageoffset", test_aa64_pagebase_pageoffset}, 1464 {"long_section_name", test_long_section_name}, 1465 {"long_symbol_name", test_long_symbol_name}, 1466 {"weak_global", test_weak_global}, 1467 {"common_symbol", test_common_symbol}, 1468 {"comdat_group", test_comdat_group}, 1469 {"static_local_symbol", test_static_local_symbol}, 1470 {"section_symbol_synthesis", test_section_symbol_synthesis}, 1471 {"tls_section", test_tls_section}, 1472 {"align_nibble", test_align_nibble}, 1473 {"empty_obj", test_empty_obj}, 1474 {"short_import_amd64", test_short_import_amd64}, 1475 {"detect_arm64ec", test_detect_arm64ec}, 1476 }; 1477 static const size_t NTESTS = sizeof TESTS / sizeof TESTS[0]; 1478 1479 int main(void) { 1480 for (size_t i = 0; i < NTESTS; ++i) { 1481 int before = g_failures; 1482 TESTS[i].fn(); 1483 if (g_failures == before) { 1484 fprintf(stderr, " ok %s\n", TESTS[i].name); 1485 } else { 1486 fprintf(stderr, " FAIL %s\n", TESTS[i].name); 1487 } 1488 } 1489 if (g_failures) { 1490 fprintf(stderr, "FAILED %d assertion(s) across %zu tests\n", g_failures, 1491 NTESTS); 1492 return 1; 1493 } 1494 fprintf(stderr, "OK %zu tests\n", NTESTS); 1495 return 0; 1496 }