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 }