1 module libd.data.coff_pe;
2 
3 import libd.datastructures : Array, Shared, makeShared;
4 
5 @nogc nothrow:
6 
7 enum DOS_HEADER_MAGIC = 0x5A4D; // MZ
8 enum COFF_HEADER_MAGIC = "PE\0\0";
9 
10 enum CoffMachineType : ushort
11 {
12     IMAGE_FILE_MACHINE_UNKNOWN = 0,
13     IMAGE_FILE_MACHINE_AM33  = 0x1d3,
14     IMAGE_FILE_MACHINE_AMD64  = 0x8664,
15     IMAGE_FILE_MACHINE_ARM = 0x1c0,
16     IMAGE_FILE_MACHINE_ARM64 = 0xaa64,
17     IMAGE_FILE_MACHINE_ARMNT = 0x1c4,
18     IMAGE_FILE_MACHINE_EBC = 0xebc,
19     IMAGE_FILE_MACHINE_I386 = 0x14c,
20     IMAGE_FILE_MACHINE_IA64 = 0x200,
21     IMAGE_FILE_MACHINE_M32R = 0x9041,
22     IMAGE_FILE_MACHINE_MIPS16 = 0x266,
23     IMAGE_FILE_MACHINE_MIPSFPU = 0x366,
24     IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466,
25     IMAGE_FILE_MACHINE_POWERPC = 0x1f0,
26     IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1,
27     IMAGE_FILE_MACHINE_R4000 = 0x166,
28     IMAGE_FILE_MACHINE_RISCV32 = 0x5032,
29     IMAGE_FILE_MACHINE_RISCV64 = 0x5064,
30     IMAGE_FILE_MACHINE_RISCV128 = 0x5128,
31     IMAGE_FILE_MACHINE_SH3 = 0x1a2,
32     IMAGE_FILE_MACHINE_SH3DSP = 0x1a3,
33     IMAGE_FILE_MACHINE_SH4 = 0x1a6,
34     IMAGE_FILE_MACHINE_SH5 = 0x1a8,
35     IMAGE_FILE_MACHINE_THUMB = 0x1c2,
36     IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169
37 }
38 
39 enum CoffCharacteristics : ushort
40 {
41     IMAGE_FILE_RELOCS_STRIPPED = 0x0001,
42     IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002,
43     IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004,
44     IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008,
45     IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010,
46     IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020,
47     IMAGE_FILE_BYTES_REVERSED_LO = 0x0080,
48     IMAGE_FILE_32BIT_MACHINE = 0x0100,
49     IMAGE_FILE_DEBUG_STRIPPED = 0x0200,
50     IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400,
51     IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800,
52     IMAGE_FILE_SYSTEM = 0x1000,
53     IMAGE_FILE_DLL = 0x2000,
54     IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000,
55     IMAGE_FILE_BYTES_REVERSED_HI = 0x8000,
56 }
57 
58 enum CoffPeSubsystem : ushort
59 {
60     IMAGE_SUBSYSTEM_UNKNOWN = 0,
61     IMAGE_SUBSYSTEM_NATIVE = 1,
62     IMAGE_SUBSYSTEM_WINDOWS_GUI = 2,
63     IMAGE_SUBSYSTEM_WINDOWS_CUI = 3,
64     IMAGE_SUBSYSTEM_OS2_CUI = 5,
65     IMAGE_SUBSYSTEM_POSIX_CUI = 7,
66     IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8,
67     IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9,
68     IMAGE_SUBSYSTEM_EFI_APPLICATION = 10,
69     IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11,
70     IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12,
71     IMAGE_SUBSYSTEM_EFI_ROM = 13,
72     IMAGE_SUBSYSTEM_XBOX = 14,
73     IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16,
74 }
75 
76 enum CoffPeDllCharacteristics : ushort
77 {
78     IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020,
79     IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040,
80     IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080,
81     IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100,
82     IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200,
83     IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400,
84     IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800,
85     IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000,
86     IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000,
87     IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000,
88     IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000,
89 }
90 
91 enum CoffOptionalHeaderMagic : ushort
92 {
93     PE32     = 0x10B,
94     PE32Plus = 0x20B
95 }
96 
97 enum CoffSectionTableCharacteristics : uint
98 {
99     IMAGE_SCN_TYPE_NO_PAD = 0x00000008,
100     IMAGE_SCN_CNT_CODE = 0x00000020,
101     IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,
102     IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080,
103     IMAGE_SCN_LNK_OTHER = 0x00000100,
104     IMAGE_SCN_LNK_INFO = 0x00000200,
105     IMAGE_SCN_LNK_REMOVE = 0x00000800,
106     IMAGE_SCN_LNK_COMDAT = 0x00001000,
107     IMAGE_SCN_GPREL = 0x00008000,
108     IMAGE_SCN_MEM_PURGEABLE = 0x00020000,
109     IMAGE_SCN_MEM_16BIT = 0x00020000,
110     IMAGE_SCN_MEM_LOCKED = 0x00040000,
111     IMAGE_SCN_MEM_PRELOAD = 0x00080000,
112     IMAGE_SCN_ALIGN_1BYTES = 0x00100000,
113     IMAGE_SCN_ALIGN_2BYTES = 0x00200000,
114     IMAGE_SCN_ALIGN_4BYTES = 0x00300000,
115     IMAGE_SCN_ALIGN_8BYTES = 0x00400000,
116     IMAGE_SCN_ALIGN_16BYTES = 0x00500000,
117     IMAGE_SCN_ALIGN_32BYTES = 0x00600000,
118     IMAGE_SCN_ALIGN_64BYTES = 0x00700000,
119     IMAGE_SCN_ALIGN_128BYTES = 0x00800000,
120     IMAGE_SCN_ALIGN_256BYTES = 0x00900000,
121     IMAGE_SCN_ALIGN_512BYTES = 0x00A00000,
122     IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000,
123     IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000,
124     IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000,
125     IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000,
126     IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000,
127     IMAGE_SCN_MEM_DISCARDABLE = 0x02000000,
128     IMAGE_SCN_MEM_NOT_CACHED = 0x04000000,
129     IMAGE_SCN_MEM_NOT_PAGED = 0x08000000,
130     IMAGE_SCN_MEM_SHARED = 0x10000000,
131     IMAGE_SCN_MEM_EXECUTE = 0x20000000,
132     IMAGE_SCN_MEM_READ = 0x40000000,
133     IMAGE_SCN_MEM_WRITE = 0x80000000,
134 }
135 
136 struct CoffPe
137 {
138     DosHeader dosHeader;
139     CoffHeader coffHeader;
140     CoffOptionalHeader optionalHeader;
141     Shared!(Array!CoffSectionTable) sectionTables;
142 
143     // D goes "grrr" sometimes.
144     @nogc nothrow
145     this(scope ref return typeof(this) copy)
146     {
147         this.dosHeader = copy.dosHeader;
148         this.coffHeader = copy.coffHeader;
149         this.optionalHeader = copy.optionalHeader;
150         this.sectionTables.__ctor(copy.sectionTables);
151     }
152 }
153 
154 struct DosHeader
155 {
156     ushort magic;
157     ushort[29] reserved;
158     uint newExeHeaderPtr;
159 }
160 static assert(DosHeader.sizeof == 64);
161 
162 struct CoffHeader
163 {
164     char[4] magic;
165     CoffMachineType machine;
166     ushort sectionCount;
167     uint timestamp;
168     uint symbolTablePtr_deprecated;
169     uint numberOfSymbolTable_deprecated;
170     ushort sizeOfOptionalHeader;
171     CoffCharacteristics characteristics;
172 }
173 static assert(CoffHeader.sizeof == 24);
174 
175 struct CoffOptionalHeader
176 {
177     CoffStandardFields coffFields;
178     CoffPeFields peFields;
179     CoffPeDataDirectories peDataDirectories;
180 }
181 
182 struct CoffStandardFields
183 {
184     CoffOptionalHeaderMagic magic;
185     ubyte linkerMajor;
186     ubyte linkerMinor;
187     uint sizeOfCode; // Sum of all sections
188     uint sizeOfInitialisedData; // .data
189     uint sizeOfUninitialisedData; // .bss
190     uint entryPointRvaPtr;
191     uint baseOfCodeRvaPtr;
192     uint baseOfDataRvaPtr; // PE32 ONLY!
193 }
194 static assert(CoffStandardFields.sizeof == 28);
195 
196 struct CoffPeFields
197 {
198     ulong imageBase;
199     uint sectionAlignment;
200     uint fileAlignment;
201     ushort osMajor;
202     ushort osMinor;
203     ushort imageMajor;
204     ushort imageMinor;
205     ushort subsystemMajor;
206     ushort subsytemMinor;
207     uint _;
208     uint sizeOfImage;
209     uint sizeOfHeaders;
210     uint checksum;
211     CoffPeSubsystem subsystem;
212     CoffPeDllCharacteristics dllCharacteristics;
213     ulong sizeOfStackReserve;
214     ulong sizeOfStackCommit;
215     ulong sizeOfHeapReserve;
216     ulong sizeOfHeapCommit;
217     uint loaderFlags;
218     uint numberOfRvaAndSizes;
219 }
220 static assert(CoffPeFields.sizeof == 88);
221 
222 struct CoffPeDataDirectory
223 {
224     uint tableRvaPtr;
225     uint size;
226 }
227 
228 struct CoffPeDataDirectories
229 {
230     CoffPeDataDirectory exportTable;
231     CoffPeDataDirectory importTable;
232     CoffPeDataDirectory resourceTable;
233     CoffPeDataDirectory exceptionTable;
234     CoffPeDataDirectory certificateTable;
235     CoffPeDataDirectory baseRelocationTable;
236     CoffPeDataDirectory debug_;
237     CoffPeDataDirectory architecture;
238     CoffPeDataDirectory globalPtr;
239     CoffPeDataDirectory tlsTable;
240     CoffPeDataDirectory loadConfigTable;
241     CoffPeDataDirectory boundImport;
242     CoffPeDataDirectory importAddressTable;
243     CoffPeDataDirectory delayImportDescriptor;
244     CoffPeDataDirectory clrRuntimeHeader;
245     CoffPeDataDirectory reservedMustBeZeroWinkyFaceSmileBlush;
246 }
247 
248 struct CoffSectionTable
249 {
250     char[8] name;
251     uint virtualSize;
252     uint virtualAddress;
253     uint sizeOfRawData;
254     uint pointerToRawData;
255     uint pointerToRelocations;
256     uint pointerToLineNumbers;
257     ushort numberOfRelocations;
258     ushort numberOfLineNumbers;
259     CoffSectionTableCharacteristics characteristics;
260 }
261 
262 SimpleResult!CoffPe coffpeParseHeader(const ubyte[] data)
263 {
264     import libd.io.memory;
265 
266     CoffPe pe;
267     auto stream = MemoryReaderStream(data);
268 
269     auto result = stream.read((&pe.dosHeader)[0..1]);
270     if(!result.isValid)                         return typeof(return)(result.error);
271     if(result.value != DosHeader.sizeof)        return typeof(return)(raise("Unexpected EOF when reading DOS header"));
272     if(pe.dosHeader.magic != DOS_HEADER_MAGIC)  return typeof(return)(raise("Invalid DOS magic number"));
273 
274     stream.setPosition(pe.dosHeader.newExeHeaderPtr).assumeValid;
275     result = stream.read((&pe.coffHeader)[0..1]);
276     if(!result.isValid)                             return typeof(return)(result.error);
277     if(result.value != CoffHeader.sizeof)           return typeof(return)(raise("Unexpected EOF when reading COFF header"));
278     if(pe.coffHeader.magic != COFF_HEADER_MAGIC)    return typeof(return)(raise("Invalid COFF magic number"));
279 
280     if(pe.coffHeader.sizeOfOptionalHeader)
281     {
282         ubyte[CoffStandardFields.sizeof] standardFieldsBuffer; // this has a dynamic size depending on optional header type
283         scope asStandardFields = cast(CoffStandardFields*)standardFieldsBuffer.ptr;
284         stream.read(standardFieldsBuffer[0..2]); // read magic
285 
286         auto size = (CoffStandardFields.sizeof - 2) - (
287             asStandardFields.magic == CoffOptionalHeaderMagic.PE32
288             ? 0
289             : 4
290         );
291         assert(asStandardFields.magic == CoffOptionalHeaderMagic.PE32Plus, "TODO");
292 
293         result = stream.read(standardFieldsBuffer[2..2+size]);
294         if(!result.isValid)         return typeof(return)(result.error);
295         if(result.value != size)    return typeof(return)(raise("Unexpected EOF when reading COFF standard fields"));
296 
297         pe.optionalHeader.coffFields = *asStandardFields;
298 
299         result = stream.read((&pe.optionalHeader.peFields)[0..1]);
300         if(!result.isValid)                         return typeof(return)(result.error);
301         if(result.value != CoffPeFields.sizeof)     return typeof(return)(raise("Unexpected EOF when reading COFF PE fields"));
302 
303         // again, this is dynamically sized
304         const directories = pe.optionalHeader.peFields.numberOfRvaAndSizes;
305         const dirSize = directories * CoffPeDataDirectory.sizeof;
306         const maxDirSize = CoffPeDataDirectories.sizeof;
307         ubyte[maxDirSize] dirBuffer;
308         scope asDataDirectories = cast(CoffPeDataDirectories*)dirBuffer.ptr;
309 
310         result = stream.read(dirBuffer[0..dirSize]);
311         if(!result.isValid)             return typeof(return)(result.error);
312         if(result.value != dirSize)     return typeof(return)(raise("Unexpected EOF when reading COFF PE data directories"));
313 
314         pe.optionalHeader.peDataDirectories = *asDataDirectories;
315     }
316 
317     pe.sectionTables = makeShared(Array!CoffSectionTable.init);
318     auto tables = pe.sectionTables.ptrUnsafe;
319     tables.length = pe.coffHeader.sectionCount;
320 
321     foreach(i, ref CoffSectionTable table; tables.range)
322     {
323         result = stream.read((&table)[0..1]);
324         if(!result.isValid)                         return typeof(return)(result.error);
325         if(result.value != CoffSectionTable.sizeof) return typeof(return)(raise("Unexpected EOF when reading COFF section table"));
326     }
327 
328     return typeof(return)(pe);
329 }