sea: `--build-sea` produces a segfaulting executable on macOS x64 (v26.7.0), while the blob + postject path works on the same runtime
还没有人认领这个 Issue。
- 主要语言
- JavaScript
- 星标
- 122k
- 派生
- 37.4k
- 平均合并
- 4 天 3 小时
- 30 天内合并 PR
- 272
描述
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.
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
首先,使用文档中记录的 node --build-sea 命令重现 macOS x64 上的失败,然后将其 Mach-O 段布局和 __init_offsets 与使用 --experimental-sea-config 加 postject 的正常路径进行比较。阅读 #61504 所引用的现有 --build-sea 覆盖;当生成的 SEA 可执行文件能够在此主机上成功启动且不需要 postject 回退时,即表示完成。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript, node.js
- 领域
- build-system, operating-systems
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 52/100