process.dlopen() should throw a clear Error (not segfault) when addon's libnode.so ABI mismatches the running Node
Personne n'a encore pris cette issue.
- Langage dominant
- JavaScript
- Étoiles
- 122k
- Forks
- 37.3k
- Merge moyen
- 4 j 2 h
- PR mergées (30 j)
- 283
Description
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
-
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.
-
The error is easy to detect.
dl_iterate_phdris already in use elsewhere in Node. A small extension would catch this. -
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.
-
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.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez par le reproducteur minimal.c et comparez la sortie de ldd pour les add-ons fonctionnel et qui plante. Lisez src/node_binding.cc et le chemin dlopen dans lib/internal/modules/cjs/loader.js, puis déterminez comment la dépendance libnode.so de l’add-on peut être vérifiée avant napi_register_module_v1. C’est terminé lorsqu’un décalage d’ABI produit une Error claire au lieu d’un segfault.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- c, cpp, javascript, linux, node.js
- Domaine
- backend, operating-systems
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 42/100