1 /** 2 Win32 Implementation for nulib.system.mod 3 4 Copyright: 5 Copyright © 2025, Kitsunebi Games 6 Copyright © 2025, Inochi2D Project 7 8 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Authors: Luna Nielsen 10 */ 11 module nulib.win32.mod; 12 import numem; 13 14 extern(Windows): 15 16 /** 17 A symbol within a section. 18 */ 19 struct Symbol { 20 @nogc: 21 22 /** 23 Name of the symbol 24 */ 25 string name; 26 27 /** 28 Pointer to the symbol 29 */ 30 void* ptr; 31 } 32 33 /** 34 Information about sections. 35 */ 36 struct SectionInfo { 37 @nogc: 38 39 /** 40 Segment of the section, if applicable. 41 */ 42 string segment; 43 44 /** 45 Name of the section 46 */ 47 string section; 48 49 /** 50 Start address of the section 51 */ 52 void* start; 53 54 /** 55 End address of the section 56 */ 57 void* end; 58 } 59 60 // 61 // FOR IMPLEMENTORS 62 // 63 64 private extern (C): 65 66 /* 67 Optional helper that defines whether paths are in UTF-16 format. 68 */ 69 bool _nu_module_utf16_paths() @nogc nothrow { 70 return true; 71 } 72 73 /* 74 Function which loads a module from the given path. 75 76 This is implemented by backends to abstract away the OS intricaices 77 of loading modules and finding symbols within. 78 */ 79 export 80 void* _nu_module_open(void* path) @nogc nothrow { 81 82 // NOTE: This if check simulates dlopen on UNIX. 83 // We want UNIX semantics here because they're nice. 84 if (!path) 85 return GetModuleHandleW(null); 86 87 return LoadLibraryW(cast(const(wchar)*)path); 88 } 89 90 /* 91 Function which loads a module from the given path. 92 93 This is implemented by backends to abstract away the OS intricaices 94 of loading modules and finding symbols within. 95 */ 96 void _nu_module_close(void* module_) @nogc nothrow { 97 cast(void) FreeLibrary(module_); 98 } 99 100 /* 101 Function which finds a symbol within a given module 102 103 This is implemented by backends to abstract away the OS intricaices 104 of loading modules and finding symbols within. 105 */ 106 void* _nu_module_get_symbol(void* module_, const(char)* symbol) @nogc nothrow { 107 return GetProcAddress(module_, symbol); 108 } 109 110 /* 111 Function which gets the "base address" of the module. 112 113 This is backend implementation defined; but is what should allow 114 $(D _nu_module_enumerate_sections) and $(D _nu_module_enumerate_symbols) 115 to function. 116 */ 117 void* _nu_module_get_base_address(void* module_) @nogc nothrow { 118 119 // Just in case we should also just verify the headers 120 // here. 121 void* handle = _nu_module_win32_verify_pe(cast(void*)module_); 122 if (!handle) 123 return null; 124 125 return cast(void*)module_; 126 } 127 128 /* 129 Function which enumerates all of the sections within a module. 130 131 This is implemented by backends to abstract away the OS intricaices 132 of loading modules and finding symbols within. 133 */ 134 SectionInfo[] _nu_module_enumerate_sections(void* base) @nogc nothrow { 135 void* handle = _nu_module_win32_verify_pe(base); 136 if (!handle) 137 return null; 138 139 // NOTE: If a file has no sections we're probably reading a very messed up 140 // PE file, so we'll stop there. 141 // 142 // NOTE: shdrs is a slice into existing memory, DO NOT FREE. 143 PeSectionHeader[] shdrs = _nu_module_win32_get_section_headers(handle); 144 if (shdrs.length == 0) 145 return null; 146 147 SectionInfo[] sections; 148 sections = sections.nu_resize(shdrs.length); 149 nogc_initialize(sections[]); 150 151 foreach(i, ref PeSectionHeader shdr; shdrs) { 152 sections[i] = SectionInfo( 153 null, 154 _nu_module_get_sect_name(shdr.mName), 155 base+shdr.mVirtualAddress, 156 base+shdr.mVirtualAddress+shdr.mVirtualSize 157 ); 158 } 159 return sections; 160 } 161 162 /* 163 Function which enumerates all of the exported symbols. 164 165 This is implemented by backends to abstract away the OS intricaices 166 of loading modules and finding symbols within. 167 */ 168 Symbol[] _nu_module_enumerate_symbols(void* base) @nogc nothrow { 169 void* handle = _nu_module_win32_verify_pe(base); 170 if (!handle) 171 return null; 172 173 // No exports since we don't have a table for it. 174 PEHeader* nthdr = cast(PEHeader*) handle; 175 if (nthdr.mSizeOfOptionalHeader == 0) 176 return null; 177 178 uint[2] exportsRVA; 179 ushort pMagic = *cast(ushort*)(handle+PEHeader.sizeof); 180 if (pMagic == 0x010b) { 181 182 // PE32 (32-bit) 183 exportsRVA = *cast(uint[2]*)(handle+PEHeader.sizeof+Pe32OptionalHeader.sizeof); 184 } else if (pMagic == 0x020b) { 185 186 // PE32+ (64-bit) 187 exportsRVA = *cast(uint[2]*)(handle+PEHeader.sizeof+Pe32PlusOptionalHeader.sizeof); 188 189 } else { 190 191 // Unrecognized PE format. 192 return null; 193 } 194 195 PeExportsTableHeader* exportsTable = cast(PeExportsTableHeader*)(base+exportsRVA[0]); 196 197 // NOTE: Create and *zero initialize* the symbols array. 198 // we wouldn't want to point to garbage data. 199 Symbol[] syms; 200 syms = syms.nu_resize(exportsTable.mNamePointerEntryCount); 201 nogc_initialize(syms[]); 202 203 uint* nameOffsetTable = cast(uint*)(base+exportsTable.mNamePointerRVA); 204 uint* addressTable = cast(uint*)(base+exportsTable.mAddressTableRVA); 205 ushort* ordinalTable = cast(ushort*)(base+exportsTable.mNameOrdinalTableRVA); 206 foreach(i; 0..exportsTable.mNamePointerEntryCount) { 207 const(char)* name = cast(const(char)*)(base+nameOffsetTable[i]); 208 uint ordinal = ordinalTable[i]+exportsTable.mOrdinalBase; 209 210 // Out of range. 211 if (ordinal >= exportsTable.mAddressTableEntryCount) 212 continue; 213 214 // NOTE: Foreign exports should be nulled. 215 // They are denoted by referencing strings *within* 216 // the export table. 217 void* paddr = base+addressTable[ordinal]; 218 if (paddr >= base+exportsRVA[0] && paddr <= base+exportsRVA[0]+exportsRVA[1]) 219 paddr = null; 220 221 syms[i] = Symbol(_fstrz(name), paddr); 222 } 223 return syms; 224 } 225 226 // 227 // SYSTEM SPECIFIC IMPLEMENTATIONS 228 // 229 230 void* _nu_module_win32_verify_pe(void* base) @nogc nothrow { 231 if (!base) return null; 232 ushort magic = *cast(ushort*) base; 233 234 // 'MZ' DOS Header 235 if (magic != 0x5A4D) 236 return null; 237 238 239 // NOTE: e_lfanew is at offset 0x3C, always. 240 // it being reserved in the DOS header, means we can make our 241 // life simpler by simply just passing that in from the start. 242 int e_lfanew = *cast(int*)(base + 0x3C); 243 void* handle = base + e_lfanew; 244 PEHeader* nthdr = cast(PEHeader*) handle; 245 246 // Not a PE file? 247 if (nthdr.mMagic != 0x00004550) 248 return null; 249 250 // No sections? what? 251 if (nthdr.mNumberOfSections == 0) 252 return null; 253 254 return handle; 255 } 256 257 string _nu_module_get_sect_name(ref char[8] name) @nogc nothrow { 258 foreach(i; 0..name.length) { 259 if (name[i] == '\0') 260 return cast(string)name[0..i]; 261 } 262 return cast(string)name; 263 } 264 265 PeSectionHeader[] _nu_module_win32_get_section_headers(void* handle) @nogc nothrow { 266 PEHeader* nthdr = cast(PEHeader*) handle; 267 PeSectionHeader* start = cast(PeSectionHeader*)(handle+PEHeader.sizeof+nthdr.mSizeOfOptionalHeader); 268 return start[0..nthdr.mNumberOfSections]; 269 } 270 271 272 // 273 // HELPERS 274 // 275 276 extern(D) 277 string _fstrz(const(char)* str) @nogc nothrow { 278 size_t i = 0; 279 while(str[i] != '\0') i++; 280 return cast(string)str[0..i]; 281 } 282 283 // 284 // LOCAL BINDINGS 285 // 286 287 extern (Windows) extern void* GetModuleHandleW(const(wchar)*) @nogc nothrow; 288 extern (Windows) extern void* LoadLibraryW(const(wchar)*) @nogc nothrow; 289 extern (Windows) extern void* GetProcAddress(void*, const(char)*) @nogc nothrow; 290 extern (Windows) extern bool FreeLibrary(void*) @nogc nothrow; 291 292 // Info taken from https://wiki.osdev.org/PE 293 struct PEHeader { 294 align(1): 295 uint mMagic; 296 ushort mMachine; 297 ushort mNumberOfSections; 298 uint mTimeDateStamp; 299 uint mPointerToSymbolTable; 300 uint mNumberOfSymbols; 301 ushort mSizeOfOptionalHeader; 302 ushort mCharacteristics; 303 } 304 305 // 0x010b - PE32 306 static assert(Pe32OptionalHeader.sizeof == 96); 307 align(1) struct Pe32OptionalHeader { 308 align(1): 309 ushort mMagic; 310 ubyte mMajorLinkerVersion; 311 ubyte mMinorLinkerVersion; 312 uint mSizeOfCode; 313 uint mSizeOfInitializedData; 314 uint mSizeOfUninitializedData; 315 uint mAddressOfEntryPoint; 316 uint mBaseOfCode; 317 uint mBaseOfData; 318 uint mImageBase; 319 uint mSectionAlignment; 320 uint mFileAlignment; 321 ushort mMajorOperatingSystemVersion; 322 ushort mMinorOperatingSystemVersion; 323 ushort mMajorImageVersion; 324 ushort mMinorImageVersion; 325 ushort mMajorSubsystemVersion; 326 ushort mMinorSubsystemVersion; 327 uint mWin32VersionValue; 328 uint mSizeOfImage; 329 uint mSizeOfHeaders; 330 uint mCheckSum; 331 ushort mSubsystem; 332 ushort mDllCharacteristics; 333 uint mSizeOfStackReserve; 334 uint mSizeOfStackCommit; 335 uint mSizeOfHeapReserve; 336 uint mSizeOfHeapCommit; 337 uint mLoaderFlags; 338 uint mNumberOfRvaAndSizes; 339 } 340 341 // 0x020b - PE32+ (64 bit) 342 static assert(Pe32PlusOptionalHeader.sizeof == 112); 343 align(1) struct Pe32PlusOptionalHeader { 344 align(1): 345 ushort mMagic; 346 ubyte mMajorLinkerVersion; 347 ubyte mMinorLinkerVersion; 348 uint mSizeOfCode; 349 uint mSizeOfInitializedData; 350 uint mSizeOfUninitializedData; 351 uint mAddressOfEntryPoint; 352 uint mBaseOfCode; 353 ulong mImageBase; 354 uint mSectionAlignment; 355 uint mFileAlignment; 356 ushort mMajorOperatingSystemVersion; 357 ushort mMinorOperatingSystemVersion; 358 ushort mMajorImageVersion; 359 ushort mMinorImageVersion; 360 ushort mMajorSubsystemVersion; 361 ushort mMinorSubsystemVersion; 362 uint mWin32VersionValue; 363 uint mSizeOfImage; 364 uint mSizeOfHeaders; 365 uint mCheckSum; 366 ushort mSubsystem; 367 ushort mDllCharacteristics; 368 ulong mSizeOfStackReserve; 369 ulong mSizeOfStackCommit; 370 ulong mSizeOfHeapReserve; 371 ulong mSizeOfHeapCommit; 372 uint mLoaderFlags; 373 uint mNumberOfRvaAndSizes; 374 } 375 376 // size 40 bytes 377 static assert(PeExportsTableHeader.sizeof == 40); 378 struct PeExportsTableHeader { 379 uint mExportFlags; 380 uint mTimeDateStamp; 381 ushort mMajorVersion; 382 ushort mMinorVersion; 383 uint mNameRVA; 384 uint mOrdinalBase; 385 uint mAddressTableEntryCount; 386 uint mNamePointerEntryCount; 387 uint mAddressTableRVA; 388 uint mNamePointerRVA; 389 uint mNameOrdinalTableRVA; 390 } 391 392 // size 40 bytes 393 static assert(PeSectionHeader.sizeof == 40); 394 struct PeSectionHeader { 395 char[8] mName; 396 uint mVirtualSize; 397 uint mVirtualAddress; 398 uint mSizeOfRawData; 399 uint mPointerToRawData; 400 uint mPointerToRelocations; 401 uint mPointerToLinenumbers; 402 ushort mNumberOfRelocations; 403 ushort mNumberOfLinenumbers; 404 uint mCharacteristics; 405 }