kit

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

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 }