read_dso.c (7991B)
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, 39 "read_coff_dso: input shorter than DOS header"); 40 u16 e_magic = coff_rd_u16(data + 0); 41 if (e_magic != IMAGE_DOS_SIGNATURE) 42 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: bad DOS magic 0x%x", 43 e_magic); 44 u32 e_lfanew = coff_rd_u32(data + 60); 45 46 u64 nt_end = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + COFF_OPT_HDR64_SIZE; 47 if (nt_end > len) 48 compiler_panic(c, SRCLOC_NONE, 49 "read_coff_dso: PE headers extend past end of file"); 50 51 u32 pe_sig = coff_rd_u32(data + e_lfanew); 52 if (pe_sig != IMAGE_NT_SIGNATURE) 53 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: bad PE signature 0x%x", 54 pe_sig); 55 56 /* ---- IMAGE_FILE_HEADER ---- */ 57 const u8* fh = data + e_lfanew + 4u; 58 u16 machine = coff_rd_u16(fh + 0); 59 u16 nsec = coff_rd_u16(fh + 2); 60 u16 size_of_opt = coff_rd_u16(fh + 16); 61 u16 chars = coff_rd_u16(fh + 18); 62 63 if (machine != IMAGE_FILE_MACHINE_AMD64 && 64 machine != IMAGE_FILE_MACHINE_ARM64) 65 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: unsupported machine 0x%x", 66 machine); 67 if (!(chars & IMAGE_FILE_DLL)) 68 compiler_panic(c, SRCLOC_NONE, 69 "read_coff_dso: not a DLL (Characteristics=0x%x)", chars); 70 if (size_of_opt < COFF_OPT_HDR64_SIZE) 71 compiler_panic(c, SRCLOC_NONE, 72 "read_coff_dso: SizeOfOptionalHeader %u too small for PE32+", 73 size_of_opt); 74 75 /* ---- IMAGE_OPTIONAL_HEADER64 ---- */ 76 const u8* oh = fh + COFF_FILE_HEADER_SIZE; 77 u16 opt_magic = coff_rd_u16(oh + 0); 78 if (opt_magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 79 compiler_panic(c, SRCLOC_NONE, 80 "read_coff_dso: not PE32+ (optional header Magic=0x%x)", 81 opt_magic); 82 83 /* DataDirectory begins at offset 112 inside the PE32+ optional header 84 * (28 standard + 84 windows-specific + NumberOfRvaAndSizes = 112). */ 85 const u8* data_dir = oh + COFF_OPT_HDR64_SIZE - 86 COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE; 87 u32 export_rva = coff_rd_u32(data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT * 88 COFF_DATA_DIRECTORY_SIZE); 89 u32 export_size = coff_rd_u32( 90 data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT * COFF_DATA_DIRECTORY_SIZE + 4u); 91 92 /* ---- section table ---- */ 93 u64 shdrs_off = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + size_of_opt; 94 u64 shdrs_end = shdrs_off + (u64)nsec * COFF_SECTION_HEADER_SIZE; 95 if (shdrs_end > len) 96 compiler_panic(c, SRCLOC_NONE, 97 "read_coff_dso: section table extends past end of file"); 98 const u8* shdrs = data + shdrs_off; 99 100 ObjBuilder* ob = obj_new(c); 101 if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: obj_new failed"); 102 103 /* No export directory => empty DSO (legal for stub DLLs). */ 104 if (export_size == 0 || export_rva == 0) { 105 obj_finalize(ob); 106 return ob; 107 } 108 109 u64 exp_off; 110 if (!coff_rva_to_offset(shdrs, nsec, export_rva, len, &exp_off)) 111 compiler_panic(c, SRCLOC_NONE, 112 "read_coff_dso: export directory RVA 0x%x out of range", 113 export_rva); 114 if (exp_off + COFF_EXPORT_DIR_SIZE > len) 115 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: export directory truncated"); 116 117 const u8* ed = data + exp_off; 118 u32 name_rva = coff_rd_u32(ed + 12); 119 u32 num_funcs = coff_rd_u32(ed + 20); 120 u32 num_names = coff_rd_u32(ed + 24); 121 u32 eat_rva = coff_rd_u32(ed + 28); 122 u32 ent_rva = coff_rd_u32(ed + 32); 123 u32 ord_rva = coff_rd_u32(ed + 36); 124 /* Base (ed + 16) is the user-visible ordinal offset; the kit linker 125 * matches imports by name, so we don't propagate it. */ 126 127 /* ---- DLL name (soname) ---- */ 128 if (name_rva) { 129 u64 name_off; 130 if (!coff_rva_to_offset(shdrs, nsec, name_rva, len, &name_off)) 131 compiler_panic(c, SRCLOC_NONE, 132 "read_coff_dso: DLL name RVA 0x%x out of range", name_rva); 133 const char* dll_name; 134 u32 nlen = coff_read_cstr(data, len, name_off, &dll_name); 135 if (nlen && soname_out) 136 *soname_out = 137 pool_intern_slice(c->global, (Slice){.s = dll_name, .len = nlen}); 138 } 139 140 /* ---- resolve EAT / ENT / ordinal table once ---- */ 141 u64 eat_off = 0, ent_off = 0, ord_off = 0; 142 if (num_names) { 143 if (!coff_rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off)) 144 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: EAT RVA 0x%x out of range", 145 eat_rva); 146 if (!coff_rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off)) 147 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: ENT RVA 0x%x out of range", 148 ent_rva); 149 if (!coff_rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off)) 150 compiler_panic(c, SRCLOC_NONE, 151 "read_coff_dso: ordinal table RVA 0x%x out of range", 152 ord_rva); 153 if (ent_off + (u64)num_names * 4u > len || 154 ord_off + (u64)num_names * 2u > len) 155 compiler_panic(c, SRCLOC_NONE, 156 "read_coff_dso: ENT/ordinal table extends past file"); 157 if (eat_off + (u64)num_funcs * 4u > len) 158 compiler_panic(c, SRCLOC_NONE, "read_coff_dso: EAT extends past file"); 159 } 160 161 /* ---- walk the ENT ---- 162 * Forwarders (EAT RVA inside [export_rva, export_rva + export_size)) 163 * still produce a symbol: kit's linker doesn't follow the chain, 164 * but the import needs to be satisfiable so the OS loader can. */ 165 for (u32 i = 0; i < num_names; ++i) { 166 u32 nrva = coff_rd_u32(data + ent_off + (u64)i * 4u); 167 u16 ord = coff_rd_u16(data + ord_off + (u64)i * 2u); 168 if (ord >= num_funcs) continue; /* malformed; skip rather than panic */ 169 /* func_rva is fetched for forwarder classification only; kit does 170 * not consume the address itself (DSO symbols are OBJ_SEC_NONE). */ 171 u32 func_rva = coff_rd_u32(data + eat_off + (u64)ord * 4u); 172 (void)func_rva; /* see comment above re: forwarders */ 173 174 u64 name_off; 175 if (!coff_rva_to_offset(shdrs, nsec, nrva, len, &name_off)) continue; 176 const char* nm; 177 u32 nlen = coff_read_cstr(data, len, name_off, &nm); 178 if (!nlen) continue; 179 180 Sym sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); 181 ObjSymId id = obj_symbol(ob, sn, SB_GLOBAL, SK_FUNC, OBJ_SEC_NONE, 0, 0); 182 obj_sym_mark_referenced(ob, id); 183 } 184 185 obj_finalize(ob); 186 return ob; 187 }