nodejs / nodejs/node

sea: `--build-sea` produces a segfaulting executable on macOS x64 (v26.7.0), while the blob + postject path works on the same runtime

未关闭
#65,479 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先,使用文档中记录的 node --build-sea 命令重现 macOS x64 上的失败,然后将其 Mach-O 段布局和 __init_offsets 与使用 --experimental-sea-configpostject 的正常路径进行比较。阅读 #61504 所引用的现有 --build-sea 覆盖;当生成的 SEA 可执行文件能够在此主机上成功启动且不需要 postject 回退时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
javascript, node.js
领域
build-system, operating-systems
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
52/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。