kit

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

emit.c (26582B)


      1 /* PE/COFF relocatable .obj writer. Walks a finalized ObjBuilder and
      2  * emits a 64-bit little-endian relocatable object via the supplied
      3  * Writer. Counterpart to emit_elf / emit_macho.
      4  *
      5  * Layout strategy:
      6  *   1. plan COFF sections (one per kept obj section), assigning
      7  *      Characteristics, alignment, raw size, and per-section reloc
      8  *      counts;
      9  *   2. build the symbol table (synthesized per-section static symbols
     10  *      with section-definition aux records, plus file symbols and
     11  *      every ObjSym kept after sweep);
     12  *   3. build per-section relocation records via the per-arch
     13  *      translator (arch_for_compiler(c)->coff->reloc_to);
     14  *   4. assign file offsets:
     15  *        file header | section headers | (bytes + relocs)* | symtab | strtab
     16  *   5. write the file in that order.
     17  *
     18  * 64-bit little-endian only — IMAGE_FILE_MACHINE_AMD64 (x86_64) and
     19  * IMAGE_FILE_MACHINE_ARM64 (aarch64). Big-endian / ptr_size != 8 panic
     20  * at entry.
     21  *
     22  * Section name mapping policy: we pass the kit Section.name through
     23  * verbatim to the COFF Name field. Callers / readers are expected to
     24  * have stored COFF-shaped names (".text", ".rdata", ".tls$", etc.) at
     25  * the obj layer; emit_coff does not rewrite ELF-style spellings like
     26  * ".rodata" -> ".rdata". Names longer than 8 bytes spill into the
     27  * string table with the "/<decimal-offset>" encoding.
     28  *
     29  * Addend handling: COFF stores the addend inline in the patched bytes
     30  * (there is no addend field in IMAGE_RELOCATION). The ObjBuilder
     31  * caller is responsible for having written the addend into the section
     32  * bytes already — matching how MSVC / mingw emit. A nonzero
     33  * Reloc::addend with has_explicit_addend set is rejected here as a
     34  * known v1 limitation. */
     35 
     36 #include <string.h>
     37 
     38 #include "core/arena.h"
     39 #include "core/buf.h"
     40 #include "core/heap.h"
     41 #include "core/pool.h"
     42 #include "core/slice.h"
     43 #include "core/util.h"
     44 #include "obj/coff/coff.h"
     45 #include "obj/format.h"
     46 
     47 static int coff_rel32_absorbs_minus4(KitArchKind arch, RelocKind kind,
     48                                      i64 addend) {
     49   if (arch != KIT_ARCH_X86_64 || addend != -4) return 0;
     50   switch (kind) {
     51     case R_PC32:
     52     case R_REL32:
     53     case R_PLT32:
     54     case R_X64_PLT32:
     55     case R_X64_GOTPCREL:
     56     case R_X64_GOTPCRELX:
     57     case R_X64_REX_GOTPCRELX:
     58       return 1;
     59     default:
     60       return 0;
     61   }
     62 }
     63 
     64 /* ---- per-COFF-section plan record ---- */
     65 
     66 typedef struct CSec {
     67   /* IMAGE_SECTION_HEADER fields (little-endian-encoded at write time). */
     68   char name8[8];        /* Name field bytes; "/N" form if long name */
     69   u32 virtual_size;     /* nonzero for NOBITS (bss size) */
     70   u32 size_of_raw_data; /* zero for NOBITS */
     71   u32 pointer_to_raw_data;
     72   u32 pointer_to_relocations;
     73   u16 number_of_relocations;
     74   u32 characteristics; /* IMAGE_SCN_* | ALIGN nibble */
     75 
     76   /* Planning state. */
     77   u32 align;   /* in bytes, power of two */
     78   u32 obj_sec; /* originating ObjSecId */
     79   int is_nobits;
     80   const Buf* obj_bytes; /* NULL when nobits */
     81   u8* reloc_bytes;      /* arena-allocated, nreloc * 10 bytes */
     82   ObjGroupId group_id;  /* OBJ_GROUP_NONE if not in a group */
     83 } CSec;
     84 
     85 /* ---- emit ---- */
     86 
     87 static u32 log2_align(u32 a) {
     88   u32 r = 0;
     89   while ((1u << r) < a) ++r;
     90   return r;
     91 }
     92 
     93 /* Map kit section flags/sem to IMAGE_SCN_* Characteristics, leaving
     94  * the alignment nibble for the caller to OR in. */
     95 static u32 sec_characteristics(const Section* s, int in_group) {
     96   u32 r = 0;
     97   int is_bss = (s->kind == SEC_BSS) || (s->sem == SSEM_NOBITS);
     98   if (s->flags & SF_EXEC) {
     99     r |= IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE;
    100   } else if (is_bss) {
    101     r |= IMAGE_SCN_CNT_UNINITIALIZED_DATA;
    102   } else if (s->flags & SF_WRITE) {
    103     r |= IMAGE_SCN_CNT_INITIALIZED_DATA;
    104   } else if (s->flags & SF_ALLOC) {
    105     /* Read-only allocated data (.rdata). */
    106     r |= IMAGE_SCN_CNT_INITIALIZED_DATA;
    107   }
    108   if (s->flags & SF_ALLOC) r |= IMAGE_SCN_MEM_READ;
    109   if (s->flags & SF_WRITE) r |= IMAGE_SCN_MEM_WRITE;
    110   if (in_group) r |= IMAGE_SCN_LNK_COMDAT;
    111   /* When a reader stashed format-specific flag bits on a COFF-origin
    112    * section, OR them back in here. ext_type carries the raw
    113    * Characteristics value (or zero if no override); ext_flags is a
    114    * sibling bag for any bits the canonical mapping above would lose. */
    115   if (s->ext_kind == OBJ_EXT_COFF) {
    116     if (s->ext_type) {
    117       /* Preserve the raw characteristics verbatim — overrides the
    118        * canonical mapping. Keeps round-trip byte-stable for sections
    119        * carrying CNT_INFO / LNK_REMOVE / MEM_DISCARDABLE / etc. */
    120       r = s->ext_type & ~IMAGE_SCN_ALIGN_MASK;
    121     }
    122     r |= s->ext_flags;
    123   }
    124   return r;
    125 }
    126 
    127 /* Append `len` bytes of `s` followed by a single NUL to `b`, returning
    128  * the offset at which `s` was placed. Dedupe linearly — strtabs are
    129  * small enough that this is fine without a hash table, and the
    130  * dedupe matches what binutils / llvm-objcopy emit. Mirror of the
    131  * helper in elf_emit. */
    132 static u32 strtab_add(Buf* b, const char* s, u32 len) {
    133   if (len == 0) return 0;
    134   u32 total = buf_pos(b);
    135   if (total > len) {
    136     u8 stack[256];
    137     u8* tmp =
    138         total <= sizeof stack ? stack : (u8*)b->heap->alloc(b->heap, total, 1);
    139     if (tmp) {
    140       buf_flatten(b, tmp);
    141       /* Skip the first 4 bytes (the size-prefix placeholder) when
    142        * searching for matches. */
    143       u32 start = COFF_STRTAB_SIZE_FIELD_BYTES;
    144       if (total > start + len) {
    145         for (u32 i = start; i + len < total; ++i) {
    146           if (tmp[i + len] == 0 && memcmp(tmp + i, s, len) == 0) {
    147             if (tmp != stack) b->heap->free(b->heap, tmp, total);
    148             return i;
    149           }
    150         }
    151       }
    152       if (tmp != stack) b->heap->free(b->heap, tmp, total);
    153     }
    154   }
    155   u32 off = total;
    156   buf_write(b, s, len);
    157   {
    158     u8 z = 0;
    159     buf_write(b, &z, 1);
    160   }
    161   return off;
    162 }
    163 
    164 /* Encode an 8-byte Name field. If the name fits in 8 bytes, copy
    165  * verbatim and zero-pad. Otherwise allocate the name in `strtab` and
    166  * write "/<decimal-offset>" (NUL-padded to 8 bytes). */
    167 static void encode_name8(char out[8], const char* name, u32 nlen, Buf* strtab) {
    168   memset(out, 0, 8);
    169   if (nlen <= 8) {
    170     if (nlen) memcpy(out, name, nlen);
    171     return;
    172   }
    173   u32 off = strtab_add(strtab, name, nlen);
    174   /* "/<decimal-offset>" — up to 7 decimal digits leaves room for the
    175    * leading slash within 8 bytes. COFF .obj strtabs are < 1 MiB in
    176    * practice, so 7 digits is plenty. */
    177   char tmp[16];
    178   int n = 0;
    179   tmp[n++] = '/';
    180   /* Decimal-format off into tmp+1. */
    181   char dig[12];
    182   int d = 0;
    183   u32 v = off;
    184   if (v == 0) {
    185     dig[d++] = '0';
    186   } else {
    187     while (v) {
    188       dig[d++] = (char)('0' + (v % 10u));
    189       v /= 10u;
    190     }
    191   }
    192   while (d > 0 && n < (int)sizeof tmp) tmp[n++] = dig[--d];
    193   if (n > 8) n = 8;
    194   memcpy(out, tmp, (size_t)n);
    195 }
    196 
    197 /* Write one 18-byte IMAGE_SYMBOL record into `dst`. */
    198 static void wr_sym(u8* dst, const char ShortName[8], u32 Zeroes, u32 Offset,
    199                    u32 Value, i16 SectionNumber, u16 Type, u8 StorageClass,
    200                    u8 NumberOfAuxSymbols) {
    201   if (Zeroes == 0 && Offset != 0) {
    202     /* LongName form: 4 zero bytes then 4-byte LE strtab offset. */
    203     memset(dst, 0, 4);
    204     wr_u32_le(dst + 4, Offset);
    205   } else {
    206     memcpy(dst, ShortName, 8);
    207   }
    208   wr_u32_le(dst + 8, Value);
    209   wr_u16_le(dst + 12, (u16)SectionNumber);
    210   wr_u16_le(dst + 14, Type);
    211   dst[16] = StorageClass;
    212   dst[17] = NumberOfAuxSymbols;
    213 }
    214 
    215 /* Write a section-definition aux record (18 bytes). */
    216 static void wr_aux_secdef(u8* dst, u32 Length, u16 NumberOfRelocations,
    217                           u16 NumberOfLinenumbers, u32 CheckSum, u16 Number,
    218                           u8 Selection) {
    219   wr_u32_le(dst + 0, Length);
    220   wr_u16_le(dst + 4, NumberOfRelocations);
    221   wr_u16_le(dst + 6, NumberOfLinenumbers);
    222   wr_u32_le(dst + 8, CheckSum);
    223   wr_u16_le(dst + 12, Number);
    224   dst[14] = Selection;
    225   dst[15] = 0;
    226   dst[16] = 0;
    227   dst[17] = 0;
    228 }
    229 
    230 /* Write a weak-externals aux record (18 bytes). */
    231 static void wr_aux_weak(u8* dst, u32 TagIndex, u32 Characteristics) {
    232   wr_u32_le(dst + 0, TagIndex);
    233   wr_u32_le(dst + 4, Characteristics);
    234   memset(dst + 8, 0, 10);
    235 }
    236 
    237 /* Look up the pool-interned string for a Sym. */
    238 static const char* sym_to_str(Compiler* c, Sym n, u32* len_out) {
    239   Slice sl = pool_slice(c->global, n);
    240   const char* s = sl.s;
    241   if (!s) {
    242     *len_out = 0;
    243     return "";
    244   }
    245   *len_out = (u32)sl.len;
    246   return s;
    247 }
    248 
    249 void emit_coff(Compiler* c, ObjBuilder* ob, Writer* w) {
    250   Heap* h = (Heap*)c->ctx->heap;
    251 
    252   /* Tombstone sweep — see obj_sweep_dead. */
    253   obj_sweep_dead(ob);
    254 
    255   /* ---- target validation ----------------------------------------- */
    256   const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF);
    257   const ObjCoffArchOps* coff =
    258       fmt && fmt->coff_arch ? fmt->coff_arch(c->target.arch) : NULL;
    259   if (!coff || !coff->reloc_to) {
    260     compiler_panic(c, SRCLOC_NONE, "emit_coff: unsupported target arch %u",
    261                    (u32)c->target.arch);
    262   }
    263   u16 machine = coff->machine;
    264   u32 (*reloc_to)(u32) = coff->reloc_to;
    265   if (c->target.big_endian) {
    266     compiler_panic(c, SRCLOC_NONE, "emit_coff: big-endian COFF not supported");
    267   }
    268   if (c->target.ptr_size != 8) {
    269     compiler_panic(c, SRCLOC_NONE, "emit_coff: ptr_size %u (expected 8)",
    270                    (u32)c->target.ptr_size);
    271   }
    272 
    273   /* ---- pass 1: plan sections ------------------------------------- */
    274   u32 nobjsec = obj_section_count(ob);
    275   CSec* secs = arena_zarray(c->scratch, CSec, nobjsec ? nobjsec : 1);
    276   u32* obj_to_coff = arena_zarray(c->scratch, u32, nobjsec ? nobjsec : 1);
    277   u32 nsecs = 0;
    278 
    279   /* String table — leading 4-byte size placeholder. Real strings start
    280    * at offset 4. */
    281   Buf strtab;
    282   buf_init(&strtab, h);
    283   {
    284     u8 zero4[COFF_STRTAB_SIZE_FIELD_BYTES] = {0, 0, 0, 0};
    285     buf_write(&strtab, zero4, COFF_STRTAB_SIZE_FIELD_BYTES);
    286   }
    287 
    288   for (u32 i = 1; i < nobjsec; ++i) {
    289     const Section* s = obj_section_get(ob, i);
    290     if (s->removed) continue;
    291     /* Skip ELF-style synthetic sections (a reader from another format
    292      * may have surfaced them) — COFF stores symtab/strtab/relocs
    293      * out-of-band, not as named sections. */
    294     if (s->sem == SSEM_SYMTAB || s->sem == SSEM_STRTAB || s->sem == SSEM_RELA ||
    295         s->sem == SSEM_REL || s->sem == SSEM_GROUP) {
    296       continue;
    297     }
    298 
    299     CSec* cs = &secs[nsecs];
    300     u32 nlen;
    301     const char* nm = sym_to_str(c, s->name, &nlen);
    302     encode_name8(cs->name8, nm, nlen, &strtab);
    303 
    304     cs->obj_sec = i;
    305     cs->group_id = s->group_id;
    306     cs->align = s->align ? s->align : 1;
    307 
    308     int in_group = (s->group_id != OBJ_GROUP_NONE);
    309     u32 ch = sec_characteristics(s, in_group);
    310     /* Alignment lives in bits 20..23. Cap at log2(8192)=13 -> nibble
    311      * value 14 (IMAGE_SCN_ALIGN_8192BYTES). */
    312     u32 lg = log2_align(cs->align);
    313     if (lg > 13) lg = 13;
    314     ch &= ~IMAGE_SCN_ALIGN_MASK;
    315     ch |= IMAGE_SCN_ALIGN_FROM_LOG2(lg);
    316     cs->characteristics = ch;
    317 
    318     if (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) {
    319       cs->is_nobits = 1;
    320       cs->virtual_size = s->bss_size;
    321       cs->size_of_raw_data = 0;
    322       cs->obj_bytes = NULL;
    323     } else {
    324       cs->is_nobits = 0;
    325       cs->virtual_size = 0;
    326       cs->size_of_raw_data = s->bytes.total;
    327       cs->obj_bytes = &s->bytes;
    328     }
    329 
    330     obj_to_coff[i] = nsecs + 1; /* 1-based; matches SectionNumber. */
    331     nsecs++;
    332   }
    333 
    334   /* ---- pass 2: count and assign per-section reloc counts --------- */
    335   /* COFF stores NumberOfRelocations as u16; sections with > 65535
    336    * relocs use the IMAGE_SCN_LNK_NRELOC_OVFL extension which we don't
    337    * implement in v1. Panic if any single section exceeds the limit. */
    338   u32 total_relocs = obj_reloc_total(ob);
    339   for (u32 ci = 0; ci < nsecs; ++ci) {
    340     CSec* cs = &secs[ci];
    341     u32 nr = obj_reloc_count(ob, cs->obj_sec);
    342     if (nr > 0xFFFFu) {
    343       compiler_panic(c, SRCLOC_NONE,
    344                      "emit_coff: section %u has %u relocs (max 65535)",
    345                      (u32)cs->obj_sec, nr);
    346     }
    347     cs->number_of_relocations = (u16)nr;
    348   }
    349 
    350   /* ---- pass 3: build the symbol table ---------------------------- */
    351   /* Count ObjSyms (incl. tombstoned — we'll skip those when emitting). */
    352   u32 nobjsym = 0;
    353   {
    354     ObjSymIter* it = obj_symiter_new(ob);
    355     ObjSymEntry e;
    356     while (obj_symiter_next(it, &e)) ++nobjsym;
    357     obj_symiter_free(it);
    358   }
    359 
    360   /* Upper bound on symbol-table records (including aux slots):
    361    *   - 2 records per section symbol (primary + 1 aux secdef)
    362    *   - 2 records per ObjSym (primary + up to 1 weak aux)
    363    *   - +2 spare for safety
    364    * Worst case is generous; we trim by tracking nrecords as we emit. */
    365   u32 max_records = 2u * nsecs + 2u * nobjsym + 4u;
    366   u8* symtab =
    367       (u8*)arena_zarray(c->scratch, u8, (size_t)COFF_SYMBOL_SIZE * max_records);
    368   u32 nrecords = 0;
    369 
    370   /* obj_id -> COFF symbol index (including aux slots). Index 0 is
    371    * reserved as "none" in our internal map (a real COFF symbol may
    372    * legitimately live at index 0, but no ObjSym ever maps there since
    373    * we never put OBJ_SYM_NONE through). */
    374   u32* sym_to_coff = arena_zarray(c->scratch, u32, nobjsym + 2);
    375 
    376   /* Section symbols first — one STATIC per kept obj section, each
    377    * followed by a SECTION DEFINITION aux. Reloc-against-section in
    378    * other tools' output uses these; emitting them unconditionally
    379    * matches what clang / mingw emit and gives readers a stable target. */
    380   u32* secsym_index = arena_zarray(c->scratch, u32, nsecs + 1);
    381   for (u32 ci = 0; ci < nsecs; ++ci) {
    382     CSec* cs = &secs[ci];
    383     char short_name[8];
    384     /* The section symbol's name is the section's own name (truncated
    385      * to 8 bytes — section symbols never use the strtab spill form in
    386      * MSVC/clang output). */
    387     memcpy(short_name, cs->name8, 8);
    388 
    389     u8* slot = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    390     wr_sym(slot, short_name, /*Zeroes*/ 1, /*Offset*/ 0,
    391            /*Value*/ 0,
    392            /*SectionNumber*/ (i16)(ci + 1),
    393            /*Type*/ IMAGE_SYM_TYPE_NULL,
    394            /*StorageClass*/ IMAGE_SYM_CLASS_STATIC,
    395            /*NumberOfAuxSymbols*/ 1);
    396     secsym_index[ci] = nrecords;
    397     nrecords++;
    398 
    399     /* Section-definition aux. For COMDAT members we encode the
    400      * Selection from the group; default to SELECT_ANY which is what
    401      * gcc/clang emit unless the user requests a specific selection
    402      * mode. The associated-section Number is left at 0 (kit does
    403      * not produce associative-COMDAT chains today). */
    404     u8 selection = 0;
    405     if (cs->group_id != OBJ_GROUP_NONE) {
    406       const ObjGroup* g = obj_group_get(ob, cs->group_id);
    407       if (g && !g->removed) {
    408         selection = g->flags ? (u8)IMAGE_COMDAT_SELECT_ANY
    409                              : (u8)IMAGE_COMDAT_SELECT_ANY;
    410       }
    411     }
    412     u8* aux = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    413     wr_aux_secdef(aux, /*Length*/ cs->size_of_raw_data,
    414                   /*NumberOfRelocations*/ cs->number_of_relocations,
    415                   /*NumberOfLinenumbers*/ 0,
    416                   /*CheckSum*/ 0,
    417                   /*Number*/ 0,
    418                   /*Selection*/ selection);
    419     nrecords++;
    420   }
    421 
    422   /* File / regular symbols. */
    423   {
    424     ObjSymIter* it = obj_symiter_new(ob);
    425     ObjSymEntry e;
    426     while (obj_symiter_next(it, &e)) {
    427       const ObjSym* s = e.sym;
    428       if (s->removed) continue;
    429       if (s->kind == SK_IFUNC) {
    430         compiler_panic(c, SRCLOC_NONE,
    431                        "emit_coff: SK_IFUNC has no PE/COFF representation");
    432       }
    433       /* Don't re-emit SK_SECTION symbols — section symbols are
    434        * synthesized above. Map any input-side SK_SECTION onto the
    435        * already-emitted one. */
    436       if (s->kind == SK_SECTION) {
    437         if (s->section_id && s->section_id < nobjsec) {
    438           u32 ci = obj_to_coff[s->section_id];
    439           if (ci) sym_to_coff[e.id] = secsym_index[ci - 1];
    440         }
    441         continue;
    442       }
    443 
    444       u32 nlen;
    445       const char* nm = sym_to_str(c, s->name, &nlen);
    446 
    447       if (s->kind == SK_FILE) {
    448         /* File symbol: name ".file" (short), section IMAGE_SYM_DEBUG,
    449          * storage class FILE, followed by aux records carrying the
    450          * NUL-padded file path (18 bytes per aux). */
    451         u32 file_len = nlen;
    452         u32 naux =
    453             file_len ? (file_len + COFF_AUX_FILE_SIZE - 1u) / COFF_AUX_FILE_SIZE
    454                      : 1u;
    455         char short_name[8] = {'.', 'f', 'i', 'l', 'e', 0, 0, 0};
    456         u8* slot = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    457         wr_sym(slot, short_name, 1, 0, /*Value*/ 0,
    458                /*SectionNumber*/ (i16)IMAGE_SYM_DEBUG,
    459                /*Type*/ IMAGE_SYM_TYPE_NULL,
    460                /*StorageClass*/ IMAGE_SYM_CLASS_FILE,
    461                /*NumberOfAuxSymbols*/ (u8)naux);
    462         sym_to_coff[e.id] = nrecords;
    463         nrecords++;
    464         for (u32 a = 0; a < naux; ++a) {
    465           u8* aux = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    466           memset(aux, 0, COFF_AUX_FILE_SIZE);
    467           u32 off = a * COFF_AUX_FILE_SIZE;
    468           u32 copy = file_len > off ? file_len - off : 0;
    469           if (copy > COFF_AUX_FILE_SIZE) copy = COFF_AUX_FILE_SIZE;
    470           if (copy) memcpy(aux, nm + off, copy);
    471           nrecords++;
    472         }
    473         continue;
    474       }
    475 
    476       /* Regular symbol. */
    477       char short_name[8];
    478       u32 zeroes = 1, offset = 0;
    479       memset(short_name, 0, 8);
    480       if (nlen <= 8) {
    481         if (nlen) memcpy(short_name, nm, nlen);
    482       } else {
    483         zeroes = 0;
    484         offset = strtab_add(&strtab, nm, nlen);
    485       }
    486 
    487       i16 section_number = 0;
    488       u32 value = 0;
    489       u8 storage_class = IMAGE_SYM_CLASS_NULL;
    490       u16 type = IMAGE_SYM_TYPE_NULL;
    491       u8 naux = 0;
    492       int emit_weak_aux = 0;
    493 
    494       switch (s->kind) {
    495         case SK_ABS:
    496           section_number = (i16)IMAGE_SYM_ABSOLUTE;
    497           value = (u32)s->value;
    498           break;
    499         case SK_COMMON:
    500           /* COFF lacks a per-common alignment field; encode size in
    501            * Value with SectionNumber=UNDEFINED and rely on the linker
    502            * to pick a default alignment. (kit's frontend uses
    503            * COMMON only via __attribute__((common)) which is rare on
    504            * PE/COFF targets.) */
    505           section_number = (i16)IMAGE_SYM_UNDEFINED;
    506           value = (u32)s->size;
    507           break;
    508         default:
    509           if (s->section_id == OBJ_SEC_NONE) {
    510             section_number = (i16)IMAGE_SYM_UNDEFINED;
    511             value = 0;
    512           } else if (s->section_id < nobjsec && obj_to_coff[s->section_id]) {
    513             section_number = (i16)obj_to_coff[s->section_id];
    514             value = (u32)s->value;
    515           } else {
    516             section_number = (i16)IMAGE_SYM_UNDEFINED;
    517             value = 0;
    518           }
    519           break;
    520       }
    521 
    522       if (s->kind == SK_FUNC) type = (u16)COFF_SYM_TYPE_FUNCTION;
    523 
    524       switch (s->bind) {
    525         case SB_LOCAL:
    526           storage_class = IMAGE_SYM_CLASS_STATIC;
    527           break;
    528         case SB_GLOBAL:
    529           storage_class = IMAGE_SYM_CLASS_EXTERNAL;
    530           break;
    531         case SB_WEAK:
    532           /* mingw / clang spell weak as EXTERNAL with a WeakExternal
    533            * aux that points at the fallback symbol. kit's obj layer
    534            * doesn't carry a separate fallback symbol today, so we emit
    535            * a self-referential weak aux (TagIndex=0) which the linker
    536            * treats as "weak, no fallback" — equivalent to ELF STB_WEAK. */
    537           storage_class = IMAGE_SYM_CLASS_WEAK_EXTERNAL;
    538           emit_weak_aux = 1;
    539           naux = 1;
    540           break;
    541         default:
    542           storage_class = IMAGE_SYM_CLASS_STATIC;
    543           break;
    544       }
    545 
    546       u8* slot = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    547       wr_sym(slot, short_name, zeroes, offset, value, section_number, type,
    548              storage_class, naux);
    549       sym_to_coff[e.id] = nrecords;
    550       nrecords++;
    551       if (emit_weak_aux) {
    552         u8* aux = symtab + (size_t)nrecords * COFF_SYMBOL_SIZE;
    553         wr_aux_weak(aux, /*TagIndex*/ 0,
    554                     /*Characteristics*/ IMAGE_WEAK_EXTERN_SEARCH_LIBRARY);
    555         nrecords++;
    556       }
    557     }
    558     obj_symiter_free(it);
    559   }
    560 
    561   /* ---- pass 4: build per-section relocation tables --------------- */
    562   for (u32 ci = 0; ci < nsecs; ++ci) {
    563     CSec* cs = &secs[ci];
    564     u32 nr = cs->number_of_relocations;
    565     if (!nr) continue;
    566     u8* buf = (u8*)arena_alloc(c->scratch, (size_t)COFF_RELOC_SIZE * nr,
    567                                _Alignof(u32));
    568     u32 j = 0;
    569     for (u32 ri = 0; ri < total_relocs; ++ri) {
    570       const Reloc* r = obj_reloc_at(ob, ri);
    571       if (r->removed) continue;
    572       if (r->section_id != cs->obj_sec) continue;
    573       if (r->sym == OBJ_SYM_NONE) {
    574         compiler_panic(c, SRCLOC_NONE,
    575                        "emit_coff: reloc without symbol not supported "
    576                        "(sec=%u offset=%u kind=%u)",
    577                        (u32)r->section_id, (u32)r->offset, (u32)r->kind);
    578       }
    579       if (r->has_explicit_addend && r->addend != 0 &&
    580           !coff_rel32_absorbs_minus4(c->target.arch, (RelocKind)r->kind,
    581                                      r->addend)) {
    582         /* v1 limitation: COFF carries the addend in the patched bytes,
    583          * and we don't currently mutate the obj's section bytes to
    584          * encode a separate explicit addend. kit's MCEmitter writes
    585          * the addend inline for COFF targets, so this branch only
    586          * fires for inputs synthesized by external tools. */
    587         compiler_panic(c, SRCLOC_NONE,
    588                        "emit_coff: explicit nonzero addend not supported "
    589                        "(sec=%u offset=%u kind=%u addend=%lld)",
    590                        (u32)r->section_id, (u32)r->offset, (u32)r->kind,
    591                        (long long)r->addend);
    592       }
    593       u32 wire = reloc_to(r->kind);
    594       /* Both arch translators use 0 (IMAGE_REL_*_ABSOLUTE) as the
    595        * unsupported-input sentinel; treat that as a panic unless the
    596        * input really is R_NONE. */
    597       if (wire == 0 && r->kind != R_NONE) {
    598         compiler_panic(c, SRCLOC_NONE,
    599                        "emit_coff: unsupported relocation kind %u for arch %u",
    600                        (u32)r->kind, (u32)c->target.arch);
    601       }
    602       u32 sym_idx = sym_to_coff[r->sym];
    603       u8* slot = buf + (size_t)j * COFF_RELOC_SIZE;
    604       wr_u32_le(slot + 0, r->offset);
    605       wr_u32_le(slot + 4, sym_idx);
    606       wr_u16_le(slot + 8, (u16)wire);
    607       ++j;
    608     }
    609     cs->reloc_bytes = buf;
    610     /* If a tombstoned reloc was skipped between count and emit, j may
    611      * be less than nr; trust the latter count for the wire field. */
    612     if (j != nr) cs->number_of_relocations = (u16)j;
    613   }
    614 
    615   /* ---- pass 5: assign file offsets ------------------------------- */
    616   /* Layout:
    617    *   [file header] [section headers] [per-section: bytes, relocs]*
    618    *   [symbol table] [string table] */
    619   u64 cur =
    620       (u64)COFF_FILE_HEADER_SIZE + (u64)COFF_SECTION_HEADER_SIZE * (u64)nsecs;
    621 
    622   for (u32 ci = 0; ci < nsecs; ++ci) {
    623     CSec* cs = &secs[ci];
    624     /* Raw data offset. NOBITS contributes nothing on disk. */
    625     if (cs->is_nobits || cs->size_of_raw_data == 0) {
    626       cs->pointer_to_raw_data = 0;
    627     } else {
    628       cur = ALIGN_UP(cur, (u64)cs->align);
    629       cs->pointer_to_raw_data = (u32)cur;
    630       cur += cs->size_of_raw_data;
    631     }
    632     /* Reloc table. COFF doesn't mandate alignment for the reloc array,
    633      * but llvm and binutils emit them naturally byte-packed; we 4-align
    634      * for tidiness. */
    635     if (cs->number_of_relocations) {
    636       cur = ALIGN_UP(cur, (u64)4);
    637       cs->pointer_to_relocations = (u32)cur;
    638       cur += (u64)cs->number_of_relocations * COFF_RELOC_SIZE;
    639     } else {
    640       cs->pointer_to_relocations = 0;
    641     }
    642   }
    643 
    644   cur = ALIGN_UP(cur, (u64)4);
    645   u64 symtab_off = cur;
    646   cur += (u64)nrecords * COFF_SYMBOL_SIZE;
    647 
    648   /* String table starts immediately after the symtab. Patch the 4-byte
    649    * size prefix (inclusive). */
    650   u32 strtab_size = buf_pos(&strtab);
    651   /* The size field is part of the on-disk strtab and is the total
    652    * inclusive byte count. Patch it now. */
    653   {
    654     u8 sz_le[4];
    655     wr_u32_le(sz_le, strtab_size);
    656     /* Buf doesn't expose in-place patch; flatten, patch, re-emit when
    657      * we write. Just remember the value. */
    658     (void)sz_le;
    659   }
    660   u64 strtab_off = cur;
    661   cur += strtab_size;
    662 
    663   /* ---- pass 6: write the file ------------------------------------ */
    664   kit_writer_seek(w, 0);
    665 
    666   /* IMAGE_FILE_HEADER */
    667   coff_wr_u16(w, machine);
    668   coff_wr_u16(w, (u16)nsecs);
    669   coff_wr_u32(w, 0); /* TimeDateStamp: reproducible */
    670   coff_wr_u32(w, (u32)symtab_off);
    671   coff_wr_u32(w, nrecords);
    672   coff_wr_u16(w, 0); /* SizeOfOptionalHeader: 0 for .obj */
    673   coff_wr_u16(w, IMAGE_FILE_LARGE_ADDRESS_AWARE);
    674 
    675   /* Section headers — one 40-byte block immediately after the file
    676    * header. */
    677   for (u32 ci = 0; ci < nsecs; ++ci) {
    678     const CSec* cs = &secs[ci];
    679     kit_writer_write(w, cs->name8, 8);
    680     coff_wr_u32(w, cs->virtual_size);
    681     coff_wr_u32(w, 0); /* VirtualAddress: 0 for .obj */
    682     coff_wr_u32(w, cs->size_of_raw_data);
    683     coff_wr_u32(w, cs->pointer_to_raw_data);
    684     coff_wr_u32(w, cs->pointer_to_relocations);
    685     coff_wr_u32(w, 0); /* PointerToLinenumbers: 0 */
    686     coff_wr_u16(w, cs->number_of_relocations);
    687     coff_wr_u16(w, 0); /* NumberOfLinenumbers: 0 */
    688     coff_wr_u32(w, cs->characteristics);
    689   }
    690 
    691   /* Section bytes + relocs (interleaved). */
    692   for (u32 ci = 0; ci < nsecs; ++ci) {
    693     const CSec* cs = &secs[ci];
    694     if (!cs->is_nobits && cs->size_of_raw_data && cs->obj_bytes) {
    695       kit_writer_seek(w, cs->pointer_to_raw_data);
    696       u32 sz = cs->obj_bytes->total;
    697       u8* tmp = (u8*)h->alloc(h, sz ? sz : 1, 1);
    698       if (sz) buf_flatten(cs->obj_bytes, tmp);
    699       kit_writer_write(w, tmp, sz);
    700       h->free(h, tmp, sz ? sz : 1);
    701     }
    702     if (cs->number_of_relocations && cs->reloc_bytes) {
    703       kit_writer_seek(w, cs->pointer_to_relocations);
    704       kit_writer_write(w, cs->reloc_bytes,
    705                        (size_t)cs->number_of_relocations * COFF_RELOC_SIZE);
    706     }
    707   }
    708 
    709   /* Symbol table. */
    710   kit_writer_seek(w, symtab_off);
    711   kit_writer_write(w, symtab, (size_t)nrecords * COFF_SYMBOL_SIZE);
    712 
    713   /* String table: 4-byte total size (inclusive) followed by the body.
    714    * `strtab` was initialized with 4 placeholder zero bytes; rewrite
    715    * them with the real size before flushing. */
    716   {
    717     u8* flat = (u8*)arena_alloc(c->scratch, strtab_size ? strtab_size : 1, 1);
    718     if (strtab_size) buf_flatten(&strtab, flat);
    719     /* Patch the 4-byte size prefix in place. */
    720     if (strtab_size >= COFF_STRTAB_SIZE_FIELD_BYTES) {
    721       wr_u32_le(flat, strtab_size);
    722     }
    723     kit_writer_seek(w, strtab_off);
    724     kit_writer_write(w, flat, strtab_size);
    725   }
    726   buf_fini(&strtab);
    727 }