nodejs / nodejs/node

process.dlopen() should throw a clear Error (not segfault) when addon's libnode.so ABI mismatches the running Node

オープン
#63,740 コメント 4 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

node-api
主要言語
JavaScript
スター
122k
フォーク
37.3k
平均マージ
4日 2時間
マージ済み PR(30日)
283

説明

Summary

When a Node.js native addon is loaded into a process where the addon's linked libnode.so.{} ABI version does not match the running node binary's process.config.variables.node_module_version, the addon segfaults during napi_register_module_v1 instead of producing a useful error.

This is a common, easily-misdiagnosed failure mode (I personally misdiagnosed it as a WSL2/libuv bug for hours before finding the real cause with a 10-line reproducer). Node could detect the mismatch and throw a clear Error before the segfault, saving users significant debugging time.


The Failure (What Currently Happens)

On a system with a stale libnode.so.109 from an old libnode109 package, plus Node 22 (ABI 127), building any NAPI addon with default node-gyp flags produces a .node file that segfaults on load:

/* minimal.c — 10-line NAPI addon, no SIMD, no threading */
#include <node_api.h>

napi_value Hello(napi_env env, napi_callback_info info) {
    napi_value result;
    napi_create_string_utf8(env, "hello from native", NAPI_AUTO_LENGTH, &result);
    return result;
}

NAPI_MODULE_INIT() {
    napi_value fn;
    napi_create_function(env, "hello", NAPI_AUTO_LENGTH, Hello, NULL, &fn);
    napi_set_named_property(env, exports, "hello", fn);
    return exports;
}
# Build with default node-gyp flags (-lnode) — segfaults
$ gcc -shared -fPIC -I/usr/include/node minimal.c -lnode -o minimal_lnode.node
$ node -e "require('./minimal_lnode.node')"
Segmentation fault (core dumped)

# Build without -lnode (rare in practice, since node-gyp adds it) — works
$ gcc -shared -fPIC -I/usr/include/node minimal.c -o minimal.node
$ node -e "require('./minimal.node')"
# works fine

The ldd difference:

$ ldd minimal.node
        libc.so.6
        (no libnode)

$ ldd minimal_lnode.node
        libnode.so.109 => /lib/x86_64-linux-gnu/libnode.so.109
        libuv.so.1 => /lib/x86_64-linux-gnu/libuv.so.1
        ...

The system has libnode.so.109 (from Node 21) at the standard library path. node-gyp defaults to -lnode, so the addon's ELF needs that exact file. Node 22 (ABI 127) maps its own internal libnode.so.127 into the same address space, and the two libraries disagree on V8/libuv global layouts. The first call that touches shared state (napi_register_module_v1) segfaults.

ldd /usr/bin/node on the same system shows Node 22 itself linked to libnode.so.109, which is its own can of worms — but the addon-level segfault is what the user actually sees.


The Request

In process.dlopen() (or wherever the addon is mapped and napi_register_module_v1 is invoked), detect a libnode.so ABI mismatch and throw a clear Error instead of segfaulting.

Concretely, something like:

Error: Cannot load native addon './minimal_lnode.node':
  the addon was linked against libnode.so.109, but this Node.js
  process is built against libnode.so.127 (Node.js 22.22.2).
This usually means the system has an outdated libnode package
(e.g. libnode109 on Ubuntu/Debian). Try:
    sudo apt remove libnode109 libnode-dev
or rebuild the addon with the correct headers for this Node version.
    at Object.Module._extensions..node (node:internal/modules/cjs/loader:1429:18)
    at Module.load (node:internal/modules/cjs/loader:1034:32)
    ...

This is a one-time check at dlopen() time and would be invaluable for debugging. The cost is negligible; the gain is a much friendlier onboarding / debugging experience for users hitting this trap.


Suggested Implementation Sketch

After dlopen() succeeds, walk the addon's DT_NEEDED entries (via dl_iterate_phdr or by parsing DT_NEEDED directly from the addon's own .dynamic section) and check whether any of them look like libnode.so.<number>. If found, compare that number to process.config.variables.node_module_version (or equivalent) and throw before calling napi_register_module_v1.

Approximate location: src/node_binding.cc / dlopen in lib/internal/modules/cjs/loader.js, around the call to napi_register_module_v1.

A non-fatal variant could just emit process.emitWarning(...) rather than throw, if maintainers prefer non-breaking behaviour. But throwing is more discoverable.


Why This Matters

  1. The segfault is silent and unactionable. Users see "Segmentation fault" and assume Node, WSL2, their CPU, or the addon is broken. They try downgrades, rebuilds, BIOS settings — none of which help. The real fix is a one-liner on the system admin side.

  2. The error is easy to detect. dl_iterate_phdr is already in use elsewhere in Node. A small extension would catch this.

  3. It avoids duplicate issues. Searching the issue tracker, this class of bug appears repeatedly under different framings (WSL2, libuv, Hyper-V, CPU, etc.). A clear error message at load time would let users self-diagnose in seconds instead of opening duplicate bug reports.

  4. I have the minimal reproducer and a 100%-confirmed root cause. I was the original reporter; I can supply additional diagnostics, machine state, or test cases on request.


Related / Similar Reports

These may all be the same root cause with different symptom descriptions:

  • nodejs/node#58690 — WSL2 segfault on Node 24.2.0
  • microsoft/Foundry-Local#626 — Native lib segfault on WSL2 during catalog.getModel()
  • bun#11882 — Bun segfaults on WSL2 (worked on v1.0.9)

If any of those reporters can share their ldd output for the affected binary, I'd bet they show the same libnode.so.<wrong-version> linkage. Adding a friendly error in Node would at minimum give Bun/Foundry-Local users a head start.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

minimal.c の再現プログラムから始め、動作する addon とクラッシュする addon の ldd 出力を比較します。src/node_binding.cc と lib/internal/modules/cjs/loader.js の dlopen パスを読み、napi_register_module_v1 の前に addon の libnode.so 依存関係を検査する方法を決定します。ABI の不一致によって segfault ではなく明確な Error が発生すれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c, cpp, javascript, linux, node.js
領域
backend, operating-systems
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。