Skip to content

Commit ebc6b0b

Browse files
committed
build,tools: fix shared library cross-compile
Fixes: #52664 Signed-off-by: PickBas <sayed.kirill@gmail.com>
1 parent 45c7071 commit ebc6b0b

2 files changed

Lines changed: 109 additions & 49 deletions

File tree

node.gyp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,11 @@
12401240
'sources': [
12411241
'src/res/node.rc',
12421242
],
1243+
'libraries': [
1244+
'Dbghelp.lib',
1245+
'winmm.lib',
1246+
'Ws2_32.lib',
1247+
],
12431248
}],
12441249
],
12451250
}, # node_lib_target_name
@@ -1747,6 +1752,10 @@
17471752

17481753
'defines': [ 'NODE_WANT_INTERNALS=1' ],
17491754

1755+
# node_mksnapshot statically links node_base; it must not use the
1756+
# dllimport path meant for executables that load the libnode DLL.
1757+
'defines!': [ 'BUILDING_NODE_EXTENSION' ],
1758+
17501759
'sources': [
17511760
'src/node_snapshot_stub.cc',
17521761
'tools/snapshot/node_mksnapshot.cc',
@@ -1851,13 +1860,27 @@
18511860
'sources': [
18521861
'tools/gen_node_def.cc'
18531862
],
1863+
'conditions': [
1864+
# When cross-compiling, build this tool for the host so it can
1865+
# run during the build. The MSVS generator expects it to be
1866+
# named gen_node_def_host.exe in that case.
1867+
['want_separate_host_toolset', {
1868+
'toolsets': ['host'],
1869+
}],
1870+
],
18541871
},
18551872
{
18561873
'target_name': 'generate_node_def',
18571874
'dependencies': [
1858-
'gen_node_def',
18591875
'<(node_lib_target_name)',
18601876
],
1877+
'conditions': [
1878+
['want_separate_host_toolset', {
1879+
'dependencies': ['gen_node_def#host'],
1880+
}, {
1881+
'dependencies': ['gen_node_def'],
1882+
}],
1883+
],
18611884
'type': 'none',
18621885
'actions': [
18631886
{

tools/gen_node_def.cc

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#include <Windows.h>
2-
#include <algorithm>
32
#include <cstdint>
43
#include <fstream>
54
#include <iostream>
@@ -13,9 +12,9 @@
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.
6678
struct 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

133165
Library 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

Comments
 (0)