Skip to content

fix: emit build-id in arm64-musl cross-compiled binaries#233

Open
awbx wants to merge 1 commit into
nodejs:mainfrom
awbx:fix-arm64-musl-build-id
Open

fix: emit build-id in arm64-musl cross-compiled binaries#233
awbx wants to merge 1 commit into
nodejs:mainfrom
awbx:fix-arm64-musl-build-id

Conversation

@awbx
Copy link
Copy Markdown

@awbx awbx commented May 20, 2026

Summary

The musl-cross-make toolchain used for arm64-musl has no specs file injecting -Wl,--build-id, unlike the distro-packaged cross-compilers used for loong64/riscv64 or the native gcc used for x64-musl. As a result, the resulting binary has no .note.gnu.build-id section and no PT_NOTE program header at all, which breaks postject-based SEA tooling (dl_iterate_phdr finds no notes → postject_find_resource() returns NULL → BlobDeserializer::ReadArithmetic SEGVs).

Adding LDFLAGS="-Wl,--build-id=sha1" to recipes/arm64-musl/run.sh restores parity with every other recipe's output and unblocks SEA on the unofficial arm64-musl tarball.

Why this is the right place to fix it

Recipe Cross-compiler source Emits PT_NOTE today?
arm64-musl Self-built richfelker/musl-cross-make (commit 3635262) ❌ No
loong64 Ubuntu's g++-14-loongarch64-linux-gnu package ✅ Yes
riscv64 / riscv64-pointer-compression Ubuntu's g++-14-riscv64-linux-gnu package ✅ Yes
musl (x64-musl) Alpine's native g++ ✅ Yes

Only arm64-musl is affected. The PR is intentionally a one-line LDFLAGS addition rather than re-configuring the musl-cross-make binutils build, to minimize blast radius.

Refs

Closes #200.

Full root-cause analysis in #200 (comment) and #200 (comment).

Test plan

  • CI: build arm64-musl on unofficial-builds.nodejs.org
  • readelf -lW node | grep '^\s*NOTE' on the resulting binary should show a NOTE program header
  • readelf -SW node | grep '\.note' should show .note.gnu.build-id
  • End-to-end SEA on Apple Silicon / arm64 Linux: node --experimental-sea-config ... && cp $(which node) hello && npx postject hello NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 && ./hello should print the embedded JS instead of segfaulting

I haven't been able to validate locally (musl-cross-make toolchain build ~30 min + Node build ~1–2 hr on my hardware); the fix is well-supported by the diagnosis but needs a CI run on this repo's infrastructure to confirm.

The musl-cross-make toolchain used for arm64-musl has no specs file
injecting -Wl,--build-id, unlike the distro-packaged cross-compilers
used for loong64/riscv64 or the native gcc used for x64-musl. As a
result, the binary has no .note.gnu.build-id section and no PT_NOTE
program header at all, which breaks postject-based SEA tooling that
relies on dl_iterate_phdr finding a PT_NOTE segment to extend.

Refs: nodejs#200
@awbx
Copy link
Copy Markdown
Author

awbx commented May 20, 2026

Validated locally end-to-end with a minimal C test (no Node build needed).

Compiled a tiny postject_find_resource test program with muslcc/x86_64:aarch64-linux-musl (a published prebuilt of musl-cross-make), once without and once with -Wl,--build-id=sha1. Then ran postject on each and executed on native arm64 (Apple Silicon, Docker Desktop):

Binary postject CLI output Size before → after Runtime result
Without --build-id 💉 Injection done! (silently no-op'd) 8848 → 8848 FAIL: find_resource returned NULL (exit 2)
With -Wl,--build-id=sha1 💉 Injection done! (real) 13056 → 67408 OK: found resource, size=24 (exit 0)

readelf -lW on the toolchain output confirms:

  • Without LDFLAGS: no PT_NOTE program header, no .note* sections
  • With LDFLAGS: PT_NOTE segment present at file offset 0x254, .note.gnu.build-id section

So the fix in this PR resolves the runtime SEGV on arm64-musl SEA binaries. Still wants a CI run to confirm against an actual Node build, but the mechanism is verified.

Side note: postject reporting "Injection done" + exit 0 on a binary it silently failed to modify is itself worth filing on nodejs/postject — it currently masks this exact bug class.

@awbx
Copy link
Copy Markdown
Author

awbx commented May 22, 2026

@sxa whenever it fits into your queue — anything else useful for me to add here (additional validation, alternative approach)? Happy to iterate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single Executable Application segfaults on musl builds but works on official node:alpine on arm64

1 participant