使用 MinGW 编译的 Node js (node-api) 插件导致访问冲突 [英] Node js (node-api) addon compiled with MinGW causes access violation

查看:34
本文介绍了使用 MinGW 编译的 Node js (node-api) 插件导致访问冲突的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

构建 node-api 链接的原生插件

经过3天的调查和研究,我对问题的原因没有想法.基本上我正在加载一个用 MinGW64 编译并链接到 C node-api 的 hello world Node JS 插件.

代码如下:

//hello.c#include napi_value 方法(napi_env env, napi_callback_info args){napi_value 问候语;napi_status status = napi_create_string_utf8(env, "hello, asshole", NAPI_AUTO_LENGTH, &greeting);返回状态 == napi_ok ?问候:(napi_value)0;}napi_value init(napi_env env, napi_value 导出){napi_value 函数;napi_status status = napi_create_function(env, 0, 0, &Method, 0, &function);如果(状态!= napi_ok)返回(napi_value)0;status = napi_set_named_property(环境,出口,你好",函数);返回状态 == napi_ok ?出口:(napi_value)0;}//static void _register_hello(void)__attribute((constructor));//调用 napi_module_registerNAPI_MODULE(你好,初始化)

我已经下载了 dist 头文件并预编译了我链接的 node.lib.这是我的 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.11.4 FATAL_ERROR)项目(你好节点 API 语言 C CXX)add_library(你好,共享 hello.c)# TODO: 下载 dist 并制作一个导入的目标target_include_directories(你好私人节点-v16.2.0/include)target_link_libraries(你好私人 ${CMAKE_CURRENT_SOURCE_DIR}/node-v16.2.0/node.lib)set_target_properties(你好属性后缀.node"前缀")target_compile_definitions(你好 PUBLIC BUILDING_NODE_EXTENSION)

加载插件的一些说明例程:

Node.exe 使用导出的符号进行编译,这些符号在 node.lib 中提供.
Node.exe 加载一个插件并调用一个 dll 入口点(在 Windows 上它是 __DllMainCRTStart).
任何一个 dll 版本(用 MSVC 或 MinGW 编译)都可以很好地加载:调用入口点,并且所有用户定义的函数都可以无错误地调用.但是尝试调用导入的 napi_ 函数会导致 MinGW 的访问冲突.

在 node.exe 中的 0x0000000000009238 处抛出异常:0xC0000005:访问冲突执行位置 0x0000000000009238.

使用 MSVC api 函数正常输入和插件注册等

模拟虚拟 MSVC c++ exe,加载一个 MinGW 插件,该插件链接到从 exe 导出的 C MSVC 符号:

首先我认为问题是由编译器引起的,但是无论用于构建 exe 的编译器如何,虚拟 exe(MSVC)->addon(MinGW)->exe_symbols(MSVC) 都可以正常工作:extern "C" 用于导出符号:

导出标题:

//esport.h#pragma once#ifndef 建筑#define EXPORT_API __declspec(dllimport)#别的#define EXPORT_API __declspec(dllexport)#万一EXPORT_API void hello_addon();EXPORT_API int sum(int a, int b);

插件:

//addon.c#include static void print_hello_from_export(void)__attribute((constructor));无效print_hello_from_export(无效){int res = sum(4, 15);你好_插件();}

exe:

extern "C";{#include "export.h";}#include 无效 hello_addon(){std::cout <<你好插件"<<std::endl;}int sum(int a, int b){返回 a + b;}#include int main(){HMODULE handle = LoadLibraryA(addon.dll");返回0;}

因此,无论用于构建可执行文件的编译器如何,都会加载库并打印一条消息.使用 C ABI 时会出现这种行为.

回到node-api的问题

我试图查看 hello.node 二进制文件中的符号,但我不知道如何处理这些信息.

<代码>...[894](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000001410 __CTOR_LIST__[895](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000014 _head_lib64_libkernel32_a[896](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000150 __imp_napi_module_register...

这里是cmake生成的忍者脚本:MSVC 构建

build CMakeFileshello.dirhello.c.obj: C_COMPILER__hello_Debug C$:dev
eposhello-node-apihello.c ||cmake_object_order_depends_target_hello定义 = -DBUILDING_NODE_EXTENSION -Dhello_EXPORTS标志 =/DWIN32/D_WINDOWS/W3/MDd/Zi/Ob0/Od/RTC1INCLUDES = -IC:dev
eposhello-node-api
ode-v16.2.0includeOBJECT_DIR = CMakeFileshello.dirOBJECT_FILE_DIR = CMakeFileshello.dirTARGET_COMPILE_PDB = CMakeFileshello.dirTARGET_PDB = hello.pdb# ==============================================================================# SHARED_LIBRARY 目标 hello 的链接构建语句############################################ 链接共享库hello.node构建 hello.node hello.lib: C_SHARED_LIBRARY_LINKER__hello_Debug CMakeFileshello.dirhello.c.obj |C$:dev
eposhello-node-api
ode-v16.2.0
ode.libLANGUAGE_COMPILE_FLAGS =/DWIN32/D_WINDOWS/W3/MDd/Zi/Ob0/Od/RTC1LINK_FLAGS =/machine:x64/debug/INCREMENTALLINK_LIBRARIES = C:dev
eposhello-node-api
ode-v16.2.0
ode.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.libOBJECT_DIR = CMakeFileshello.dirPOST_BUILD = cd .PRE_LINK = cd .复位 = 1TARGET_COMPILE_PDB = CMakeFileshello.dirTARGET_FILE = hello.nodeTARGET_IMPLIB = hello.libTARGET_PDB = hello.pdb

MinGW 构建

########################################### hello 的仅限订单的虚假目标构建 cmake_object_order_depends_target_hello: 虚假 ||CMakeFiles/hello.dir构建 CMakeFiles/hello.dir/hello.c.obj: C_COMPILER__hello_Debug C$:/dev/repos/hello-node-api/hello.c ||cmake_object_order_depends_target_hello定义 = -DBUILDING_NODE_EXTENSION -Dhello_EXPORTSDEP_FILE = CMakeFileshello.dirhello.c.obj.d标志 = -g包括 = -IC:/dev/repos/hello-node-api/node-v16.2.0/includeOBJECT_DIR = CMakeFileshello.dirOBJECT_FILE_DIR = CMakeFileshello.dir# ==============================================================================# SHARED_LIBRARY 目标 hello 的链接构建语句############################################ 链接共享库hello.node构建 hello.node libhello.dll.a: C_SHARED_LIBRARY_LINKER__hello_Debug CMakeFiles/hello.dir/hello.c.obj |C$:/dev/repos/hello-node-api/node-v16.2.0/node.libLANGUAGE_COMPILE_FLAGS = -gLINK_LIBRARIES = C:/dev/repos/hello-node-api/node-v16.2.0/node.lib -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32OBJECT_DIR = CMakeFileshello.dirPOST_BUILD = cd .PRE_LINK = cd .复位 = 1TARGET_FILE = hello.nodeTARGET_IMPLIB = libhello.dll.aTARGET_PDB = hello.node.dbg

我在编译和链接方面没有发现任何显着差异.

我还注意到为构建 Node 而生成的 Visual Studio 项目有一个明确的目标来构建 node.lib.我不知道这是否重要,但这是来自 .vsproj

的命令行参数

/Yunode_pch.h";/MP/GS/W3/wd4351"/wd4355"/wd4800"/wd4251"/wd4275"/wd4267"/Zc:wchar_t/Isrc"/IoutDebugobjglobal_intermediate"/IoutDebugobjglobal_intermediateinclude"/IoutDebugobjglobal_intermediatesrc"/I工具msvsgenfiles"/Idepshistogramsrc"/Idepsuvwasiinclude"/Idepsv8include"/Idepsicu-smallsourcei18n"/Idepsicu-smallsourcecommon"/Idepszlib"/Idepsllhttpinclude"/Idepscaresinclude"/Idepsuvinclude"/Ideps
ghttp2libincludes"/Idepsrotlicinclude"/Idepsopensslopensslinclude"/Ideps
gtcp2";/Ideps
gtcp2
gtcp2libincludes"/Ideps
gtcp2
gtcp2cryptoincludes"/Ideps
gtcp2
ghttp3libincludes"/Z7/Gm-/Od/FdoutDebugobjlibnodelibnode.pdb"/FInode_pch.h"/Zc:inline/fp:precise/D "V8_DEPRECATION_WARNINGS";/D "V8_IMMINENT_DEPRECATION_WARNINGS";/D "_GLIBCXX_USE_CXX11_ABI=1";/D WIN32"/D "_CRT_SECURE_NO_DEPRECATE";/D "_CRT_NONSTDC_NO_DEPRECATE";/D "_HAS_EXCEPTIONS=0";/D "BUILDING_V8_SHARED=1";/D "BUILDING_UV_SHARED=1";/D "OPENSSL_NO_PINSHARED";/D "OPENSSL_THREADS";/D "OPENSSL_NO_ASM";/D "NODE_ARCH="x64"";/D "NODE_WANT_INTERNALS=1";/D "V8_DEPRECATION_WARNINGS=1";/D "NODE_OPENSSL_SYSTEM_CERT_PATH=""";/D "HAVE_INSPECTOR=1";/D "HAVE_ETW=1";/D "FD_SETSIZE=1024";/D "NODE_PLATFORM="win32"";/D NOMINMAX"/D "_UNICODE=1";/D "NODE_USE_V8_PLATFORM=1";/D "NODE_HAVE_I18N_SUPPORT=1";/D "HAVE_OPENSSL=1";/D "UCONFIG_NO_SERVICE=1";/D "U_ENABLE_DYLOAD=0";/D "U_STATIC_IMPLEMENTATION=1";/D "U_HAVE_STD_STRING=1";/D "UCONFIG_NO_BREAK_ITERATION=0";/D "NGHTTP2_STATICLIB";/D "NGTCP2_STATICLIB";/D "NGHTTP3_STATICLIB";/D 调试"/D "_D​​EBUG";/D "V8_ENABLE_CHECKS";/errorReport:prompt/GF/WX-/Zc:forScope/RTC1/Gd/Oy-/MTd/FC/Fa"outDebugobjlibnode";/nologo/Fo"outDebugobjlibnode";/Fp"outDebugobjlibnodelibnode.pch";/诊断:列

我没有想法,在网络上有一些尝试加载 mingw 编译的插件的徒劳无功.其中一些是几年前的,仍然没有结果.所以我向社区寻求帮助以解决这个问题,或者至少理解为什么它无法解决.

所以归结为:

  1. 导致访问冲突的原因是什么?是因为导入的符号地址不在地址空间内(如何检查?),还是链接时使用的符号地址与node.exe<内的地址不符/code>?
  2. 如果问题不能代表插件解决,那么Node编译可能是什么问题?


检查函数地址:

我决定研究加载的(究竟是谁加载的?是__DllMainRTCStartup吗?)地址与node.exe中的地址不同的可能性>

node.exe 中:0x00007ff629d198e0 {node.exe!napi_module_register(napi_module *)}

拆解:

//注册一个 NAPI 模块.void napi_module_register(napi_module* mod) {00007FF629D198E0 mov qword ptr [rsp+8],rcx00007FF629D198E5 推 rsi00007FF629D198E6 推 rdi00007FF629D198E7 sub rsp,0C8h00007FF629D198EE mov rdi,rsp00007FF629D198F1 mov ecx,32h00007FF629D198F6 mov eax,0CCCCCCCC00007FF629D198FB rep stos dword ptr [rdi]00007FF629D198FD mov rcx,qword ptr [mod]...

但是当在 hello.node 中时:标识符napi_module_register";未定义 - 我不知道是否符合预期.拆解:

static void _register_hello(void) __attribute((constructor));静态无效_register_hello(无效){00007FFC02F81479 推送 rbp00007FFC02F8147A mov rbp,rsp00007FFC02F8147D sub rsp,20hnapi_module_register(&_module);00007FFC02F81481 lea rcx,[7FFC02F83020h]00007FFC02F81488 mov rax,qword ptr [7FFC02F89150h]00007FFC02F8148F 呼叫 rax}00007FFC02F81491 nop00007FFC02F81492 添加 rsp,20h00007FFC02F81496 弹出 rbp00007FFC02F81497 回复

call rax 导致 0000000000009238 ???????? 然后抛出访问冲突.


看起来node.exe的导入表是空的:

.idata 中 0xb32a9000 处有一个导入表导入表(解释 .idata 部分内容)vma:首先提示时间转发 DLL表章链名称 Thunk00009000 00009084 00000000 00000000 000092a0 00009170DLL 名称:node.exevma:提示/Ord 成员名称绑定

对于用 MSVC 编译的 dll,它NOT EMPTY:

转储文件 .hello.node文件类型:DLL部分包含以下导入:节点程序18000E1F8 导入地址表18000E5E8 导入名称表0 时间日期戳0 第一个转发器参考的索引6707 napi_set_named_property66A4 napi_create_function66F3 napi_module_register66AD napi_create_string_utf8

看来应该是这个原因.

导入地址表为空可能是原因吗?

解决方案

这是一个合法的解决方法,它提供了一些临时"的解决方案.解决方案.它涉及从调用应用程序显式加载符号,它必须是 node.exe.

虽然它确实有效,但我仍在寻找一种传统解决方案来正确地隐式加载导入的符号.

<代码>#include #include 静态无效(*pRegisterModule)(napi_module*);静态 napi_status (*pCreateStringUtf8)(napi_env, const char*, size_t, napi_value *);静态 napi_status (*pCreateFunction)(napi_env, const char*, size_t, napi_callback, void*, napi_value*);静态 napi_status (*pSetNamedProperty)(napi_env, napi_value, const char*, napi_value);napi_value 方法(napi_env env, napi_callback_info args){napi_value 问候语;napi_status status =//napi_create_string_utf8(env, "hello workaround", NAPI_AUTO_LENGTH, &greeting);pCreateStringUtf8(env, hello workaround", NAPI_AUTO_LENGTH, &greeting);返回状态 == napi_ok ?问候:(napi_value)0;}napi_value init(napi_env env, napi_value 导出){napi_value 函数;napi_status status =//napi_create_function(env, 0, 0, &Method, 0, &function);pCreateFunction(env, 0, 0, &Method, 0, &function);如果(状态!= napi_ok)返回(napi_value)0;status =//napi_set_named_property(env,exports, hello", function);pSetNamedProperty(环境,出口,你好",函数);返回状态 == napi_ok ?出口:(napi_value)0;}//static void _register_hello(void)__attribute((constructor));//调用 napi_module_register//NAPI_MODULE(你好,初始化)静态 napi_module _module = {NAPI_MODULE_VERSION,0,__文件__,在里面,你好",(空*)0,{0}};typedef void(WINAPI *void_func_ptr_t)(void);static void _register_hello(void) __attribute((constructor));静态无效_register_hello(无效){//TODO: 从调用 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);//napi_module_register(&_module);}

Building node-api linked native addon

I am out of thoughts on the cause of the problem after 3 days of investigating and researching. Basically I am loading a hello world Node JS addon compiled with MinGW64 and linked against C node-api.

The code is as follows:

// hello.c

#include <node/node_api.h>

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);

    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);
    if (status != napi_ok)
        return (napi_value)0;

    status = napi_set_named_property(env, exports, "hello", function);
    return status == napi_ok ? exports : (napi_value)0;
}

// static void _register_hello(void)__attribute((constructor));
// calls napi_module_register
NAPI_MODULE(hello, init)

I have downloaded dist headers and precompiled node.lib which I link against. This is my CMakeLists.txt file:

cmake_minimum_required(VERSION 3.11.4 FATAL_ERROR)

project(hello-node-api LANGUAGES C CXX)

add_library(hello SHARED hello.c)

# TODO: download dist and make an imported target
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)

Some explanation on the addon loading routine:

Node.exe is compiled with exported symbols, that are provided within the node.lib.
Node.exe loads an addon and a dll entry point (on Windows it is __DllMainCRTStart) is called.
Either of the dll versions (compiled either with MSVC or MinGW) are loaded just fine: the entry point is called, and all the user-defined functions are callable without errors. But an attempt to invoke an imported napi_ function results in access violation with MinGW.

Exception thrown at 0x0000000000009238 in node.exe: 0xC0000005: Access violation executing location 0x0000000000009238.

With MSVC api functions are entered normally and the addon gets registered etc.

Analog dummy MSVC c++ exe that loads a MinGW addon that links to exported C MSVC symbols from exe:

First I thought that the problem is caused by the compiler, but a dummy exe(MSVC)->addon(MinGW)->exe_symbols(MSVC) works just fine regardless of the compiler used to build an exe: extern "C" is used to export symbols:

Export header:

// esport.h

#pragma once

#ifndef BUILDING
#define EXPORT_API __declspec(dllimport)
#else
#define EXPORT_API __declspec(dllexport)
#endif

EXPORT_API void hello_addon();
EXPORT_API int sum(int a, int b);

Addon:

// addon.c
#include <export.h>

static void print_hello_from_export(void)__attribute((constructor));

void print_hello_from_export(void)
{
    int res = sum(4, 15);
    hello_addon();
}

exe:

extern "C" {
#include "export.h"
}

#include <iostream>

void hello_addon()
{
    std::cout << "hello addon" << std::endl;
}

int sum(int a, int b)
{
    return a + b;
}

#include <windows.h>

int main()
{
    HMODULE handle = LoadLibraryA("addon.dll");
    return 0;
}

So, the library is loaded and a message is printed, regardless of the compiler used to build the executable. This behavior is expected when using the C ABI.

Back to the problem with node-api

I tried to look through the symbols within the hello.node binary but I don't know what to do with this info.

...
[894](sec  1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000001410 __CTOR_LIST__
[895](sec  8)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000014 _head_lib64_libkernel32_a
[896](sec  8)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000150 __imp_napi_module_register
...

Here is the Ninja script generated by cmake: MSVC build

build CMakeFileshello.dirhello.c.obj: C_COMPILER__hello_Debug C$:dev
eposhello-node-apihello.c || cmake_object_order_depends_target_hello
  DEFINES = -DBUILDING_NODE_EXTENSION -Dhello_EXPORTS
  FLAGS = /DWIN32 /D_WINDOWS /W3 /MDd /Zi /Ob0 /Od /RTC1
  INCLUDES = -IC:dev
eposhello-node-api
ode-v16.2.0include
  OBJECT_DIR = CMakeFileshello.dir
  OBJECT_FILE_DIR = CMakeFileshello.dir
  TARGET_COMPILE_PDB = CMakeFileshello.dir
  TARGET_PDB = hello.pdb


# =============================================================================
# Link build statements for SHARED_LIBRARY target hello


#############################################
# Link the shared library hello.node

build hello.node hello.lib: C_SHARED_LIBRARY_LINKER__hello_Debug CMakeFileshello.dirhello.c.obj | C$:dev
eposhello-node-api
ode-v16.2.0
ode.lib
  LANGUAGE_COMPILE_FLAGS = /DWIN32 /D_WINDOWS /W3 /MDd /Zi /Ob0 /Od /RTC1
  LINK_FLAGS = /machine:x64 /debug /INCREMENTAL
  LINK_LIBRARIES = C:dev
eposhello-node-api
ode-v16.2.0
ode.lib  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib
  OBJECT_DIR = CMakeFileshello.dir
  POST_BUILD = cd .
  PRE_LINK = cd .
  RESTAT = 1
  TARGET_COMPILE_PDB = CMakeFileshello.dir
  TARGET_FILE = hello.node
  TARGET_IMPLIB = hello.lib
  TARGET_PDB = hello.pdb

MinGW build

#############################################
# Order-only phony target for hello

build cmake_object_order_depends_target_hello: phony || CMakeFiles/hello.dir

build CMakeFiles/hello.dir/hello.c.obj: C_COMPILER__hello_Debug C$:/dev/repos/hello-node-api/hello.c || cmake_object_order_depends_target_hello
  DEFINES = -DBUILDING_NODE_EXTENSION -Dhello_EXPORTS
  DEP_FILE = CMakeFileshello.dirhello.c.obj.d
  FLAGS = -g
  INCLUDES = -IC:/dev/repos/hello-node-api/node-v16.2.0/include
  OBJECT_DIR = CMakeFileshello.dir
  OBJECT_FILE_DIR = CMakeFileshello.dir


# =============================================================================
# Link build statements for SHARED_LIBRARY target hello


#############################################
# Link the shared library hello.node

build hello.node libhello.dll.a: C_SHARED_LIBRARY_LINKER__hello_Debug CMakeFiles/hello.dir/hello.c.obj | C$:/dev/repos/hello-node-api/node-v16.2.0/node.lib
  LANGUAGE_COMPILE_FLAGS = -g
  LINK_LIBRARIES = C:/dev/repos/hello-node-api/node-v16.2.0/node.lib  -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32
  OBJECT_DIR = CMakeFileshello.dir
  POST_BUILD = cd .
  PRE_LINK = cd .
  RESTAT = 1
  TARGET_FILE = hello.node
  TARGET_IMPLIB = libhello.dll.a
  TARGET_PDB = hello.node.dbg

I can't spot any significant differences in compilation and linking.

I also noticed that visual studio project generated to build Node has a distinct target to build node.lib. I don't know if it matters, but here is the command line args from .vsproj

/Yu"node_pch.h" /MP /GS /W3 /wd"4351" /wd"4355" /wd"4800" /wd"4251" /wd"4275" /wd"4267" /Zc:wchar_t /I"src" /I"outDebugobjglobal_intermediate" /I"outDebugobjglobal_intermediateinclude" /I"outDebugobjglobal_intermediatesrc" /I"toolsmsvsgenfiles" /I"depshistogramsrc" /I"depsuvwasiinclude" /I"depsv8include" /I"depsicu-smallsourcei18n" /I"depsicu-smallsourcecommon" /I"depszlib" /I"depsllhttpinclude" /I"depscaresinclude" /I"depsuvinclude" /I"deps
ghttp2libincludes" /I"depsrotlicinclude" /I"depsopensslopensslinclude" /I"deps
gtcp2" /I"deps
gtcp2
gtcp2libincludes" /I"deps
gtcp2
gtcp2cryptoincludes" /I"deps
gtcp2
ghttp3libincludes" /Z7 /Gm- /Od /Fd"outDebugobjlibnodelibnode.pdb" /FI"node_pch.h" /Zc:inline /fp:precise /D "V8_DEPRECATION_WARNINGS" /D "V8_IMMINENT_DEPRECATION_WARNINGS" /D "_GLIBCXX_USE_CXX11_ABI=1" /D "WIN32" /D "_CRT_SECURE_NO_DEPRECATE" /D "_CRT_NONSTDC_NO_DEPRECATE" /D "_HAS_EXCEPTIONS=0" /D "BUILDING_V8_SHARED=1" /D "BUILDING_UV_SHARED=1" /D "OPENSSL_NO_PINSHARED" /D "OPENSSL_THREADS" /D "OPENSSL_NO_ASM" /D "NODE_ARCH="x64"" /D "NODE_WANT_INTERNALS=1" /D "V8_DEPRECATION_WARNINGS=1" /D "NODE_OPENSSL_SYSTEM_CERT_PATH=""" /D "HAVE_INSPECTOR=1" /D "HAVE_ETW=1" /D "FD_SETSIZE=1024" /D "NODE_PLATFORM="win32"" /D "NOMINMAX" /D "_UNICODE=1" /D "NODE_USE_V8_PLATFORM=1" /D "NODE_HAVE_I18N_SUPPORT=1" /D "HAVE_OPENSSL=1" /D "UCONFIG_NO_SERVICE=1" /D "U_ENABLE_DYLOAD=0" /D "U_STATIC_IMPLEMENTATION=1" /D "U_HAVE_STD_STRING=1" /D "UCONFIG_NO_BREAK_ITERATION=0" /D "NGHTTP2_STATICLIB" /D "NGTCP2_STATICLIB" /D "NGHTTP3_STATICLIB" /D "DEBUG" /D "_DEBUG" /D "V8_ENABLE_CHECKS" /errorReport:prompt /GF /WX- /Zc:forScope /RTC1 /Gd /Oy- /MTd /FC /Fa"outDebugobjlibnode" /nologo /Fo"outDebugobjlibnode" /Fp"outDebugobjlibnodelibnode.pch" /diagnostics:column 

I am out of ideas, on the web there are some fruitless attempts to load a mingw-compiled addon. Some of them are from several years ago and still no result. So I ask the community for help in order to solve this issue or at least understand why it can't be solved.

So it boils down to:

  1. What causes the access violation? Is it because of the imported symbol address is not within the address space (how can I check it?), or maybe the address of the symbol that was used during linking does not correspond to the address within the node.exe?
  2. If the problem can't be solved on behalf of the addon, what might be the problem in Node compilation?


Checking function addresses:

I decided to look into the possibility that the loaded (who exactly loads it? Is it __DllMainRTCStartup?) addresses differ from the addresses within the node.exe

While inside the node.exe: 0x00007ff629d198e0 {node.exe!napi_module_register(napi_module *)}

Disassembly:

// Registers a NAPI module.
void napi_module_register(napi_module* mod) {
00007FF629D198E0  mov         qword ptr [rsp+8],rcx  
00007FF629D198E5  push        rsi  
00007FF629D198E6  push        rdi  
00007FF629D198E7  sub         rsp,0C8h  
00007FF629D198EE  mov         rdi,rsp  
00007FF629D198F1  mov         ecx,32h  
00007FF629D198F6  mov         eax,0CCCCCCCCh  
00007FF629D198FB  rep stos    dword ptr [rdi]  
00007FF629D198FD  mov         rcx,qword ptr [mod]   
...

But when inside the hello.node: identifier "napi_module_register" is undefined - I don't know if it is expected. Disassembly:

static void _register_hello(void) __attribute((constructor));
static void _register_hello(void)
{
00007FFC02F81479  push        rbp  
00007FFC02F8147A  mov         rbp,rsp  
00007FFC02F8147D  sub         rsp,20h  
    napi_module_register(&_module);
00007FFC02F81481  lea         rcx,[7FFC02F83020h]  
00007FFC02F81488  mov         rax,qword ptr [7FFC02F89150h]  
00007FFC02F8148F  call        rax  
}
00007FFC02F81491  nop  
00007FFC02F81492  add         rsp,20h  
00007FFC02F81496  pop         rbp  
00007FFC02F81497  ret  

call rax leads to 0000000000009238 ?? ?????? and then access violation is thrown.


It looks like the import table for node.exe is empty:

There is an import table in .idata at 0xb32a9000

The Import Tables (interpreted .idata section contents)
 vma:            Hint    Time      Forward  DLL       First
                 Table   Stamp     Chain    Name      Thunk
 00009000   00009084 00000000 00000000 000092a0 00009170

    DLL Name: node.exe
    vma:  Hint/Ord Member-Name Bound-To

And for dll compiled with MSVC it is NOT EMPTY:

Dump of file .hello.node

File Type: DLL

  Section contains the following imports:

    node.exe
             18000E1F8 Import Address Table
             18000E5E8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                        6707 napi_set_named_property
                        66A4 napi_create_function
                        66F3 napi_module_register
                        66AD napi_create_string_utf8

Looks like it mught be the reasin after all.

Can an empty import address table be the reason?

解决方案

This is a legit workaround, that provides some "temporary" solution. It involves explicit loading of symbols from the calling application, which has to be node.exe.

Though it does work, I am still looking for a conventional solution to correctly load imported symbols implicitly.


#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 workaround", NAPI_AUTO_LENGTH, &greeting);
            pCreateStringUtf8(env, "hello workaround", 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;
}

// static void _register_hello(void)__attribute((constructor));
// calls napi_module_register
//NAPI_MODULE(hello, init)
static napi_module _module = {NAPI_MODULE_VERSION,
                           0,
                           __FILE__,
                           init,
                           "hello",
                           (void*)0,
                           {0}};

typedef void(WINAPI *void_func_ptr_t)(void);

static void _register_hello(void) __attribute((constructor));
static void _register_hello(void)
{
    // TODO: load pointers 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);
//    napi_module_register(&_module);
}


这篇关于使用 MinGW 编译的 Node js (node-api) 插件导致访问冲突的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆