sea: `--build-sea` produces a segfaulting executable on macOS x64 (v26.7.0), while the blob + postject path works on the same runtime
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 52/100
- Type d'issue
- Bug
- Clarté
- Plutôt claire
- Activité
- Active
- Stack technique
- javascript, node.js
- Domaine
- build-system, operating-systems
Piste de recherche
Commencez par reproduire l’échec sur macOS x64 avec la commande documentée node --build-sea, puis comparez sa disposition des segments Mach-O et __init_offsets avec le chemin fonctionnel utilisant --experimental-sea-config et postject. Lisez la couverture existante de --build-sea mentionnée dans #61504 ; le travail est considéré comme terminé lorsqu’un exécutable SEA généré se lance correctement sur cet hôte sans nécessiter le fallback de postject.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Version
v26.7.0
Platform
Darwin 24.6.0 Darwin Kernel Version 24.6.0 x86_64
macOS 15.7.3 (24G419), Intel Core i5-7500
Subsystem
sea
What steps will reproduce the bug?
On macOS x64, an executable produced by node --build-sea segfaults on launch — including
for a one-line hello world. The same runtime, given the same script through
--experimental-sea-config + postject, produces an executable that runs.
$ echo 'console.log("hello");' > hello.js
# --build-sea
$ printf '{"main":"hello.js","output":"a-build-sea","disableExperimentalSEAWarning":true}' > a.json
$ node --build-sea a.json
Generated single executable .../bin/node + a.json -> a-build-sea
$ codesign --remove-signature a-build-sea && codesign --sign - a-build-sea
$ ./a-build-sea
Segmentation fault: 11 # exit 139
# the blob path, same node, same script
$ printf '{"main":"hello.js","output":"hello.blob","disableExperimentalSEAWarning":true}' > blob.json
$ node --experimental-sea-config blob.json
Wrote single executable preparation blob to hello.blob
$ cp "$(command -v node)" d-postject && chmod 755 d-postject
$ npx postject d-postject NODE_SEA_BLOB hello.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho-segment-name NODE_SEA --overwrite
$ codesign --remove-signature d-postject && codesign --sign - d-postject
$ ./d-postject
hello # exit 0
How often does it reproduce? Is there a required condition?
Every time, on this host. Not conditional on anything in the config:
| variant | result |
|---|---|
--build-sea, CommonJS (no mainFormat) |
SIGSEGV |
--build-sea, "mainFormat": "module" |
SIGSEGV |
--build-sea, no codesign step at all |
SIGSEGV |
--experimental-sea-config + postject, same node |
runs |
So it is neither the module format nor the ad-hoc signature.
What is the expected behavior? Why is that the expected behavior?
node --build-sea should produce a runnable executable — it is documented as the
single-command replacement for the blob + postject procedure, and the procedure it
replaces works on this exact runtime.
What do you see instead?
SIGSEGV before main, while dyld is running the binary's static initializers:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000001
thread #1, stop reason = EXC_BAD_ACCESS (code=1, address=0x1)
frame #0: 0x000000010000c8f0 a-build-sea`__cxx_global_var_init
dyld invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(...)
dyld mach_o::Header::forEachSection(...)
dyld dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const
dyld dyld4::JustInTimeLoader::runInitializers(dyld4::RuntimeState&) const
dyld dyld4::Loader::runInitializersBottomUp(...)
dyld dyld4::APIs::runAllInitializersForMain()
The faulting address is 0x1, which reads like an initializer entry that was not
relocated rather than a fault inside any particular initializer. (The frame #0 symbol
is a nearest-symbol guess and is probably not meaningful — the appended blob shifts
the symbolication.)
Additional information
Comparing the two outputs, the segment layout is structurally identical but
--build-sea places __DATA_CONST (and everything after it) one page higher than the
postject output does:
$ otool -l a-build-sea | grep -A4 LC_SEGMENT_64 | grep -E 'segname|vmaddr'
segname __TEXT
vmaddr 0x0000000100000000
segname __DATA_CONST
vmaddr 0x00000001061e1000
segname __DATA
vmaddr 0x0000000106394000
segname NODE_SEA
vmaddr 0x000000010640f000
segname __LINKEDIT
vmaddr 0x0000000106410000
$ otool -l d-postject | grep -A4 LC_SEGMENT_64 | grep -E 'segname|vmaddr'
segname __TEXT
vmaddr 0x0000000100000000
segname __DATA_CONST
vmaddr 0x00000001061e0000 # one page lower
segname __DATA
vmaddr 0x0000000106393000
segname NODE_SEA
vmaddr 0x000000010640e000
segname __LINKEDIT
vmaddr 0x000000010640f000
Both binaries carry __init_offsets and report identical Mach-O headers (ncmds 22,
sizeofcmds 2728), so the load commands themselves are not obviously malformed — but
__DATA_CONST moving without the initializer offsets following it would produce exactly
the observed 0x1. I have not confirmed that causally; it is where I would look first.
This is the same class of problem as #61483 (SEA build corrupting .gnu.hash on Linux
arm64) — the build step rewriting the container in a way the loader then rejects — on a
different platform and a different section. #61504 already skips --build-sea tests on
platforms where SEA is flaky; macOS x64 may belong in that set until this is understood.
Node was installed via nvm from the official tarball
(process.release.sourceUrl = https://nodejs.org/download/release/v26.7.0/node-v26.7.0.tar.gz),
so this is an official x64 build, not a self-compiled or Rosetta one.
Practical impact: a project shipping standalone executables cannot rely on --build-sea
on macOS x64 and has to keep the postject fallback alive, detecting the failure by
running the produced binary before trusting it.
- Langage dominant
- JavaScript
- Étoiles
- 122k
- Forks
- 37.4k
- Merge moyen
- 4 j 3 h
- PR mergées (30 j)
- 279
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.
Autres issues de nodejs/node
-
doc
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
-
build
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
-
Difficulté 2/5 1-3 heures Accessibilité débutants 84/100
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 90/100
-
feature request
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
Toutes les issues de nodejs/node
Issues similaires
-
awaiting triage bug Causes friction Hop Gui P1 P2 Transforms
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
-
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
-
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
-
Improve Title Support Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
georgestephanis/p2026#40 ·
-
Enatega Customer and Rider app: Add-ons price is not visible to customer after order is placed. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
Margaret-Petersen/food-delivery-app-clone-react-native#1981 ·