read_image.c (15957B)
1 /* PE32+ linked-image reader. Peer of read_elf_image / read_macho_image: 2 * parses a linked Windows executable (.exe) or DLL (.dll) into the neutral 3 * ObjImage view that kit_obj_open / objdump consume — segments, entry point, 4 * image base, dependencies + imports, dynamic symbols (exports + imports), 5 * and dynamic relocations (base relocs). A full section/symbol view is 6 * populated through the ObjBuilder Section table as well, so -h / -s / -d 7 * work the same way they do for ELF / Mach-O images. 8 * 9 * Dispatched from read_coff on the DOS 'MZ' magic (read.c). Handles both 10 * subkinds: IMAGE_FILE_DLL clear -> OBJ_KIND_EXEC, set -> OBJ_KIND_DYN. 11 * 12 * Leniency: truncated *core* headers (DOS / PE sig / file / optional / 13 * section table) panic -> the kit_obj_open setjmp turns that into 14 * KIT_MALFORMED. Malformed *sub-tables* (export / import / base-reloc 15 * directories) are skipped, yielding a partial-but-useful inspection view, 16 * matching read_elf_image / read_macho_image. */ 17 18 #include <kit/cg.h> 19 #include <kit/object.h> /* KIT_OBJ_RAW_PE_* reserved tags */ 20 #include <string.h> 21 22 #include "core/arena.h" 23 #include "core/heap.h" 24 #include "core/pool.h" 25 #include "core/slice.h" 26 #include "obj/coff/coff.h" 27 #include "obj/coff/read_util.h" 28 #include "obj/format.h" 29 30 static Sym intern(Compiler* c, const char* s, u32 n) { 31 return n ? pool_intern_slice(c->global, (Slice){.s = s, .len = n}) : 0; 32 } 33 34 /* ---- exports -> dynsyms + soname ---- 35 * Mirrors read_coff_dso's export-directory walk, but emits ObjImageSym 36 * entries (defined, value = ImageBase + func RVA) and sets the DLL's own 37 * Name as the image soname. Lenient: any out-of-range sub-table aborts the 38 * export view rather than panicking. */ 39 static void read_pe_exports(Compiler* c, ObjImage* im, const u8* data, 40 size_t len, const u8* shdrs, u16 nsec, 41 const u8* data_dir, u32 num_dirs, u64 image_base) { 42 if ((u32)IMAGE_DIRECTORY_ENTRY_EXPORT >= num_dirs) return; 43 const u8* dd = 44 data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT * COFF_DATA_DIRECTORY_SIZE; 45 u32 export_rva = coff_rd_u32(dd); 46 u32 export_size = coff_rd_u32(dd + 4); 47 if (!export_rva || !export_size) return; 48 49 u64 exp_off; 50 if (!coff_rva_to_offset(shdrs, nsec, export_rva, len, &exp_off)) return; 51 if (exp_off + COFF_EXPORT_DIR_SIZE > len) return; 52 const u8* ed = data + exp_off; 53 u32 name_rva = coff_rd_u32(ed + 12); 54 u32 num_funcs = coff_rd_u32(ed + 20); 55 u32 num_names = coff_rd_u32(ed + 24); 56 u32 eat_rva = coff_rd_u32(ed + 28); 57 u32 ent_rva = coff_rd_u32(ed + 32); 58 u32 ord_rva = coff_rd_u32(ed + 36); 59 60 /* soname = the DLL's own Name (DT_SONAME / LC_ID_DYLIB analogue). */ 61 if (name_rva) { 62 u64 noff; 63 if (coff_rva_to_offset(shdrs, nsec, name_rva, len, &noff)) { 64 const char* dn; 65 u32 dl = coff_read_cstr(data, len, noff, &dn); 66 if (dl) obj_image_set_soname(im, intern(c, dn, dl)); 67 } 68 } 69 70 if (!num_names) return; 71 u64 eat_off, ent_off, ord_off; 72 if (!coff_rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off)) return; 73 if (!coff_rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off)) return; 74 if (!coff_rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off)) return; 75 if (ent_off + (u64)num_names * 4u > len || 76 ord_off + (u64)num_names * 2u > len) 77 return; 78 if (eat_off + (u64)num_funcs * 4u > len) return; 79 80 for (u32 i = 0; i < num_names; ++i) { 81 u32 nrva = coff_rd_u32(data + ent_off + (u64)i * 4u); 82 u16 ord = coff_rd_u16(data + ord_off + (u64)i * 2u); 83 if (ord >= num_funcs) continue; /* malformed; skip */ 84 u32 func_rva = coff_rd_u32(data + eat_off + (u64)ord * 4u); 85 u64 noff; 86 if (!coff_rva_to_offset(shdrs, nsec, nrva, len, &noff)) continue; 87 const char* en; 88 u32 el = coff_read_cstr(data, len, noff, &en); 89 if (!el) continue; 90 91 ObjImageSym ds; 92 memset(&ds, 0, sizeof ds); 93 ds.name = intern(c, en, el); 94 ds.bind = SB_GLOBAL; 95 ds.kind = 96 SK_FUNC; /* forwarders point at the export-dir string; still SK_FUNC */ 97 ds.section = OBJ_SEC_NONE; 98 ds.value = image_base + func_rva; 99 obj_image_add_dynsym(im, &ds); 100 } 101 } 102 103 /* ---- imports -> deps + undefined dynsyms ---- 104 * Walks the import directory descriptors. Each provider DLL becomes one 105 * ObjImageDep carrying its imported-name list; every by-name import also 106 * lands as an undefined ObjImageSym so -T lists imports like ELF .dynsym. 107 * By-ordinal imports are not named in v1 and are skipped. */ 108 static void read_pe_imports(Compiler* c, ObjImage* im, const u8* data, 109 size_t len, const u8* shdrs, u16 nsec, 110 const u8* data_dir, u32 num_dirs) { 111 if ((u32)IMAGE_DIRECTORY_ENTRY_IMPORT >= num_dirs) return; 112 const u8* dd = 113 data_dir + IMAGE_DIRECTORY_ENTRY_IMPORT * COFF_DATA_DIRECTORY_SIZE; 114 u32 imp_rva = coff_rd_u32(dd); 115 if (!imp_rva) return; 116 u64 imp_off; 117 if (!coff_rva_to_offset(shdrs, nsec, imp_rva, len, &imp_off)) return; 118 119 for (u32 d = 0;; ++d) { 120 u64 desc = imp_off + (u64)d * COFF_IMPORT_DESCRIPTOR_SIZE; 121 if (desc + COFF_IMPORT_DESCRIPTOR_SIZE > len) break; 122 u32 oft = coff_rd_u32(data + desc + 0); /* OriginalFirstThunk (ILT) */ 123 u32 dll_name_rva = coff_rd_u32(data + desc + 12); 124 u32 ft = coff_rd_u32(data + desc + 16); /* FirstThunk (IAT) */ 125 if (oft == 0 && dll_name_rva == 0 && ft == 0) break; /* null terminator */ 126 if (dll_name_rva == 0) continue; 127 128 u64 noff; 129 if (!coff_rva_to_offset(shdrs, nsec, dll_name_rva, len, &noff)) continue; 130 const char* dll; 131 u32 dll_len = coff_read_cstr(data, len, noff, &dll); 132 if (!dll_len) continue; 133 Sym dep_name = intern(c, dll, dll_len); 134 135 /* Prefer the ILT (OriginalFirstThunk); fall back to the IAT when the 136 * image was bound and the ILT is absent. */ 137 u32 thunk_rva = oft ? oft : ft; 138 Sym* imports = NULL; 139 u32 nimports = 0, cap = 0; 140 u64 toff; 141 if (thunk_rva && coff_rva_to_offset(shdrs, nsec, thunk_rva, len, &toff)) { 142 for (u32 t = 0;; ++t) { 143 u64 te = toff + (u64)t * COFF_THUNK_DATA64_SIZE; 144 if (te + COFF_THUNK_DATA64_SIZE > len) break; 145 u64 thunk = coff_rd_u64(data + te); 146 if (thunk == 0) break; /* null-terminated table */ 147 if (thunk & IMAGE_ORDINAL_FLAG64) continue; /* by-ordinal: skip (v1) */ 148 u32 ibn_rva = (u32)(thunk & 0x7fffffffu); 149 u64 hoff; 150 if (!coff_rva_to_offset(shdrs, nsec, ibn_rva, len, &hoff)) continue; 151 /* IMAGE_IMPORT_BY_NAME: u16 Hint, then NUL-terminated name. */ 152 const char* inm; 153 u32 il = coff_read_cstr(data, len, hoff + 2u, &inm); 154 if (!il) continue; 155 Sym isym = intern(c, inm, il); 156 157 if (nimports == cap) { 158 u32 ncap = cap ? cap * 2u : 8u; 159 Sym* grown = arena_array(c->scratch, Sym, ncap); 160 if (nimports) memcpy(grown, imports, sizeof(Sym) * nimports); 161 imports = grown; 162 cap = ncap; 163 } 164 imports[nimports++] = isym; 165 166 ObjImageSym us; 167 memset(&us, 0, sizeof us); 168 us.name = isym; 169 us.bind = SB_GLOBAL; 170 us.kind = 171 SK_NOTYPE; /* PE import descriptors don't distinguish func/data */ 172 us.section = OBJ_SEC_NONE; 173 obj_image_add_dynsym(im, &us); 174 } 175 } 176 177 ObjImageDep dep; 178 dep.name = dep_name; 179 dep.imports = imports; /* transient scratch; add_dep deep-copies */ 180 dep.nimports = nimports; 181 obj_image_add_dep(im, &dep); 182 } 183 } 184 185 /* ---- base relocations -> dynrelocs ---- 186 * Walks the .reloc base-relocation blocks. Each DIR64/HIGHLOW fixup is a 187 * symbol-less load-bias adjustment, mapped to the arch's RELATIVE kind. 188 * ABSOLUTE entries are block padding and skipped. */ 189 static void read_pe_basereloc(ObjImage* im, const u8* data, size_t len, 190 const u8* shdrs, u16 nsec, const u8* data_dir, 191 u32 num_dirs, u64 image_base, 192 RelocKind relative_kind) { 193 if ((u32)IMAGE_DIRECTORY_ENTRY_BASERELOC >= num_dirs) return; 194 const u8* dd = 195 data_dir + IMAGE_DIRECTORY_ENTRY_BASERELOC * COFF_DATA_DIRECTORY_SIZE; 196 u32 rel_rva = coff_rd_u32(dd); 197 u32 rel_size = coff_rd_u32(dd + 4); 198 if (!rel_rva || !rel_size) return; 199 u64 rel_off; 200 if (!coff_rva_to_offset(shdrs, nsec, rel_rva, len, &rel_off)) return; 201 u64 end = rel_off + rel_size; 202 if (end > len) end = len; 203 204 u64 pos = rel_off; 205 while (pos + COFF_BASE_RELOCATION_SIZE <= end) { 206 u32 page_rva = coff_rd_u32(data + pos + 0); 207 u32 block_size = coff_rd_u32(data + pos + 4); 208 if (block_size < COFF_BASE_RELOCATION_SIZE) break; /* malformed */ 209 if (pos + block_size > end) block_size = (u32)(end - pos); 210 u32 nent = (block_size - COFF_BASE_RELOCATION_SIZE) / 2u; 211 for (u32 e = 0; e < nent; ++e) { 212 u16 ent = 213 coff_rd_u16(data + pos + COFF_BASE_RELOCATION_SIZE + (u64)e * 2u); 214 u32 type = (u32)ent >> 12; 215 u32 off = (u32)ent & 0x0fffu; 216 if (type == IMAGE_REL_BASED_ABSOLUTE) continue; /* padding */ 217 if (type != IMAGE_REL_BASED_DIR64 && type != IMAGE_REL_BASED_HIGHLOW) 218 continue; 219 ObjImageReloc dr; 220 memset(&dr, 0, sizeof dr); 221 dr.section = OBJ_SEC_NONE; /* offset is a vaddr */ 222 dr.offset = image_base + page_rva + off; 223 dr.kind = relative_kind; 224 obj_image_add_dynreloc(im, &dr); 225 } 226 pos += block_size; 227 } 228 } 229 230 ObjBuilder* read_coff_image(Compiler* c, const char* name, const u8* data, 231 size_t len) { 232 (void)name; 233 234 /* ---- DOS header + PE signature (truncation panics) ---- */ 235 if (len < COFF_DOS_HEADER_SIZE) 236 compiler_panic(c, SRCLOC_NONE, 237 "read_coff_image: input shorter than DOS header"); 238 if (coff_rd_u16(data + 0) != IMAGE_DOS_SIGNATURE) 239 compiler_panic(c, SRCLOC_NONE, "read_coff_image: bad DOS magic"); 240 u32 e_lfanew = coff_rd_u32(data + 60); 241 u64 nt_end = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + COFF_OPT_HDR64_SIZE; 242 if (nt_end > len) 243 compiler_panic(c, SRCLOC_NONE, 244 "read_coff_image: PE headers extend past end of file"); 245 if (coff_rd_u32(data + e_lfanew) != IMAGE_NT_SIGNATURE) 246 compiler_panic(c, SRCLOC_NONE, "read_coff_image: bad PE signature"); 247 248 /* ---- IMAGE_FILE_HEADER ---- */ 249 const u8* fh = data + e_lfanew + 4u; 250 u16 machine = coff_rd_u16(fh + 0); 251 u16 nsec = coff_rd_u16(fh + 2); 252 u16 size_of_opt = coff_rd_u16(fh + 16); 253 u16 chars = coff_rd_u16(fh + 18); 254 if (machine != IMAGE_FILE_MACHINE_AMD64 && 255 machine != IMAGE_FILE_MACHINE_ARM64 && 256 machine != IMAGE_FILE_MACHINE_ARM64EC) 257 compiler_panic(c, SRCLOC_NONE, "read_coff_image: unsupported machine %#x", 258 (u32)machine); 259 if (size_of_opt < COFF_OPT_HDR64_SIZE) 260 compiler_panic(c, SRCLOC_NONE, 261 "read_coff_image: optional header %u too small for PE32+", 262 (u32)size_of_opt); 263 264 /* ---- IMAGE_OPTIONAL_HEADER64 ---- */ 265 const u8* oh = fh + COFF_FILE_HEADER_SIZE; 266 if (coff_rd_u16(oh + 0) != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 267 compiler_panic(c, SRCLOC_NONE, "read_coff_image: not PE32+"); 268 u32 entry_rva = coff_rd_u32(oh + 16); 269 u64 image_base = coff_rd_u64(oh + 24); 270 u32 sect_align = coff_rd_u32(oh + 32); 271 u16 subsystem = coff_rd_u16(oh + 68); 272 u16 dllchars = coff_rd_u16(oh + 70); 273 u32 num_dirs = coff_rd_u32(oh + 108); 274 if (num_dirs > COFF_NUM_DATA_DIRECTORIES) 275 num_dirs = COFF_NUM_DATA_DIRECTORIES; 276 const u8* data_dir = oh + COFF_OPT_HDR64_SIZE - 277 COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE; 278 279 /* ---- section table ---- */ 280 u64 shdrs_off = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + size_of_opt; 281 if (shdrs_off + (u64)nsec * COFF_SECTION_HEADER_SIZE > len) 282 compiler_panic(c, SRCLOC_NONE, 283 "read_coff_image: section table extends past end of file"); 284 const u8* shdrs = data + shdrs_off; 285 286 /* Arch ops resolve the RELATIVE base-reloc kind (machine validated above). */ 287 const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF); 288 const ObjCoffArchOps* aops = 289 (fmt && fmt->coff_machine) ? fmt->coff_machine(machine) : NULL; 290 if (!aops) 291 compiler_panic(c, SRCLOC_NONE, 292 "read_coff_image: no arch impl for machine %#x", 293 (u32)machine); 294 RelocKind relative_kind = (aops->arch == KIT_ARCH_X86_64) ? R_X64_RELATIVE 295 : (aops->arch == KIT_ARCH_ARM_64) 296 ? R_AARCH64_RELATIVE 297 : R_X64_RELATIVE; 298 299 ObjBuilder* ob = obj_new(c); 300 if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff_image: obj_new failed"); 301 ObjImage* im = obj_image_ensure( 302 ob, (chars & IMAGE_FILE_DLL) ? OBJ_KIND_DYN : OBJ_KIND_EXEC); 303 if (!im) 304 compiler_panic(c, SRCLOC_NONE, "read_coff_image: obj_image_ensure failed"); 305 obj_image_set_base(im, image_base); 306 obj_image_set_entry(im, entry_rva ? image_base + entry_rva : 0); 307 308 /* ---- sections + segments (dual-emit) ---- */ 309 for (u16 i = 0; i < nsec; ++i) { 310 const u8* sh = shdrs + (u64)i * COFF_SECTION_HEADER_SIZE; 311 const char* raw = (const char*)sh; /* Name[8], NUL-padded (no long form) */ 312 u32 nlen = 0; 313 while (nlen < 8 && raw[nlen] != '\0') ++nlen; 314 u32 vsize = coff_rd_u32(sh + 8); 315 u32 vaddr = coff_rd_u32(sh + 12); 316 u32 rawsize = coff_rd_u32(sh + 16); 317 u32 rawptr = coff_rd_u32(sh + 20); 318 u32 ch = coff_rd_u32(sh + 36); 319 320 Sym sn = intern(c, raw, nlen); 321 u16 kind = coff_sec_kind(raw, nlen, ch); 322 u16 flags = coff_sec_flags(raw, nlen, ch); 323 u32 align = coff_sec_align(ch); 324 int is_bss = (ch & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0; 325 u16 sem = is_bss ? SSEM_NOBITS : SSEM_PROGBITS; 326 327 ObjSecId id = obj_section_ex(ob, sn, (SecKind)kind, (SecSem)sem, flags, 328 align, 0u, 0u, 0u); 329 if (id != OBJ_SEC_NONE) { 330 obj_section_set_ext(ob, id, OBJ_EXT_COFF, ch, 0); 331 obj_section_set_addr(ob, id, image_base + vaddr); 332 if (is_bss) { 333 obj_reserve_bss(ob, id, vsize ? vsize : rawsize, align); 334 } else if (rawsize) { 335 /* Images FileAlignment-pad raw data; copy at most VirtualSize, and 336 * clamp leniently to the file length (vs the strict .obj path). */ 337 u32 copy = rawsize; 338 if (vsize && vsize < copy) copy = vsize; 339 if ((u64)rawptr + copy > len) 340 copy = (rawptr < len) ? (u32)(len - rawptr) : 0; 341 if (copy) { 342 u8* dst = obj_reserve(ob, id, copy); 343 if (dst) memcpy(dst, data + rawptr, copy); 344 } 345 } 346 } 347 348 ObjSegment seg; 349 memset(&seg, 0, sizeof seg); 350 seg.name = sn; 351 seg.vaddr = image_base + vaddr; 352 seg.paddr = seg.vaddr; 353 seg.vsize = vsize; 354 seg.file_off = rawptr; 355 seg.file_size = rawsize; 356 seg.perms = ((ch & IMAGE_SCN_MEM_READ) ? OBJ_SEG_R : 0u) | 357 ((ch & IMAGE_SCN_MEM_WRITE) ? OBJ_SEG_W : 0u) | 358 ((ch & IMAGE_SCN_MEM_EXECUTE) ? OBJ_SEG_X : 0u); 359 seg.align = sect_align ? sect_align : 1u; 360 obj_image_add_segment(im, &seg); 361 } 362 363 /* ---- raw escape-hatch entries: 16 data dirs + subsystem + dllchars ---- */ 364 for (u32 i = 0; i < COFF_NUM_DATA_DIRECTORIES; ++i) { 365 const u8* e = data_dir + (u64)i * COFF_DATA_DIRECTORY_SIZE; 366 ObjImageRaw r; 367 r.tag = i; 368 r.value = (i < num_dirs) ? coff_rd_u32(e) : 0; 369 r.extra = (i < num_dirs) ? coff_rd_u32(e + 4) : 0; 370 obj_image_add_raw(im, &r); 371 } 372 { 373 ObjImageRaw r = {KIT_OBJ_RAW_PE_SUBSYSTEM, subsystem, 0}; 374 obj_image_add_raw(im, &r); 375 } 376 { 377 ObjImageRaw r = {KIT_OBJ_RAW_PE_DLLCHARS, dllchars, 0}; 378 obj_image_add_raw(im, &r); 379 } 380 381 read_pe_exports(c, im, data, len, shdrs, nsec, data_dir, num_dirs, 382 image_base); 383 read_pe_imports(c, im, data, len, shdrs, nsec, data_dir, num_dirs); 384 read_pe_basereloc(im, data, len, shdrs, nsec, data_dir, num_dirs, image_base, 385 relative_kind); 386 387 obj_finalize(ob); 388 return ob; 389 }