kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

read.c (33366B)


      1 /* PE/COFF .obj (IMAGE_FILE_HEADER + sections) reader.  Parses a 64-bit
      2  * little-endian relocatable object back into a fresh ObjBuilder.  Peer
      3  * of read_elf / read_macho; the post-finalize ObjBuilder shape is the
      4  * canonical superset doc/DESIGN.md §5.5 promises: read_coff of an
      5  * emit_coff output produces an ObjBuilder shape-equivalent to the
      6  * writer's input, modulo synthesized SECTION symbols and the COMDAT
      7  * section-definition aux records.
      8  *
      9  * Scope: IMAGE_FILE_MACHINE_AMD64 and IMAGE_FILE_MACHINE_ARM64.  PE
     10  * *images* (executables / DLLs, beginning with the DOS 'MZ' stub) are
     11  * detected at entry and dispatched to read_coff_image (read_image.c).
     12  * Microsoft "short import" records (Sig1=0, Sig2=0xFFFF) found inside
     13  * .lib archive members are likewise detected at entry and dispatched to
     14  * read_coff_short_import, which synthesizes a DSO-shaped ObjBuilder
     15  * annotated with the providing DLL name via obj_set_coff_import_dll. */
     16 
     17 #include <string.h>
     18 
     19 #include "core/arena.h"
     20 #include "core/heap.h"
     21 #include "core/pool.h"
     22 #include "core/slice.h"
     23 #include "obj/coff/coff.h"
     24 #include "obj/coff/read_util.h"
     25 #include "obj/format.h"
     26 
     27 /* ---- section-header scratch ---- */
     28 
     29 typedef struct CSecRec {
     30   char raw_name[8];
     31   u32 virtual_size;
     32   u32 size_of_raw_data;
     33   u32 pointer_to_raw_data;
     34   u32 pointer_to_relocations;
     35   u16 number_of_relocations;
     36   u32 characteristics;
     37   ObjSecId obj_sec; /* OBJ_SEC_NONE if skipped */
     38 } CSecRec;
     39 
     40 static void parse_shdr(const u8* p, CSecRec* out) {
     41   memcpy(out->raw_name, p, 8);
     42   out->virtual_size = coff_rd_u32(p + 8);
     43   out->size_of_raw_data = coff_rd_u32(p + 16);
     44   out->pointer_to_raw_data = coff_rd_u32(p + 20);
     45   out->pointer_to_relocations = coff_rd_u32(p + 24);
     46   out->number_of_relocations = coff_rd_u16(p + 32);
     47   out->characteristics = coff_rd_u32(p + 36);
     48   out->obj_sec = OBJ_SEC_NONE;
     49 }
     50 
     51 /* ---- string-table lookup (4-byte size prefix, NUL-terminated entries) ---- */
     52 
     53 static const char* strtab_lookup(const u8* tab, u32 tab_size, u32 off,
     54                                  u32* len_out) {
     55   if (off >= tab_size) {
     56     *len_out = 0;
     57     return "";
     58   }
     59   const char* s = (const char*)(tab + off);
     60   u32 max = tab_size - off;
     61   u32 n = 0;
     62   while (n < max && s[n] != '\0') ++n;
     63   *len_out = n;
     64   return s;
     65 }
     66 
     67 /* Resolve a section/symbol short-or-long name into (ptr, len).  COFF
     68  * section names use the "/<decimal>" convention for >8-byte names; COFF
     69  * symbol names use the (Zeroes==0, Offset) form instead.  This helper
     70  * handles the section form (8 raw bytes; leading '/' triggers strtab
     71  * lookup). */
     72 static void resolve_section_name(const char raw[8], const u8* strtab,
     73                                  u32 strtab_size, const char** name_out,
     74                                  u32* len_out) {
     75   if (raw[0] == '/') {
     76     /* Parse decimal offset.  Up to 7 ASCII digits. */
     77     u32 off = 0;
     78     for (u32 i = 1; i < 8 && raw[i] >= '0' && raw[i] <= '9'; ++i) {
     79       off = off * 10u + (u32)(raw[i] - '0');
     80     }
     81     *name_out = strtab_lookup(strtab, strtab_size, off, len_out);
     82     return;
     83   }
     84   /* Inline: up to 8 bytes, NUL-padded (not necessarily NUL-terminated). */
     85   u32 n = 0;
     86   while (n < 8 && raw[n] != '\0') ++n;
     87   *name_out = raw;
     88   *len_out = n;
     89 }
     90 
     91 /* characteristics -> SecKind / SecFlag / alignment live in read_util.c
     92  * (coff_sec_kind / coff_sec_flags / coff_sec_align), shared with the
     93  * image reader. */
     94 
     95 /* ---- symbol-name resolution ---- */
     96 
     97 static void resolve_sym_name(const u8* rec, const u8* strtab, u32 strtab_size,
     98                              const char** name_out, u32* len_out) {
     99   /* ShortName: 8 bytes.  If first 4 bytes are zero, second 4 bytes is
    100    * the strtab offset (LongName form). */
    101   u32 z = coff_rd_u32(rec + 0);
    102   if (z == 0) {
    103     u32 off = coff_rd_u32(rec + 4);
    104     *name_out = strtab_lookup(strtab, strtab_size, off, len_out);
    105     return;
    106   }
    107   u32 n = 0;
    108   while (n < 8 && rec[n] != '\0') ++n;
    109   *name_out = (const char*)rec;
    110   *len_out = n;
    111 }
    112 
    113 static int coff_reloc_inline_addend(const u8* data, size_t len,
    114                                     const CSecRec* s, u32 off, u32 width,
    115                                     i64* out) {
    116   if (!s || !s->size_of_raw_data) return 0;
    117   if ((u64)off + (u64)width > (u64)s->size_of_raw_data) return 0;
    118   if ((u64)s->pointer_to_raw_data + (u64)off + (u64)width > (u64)len) return 0;
    119   const u8* p = data + s->pointer_to_raw_data + off;
    120   switch (width) {
    121     case 4:
    122       *out = (i64)(i32)coff_rd_u32(p);
    123       return 1;
    124     case 8:
    125       *out = (i64)coff_rd_u64(p);
    126       return 1;
    127     default:
    128       return 0;
    129   }
    130 }
    131 
    132 /* ---- short-import record handler ----
    133  * Microsoft "short import" format: a 20-byte ImportObjectHeader
    134  * followed by SizeOfData bytes containing two NUL-terminated strings —
    135  * the imported symbol name then the DLL name.  These live as members
    136  * of .lib archives (mingw's libkernel32.dll.a etc.) and stand in for
    137  * a full long-form COFF import object.
    138  *
    139  * kit-side model: synthesize a DSO-shaped ObjBuilder with the
    140  * imported symbol defined at section_id = OBJ_SEC_NONE (the same
    141  * shape read_coff_dso / read_elf_dso produce for an exported name),
    142  * and stash the providing DLL name on the builder via
    143  * obj_set_coff_import_dll so the archive-ingestion layer can route
    144  * the resulting LinkInput as a DSO with this name as the soname.
    145  *
    146  * We also synthesize the `__imp_<name>` alias mingw codegen uses to
    147  * spell explicit IAT-slot access; both names ultimately resolve to
    148  * the same DLL export at link time. */
    149 static ObjBuilder* read_coff_short_import(Compiler* c, const char* name,
    150                                           const u8* data, size_t len) {
    151   if (len < COFF_IMPORT_OBJECT_HEADER_SIZE)
    152     compiler_panic(c, SRCLOC_NONE,
    153                    "read_coff: short-import record shorter than header");
    154 
    155   /* Sig1 / Sig2 already checked by the caller. */
    156   /* data + 4: Version (2 bytes, ignored). */
    157   u16 machine = coff_rd_u16(data + 6);
    158   /* data + 8: TimeDateStamp (4 bytes, ignored). */
    159   u32 size_of_data = coff_rd_u32(data + 12);
    160   u16 ordinal_or_hint = coff_rd_u16(data + 16);
    161   u16 type_flags = coff_rd_u16(data + 18);
    162 
    163   if ((u64)COFF_IMPORT_OBJECT_HEADER_SIZE + (u64)size_of_data > (u64)len)
    164     compiler_panic(c, SRCLOC_NONE,
    165                    "read_coff: short-import SizeOfData=%u extends past input "
    166                    "(len=%zu)",
    167                    size_of_data, len);
    168 
    169   if (machine != IMAGE_FILE_MACHINE_AMD64 &&
    170       machine != IMAGE_FILE_MACHINE_ARM64)
    171     compiler_panic(c, SRCLOC_NONE,
    172                    "read_coff: short-import unsupported machine %#x",
    173                    (u32)machine);
    174 
    175   /* Decode TypeFlags bitfield (Type:2, NameType:3, Reserved:11). */
    176   u32 import_type = (u32)(type_flags & 0x3u);
    177   u32 name_type = (u32)((type_flags >> 2) & 0x7u);
    178 
    179   /* Ordinal-only imports (NameType=IMPORT_OBJECT_ORDINAL) are not yet
    180    * implemented in kit.  None of the mingw / llvm-mingw system import
    181    * archives use this shape — every libfoo.a member in the supported
    182    * sysroots imports by name — so refusing here is a clean diagnostic,
    183    * not an internal panic.  When a real consumer surfaces, the work is
    184    * to thread the ordinal through link_resolve and into the PE import
    185    * directory hint/name tables. */
    186   if (name_type == IMPORT_OBJECT_ORDINAL)
    187     compiler_panic(
    188         c, SRCLOC_NONE,
    189         "read_coff: short-import by ordinal not implemented "
    190         "(archive member \"%.*s\", ordinal %u). kit links "
    191         "imports by name only; rebuild the consumer to import "
    192         "by name, or omit this archive from the link.",
    193         SLICE_ARG(name ? slice_from_cstr(name) : SLICE_LIT("<unnamed>")),
    194         (unsigned)ordinal_or_hint);
    195 
    196   /* Symbol name: NUL-terminated starting at data + 20. */
    197   const u8* body = data + COFF_IMPORT_OBJECT_HEADER_SIZE;
    198   u32 sym_name_max = size_of_data;
    199   u32 sym_name_len = 0;
    200   while (sym_name_len < sym_name_max && body[sym_name_len] != '\0')
    201     ++sym_name_len;
    202   if (sym_name_len == sym_name_max)
    203     compiler_panic(c, SRCLOC_NONE,
    204                    "read_coff: short-import symbol name not NUL-terminated");
    205 
    206   /* DLL name: NUL-terminated starting after the symbol name's NUL. */
    207   u32 dll_name_off = sym_name_len + 1u;
    208   if (dll_name_off >= size_of_data)
    209     compiler_panic(c, SRCLOC_NONE, "read_coff: short-import missing DLL name");
    210   const u8* dll_p = body + dll_name_off;
    211   u32 dll_name_max = size_of_data - dll_name_off;
    212   u32 dll_name_len = 0;
    213   while (dll_name_len < dll_name_max && dll_p[dll_name_len] != '\0')
    214     ++dll_name_len;
    215   if (dll_name_len == dll_name_max)
    216     compiler_panic(c, SRCLOC_NONE,
    217                    "read_coff: short-import DLL name not NUL-terminated");
    218 
    219   ObjBuilder* ob = obj_new(c);
    220   if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff: obj_new failed");
    221 
    222   /* Pick SymKind by import type: CODE -> function, DATA/CONST -> object.
    223    * Both are defined at section_id=OBJ_SEC_NONE, value=0, size=0 — the
    224    * shape read_coff_dso would produce for a DLL export. */
    225   SymKind k = (import_type == IMPORT_OBJECT_CODE) ? SK_FUNC : SK_OBJ;
    226 
    227   Sym sn = pool_intern_slice(
    228       c->global, (Slice){.s = (const char*)body, .len = sym_name_len});
    229   ObjSymId id =
    230       obj_symbol_ex(ob, sn, SB_GLOBAL, SV_DEFAULT, k, OBJ_SEC_NONE, 0, 0, 0);
    231   obj_sym_mark_referenced(ob, id);
    232 
    233   /* `__imp_<name>` alias for codegen that refers to the IAT slot
    234    * directly (mingw convention).  Even code imports use an object-like
    235    * `__imp_` symbol because references to it want the IAT data slot, not
    236    * the callable import stub. */
    237   static const char kImpPrefix[] = "__imp_";
    238   u32 imp_len = (u32)(sizeof kImpPrefix - 1u) + sym_name_len;
    239   char* imp_buf = arena_array(c->scratch, char, imp_len);
    240   memcpy(imp_buf, kImpPrefix, sizeof kImpPrefix - 1u);
    241   memcpy(imp_buf + (sizeof kImpPrefix - 1u), body, sym_name_len);
    242   Sym imp_sn =
    243       pool_intern_slice(c->global, (Slice){.s = imp_buf, .len = imp_len});
    244   ObjSymId imp_id = obj_symbol_ex(ob, imp_sn, SB_GLOBAL, SV_DEFAULT, SK_OBJ,
    245                                   OBJ_SEC_NONE, 0, 0, 0);
    246   obj_sym_mark_referenced(ob, imp_id);
    247 
    248   /* Stash the DLL name so the archive-ingestion layer (Phase 4.3) can
    249    * route this builder as a DSO with the DLL as soname. */
    250   Sym dll_sn = pool_intern_slice(
    251       c->global, (Slice){.s = (const char*)dll_p, .len = dll_name_len});
    252   obj_set_coff_import_dll(ob, dll_sn);
    253 
    254   /* NameType decides what the loader resolves IN THE DLL, which can differ
    255    * from the local symbol name. The local symbol keeps its own name (so kit's
    256    * references resolve); the PE hint/name-table entry must use the real
    257    * export name. Record an override whenever they differ. */
    258   Slice imp_name = {.s = (const char*)body, .len = sym_name_len};
    259   if (name_type == IMPORT_OBJECT_NAME_NOPREFIX ||
    260       name_type == IMPORT_OBJECT_NAME_UNDECORATE) {
    261     /* Strip one leading decoration char (?, @, or _). UNDECORATE also
    262      * truncates at the first '@' (MS @argbytes stdcall/fastcall suffix). */
    263     if (imp_name.len > 0 && (imp_name.s[0] == '?' || imp_name.s[0] == '@' ||
    264                              imp_name.s[0] == '_')) {
    265       ++imp_name.s;
    266       --imp_name.len;
    267     }
    268     if (name_type == IMPORT_OBJECT_NAME_UNDECORATE) {
    269       u32 at = 0;
    270       while (at < imp_name.len && imp_name.s[at] != '@') ++at;
    271       imp_name.len = at;
    272     }
    273   } else if (name_type == IMPORT_OBJECT_NAME_EXPORTAS) {
    274     /* The real export name is a third NUL-terminated string after the DLL. */
    275     u32 exp_off = dll_name_off + dll_name_len + 1u;
    276     if (exp_off >= size_of_data)
    277       compiler_panic(c, SRCLOC_NONE,
    278                      "read_coff: short-import EXPORTAS missing export name");
    279     const u8* exp_p = body + exp_off;
    280     u32 exp_max = size_of_data - exp_off;
    281     u32 exp_len = 0;
    282     while (exp_len < exp_max && exp_p[exp_len] != '\0') ++exp_len;
    283     if (exp_len == exp_max)
    284       compiler_panic(
    285           c, SRCLOC_NONE,
    286           "read_coff: short-import EXPORTAS name not NUL-terminated");
    287     imp_name.s = (const char*)exp_p;
    288     imp_name.len = exp_len;
    289   }
    290   if (imp_name.len != sym_name_len ||
    291       memcmp(imp_name.s, body, sym_name_len) != 0) {
    292     obj_set_coff_import_name(ob, pool_intern_slice(c->global, imp_name));
    293   }
    294 
    295   obj_finalize(ob);
    296   return ob;
    297 }
    298 
    299 ObjBuilder* read_coff(Compiler* c, const char* name, const u8* data,
    300                       size_t len) {
    301   (void)name;
    302 
    303   /* ---- Step 0: header validation ---- */
    304   if (len < COFF_FILE_HEADER_SIZE)
    305     compiler_panic(c, SRCLOC_NONE, "read_coff: input shorter than COFF header");
    306 
    307   /* Microsoft short-import record? (Sig1=0, Sig2=0xFFFF.) These live
    308    * as members of .lib archives and stand in for a long-form import
    309    * object.  Detect at entry; the rest of read_coff assumes the
    310    * input is a real IMAGE_FILE_HEADER. */
    311   if (len >= 4 && coff_rd_u16(data + 0) == IMPORT_OBJECT_HDR_SIG1 &&
    312       coff_rd_u16(data + 2) == IMPORT_OBJECT_HDR_SIG2) {
    313     return read_coff_short_import(c, name, data, len);
    314   }
    315 
    316   /* PE image? A linked .exe/.dll begins with the DOS 'MZ' stub, not a bare
    317    * IMAGE_FILE_HEADER — dispatch to the image reader, which walks the
    318    * DOS -> PE-sig -> file/optional headers.  (Placed before the offset-0
    319    * machine read below, which assumes a bare header, and before the
    320    * optional-header rejection.) */
    321   if (len >= 2 && coff_rd_u16(data + 0) == IMAGE_DOS_SIGNATURE)
    322     return read_coff_image(c, name, data, len);
    323 
    324   u16 machine = coff_rd_u16(data + 0);
    325   u16 nsections = coff_rd_u16(data + 2);
    326   /* data + 4: TimeDateStamp (4 bytes, ignored). */
    327   u32 ptr_to_symtab = coff_rd_u32(data + 8);
    328   u32 nsymbols = coff_rd_u32(data + 12);
    329   u16 size_opt_hdr = coff_rd_u16(data + 16);
    330   /* data + 18: Characteristics (2 bytes, currently ignored). */
    331 
    332   if (size_opt_hdr != 0)
    333     compiler_panic(c, SRCLOC_NONE,
    334                    "read_coff: input has optional header (size=%u); "
    335                    "use read_coff_pe for executables",
    336                    (u32)size_opt_hdr);
    337 
    338   if (machine != IMAGE_FILE_MACHINE_AMD64 &&
    339       machine != IMAGE_FILE_MACHINE_ARM64 &&
    340       machine != IMAGE_FILE_MACHINE_ARM64EC)
    341     compiler_panic(c, SRCLOC_NONE, "read_coff: unsupported machine %#x",
    342                    (u32)machine);
    343 
    344   const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF);
    345   const ObjCoffArchOps* coff =
    346       fmt && fmt->coff_machine ? fmt->coff_machine(machine) : NULL;
    347   if (!coff || !coff->reloc_from)
    348     compiler_panic(c, SRCLOC_NONE, "read_coff: no arch impl for machine %#x",
    349                    (u32)machine);
    350   u32 (*reloc_from)(u32) = coff->reloc_from;
    351 
    352   if ((u64)COFF_FILE_HEADER_SIZE +
    353           (u64)nsections * (u64)COFF_SECTION_HEADER_SIZE >
    354       (u64)len)
    355     compiler_panic(c, SRCLOC_NONE,
    356                    "read_coff: section header table out of range");
    357 
    358   /* ---- Step 1: bootstrap, locate strtab ---- */
    359   /* Strtab is at PointerToSymbolTable + NumberOfSymbols * 18.  When the
    360    * file has no symbol table (ptr=0, n=0) we treat strtab as empty. */
    361   const u8* strtab = NULL;
    362   u32 strtab_size = 0;
    363   if (ptr_to_symtab && nsymbols) {
    364     u64 symtab_end = (u64)ptr_to_symtab + (u64)nsymbols * (u64)COFF_SYMBOL_SIZE;
    365     if (symtab_end + COFF_STRTAB_SIZE_FIELD_BYTES > (u64)len)
    366       compiler_panic(c, SRCLOC_NONE,
    367                      "read_coff: symbol table / strtab header out of range");
    368     u32 declared = coff_rd_u32(data + symtab_end);
    369     /* The size field is inclusive of the 4-byte prefix; treat <4 as
    370      * "empty" (some tools write 0). */
    371     if (declared < COFF_STRTAB_SIZE_FIELD_BYTES) declared = 0;
    372     if (declared) {
    373       if (symtab_end + (u64)declared > (u64)len)
    374         compiler_panic(c, SRCLOC_NONE, "read_coff: strtab body out of range");
    375       strtab = data + symtab_end;
    376       strtab_size = declared;
    377     } else {
    378       strtab = data + symtab_end;
    379       strtab_size = COFF_STRTAB_SIZE_FIELD_BYTES;
    380     }
    381   }
    382 
    383   ObjBuilder* ob = obj_new(c);
    384   if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff: obj_new failed");
    385   obj_reserve_symbols(ob, nsymbols); /* skip the 256->nsymbols resize cascade */
    386 
    387   /* ---- Step 2: ingest sections ---- */
    388   CSecRec* secs = arena_array(c->scratch, CSecRec, nsections ? nsections : 1);
    389   const u8* shdr_base = data + COFF_FILE_HEADER_SIZE;
    390   for (u32 i = 0; i < nsections; ++i) {
    391     CSecRec* s = &secs[i];
    392     parse_shdr(shdr_base + (u64)i * COFF_SECTION_HEADER_SIZE, s);
    393 
    394     const char* nm;
    395     u32 nlen;
    396     resolve_section_name(s->raw_name, strtab, strtab_size, &nm, &nlen);
    397     Sym sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
    398 
    399     u16 kind = coff_sec_kind(nm, nlen, s->characteristics);
    400     u16 flags = coff_sec_flags(nm, nlen, s->characteristics);
    401     u32 align = coff_sec_align(s->characteristics);
    402 
    403     int is_bss = (s->characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0;
    404     u16 sem = is_bss ? SSEM_NOBITS : SSEM_PROGBITS;
    405 
    406     ObjSecId id = obj_section_ex(ob, sn, (SecKind)kind, (SecSem)sem, flags,
    407                                  align, 0u, 0u, 0u);
    408     if (id == OBJ_SEC_NONE)
    409       compiler_panic(c, SRCLOC_NONE,
    410                      "read_coff: obj_section_ex failed for section %u", i);
    411     s->obj_sec = id;
    412 
    413     /* Preserve raw Characteristics so emit_coff can write back any bits
    414      * the canonical SecFlag/SecSem mapping doesn't model (LNK_INFO,
    415      * LNK_REMOVE, MEM_DISCARDABLE, MEM_SHARED, GPREL, alignment nibble). */
    416     obj_section_set_ext(ob, id, OBJ_EXT_COFF, s->characteristics, 0);
    417 
    418     if (is_bss) {
    419       u32 bss_size = s->virtual_size ? s->virtual_size : s->size_of_raw_data;
    420       obj_reserve_bss(ob, id, bss_size, align);
    421     } else if (s->size_of_raw_data) {
    422       u64 end = (u64)s->pointer_to_raw_data + (u64)s->size_of_raw_data;
    423       if (end > (u64)len)
    424         compiler_panic(c, SRCLOC_NONE,
    425                        "read_coff: section %u bytes out of range", i);
    426       u8* dst = obj_reserve(ob, id, s->size_of_raw_data);
    427       memcpy(dst, data + s->pointer_to_raw_data, s->size_of_raw_data);
    428     }
    429   }
    430 
    431   /* ---- Step 3: ingest symbols (with aux-record awareness) ----
    432    * sym_to_obj is indexed by RAW symbol-table index (including aux
    433    * slots), so reloc.SymbolTableIndex resolves directly without
    434    * adjusting for skipped aux records.  Aux slots map to OBJ_SYM_NONE. */
    435   ObjSymId* sym_to_obj =
    436       arena_zarray(c->scratch, ObjSymId, nsymbols ? nsymbols : 1);
    437 
    438   /* Track section-symbol primary symtab index per section, stored as
    439    * (raw_index + 1) so 0 can mean "not seen yet" without colliding
    440    * with the (legitimate) first symbol-table slot — emit_coff always
    441    * lays the first section's section-symbol at index 0. */
    442   u32* sec_sym_primary = arena_zarray(c->scratch, u32, nsections + 1u);
    443 
    444   const u8* sym_base = data + ptr_to_symtab;
    445   if (nsymbols) {
    446     if ((u64)ptr_to_symtab + (u64)nsymbols * (u64)COFF_SYMBOL_SIZE > (u64)len)
    447       compiler_panic(c, SRCLOC_NONE,
    448                      "read_coff: symbol table body out of range");
    449   }
    450 
    451   for (u32 i = 0; i < nsymbols;) {
    452     const u8* p = sym_base + (u64)i * COFF_SYMBOL_SIZE;
    453     const char* nm;
    454     u32 nlen;
    455     resolve_sym_name(p, strtab, strtab_size, &nm, &nlen);
    456 
    457     u32 value = coff_rd_u32(p + 8);
    458     i16 sec_num = (i16)coff_rd_u16(p + 12);
    459     u16 type = coff_rd_u16(p + 14);
    460     u8 sclass = p[16];
    461     u8 naux = p[17];
    462 
    463     /* FILE storage class: concatenate aux records' raw bytes (each
    464      * 18 bytes, NUL-padded) for the source-file name. */
    465     if (sclass == IMAGE_SYM_CLASS_FILE) {
    466       /* Build name from aux records (up to naux*18 bytes); fall back
    467        * to the primary record's name if naux==0. */
    468       const char* fnm = nm;
    469       u32 fnlen = nlen;
    470       if (naux) {
    471         /* Each aux record's 18 bytes are interpreted as raw file-name
    472          * bytes; concatenate then trim trailing NULs. */
    473         u32 total = (u32)naux * COFF_SYMBOL_SIZE;
    474         if ((u64)i + 1u + (u64)naux > (u64)nsymbols)
    475           compiler_panic(c, SRCLOC_NONE,
    476                          "read_coff: FILE aux records extend past symbol "
    477                          "table");
    478         const u8* aux = p + COFF_SYMBOL_SIZE;
    479         u32 n = 0;
    480         while (n < total && aux[n] != '\0') ++n;
    481         fnm = (const char*)aux;
    482         fnlen = n;
    483       }
    484       Sym fsn =
    485           fnlen ? pool_intern_slice(c->global, (Slice){.s = fnm, .len = fnlen})
    486                 : 0;
    487       ObjSymId id = obj_symbol_ex(ob, fsn, SB_LOCAL, SV_DEFAULT, SK_FILE,
    488                                   OBJ_SEC_NONE, 0, 0, 0);
    489       obj_sym_mark_referenced(ob, id);
    490       sym_to_obj[i] = id;
    491       i += 1u + naux;
    492       continue;
    493     }
    494 
    495     /* Skip .bf/.ef debug pair primaries (FUNCTION storage class) and
    496      * the END_OF_FUNCTION marker: they carry no symbol kit models. */
    497     if (sclass == IMAGE_SYM_CLASS_FUNCTION ||
    498         sclass == IMAGE_SYM_CLASS_END_OF_FUNCTION) {
    499       sym_to_obj[i] = OBJ_SYM_NONE;
    500       i += 1u + naux;
    501       continue;
    502     }
    503 
    504     /* Resolve (bind, vis, kind, section_id, value, size, cmnalign). */
    505     SymBind bind = SB_LOCAL;
    506     SymVis vis = SV_DEFAULT;
    507     SymKind kind = SK_NOTYPE;
    508     ObjSecId target_sec = OBJ_SEC_NONE;
    509     u64 sym_value = 0;
    510     u64 sym_size = 0;
    511     u64 cmnalign = 0;
    512 
    513     if (sec_num == IMAGE_SYM_UNDEFINED) {
    514       /* Undef or common.  EXTERNAL with Value > 0 is a common. */
    515       if (sclass == IMAGE_SYM_CLASS_EXTERNAL && value > 0) {
    516         bind = SB_GLOBAL;
    517         kind = SK_COMMON;
    518         sym_size = value;
    519         cmnalign = 1; /* COFF doesn't carry per-common alignment */
    520       } else {
    521         bind = (sclass == IMAGE_SYM_CLASS_WEAK_EXTERNAL) ? SB_WEAK
    522                : (sclass == IMAGE_SYM_CLASS_EXTERNAL)    ? SB_GLOBAL
    523                                                          : SB_LOCAL;
    524         kind = SK_UNDEF;
    525       }
    526     } else if (sec_num == IMAGE_SYM_ABSOLUTE) {
    527       kind = SK_ABS;
    528       sym_value = value;
    529       bind = (sclass == IMAGE_SYM_CLASS_EXTERNAL) ? SB_GLOBAL : SB_LOCAL;
    530     } else if (sec_num == IMAGE_SYM_DEBUG) {
    531       /* Defined-in-debug — kit has no model for it.  Skip with an
    532        * OBJ_SYM_NONE entry; relocations against this slot will resolve
    533        * to OBJ_SYM_NONE, which obj_reloc_ex tolerates. */
    534       sym_to_obj[i] = OBJ_SYM_NONE;
    535       i += 1u + naux;
    536       continue;
    537     } else if (sec_num >= 1 && (u32)sec_num <= nsections) {
    538       target_sec = secs[sec_num - 1].obj_sec;
    539       sym_value = value;
    540       switch (sclass) {
    541         case IMAGE_SYM_CLASS_EXTERNAL:
    542           bind = SB_GLOBAL;
    543           break;
    544         case IMAGE_SYM_CLASS_WEAK_EXTERNAL:
    545           bind = SB_WEAK;
    546           break;
    547         case IMAGE_SYM_CLASS_STATIC:
    548         case IMAGE_SYM_CLASS_LABEL:
    549         default:
    550           bind = SB_LOCAL;
    551           break;
    552       }
    553 
    554       /* Detect SECTION symbols: STATIC, Value==0, name matches the
    555        * section's own name, and the section has at least one aux
    556        * record (the section-definition aux).  Mark as SK_SECTION so
    557        * emit_coff regenerates the synthetic entry. */
    558       int is_section_sym = 0;
    559       if (sclass == IMAGE_SYM_CLASS_STATIC && value == 0 && naux >= 1) {
    560         const CSecRec* cs = &secs[sec_num - 1];
    561         u32 raw_nlen = 0;
    562         while (raw_nlen < 8 && cs->raw_name[raw_nlen] != '\0') ++raw_nlen;
    563         if (raw_nlen == nlen && memcmp(cs->raw_name, nm, nlen) == 0) {
    564           is_section_sym = 1;
    565         } else if (cs->raw_name[0] == '/') {
    566           /* Long-named section: compare the resolved name. */
    567           const char* rn;
    568           u32 rnlen;
    569           resolve_section_name(cs->raw_name, strtab, strtab_size, &rn, &rnlen);
    570           if (rnlen == nlen && memcmp(rn, nm, nlen) == 0) is_section_sym = 1;
    571         }
    572       }
    573 
    574       if (is_section_sym) {
    575         kind = SK_SECTION;
    576         sec_sym_primary[sec_num] = i + 1u;
    577       } else if (sclass == IMAGE_SYM_CLASS_SECTION) {
    578         kind = SK_SECTION;
    579       } else if (sclass == IMAGE_SYM_CLASS_LABEL) {
    580         kind = SK_NOTYPE;
    581       } else if ((type >> 8) == IMAGE_SYM_DTYPE_FUNCTION) {
    582         kind = SK_FUNC;
    583       } else if (type == IMAGE_SYM_TYPE_NULL) {
    584         kind = (bind == SB_LOCAL) ? SK_NOTYPE : SK_OBJ;
    585       } else {
    586         kind = SK_OBJ;
    587       }
    588     } else {
    589       compiler_panic(c, SRCLOC_NONE,
    590                      "read_coff: symbol section number %d out of range",
    591                      (int)sec_num);
    592     }
    593 
    594     /* WEAK_EXTERNAL primary: aux record carries TagIndex + Characteristics. */
    595     if (sclass == IMAGE_SYM_CLASS_WEAK_EXTERNAL) bind = SB_WEAK;
    596 
    597     Sym sn =
    598         nlen ? pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}) : 0;
    599     ObjSymId id = obj_symbol_ex(ob, sn, bind, vis, kind, target_sec, sym_value,
    600                                 sym_size, cmnalign);
    601     obj_sym_mark_referenced(ob, id);
    602     sym_to_obj[i] = id;
    603 
    604     /* Genuine WEAK_EXTERNAL alias declaration (IMAGE_WEAK_EXTERN_SEARCH_ALIAS):
    605      * record the fall-back symbol (aux TagIndex) by name so the linker can
    606      * resolve this weak symbol to its target directly. mingw x86_64 spells
    607      * `_setjmp` this way, aliasing `__intrinsic_setjmp` — a redirection the
    608      * link-time single-underscore heuristic can't derive. Other weak-external
    609      * search policies (kit's own SB_WEAK emit uses SEARCH_LIBRARY with a
    610      * self/zero TagIndex, i.e. "weak, no fallback") are left to that heuristic
    611      * and the plain SB_WEAK-undef path. */
    612     if (sclass == IMAGE_SYM_CLASS_WEAK_EXTERNAL && naux >= 1 && sn != 0) {
    613       const u8* aux = p + COFF_SYMBOL_SIZE;
    614       u32 tag_index = coff_rd_u32(aux + 0);
    615       u32 characteristics = coff_rd_u32(aux + 4);
    616       if (characteristics == IMAGE_WEAK_EXTERN_SEARCH_ALIAS &&
    617           tag_index < nsymbols && tag_index != i) {
    618         const u8* tp = sym_base + (u64)tag_index * COFF_SYMBOL_SIZE;
    619         const char* tnm;
    620         u32 tnlen;
    621         resolve_sym_name(tp, strtab, strtab_size, &tnm, &tnlen);
    622         if (tnlen != 0 && (tnlen != nlen || memcmp(tnm, nm, nlen) != 0)) {
    623           Sym target =
    624               pool_intern_slice(c->global, (Slice){.s = tnm, .len = tnlen});
    625           obj_set_weak_alias(ob, id, target);
    626         }
    627       }
    628     }
    629     i += 1u + naux;
    630   }
    631 
    632   /* ---- Step 4: stitch COMDAT groups from section-definition aux ----
    633    * Each COMDAT section has a STATIC primary symbol (the section
    634    * symbol) followed by one section-definition aux record.  Selection
    635    * != 0 marks the section as a COMDAT member; the signature symbol
    636    * is the section symbol itself (Number field's selection variant
    637    * controls dedup policy at link time). */
    638   for (u32 s = 1; s <= nsections; ++s) {
    639     u32 prim_plus1 = sec_sym_primary[s];
    640     if (!prim_plus1) continue;
    641     u32 prim = prim_plus1 - 1u;
    642     const CSecRec* cs = &secs[s - 1];
    643     if (!(cs->characteristics & IMAGE_SCN_LNK_COMDAT)) continue;
    644     const u8* p = sym_base + (u64)prim * COFF_SYMBOL_SIZE;
    645     u8 naux = p[17];
    646     if (!naux) continue;
    647     const u8* aux = p + COFF_SYMBOL_SIZE;
    648     /* Aux layout: Length(4), NumberOfRelocations(2), NumberOfLinenumbers(2),
    649      * CheckSum(4), Number(2), Selection(1), Unused(3). */
    650     u16 assoc_number = coff_rd_u16(aux + 12);
    651     u8 selection = aux[14];
    652     if (selection == 0) continue;
    653 
    654     ObjSymId sig = sym_to_obj[prim];
    655     const ObjSym* sigsym = obj_symbol_get(ob, sig);
    656     Sym gname = sigsym ? sigsym->name : 0;
    657     ObjGroupId gid = obj_group(ob, gname, sig, (u32)selection);
    658     obj_group_add_section(ob, gid, cs->obj_sec);
    659     obj_section_set_group(ob, cs->obj_sec, gid);
    660 
    661     /* ASSOCIATIVE: the COMDAT member is associated with another
    662      * section's group.  Add this section to that group's list too so
    663      * dead-strip keeps them paired. */
    664     if (selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE && assoc_number >= 1 &&
    665         (u32)assoc_number <= nsections) {
    666       u32 other_prim_plus1 = sec_sym_primary[assoc_number];
    667       if (other_prim_plus1) {
    668         u32 other_prim = other_prim_plus1 - 1u;
    669         const u8* op = sym_base + (u64)other_prim * COFF_SYMBOL_SIZE;
    670         if (op[17]) {
    671           const u8* oaux = op + COFF_SYMBOL_SIZE;
    672           u8 osel = oaux[14];
    673           if (osel != 0) {
    674             ObjSymId osig = sym_to_obj[other_prim];
    675             const ObjSym* osigsym = obj_symbol_get(ob, osig);
    676             Sym ogname = osigsym ? osigsym->name : 0;
    677             ObjGroupId ogid = obj_group(ob, ogname, osig, (u32)osel);
    678             obj_group_add_section(ob, ogid, cs->obj_sec);
    679           }
    680         }
    681       }
    682     }
    683   }
    684 
    685   /* ---- Step 5: per-section relocations ---- */
    686   for (u32 i = 0; i < nsections; ++i) {
    687     const CSecRec* s = &secs[i];
    688     if (!s->number_of_relocations) continue;
    689     u64 reloc_end = (u64)s->pointer_to_relocations +
    690                     (u64)s->number_of_relocations * (u64)COFF_RELOC_SIZE;
    691     if (reloc_end > (u64)len)
    692       compiler_panic(c, SRCLOC_NONE,
    693                      "read_coff: relocation table for section %u out of range",
    694                      i);
    695     const u8* rbase = data + s->pointer_to_relocations;
    696     for (u32 j = 0; j < s->number_of_relocations; ++j) {
    697       const u8* rp = rbase + (u64)j * COFF_RELOC_SIZE;
    698       u32 r_va = coff_rd_u32(rp + 0);
    699       u32 r_sym = coff_rd_u32(rp + 4);
    700       u16 r_type = coff_rd_u16(rp + 8);
    701 
    702       u32 kind = reloc_from(r_type);
    703       if (kind == (u32)-1)
    704         compiler_panic(c, SRCLOC_NONE,
    705                        "read_coff: unsupported reloc type %u for machine %#x",
    706                        (u32)r_type, (u32)machine);
    707 
    708       ObjSymId target = OBJ_SYM_NONE;
    709       if (r_sym < nsymbols) target = sym_to_obj[r_sym];
    710 
    711       /* COFF stores addends inline in the relocated field.  Fold those
    712        * bytes into Reloc.addend for the reloc kinds whose apply path
    713        * overwrites the field.  AMD64 REL32 also subtracts from a PC after
    714        * the relocated field: plain REL32 is relative to P+4, and REL32_N is
    715        * relative to P+N.  Record that convention as an implicit negative
    716        * addend so link_reloc_apply can stay format neutral. */
    717       /* ARM64 PAGEOFFSET_12L is one wire code for LDST{8,16,32,64,128}.
    718        * The per-arch translator returns R_AARCH64_LDST64_ABS_LO12_NC by
    719        * default; recover the actual access width from the patched LDR/
    720        * STR instruction's size field at bits [31:30] (and a SIMD/FP
    721        * extension via bit 26 + opc[23]) so the linker applies the right
    722        * scale.  Mismatch panics at apply-time with "misaligned
    723        * address" otherwise — see link_reloc.c. */
    724       if ((machine == IMAGE_FILE_MACHINE_ARM64 ||
    725            machine == IMAGE_FILE_MACHINE_ARM64EC) &&
    726           r_type == IMAGE_REL_ARM64_PAGEOFFSET_12L && s->size_of_raw_data &&
    727           (u64)r_va + 4u <= (u64)s->size_of_raw_data) {
    728         const u8* ibytes = data + s->pointer_to_raw_data + r_va;
    729         u32 instr = (u32)ibytes[0] | ((u32)ibytes[1] << 8) |
    730                     ((u32)ibytes[2] << 16) | ((u32)ibytes[3] << 24);
    731         u32 sz = (instr >> 30) & 0x3u;
    732         int is_simd = (instr >> 26) & 0x1u;
    733         if (is_simd && ((instr >> 23) & 0x1u)) {
    734           kind = R_AARCH64_LDST128_ABS_LO12_NC;
    735         } else {
    736           switch (sz) {
    737             case 0:
    738               kind = R_AARCH64_LDST8_ABS_LO12_NC;
    739               break;
    740             case 1:
    741               kind = R_AARCH64_LDST16_ABS_LO12_NC;
    742               break;
    743             case 2:
    744               kind = R_AARCH64_LDST32_ABS_LO12_NC;
    745               break;
    746             default:
    747               kind = R_AARCH64_LDST64_ABS_LO12_NC;
    748               break;
    749           }
    750         }
    751       }
    752 
    753       i64 addend = 0;
    754       int has_explicit = 0;
    755       if (machine == IMAGE_FILE_MACHINE_AMD64) {
    756         i64 inline_addend = 0;
    757         switch (r_type) {
    758           case IMAGE_REL_AMD64_ADDR64:
    759             if (coff_reloc_inline_addend(data, len, s, r_va, 8, &inline_addend))
    760               addend = inline_addend;
    761             break;
    762           case IMAGE_REL_AMD64_ADDR32:
    763             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    764               addend = inline_addend;
    765             break;
    766           case IMAGE_REL_AMD64_REL32:
    767             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    768               addend = inline_addend;
    769             addend -= 4;
    770             break;
    771           case IMAGE_REL_AMD64_REL32_1:
    772             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    773               addend = inline_addend;
    774             addend -= 1;
    775             break;
    776           case IMAGE_REL_AMD64_REL32_2:
    777             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    778               addend = inline_addend;
    779             addend -= 2;
    780             break;
    781           case IMAGE_REL_AMD64_REL32_3:
    782             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    783               addend = inline_addend;
    784             addend -= 3;
    785             break;
    786           case IMAGE_REL_AMD64_REL32_4:
    787             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    788               addend = inline_addend;
    789             addend -= 4;
    790             break;
    791           case IMAGE_REL_AMD64_REL32_5:
    792             if (coff_reloc_inline_addend(data, len, s, r_va, 4, &inline_addend))
    793               addend = inline_addend;
    794             addend -= 5;
    795             break;
    796           default:
    797             break;
    798         }
    799       }
    800 
    801       obj_reloc_ex(ob, s->obj_sec, r_va, (RelocKind)kind, target, addend,
    802                    has_explicit, 0);
    803     }
    804   }
    805 
    806   /* ---- Step 6: finalize and return ---- */
    807   obj_finalize(ob);
    808   return ob;
    809 }