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 }