Skip to content

fix(mac): don't SIGKILL Apple toolchain binaries under samply record#827

Open
not-matthias wants to merge 1 commit into
mstange:mainfrom
CodSpeedHQ:cod-2762-samply-sigkills-dsymutil-upstream-pr
Open

fix(mac): don't SIGKILL Apple toolchain binaries under samply record#827
not-matthias wants to merge 1 commit into
mstange:mainfrom
CodSpeedHQ:cod-2762-samply-sigkills-dsymutil-upstream-pr

Conversation

@not-matthias

Copy link
Copy Markdown
Contributor

Ran into this when profiling Go binaries with -ldflags=-s=false -w=false which invokes dsymutils.

samply injects DYLD_INSERT_LIBRARIES + SAMPLY_BOOTSTRAP_SERVER_NAME into the
whole descendant tree. Each descendant that loads samply-mac-preload runs a
dyld initializer that sends its mach task-self port to samply so it can be
profiled.

For an Apple *platform binary* (CS_PLATFORM_BINARY) the task-self port is
immovable. Transferring an immovable port to another process raises a fatal
EXC_GUARD / GUARD_TYPE_MACH_PORT (ILLEGAL_MOVE) and the kernel SIGKILLs the
process — inside the preload's mach_msg, before main().

This is not specific to dsymutil: the entire Xcode toolchain is affected
(clang, ld, nm, strip, lipo, dsymutil, ... all confirmed killed by signal 9;
clang++, swift(c), ar, ranlib, otool, dwarfdump, objdump, llvm-* share the same
CS_PLATFORM_BINARY + non-restricted property). dsymutil was just the binary the
original report hit (Go's linker invokes it). Any compile/link step run under
`samply record` breaks as soon as a build tool invokes a toolchain binary by
absolute path (the norm); going through the restricted /usr/bin shims hides it
because they strip DYLD_*. Symptom: "running dsymutil failed: signal: killed".

Confirmed via the crash report (EXC_GUARD "ILLEGAL_MOVE on mach port 515",
port 515 == mach_task_self()) and by probing csops(2): the only distinguishing
bit between a killed platform binary and a surviving locally-built binary is
CS_PLATFORM_BINARY.

Fix: in the preload, detect platform binaries via csops(getpid(),
CS_OPS_STATUS) and skip the task handoff for them. samply cannot profile a
platform binary through this mechanism anyway (its task port is protected), so
nothing is lost; the process runs normally instead of being killed. Descendant
profiling of all normal binaries is unchanged.

Includes a regression test (samply/tests/dsymutil_sigkill.rs). Preload rebuilt
for x86_64/arm64/arm64e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@not-matthias not-matthias force-pushed the cod-2762-samply-sigkills-dsymutil-upstream-pr branch from 4c4d29e to e3f66c1 Compare June 1, 2026 15:50

@mstange mstange left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually surprised that DYLD_INSERT_LIBRARIES isn't blocked for dsymutil. Are there any other shipping macOS binaries that allow DYLD_INSERT_LIBRARIES but don't allow task port sending?

Do you know if the kernel code which triggers the SIGKILL is public? Maybe it contains hints for other detection mechanisms that don't require private APIs.

Comment on lines +57 to +64
extern "C" {
fn csops(
pid: libc::pid_t,
ops: u32,
useraddr: *mut libc::c_void,
usersize: libc::size_t,
) -> libc::c_int;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a private API. Is this really the only way to detect this? I'd rather not rely on private APIs.

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.

2 participants