Ser*_*nik 5 c++ dll access-violation node.js
经过3天的调查和研究,我对问题的原因已经一无所知。基本上我正在加载一个用 MinGW64 编译并链接到 C node-api 的 hello world Node JS 插件。
代码如下:
// 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)
Run Code Online (Sandbox Code Playgroud)
我已经下载了 dist 标头并预编译了node.lib我链接的标头。这是我的CMakeLists.txt文件:
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)
Run Code Online (Sandbox Code Playgroud)
Node.exe 使用导出符号进行编译,这些符号在node.lib. Node.exe 加载一个插件并调用
一个 dll 入口点(在 Windows 上是)。
任一 dll 版本(使用 MSVC 或 MinGW 编译)都可以正常加载:调用入口点,并且可以毫无错误地调用所有用户定义的函数。但是尝试调用导入的函数会导致 MinGW 访问冲突。__DllMainCRTStartnapi_
Exception thrown at 0x0000000000009238 in node.exe: 0xC0000005: Access violation executing location 0x0000000000009238.
Run Code Online (Sandbox Code Playgroud)
使用 MSVC api 函数可以正常输入,并且插件可以注册等。
首先我认为问题是由编译器引起的,但是无论用于构建 exe 的编译器如何,虚拟 exe(MSVC)->addon(MinGW)->exe_symbols(MSVC) 都可以正常工作:extern "C"用于导出符号:
导出标头:
// 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);
Run Code Online (Sandbox Code Playgroud)
添加在:
// 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();
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
因此,无论用于构建可执行文件的编译器是什么,都会加载该库并打印一条消息。使用 C ABI 时会出现此行为。
我试图查看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
...
Run Code Online (Sandbox Code Playgroud)
这是由 cmake 生成的 Ninja 脚本:MSVC build
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
FLAGS = /DWIN32 /D_WINDOWS /W3 /MDd /Zi /Ob0 /Od /RTC1
INCLUDES = -IC:\dev\repos\hello-node-api\node-v16.2.0\include
OBJECT_DIR = CMakeFiles\hello.dir
OBJECT_FILE_DIR = CMakeFiles\hello.dir
TARGET_COMPILE_PDB = CMakeFiles\hello.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 CMakeFiles\hello.dir\hello.c.obj | C$:\dev\repos\hello-node-api\node-v16.2.0\node.lib
LANGUAGE_COMPILE_FLAGS = /DWIN32 /D_WINDOWS /W3 /MDd /Zi /Ob0 /Od /RTC1
LINK_FLAGS = /machine:x64 /debug /INCREMENTAL
LINK_LIBRARIES = C:\dev\repos\hello-node-api\node-v16.2.0\node.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib
OBJECT_DIR = CMakeFiles\hello.dir
POST_BUILD = cd .
PRE_LINK = cd .
RESTAT = 1
TARGET_COMPILE_PDB = CMakeFiles\hello.dir\
TARGET_FILE = hello.node
TARGET_IMPLIB = hello.lib
TARGET_PDB = hello.pdb
Run Code Online (Sandbox Code Playgroud)
MinGW构建
#############################################
# 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 = CMakeFiles\hello.dir\hello.c.obj.d
FLAGS = -g
INCLUDES = -IC:/dev/repos/hello-node-api/node-v16.2.0/include
OBJECT_DIR = CMakeFiles\hello.dir
OBJECT_FILE_DIR = CMakeFiles\hello.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 = CMakeFiles\hello.dir
POST_BUILD = cd .
PRE_LINK = cd .
RESTAT = 1
TARGET_FILE = hello.node
TARGET_IMPLIB = libhello.dll.a
TARGET_PDB = hello.node.dbg
Run Code Online (Sandbox Code Playgroud)
我看不出编译和链接方面有任何显着差异。
我还注意到,为构建 Node 生成的 Visual Studio 项目有一个明确的构建目标node.lib。我不知道这是否重要,但这是来自的命令行参数.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"out\Debug\obj\global_intermediate" /I"out\Debug\obj\global_intermediate\include" /I"out\Debug\obj\global_intermediate\src" /I"tools\msvs\genfiles" /I"deps\histogram\src" /I"deps\uvwasi\include" /I"deps\v8\include" /I"deps\icu-small\source\i18n" /I"deps\icu-small\source\common" /I"deps\zlib" /I"deps\llhttp\include" /I"deps\cares\include" /I"deps\uv\include" /I"deps\nghttp2\lib\includes" /I"deps\brotli\c\include" /I"deps\openssl\openssl\include" /I"deps\ngtcp2" /I"deps\ngtcp2\ngtcp2\lib\includes" /I"deps\ngtcp2\ngtcp2\crypto\includes" /I"deps\ngtcp2\nghttp3\lib\includes" /Z7 /Gm- /Od /Fd"out\Debug\obj\libnode\libnode.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"out\Debug\obj\libnode\" /nologo /Fo"out\Debug\obj\libnode\" /Fp"out\Debug\obj\libnode\libnode.pch" /diagnostics:column
Run Code Online (Sandbox Code Playgroud)
我没有想法,在网络上有一些徒劳的尝试来加载 mingw 编译的插件。其中一些是几年前的,但仍然没有结果。因此,我向社区寻求帮助,以解决这个问题,或者至少了解为什么它无法解决。
所以归结为:
node.exe?我决定研究加载的(到底是谁加载的?是吗__DllMainRTCStartup?)地址与node.exe
在里面时node.exe:
0x00007ff629d198e0 {node.exe!napi_module_register(napi_module *)}
拆卸:
// 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]
...
Run Code Online (Sandbox Code Playgroud)
但是当在hello.node:
identifier "napi_module_register" is undefined- 我不知道这是否是预期的。拆卸:
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
Run Code Online (Sandbox Code Playgroud)
call rax导致0000000000009238 ?? ?????? 然后抛出访问冲突。
看起来导入表node.exe是空的:
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
Run Code Online (Sandbox Code Playgroud)
对于使用 MSVC 编译的 dll,它不是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
Run Code Online (Sandbox Code Playgroud)
看起来这可能就是原因所在。
空的导入地址表可能是原因吗?
这是一个合法的解决方法,提供了一些“临时”解决方案。它涉及从调用应用程序显式加载符号,该应用程序必须是node.exe.
尽管它确实有效,但我仍在寻找一种传统的解决方案来正确地隐式加载导入的符号。
#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);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
979 次 |
| 最近记录: |