kit

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

read_dso.c (7934B)


      1 /* PE32+ DLL reader.  Peer of read_elf_dso / read_macho_dso: walks the
      2  * IMAGE_DIRECTORY_ENTRY_EXPORT data directory of a Windows .dll and
      3  * produces an ObjBuilder of defined OBJ_SEC_NONE symbols — one per
      4  * name in the Export Name Table.  The DLL's own Name string (the
      5  * analogue of DT_SONAME / LC_ID_DYLIB) is returned via *soname_out.
      6  *
      7  * The produced ObjBuilder carries no sections, relocations, or groups
      8  * — DSO inputs contribute no bytes to the link.  The consumer's
      9  * resolve_undefs pass sees the exports as defined globals and marks
     10  * matching consumer-side undefs as `imported`; the import-table
     11  * emitter (Phase 3 / 4.4) later groups them by providing DLL.
     12  *
     13  * Scope: PE32+ only (IMAGE_NT_OPTIONAL_HDR64_MAGIC), AMD64 or ARM64,
     14  * with IMAGE_FILE_DLL set.  Ordinal-only exports (entries present in
     15  * the EAT but absent from the ENT) are not synthesized in v1 — almost
     16  * all real-world imports are by name.  Forwarder entries (EAT RVA
     17  * falls within the export directory's own range) are still emitted as
     18  * symbols so the linker can satisfy imports against them; the OS
     19  * loader follows the forwarder chain at runtime.  This contract is
     20  * pinned by test/coff/pe-dso-forwarder.c. */
     21 
     22 #include <string.h>
     23 
     24 #include "core/arena.h"
     25 #include "core/heap.h"
     26 #include "core/pool.h"
     27 #include "core/slice.h"
     28 #include "obj/coff/coff.h"
     29 #include "obj/coff/read_util.h"
     30 
     31 ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data,
     32                           size_t len, Sym* soname_out) {
     33   (void)name;
     34   if (soname_out) *soname_out = 0;
     35 
     36   /* ---- DOS header + PE signature ---- */
     37   if (len < COFF_DOS_HEADER_SIZE)
     38     compiler_panic(c, SRCLOC_NONE, "read_coff_dso: input shorter than DOS header");
     39   u16 e_magic = coff_rd_u16(data + 0);
     40   if (e_magic != IMAGE_DOS_SIGNATURE)
     41     compiler_panic(c, SRCLOC_NONE, "read_coff_dso: bad DOS magic 0x%x", e_magic);
     42   u32 e_lfanew = coff_rd_u32(data + 60);
     43 
     44   u64 nt_end = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + COFF_OPT_HDR64_SIZE;
     45   if (nt_end > len)
     46     compiler_panic(c, SRCLOC_NONE,
     47                    "read_coff_dso: PE headers extend past end of file");
     48 
     49   u32 pe_sig = coff_rd_u32(data + e_lfanew);
     50   if (pe_sig != IMAGE_NT_SIGNATURE)
     51     compiler_panic(c, SRCLOC_NONE, "read_coff_dso: bad PE signature 0x%x", pe_sig);
     52 
     53   /* ---- IMAGE_FILE_HEADER ---- */
     54   const u8* fh = data + e_lfanew + 4u;
     55   u16 machine = coff_rd_u16(fh + 0);
     56   u16 nsec = coff_rd_u16(fh + 2);
     57   u16 size_of_opt = coff_rd_u16(fh + 16);
     58   u16 chars = coff_rd_u16(fh + 18);
     59 
     60   if (machine != IMAGE_FILE_MACHINE_AMD64 &&
     61       machine != IMAGE_FILE_MACHINE_ARM64)
     62     compiler_panic(c, SRCLOC_NONE, "read_coff_dso: unsupported machine 0x%x",
     63                    machine);
     64   if (!(chars & IMAGE_FILE_DLL))
     65     compiler_panic(c, SRCLOC_NONE,
     66                    "read_coff_dso: not a DLL (Characteristics=0x%x)", chars);
     67   if (size_of_opt < COFF_OPT_HDR64_SIZE)
     68     compiler_panic(c, SRCLOC_NONE,
     69                    "read_coff_dso: SizeOfOptionalHeader %u too small for PE32+",
     70                    size_of_opt);
     71 
     72   /* ---- IMAGE_OPTIONAL_HEADER64 ---- */
     73   const u8* oh = fh + COFF_FILE_HEADER_SIZE;
     74   u16 opt_magic = coff_rd_u16(oh + 0);
     75   if (opt_magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
     76     compiler_panic(c, SRCLOC_NONE,
     77                    "read_coff_dso: not PE32+ (optional header Magic=0x%x)",
     78                    opt_magic);
     79 
     80   /* DataDirectory begins at offset 112 inside the PE32+ optional header
     81    * (28 standard + 84 windows-specific + NumberOfRvaAndSizes = 112). */
     82   const u8* data_dir = oh + COFF_OPT_HDR64_SIZE -
     83                        COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE;
     84   u32 export_rva = coff_rd_u32(data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT *
     85                                               COFF_DATA_DIRECTORY_SIZE);
     86   u32 export_size = coff_rd_u32(
     87       data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT * COFF_DATA_DIRECTORY_SIZE + 4u);
     88 
     89   /* ---- section table ---- */
     90   u64 shdrs_off = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + size_of_opt;
     91   u64 shdrs_end = shdrs_off + (u64)nsec * COFF_SECTION_HEADER_SIZE;
     92   if (shdrs_end > len)
     93     compiler_panic(c, SRCLOC_NONE,
     94                    "read_coff_dso: section table extends past end of file");
     95   const u8* shdrs = data + shdrs_off;
     96 
     97   ObjBuilder* ob = obj_new(c);
     98   if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: obj_new failed");
     99 
    100   /* No export directory => empty DSO (legal for stub DLLs). */
    101   if (export_size == 0 || export_rva == 0) {
    102     obj_finalize(ob);
    103     return ob;
    104   }
    105 
    106   u64 exp_off;
    107   if (!coff_rva_to_offset(shdrs, nsec, export_rva, len, &exp_off))
    108     compiler_panic(c, SRCLOC_NONE,
    109                    "read_coff_dso: export directory RVA 0x%x out of range",
    110                    export_rva);
    111   if (exp_off + COFF_EXPORT_DIR_SIZE > len)
    112     compiler_panic(c, SRCLOC_NONE, "read_coff_dso: export directory truncated");
    113 
    114   const u8* ed = data + exp_off;
    115   u32 name_rva = coff_rd_u32(ed + 12);
    116   u32 num_funcs = coff_rd_u32(ed + 20);
    117   u32 num_names = coff_rd_u32(ed + 24);
    118   u32 eat_rva = coff_rd_u32(ed + 28);
    119   u32 ent_rva = coff_rd_u32(ed + 32);
    120   u32 ord_rva = coff_rd_u32(ed + 36);
    121   /* Base (ed + 16) is the user-visible ordinal offset; the kit linker
    122    * matches imports by name, so we don't propagate it. */
    123 
    124   /* ---- DLL name (soname) ---- */
    125   if (name_rva) {
    126     u64 name_off;
    127     if (!coff_rva_to_offset(shdrs, nsec, name_rva, len, &name_off))
    128       compiler_panic(c, SRCLOC_NONE,
    129                      "read_coff_dso: DLL name RVA 0x%x out of range", name_rva);
    130     const char* dll_name;
    131     u32 nlen = coff_read_cstr(data, len, name_off, &dll_name);
    132     if (nlen && soname_out)
    133       *soname_out =
    134           pool_intern_slice(c->global, (Slice){.s = dll_name, .len = nlen});
    135   }
    136 
    137   /* ---- resolve EAT / ENT / ordinal table once ---- */
    138   u64 eat_off = 0, ent_off = 0, ord_off = 0;
    139   if (num_names) {
    140     if (!coff_rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off))
    141       compiler_panic(c, SRCLOC_NONE, "read_coff_dso: EAT RVA 0x%x out of range",
    142                      eat_rva);
    143     if (!coff_rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off))
    144       compiler_panic(c, SRCLOC_NONE, "read_coff_dso: ENT RVA 0x%x out of range",
    145                      ent_rva);
    146     if (!coff_rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off))
    147       compiler_panic(c, SRCLOC_NONE,
    148                      "read_coff_dso: ordinal table RVA 0x%x out of range",
    149                      ord_rva);
    150     if (ent_off + (u64)num_names * 4u > len ||
    151         ord_off + (u64)num_names * 2u > len)
    152       compiler_panic(c, SRCLOC_NONE,
    153                      "read_coff_dso: ENT/ordinal table extends past file");
    154     if (eat_off + (u64)num_funcs * 4u > len)
    155       compiler_panic(c, SRCLOC_NONE, "read_coff_dso: EAT extends past file");
    156   }
    157 
    158   /* ---- walk the ENT ----
    159    * Forwarders (EAT RVA inside [export_rva, export_rva + export_size))
    160    * still produce a symbol: kit's linker doesn't follow the chain,
    161    * but the import needs to be satisfiable so the OS loader can. */
    162   for (u32 i = 0; i < num_names; ++i) {
    163     u32 nrva = coff_rd_u32(data + ent_off + (u64)i * 4u);
    164     u16 ord = coff_rd_u16(data + ord_off + (u64)i * 2u);
    165     if (ord >= num_funcs) continue; /* malformed; skip rather than panic */
    166     /* func_rva is fetched for forwarder classification only; kit does
    167      * not consume the address itself (DSO symbols are OBJ_SEC_NONE). */
    168     u32 func_rva = coff_rd_u32(data + eat_off + (u64)ord * 4u);
    169     (void)func_rva; /* see comment above re: forwarders */
    170 
    171     u64 name_off;
    172     if (!coff_rva_to_offset(shdrs, nsec, nrva, len, &name_off)) continue;
    173     const char* nm;
    174     u32 nlen = coff_read_cstr(data, len, name_off, &nm);
    175     if (!nlen) continue;
    176 
    177     Sym sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
    178     ObjSymId id = obj_symbol(ob, sn, SB_GLOBAL, SK_FUNC, OBJ_SEC_NONE, 0, 0);
    179     obj_sym_mark_referenced(ob, id);
    180   }
    181 
    182   obj_finalize(ob);
    183   return ob;
    184 }