11#include < Windows.h>
2- #include < algorithm>
32#include < cstdint>
43#include < fstream>
54#include < iostream>
1312// when building Node.js as a shared library. This is conceptually
1413// similar to the create_expfile.sh script used on AIX.
1514//
16- // Generating this .def file requires parsing data out of the
15+ // Generating this .def file requires parsing data out of the
1716// PE32/PE32+ file format. Helper structs are defined in <Windows.h>
18- // hence why this is an executable and not a script. See [2] for
17+ // hence why this is an executable and not a script. See [2] for
1918// details on the PE format.
2019//
2120// [1]: https://docs.microsoft.com/en-us/cpp/build/reference/module-definition-dot-def-files
@@ -28,11 +27,16 @@ struct RelativeAddress {
2827 uintptr_t root;
2928 uintptr_t offset = 0 ;
3029
31- RelativeAddress (HMODULE handle) noexcept
32- : root( reinterpret_cast < uintptr_t >( handle) ) {}
30+ explicit RelativeAddress (HMODULE handle) noexcept
31+ : RelativeAddress( handle, 0 ) {}
3332
33+ // LoadLibraryEx with LOAD_LIBRARY_AS_IMAGE_RESOURCE tags the returned
34+ // handle by setting one of its two lowest bits. Mask them off to recover
35+ // the actual base address of the mapping.
3436 RelativeAddress (HMODULE handle, uintptr_t offset) noexcept
35- : root(reinterpret_cast <uintptr_t >(handle)), offset(offset) {}
37+ : root(reinterpret_cast <uintptr_t >(handle) &
38+ ~static_cast <uintptr_t >(3 )),
39+ offset (offset) {}
3640
3741 RelativeAddress (uintptr_t root, uintptr_t offset) noexcept
3842 : root(root), offset(offset) {}
@@ -60,15 +64,28 @@ struct RelativeAddress {
6064 }
6165};
6266
63- // A wrapper around a dynamically loaded Windows DLL. This steps through the
64- // PE file structure to find the export directory and pulls out a list of
65- // all the exported symbol names.
67+ struct Symbol {
68+ std::string name;
69+ uint32_t rva;
70+ };
71+
72+ // A wrapper around a memory-mapped Windows DLL image. The DLL is mapped as
73+ // an image resource (laid out as if loaded, but never executed), so its
74+ // architecture does not need to match ours; this allows generating the
75+ // .def file for a cross-compiled DLL. This steps through the PE file
76+ // structure to find the export directory and pulls out a list of all the
77+ // exported symbols.
6678struct Library {
6779 HMODULE library;
6880 std::string libraryName;
69- std::vector<std::string> exportedSymbols;
81+ std::vector<IMAGE_SECTION_HEADER > sections;
82+ std::vector<Symbol> exportedSymbols;
83+
84+ // Location of the export directory itself, used to detect forwarders.
85+ uint32_t exportDirStart;
86+ uint32_t exportDirSize;
7087
71- Library (HMODULE library) : library(library) {
88+ explicit Library (HMODULE library) : library(library) {
7289 auto libnode = RelativeAddress (library);
7390
7491 // At relative offset 0x3C is a 32 bit offset to the COFF signature, 4 bytes
@@ -83,11 +100,21 @@ struct Library {
83100 auto optionalHeaderPtr = coffHeaderPtr.AtOffset (sizeof (IMAGE_FILE_HEADER ));
84101 auto optionalHeader = optionalHeaderPtr.AsPtrTo <IMAGE_OPTIONAL_HEADER >();
85102
103+ // The section table starts right after the optional header.
104+ auto sectionTablePtr =
105+ optionalHeaderPtr.AtOffset (coffHeader->SizeOfOptionalHeader );
106+ const IMAGE_SECTION_HEADER * firstSection =
107+ sectionTablePtr.AsPtrTo <IMAGE_SECTION_HEADER >();
108+ sections.assign (firstSection, firstSection + coffHeader->NumberOfSections );
109+
86110 auto exportDirectory =
87- (optionalHeader->Magic == 0x20b ) ? optionalHeaderPtr.AsPtrTo <IMAGE_OPTIONAL_HEADER64 >()
88- ->DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT ]
89- : optionalHeaderPtr.AsPtrTo <IMAGE_OPTIONAL_HEADER32 >()
90- ->DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT ];
111+ (optionalHeader->Magic == 0x20b )
112+ ? optionalHeaderPtr.AsPtrTo <IMAGE_OPTIONAL_HEADER64 >()
113+ ->DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT ]
114+ : optionalHeaderPtr.AsPtrTo <IMAGE_OPTIONAL_HEADER32 >()
115+ ->DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT ];
116+ exportDirStart = exportDirectory.VirtualAddress ;
117+ exportDirSize = exportDirectory.Size ;
91118
92119 auto exportTable = libnode.AtOffset (exportDirectory.VirtualAddress )
93120 .AsPtrTo <IMAGE_EXPORT_DIRECTORY >();
@@ -99,6 +126,11 @@ struct Library {
99126
100127 const uint32_t * functionNameTable =
101128 libnode.AtOffset (exportTable->AddressOfNames ).AsPtrTo <uint32_t >();
129+ const uint32_t * functionLocations =
130+ libnode.AtOffset (exportTable->AddressOfFunctions ).AsPtrTo <uint32_t >();
131+ const uint16_t * functionOrdinals =
132+ libnode.AtOffset (exportTable->AddressOfNameOrdinals )
133+ .AsPtrTo <uint16_t >();
102134
103135 // Given an RVA, parse it as a std::string. The resulting string is empty
104136 // if the symbol does not have a name (i.e. it is ordinal only).
@@ -107,32 +139,33 @@ struct Library {
107139 if (namePtr == nullptr ) return {};
108140 return {namePtr};
109141 };
110- std::transform (functionNameTable,
111- functionNameTable + exportTable-> NumberOfNames ,
112- std::back_inserter (exportedSymbols),
113- nameRvaToName);
142+ for ( uint32_t i = 0 ; i < exportTable-> NumberOfNames ; ++i) {
143+ exportedSymbols. push_back ({ nameRvaToName ( functionNameTable[i]) ,
144+ functionLocations[functionOrdinals[i]]});
145+ }
114146 }
115147
116148 ~Library () { FreeLibrary (library); }
117- };
118149
119- bool IsPageExecutable (void * address) {
120- MEMORY_BASIC_INFORMATION memoryInformation;
121- size_t rc = VirtualQuery (
122- address, &memoryInformation, sizeof (MEMORY_BASIC_INFORMATION ));
150+ bool IsRvaExecutable (uint32_t rva) const {
151+ for (const auto & s : sections) {
152+ if (rva >= s.VirtualAddress &&
153+ rva < s.VirtualAddress + s.Misc .VirtualSize ) {
154+ return (s.Characteristics & IMAGE_SCN_MEM_EXECUTE ) != 0 ;
155+ }
156+ }
157+ return true ;
158+ }
123159
124- if (rc != 0 && memoryInformation.Protect != 0 ) {
125- return memoryInformation.Protect == PAGE_EXECUTE ||
126- memoryInformation.Protect == PAGE_EXECUTE_READ ||
127- memoryInformation.Protect == PAGE_EXECUTE_READWRITE ||
128- memoryInformation.Protect == PAGE_EXECUTE_WRITECOPY ;
160+ bool IsForwarderRva (uint32_t rva) const {
161+ return rva >= exportDirStart && rva < exportDirStart + exportDirSize;
129162 }
130- return false ;
131- }
163+ };
132164
133165Library LoadLibraryOrExit (const char * dllPath) {
134- auto library = LoadLibrary (dllPath);
135- if (library != nullptr ) return library;
166+ auto library =
167+ LoadLibraryEx (dllPath, nullptr , LOAD_LIBRARY_AS_IMAGE_RESOURCE );
168+ if (library != nullptr ) return Library (library);
136169
137170 auto error = GetLastError ();
138171 std::cerr << " ERROR: Failed to load " << dllPath << std::endl;
@@ -163,31 +196,35 @@ int main(int argc, char** argv) {
163196 auto defFile = std::ofstream (argv[2 ]);
164197 defFile << " EXPORTS" << std::endl;
165198
166- for (const std::string& functionName : libnode.exportedSymbols ) {
199+ for (const Symbol& symbol : libnode.exportedSymbols ) {
167200 // If a symbol doesn't have a name then it has been exported as an
168201 // ordinal only. We assume that only named symbols are exported.
169- if (functionName.empty ()) continue ;
170-
171- // Every name in the exported symbols table should be resolvable
172- // to an address because we have actually loaded the library into
173- // our address space.
174- auto address = GetProcAddress (libnode.library , functionName.c_str ());
175- if (address == nullptr ) {
176- std::cerr << " WARNING: " << functionName
202+ if (symbol.name .empty ()) continue ;
203+
204+ if (symbol.rva == 0 ) {
205+ std::cerr << " WARNING: " << symbol.name
177206 << " appears in export table but is not a valid symbol"
178207 << std::endl;
179208 continue ;
180209 }
181210
182- defFile << " " << functionName << " = " << libnode.libraryName << " ."
183- << functionName ;
184-
211+ defFile << " " << symbol. name << " = " << libnode.libraryName << " ."
212+ << symbol. name ;
213+
185214 // Nothing distinguishes exported global data from exported functions
186215 // with C linkage. If we do not specify the DATA keyword for such symbols
187216 // then consumers of the .def file will get a linker error. This manifests
188- // as nodedbg_ symbols not being found. We assert that if the symbol is in
189- // an executable page in this process then it is a function, not data.
190- if (!IsPageExecutable (address)) {
217+ // as nodedbg_ symbols not being found. We assert that if the symbol's
218+ // RVA falls in a section with the IMAGE_SCN_MEM_EXECUTE characteristic
219+ // then it is a function, not data.
220+ //
221+ // A forwarder export is the exception: its RVA points back inside the
222+ // export directory, at a redirect string like "NTDLL.RtlAllocateHeap",
223+ // rather than at code or data. The export directory lives in a
224+ // non-executable section, but forwarders resolve to functions, so they
225+ // must not be marked DATA.
226+ if (!libnode.IsForwarderRva (symbol.rva ) &&
227+ !libnode.IsRvaExecutable (symbol.rva )) {
191228 defFile << " DATA" ;
192229 }
193230 defFile << std::endl;
0 commit comments