Repository navigation
Calling napi_ functions from MinGW-compiled addon results in access violation #38782
Description
- 1.62.0:
- Windows 10:
What steps will reproduce the bug?
- Compile a simple hello-world addon dll with MinGW64 linking to C node-api.
- Launch
node.exeand load addonrequire("path_to_addon/hello.nod")
If the compiled with MSVC, everything is fine, you can then call hello.hello().
With MinGW64 dll is loaded as expected via the system call to LoadLibraryExW. All the user-defined code can be invoked just fine with expected behavior. But an attempt to call an imported napi_ function results in access violation.
If you dynamically load napi_ functions from the process's address (uncommented code in hello.c), everything works. I failed to find the reason why it failed to properly link to node.lib.
This is my CMakeLists.txt
cmake_minimum_required(VERSION 3.11.4 FATAL_ERROR)
project(hello-node-api LANGUAGES C CXX)
add_library(hello SHARED hello.c)
target_include_directories(hello PRIVATE node-v16.2.0/include)
target_link_libraries(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/node-v16.2.0/node.lib)
set_target_properties(hello PROPERTIES
SUFFIX ".node"
PREFIX ""
)
target_compile_definitions(hello PUBLIC BUILDING_NODE_EXTENSION)
The addon's source. Uncommented code loads napi_ functions dynamically. In case you want to try it, comment out NAPI_MODULE macro.
// hello.c
#include <node/node_api.h>
#include <windows.h>
static void (*pRegisterModule)(napi_module*);
static napi_status (*pCreateStringUtf8)(napi_env, const char*, size_t, napi_value *);
static napi_status (*pCreateFunction)(napi_env, const char*, size_t, napi_callback, void*, napi_value*);
static napi_status (*pSetNamedProperty)(napi_env, napi_value, const char*, napi_value);
napi_value Method(napi_env env, napi_callback_info args)
{
napi_value greeting;
napi_status status = napi_create_string_utf8(env, "hello, asshole", NAPI_AUTO_LENGTH, &greeting);
// pCreateStringUtf8(env, "hello, asshole", NAPI_AUTO_LENGTH, &greeting);
return status == napi_ok ? greeting : (napi_value)0;
}
napi_value init(napi_env env, napi_value exports)
{
napi_value function;
napi_status status = napi_create_function(env, 0, 0, &Method, 0, &function);
// pCreateFunction(env, 0, 0, &Method, 0, &function);
if (status != napi_ok)
return (napi_value)0;
status = napi_set_named_property(env, exports, "hello", function);
// pSetNamedProperty(env, exports, "hello", function);
return status == napi_ok ? exports : (napi_value)0;
}
NAPI_MODULE(hello, init)
//static napi_module _module = {NAPI_MODULE_VERSION,
// 0,
// __FILE__,
// init,
// "hello",
// (void*)0,
// {0}};
//
//static void _register_hello(void) __attribute((constructor));
//static void _register_hello(void)
//{
// // loading from calling exe
// *(void**)&pRegisterModule = GetProcAddress(GetModuleHandleW(0),
// "napi_module_register");
// *(void**)&pCreateStringUtf8 = GetProcAddress(GetModuleHandleW(0),
// "napi_create_string_utf8");
// *(void**)&pCreateFunction = GetProcAddress(GetModuleHandleW(0),
// "napi_create_function");
// *(void**)&pSetNamedProperty = GetProcAddress(GetModuleHandleW(0),
// "napi_set_named_property");
//
// pRegisterModule(&_module);
//}
What is the expected behavior?
Node API exports C functions, so the are expected to be available no matter the compiler was used.
Additional information
I didn't notice any significant differences in compile and linker flags. I tried to reproduce this problem with MSVC by disabling default flags, but everything was fine still.
I also asked a question on SO, but no answers so far.
Activity
sergio-eld commentedon May 23, 2021
I've compared import address tables for both versions, and here is what I have found:
MSVC-compiled
hello.nodeMinGW-compiled
hello.nodeAs can be seen the second one is empty...
I guess it might actually be the reason. But I am still missing on the difference between linker and compiler flags
NickNaso commentedon Jul 21, 2021
Hi @ElDesalmado,
in the past I had similar problem and I'm working to make it easy to solve. The first step was to create a repository that contains the Node-API headers here: https://github.com/nodejs/node-api-headers with Node-API you don't need to download all the Node.js include files.
addon.cc
CMakeLists.txt
In my CMakeLists.txt file you can see a library called
libnode_api.athat I created from the list of symbols exported by Node-API. This is possible thanks tonode-api-headersthat is also a module (not yet published on npm) that you can install directly from gitnpm i https://github.com/nodejs/node-api-headers.Then you can use node-api-headers to build the node-api lib for example using a script like this:
The
dlltoolis part of MinGW.I hope that this info could help you.
sergio-eld commentedon Jul 22, 2021
@NickNaso The problem is caused by a bug within the MinGW linker in binutils. It corrupts the import table for some reason when there are a lot of mangled C++ symbols in an export lib provided by MSVC. It results in an empty import table.
So, what you did is a solid workaround - you regenerate the export lib with only C API (n_api) functions. I admire your effort and certainly your solution is helpful.
However, given that you still use MinGW binutils, it is not necessary to mess with node-api headers etc. All you have to do is to get all the C API symbols exported from
node.exe(you could use regex, for instance) and generate an export lib. Simple python script would suffice.However "however", there is a third approach. Since MinGW and Clang on Windows can link directly to a binary, there might be no need for an export lib period. But this produces unresolved issues due to
node.exebeing basically an executable. Also, CMake on Windows does not support direct linking to a binary when using targets withtarget_link_libraries.To sum up: you did a good job.
mhdawson commentedon Sep 3, 2021
Looks like the question has been answered, closing. Please let us know if you think that was not the right thing to do.