微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 MinGW 编译的 Node js (node-api) 插件导致访问冲突

如何解决使用 MinGW 编译的 Node js (node-api) 插件导致访问冲突

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

经过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,&Method,&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)

我已经下载了 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)

关于插件加载routine的一些解释:

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

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

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

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

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

导出标题

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

exe:

// 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();
}

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

回到node-api的问题

我试图查看 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; } 二进制文件中的符号,但我不知道如何处理这些信息。

hello.node

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

...
[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
...

MinGW 构建

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

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

我还注意到为构建 Node 而生成的 Visual Studio 项目有一个明确的目标来构建 ############################################# # 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 。我不知道这是否重要,但这里是来自 node.lib

的命令行参数
.vsproj

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

所以归结为:

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

检查函数地址:

我决定研究加载的(究竟是谁加载的?是 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] ... 内部时: 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 指向 call rax,然后抛出访问冲突。


看起来 0000000000009238 ?? ?????? 的导入表是空的:

node.exe

对于用 MSVC 编译的 dll,它NOT 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

看来应该是这个原因。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。