问题的说明跟复现请看这里,这篇文章主要记录一下如何排查 node addon 代码引发的问题。
保存进程崩溃的翻车现场
在 Linux/KDE 环境下,当应用进程崩溃的时候,systemd 会记录下该进程的翻车现场(aka,coredump)。我们可以使用 coredumpctl
这个工具查看当前机器上发生过的 coredump。
List all captured core dumps:
coredumpctl
List captured core dumps for a program:
coredumpctl list program
Show information about the core dumps matching a program with `PID`:
coredumpctl info PID
Invoke debugger using the last core dump:
coredumpctl debug
Invoke debugger using the last core dump of a program:
coredumpctl debug program
Extract the last core dump of a program to a file:
coredumpctl --output path/to/file dump program
Skip debuginfod and pagination prompts and then print the backtrace when using `gdb`:
coredumpctl debug --debugger-arguments "-iex 'set debuginfod enabled on' -iex 'set pagination off' -ex bt"
还原翻车现场
通过上面的步骤获取到 coredump 后,可以搭配 debugger 工具跟 symbol file 定位出问题的代码。我选择的 debugger 工具是 gdb 搭配 CLion 作 UI。这样只要将 node addon 项目用 CLion 打开,并导入 coredump 后,就可以获得一个功能齐全的 GUI Debugger。
用 CLion 打开 node addon 项目
以 duckdb-node-neo 为例,这个项目使用 node-gyp 作为构建工具,而 CLion 只支持 CMAKE 工程,所以需要手动创建一份 CMakefile 文件,让 IDE 的智能提示正常工具,方便在 debug 的过程中查看定义、搜索引用等。
下面的这份 CMake 文件是我跟 Claude 一起编写的,不一定能够通过 IDE 编译 node addon,但是可以提供智能补全。
cmake_minimum_required(VERSION 3.10)
project(duckdb_node_neo)
set(CMAKE_CXX_STANDARD 17)
# include node headers
include_directories(/usr/include/node)
# Node.js addon API headers
include_directories(${CMAKE_SOURCE_DIR}/bindings/node_modules/node-addon-api)
# DuckDB headers
include_directories(${CMAKE_SOURCE_DIR}/bindings/libduckdb)
# Add source files
file(GLOB SOURCE_FILES
"bindings/src/*.cpp"
)
# This is just for CLion's code intelligence - not for actual building
add_library(duckdb_node_bindings SHARED ${SOURCE_FILES})
# Define preprocessor macros used in the code
add_definitions(-DNODE_ADDON_API_DISABLE_DEPRECATED)
add_definitions(-DNODE_ADDON_API_REQUIRE_BASIC_FINALIZERS)
add_definitions(-DNODE_API_NO_EXTERNAL_BUFFERS_ALLOWED)
# Add Node.js specific definitions to help CLion's IntelliSense
add_definitions(-DNAPI_VERSION=8)
# Tell CLion to treat this as a Node.js addon project
set_target_properties(duckdb_node_bindings PROPERTIES
CXX_STANDARD 17
PREFIX ""
SUFFIX ".node"
)
生成带有 symbol 的二进制
设置后 debugger 后,还需要提供一个 symbol 文件,帮助 debugger 定位代码位置。对于 node-gyp 而言,只需要在编译时设置 --debug
参数即可生成带有 symbol 的二进制。
cross-replace node-gyp configure --verbose --arch=$TARGET_ARCH --debug && node-gyp build --verbose --arch=$TARGET_ARCH --debug
接下来我们只要用带有 symbol 的二进制复现进程崩溃的问题,就可以得到一个方便 debug 的 coredump。
定位问题
通过 debug coredump 文件,可以很容易找到抛出错误的地方,但这次很不走运的是, error
变量是 NULL
,没有提供任何有价值的信息。另外,可以发现 result_ptr_
似乎处于一个没有被初始化的状态。
查看 duckdb 的源码后发现,虽然 duckdb 对外提供的是 C API,但是其本身是用 C++ 开发的,在将 C++ 操作以 C API 的形式暴露时,duckdb 会捕获内部的异常,然后将其中的错误信息写入到 duckdb_result
中。
目前 coredump 表明 result_ptr_
未被 duckdb 正常初始化,而异常对象很可能已经在跳出 catch 作用域时被销毁,现在只能在运行时 debug 才能捕获到具体的错误信息了。
附加到进程上 debug
CLion 提供了非常好用的附加到未启动进程上 debug 的功能,只需要向 CLion 提供进程的 commandline,它就可以自动将 debugger 附加到该进程上。

在开始 debug 之前,还需要设置一下 C++ 异常断点:

当启动问题进程,并复现崩溃后,就可以在 debugger 视图查看到实时的异常对象信息:

这里的 obj
就是异常对象,但是由于 CLion 没有识别出异常的类型,我们得手动用 gdb 指令查看 obj 的信息。
通过 gdb 的输出,可以看到异常对象的类型是 duckdb::InvalidInputException
:

这时在变量页面输入下面的指令就行:
*(duckdb::InvalidInputException*)obj

终于,更加具体的错误信息被挖掘出来了~