Skip to content

[sanitizer_common] [Darwin] Adopt _dyld_get_dyld_header - #182943

Merged
ndrewh merged 1 commit into
llvm:mainfrom
ndrewh:GetDyldImageHeaderViaSharedCache
Feb 24, 2026
Merged

ndrewh merged 1 commit into
llvm:mainfrom
ndrewh:GetDyldImageHeaderViaSharedCache

Conversation

@ndrewh

@ndrewh ndrewh commented Feb 23, 2026 •

Copy link
Copy Markdown
Contributor

There is an issue on recent macOS versions with GetDyldImageHeaderViaSharedCache, which is fixed by adopting _dyld_get_dyld_header. We weakly declare this to ensure runtimes compile with older SDK (currently on CI bots).

rdar://167854578

@llvmbot

llvmbot commented Feb 23, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Andrew Haberlandt (ndrewh)

Changes

There is an issue on recent macOS versions with GetDyldImageHeaderViaSharedCache, which is fixed by adopting _dyld_get_dyld_header. We weakly declare this to ensure runtimes work on older OS, and also newer OS compiled with older SDK (i.e. the current bots).

rdar://167854578


Full diff: https://github.com/llvm/llvm-project/pull/182943.diff

3 Files Affected:

  • (modified) compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp (+6-2)
  • (modified) compiler-rt/lib/sanitizer_common/weak_symbols.txt (+1)
  • (modified) compiler-rt/lib/tsan/go/buildgo.sh (+1-1)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
index 979729f15aa16..0e80f2e3916b2 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
@@ -176,7 +176,7 @@ void MemoryMappingLayout::Reset() {
 // The dyld load address should be unchanged throughout process execution,
 // and it is expensive to compute once many libraries have been loaded,
 // so cache it here and do not reset.
-static mach_header *dyld_hdr = 0;
+static const mach_header *dyld_hdr = 0;
 static const char kDyldPath[] = "/usr/lib/dyld";
 static const int kDyldImageIdx = -1;
 
@@ -244,14 +244,18 @@ extern intptr_t _dyld_get_image_slide(const struct mach_header* mh);
 extern int dyld_shared_cache_iterate_text(
     const uuid_t cacheUuid,
     void (^callback)(const dyld_shared_cache_dylib_text_info *info));
+SANITIZER_WEAK_IMPORT const struct mach_header *_dyld_get_dyld_header(void);
 }  // extern "C"
 
-static mach_header *GetDyldImageHeaderViaSharedCache() {
+static const mach_header *GetDyldImageHeaderViaSharedCache() {
   uuid_t uuid;
   bool hasCache = _dyld_get_shared_cache_uuid(uuid);
   if (!hasCache)
     return nullptr;
 
+  if (&_dyld_get_dyld_header != nullptr)
+    return _dyld_get_dyld_header();
+
   size_t cacheLength;
   __block uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);
   CHECK(cacheStart && cacheLength);
diff --git a/compiler-rt/lib/sanitizer_common/weak_symbols.txt b/compiler-rt/lib/sanitizer_common/weak_symbols.txt
index 77e7b5d9f702e..600ab8a7c649c 100644
--- a/compiler-rt/lib/sanitizer_common/weak_symbols.txt
+++ b/compiler-rt/lib/sanitizer_common/weak_symbols.txt
@@ -10,3 +10,4 @@ ___sanitizer_symbolize_demangle
 ___sanitizer_symbolize_flush
 ___sanitizer_symbolize_set_demangle
 ___sanitizer_symbolize_set_inline_frames
+__dyld_get_dyld_header
diff --git a/compiler-rt/lib/tsan/go/buildgo.sh b/compiler-rt/lib/tsan/go/buildgo.sh
index d9e56402ad48f..1340071819fcb 100755
--- a/compiler-rt/lib/tsan/go/buildgo.sh
+++ b/compiler-rt/lib/tsan/go/buildgo.sh
@@ -165,7 +165,7 @@ elif [ "$GOOS" = "netbsd" ]; then
 	"
 elif [ "$GOOS" = "darwin" ]; then
 	OSCFLAGS="-fPIC -Wno-unused-const-variable -Wno-unknown-warning-option -mmacosx-version-min=10.7"
-	OSLDFLAGS="-lpthread -fPIC -fpie -mmacosx-version-min=10.7"
+	OSLDFLAGS="-lpthread -fPIC -fpie -mmacosx-version-min=10.7 -Wl,-U,__dyld_get_dyld_header"
 	SRCS="
 		$SRCS
 		../rtl/tsan_platform_mac.cpp

@github-actions

github-actions Bot commented Feb 23, 2026 •

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@ndrewh
ndrewh force-pushed the GetDyldImageHeaderViaSharedCache branch from ea41f5f to bdf57bf Compare February 23, 2026 21:44
@ndrewh

ndrewh commented Feb 23, 2026

Copy link
Copy Markdown
Contributor Author

Fixed clang-format

@ndrewh
ndrewh merged commit 2e7d07a into llvm:main Feb 24, 2026
10 checks passed
gopherbot pushed a commit to golang/go that referenced this pull request Mar 19, 2026
With llvm/llvm-project#182943, the race
detector syso has a weak import of __dyld_get_dyld_header, which
is only defined on newer macOS (26.4+). For external linking with
a pre-Xcode 26.4 C toolchain, we need to tell the C linker to
permit that symbol not being defined. Pass a flag to do so.

Change-Id: I95a3cd2c7fd3ad50bc47985b3ecca0d4e8352162
Reviewed-on: https://go-review.googlesource.com/c/go/+/755261
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Lasse Folger <lassefolger@google.com>
@ndrewh

ndrewh commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

/cherry-pick 2e7d07a

@ndrewh

ndrewh commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

I think we should cherry-pick this to make sure that clang 22 supports ASAN on macOS 26.4.

@llvmbot

llvmbot commented Mar 27, 2026

Copy link
Copy Markdown
Member

/cherry-pick 2e7d07a

Error: Command failed due to missing milestone.

@ndrewh ndrewh added this to the LLVM 22.x Release milestone Mar 27, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in LLVM Release Status Mar 27, 2026
@github-project-automation github-project-automation Bot moved this from Needs Triage to Done in LLVM Release Status Mar 27, 2026
@ndrewh

ndrewh commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

/cherry-pick 2e7d07a

@llvmbot

llvmbot commented Mar 27, 2026

Copy link
Copy Markdown
Member

/pull-request #188913

dylan-conway added a commit to oven-sh/bun that referenced this pull request Mar 30, 2026
…t mode

DirectBuild: deps simple enough to list files compile as first-class
ninja edges instead of a cmake sub-process. tinycc converted — drops
the overlay CMakeLists.txt and the recurring ASAN workarounds on the
c2str host tool, which now gets -fno-sanitize=all unconditionally.

ASAN dyld shim: macOS 26.4's Dyld.framework reimplemented
dyld_shared_cache_iterate_text in Swift with a _Block_copy that
deadlocks ASAN init (llvm/llvm-project#182943). Shim interposes a
non-allocating version using _dyld_get_dyld_header. Linked via
LC_LOAD_DYLIB + @rpath so it auto-loads with bun-debug — no env var.
Darwin+ASAN only.

workarounds.ts: self-obsoleting registry. Every temporary fix registers
an expectedToBeFixed predicate; configure fails with cleanup
instructions once the upstream fix ships.

build.ts: quiet build output when exec args present (original bd
behavior), `--` separator for flag disambiguation, `--target` accepts
space-separated value, colored target name in done message, signal
exit re-raised. Arg routing documented in file header.

tty.ts: centralized ANSI/TTY abstraction. nameColor() hashes names to
stable colors; stream.ts and the done message share it.

config.ts/tools.ts: clangVersion captured from the existing
toolchain-resolve --version spawn, exposed on Config. findTool returns
{path, version}.

Also:
- configure() no longer prints; build.ts decides based on quiet mode
- provides.sources deps get phonies pointing at compiled .o files
- scripts/bd, scripts/bd.ps1 removed (bd is a package.json script)
- scripts/build/CLAUDE.md: architecture doc with goals, ninja primer,
  common tasks, arg routing
c-rhodes pushed a commit to llvmbot/llvm-project that referenced this pull request Mar 31, 2026
ndrewh referenced this pull request in modular/modular Apr 12, 2026
past the ASAN runtime fix (llvm/llvm-project#191039).

MODULAR_ORIG_COMMIT_REV_ID: 1773da5eabdc1456e1fac443ccf0aac439c6a895
dylan-conway pushed a commit to oven-sh/bun that referenced this pull request Jul 17, 2026
…macOS (#34508)

## Problem

`bun bd` aborts at startup on macOS releases older than 26.4 (seen on
15.6.1, Homebrew llvm@21):

```
AddressSanitizer: CHECK failed: sanitizer_procmaps_mac.cpp:214 "((res)) == ((0))" (0xffffffffffffffff, 0x0)
    <empty stack>
Abort trap: 6
```

The post-link `bun-debug --revision` smoke test dies with this, so the
debug build never completes. Workaround has been `bun run build
--asan=off`.

## Cause

`scripts/build/shims/asan-dyld-shim.c` interposes
`dyld_shared_cache_iterate_text` to keep ASAN init from deadlocking on
macOS 26.4 (llvm/llvm-project#182943). It looks up the private
`_dyld_get_dyld_header` via `dlsym` to synthesize the one cache entry
ASAN needs.

Apple's own `dyld_priv.h` marks that symbol ["Added in macOS/iOS
26.4"](https://github.com/apple-oss-distributions/dyld/blob/dyld-1376.6/include/mach-o/dyld_priv.h)
(first appears in dyld-1376.6; absent from dyld-1340 and every earlier
tag). On any macOS < 26.4, `dlsym` returns `NULL`, the shim returns
`-1`, and compiler-rt's `CHECK_EQ(res, 0)` aborts.

The shim is linked into every `cfg.darwin && cfg.asan` build with no
runtime check, so it breaks every debug build on macOS hosts that
predate 26.4.

## Fix

When the shim can't synthesize the entry (`dlsym` returned `NULL`, or no
shared cache), delegate to the real `dyld_shared_cache_iterate_text`
instead of returning `-1`. The deadlock and the getter were introduced
together in 26.4, so "getter absent" is exactly "real iterate is safe".

This is the same shape as the upstream compiler-rt fix
(llvm/llvm-project#188913): weak-import `_dyld_get_dyld_header`, use it
when present, otherwise fall through to the existing iterate path.

Calling the original by name from inside the interposer does not
recurse. dyld explicitly adds an identity-mapping tuple for the defining
image; from
[DyldRuntimeState.cpp](https://github.com/apple-oss-distributions/dyld/blob/dyld-1378/dyld/DyldRuntimeState.cpp):

> `// now add specific interpose so that the generic is not applied to
the interposing dylib, so it can call through to old impl`

(This is also how Apple's own `DYLD_INTERPOSE` wrapper example in
[dyld-interposing.h](https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/dyld-interposing.h)
works.)

## Verification

Shim is only compiled for `darwin && asan` (no CI lane exists for that
combination); verified the change compiles clean with `-Wall -Wextra
-fblocks` against stubbed darwin headers. Behaviour on 26.4+ is
unchanged: `dlsym` finds the symbol there and the same synthesize path
runs as before.

## Why this is the right fix

The alternative is gating shim emission on host macOS version in
`shims.ts`, but the runtime check is strictly better: one binary works
on both, and it matches upstream exactly. The shim is temporary
regardless (self-obsoletes via `workarounds.ts` once LLVM 22.1.4+ is the
floor; #34299 deletes it as part of the LLVM 22 bump), so the goal here
is just to stop breaking pre-26.4 macOS until that lands.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 0 · build/CI scripts only; test-proof not
applicable

<!-- robobun:evidence:end -->
liooil pushed a commit to liooil/poly that referenced this pull request Aug 7, 2026
…macOS (#34508)

## Problem

`bun bd` aborts at startup on macOS releases older than 26.4 (seen on
15.6.1, Homebrew llvm@21):

```
AddressSanitizer: CHECK failed: sanitizer_procmaps_mac.cpp:214 "((res)) == ((0))" (0xffffffffffffffff, 0x0)
    <empty stack>
Abort trap: 6
```

The post-link `bun-debug --revision` smoke test dies with this, so the
debug build never completes. Workaround has been `bun run build
--asan=off`.

## Cause

`scripts/build/shims/asan-dyld-shim.c` interposes
`dyld_shared_cache_iterate_text` to keep ASAN init from deadlocking on
macOS 26.4 (llvm/llvm-project#182943). It looks up the private
`_dyld_get_dyld_header` via `dlsym` to synthesize the one cache entry
ASAN needs.

Apple's own `dyld_priv.h` marks that symbol ["Added in macOS/iOS
26.4"](https://github.com/apple-oss-distributions/dyld/blob/dyld-1376.6/include/mach-o/dyld_priv.h)
(first appears in dyld-1376.6; absent from dyld-1340 and every earlier
tag). On any macOS < 26.4, `dlsym` returns `NULL`, the shim returns
`-1`, and compiler-rt's `CHECK_EQ(res, 0)` aborts.

The shim is linked into every `cfg.darwin && cfg.asan` build with no
runtime check, so it breaks every debug build on macOS hosts that
predate 26.4.

## Fix

When the shim can't synthesize the entry (`dlsym` returned `NULL`, or no
shared cache), delegate to the real `dyld_shared_cache_iterate_text`
instead of returning `-1`. The deadlock and the getter were introduced
together in 26.4, so "getter absent" is exactly "real iterate is safe".

This is the same shape as the upstream compiler-rt fix
(llvm/llvm-project#188913): weak-import `_dyld_get_dyld_header`, use it
when present, otherwise fall through to the existing iterate path.

Calling the original by name from inside the interposer does not
recurse. dyld explicitly adds an identity-mapping tuple for the defining
image; from
[DyldRuntimeState.cpp](https://github.com/apple-oss-distributions/dyld/blob/dyld-1378/dyld/DyldRuntimeState.cpp):

> `// now add specific interpose so that the generic is not applied to
the interposing dylib, so it can call through to old impl`

(This is also how Apple's own `DYLD_INTERPOSE` wrapper example in
[dyld-interposing.h](https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/dyld-interposing.h)
works.)

## Verification

Shim is only compiled for `darwin && asan` (no CI lane exists for that
combination); verified the change compiles clean with `-Wall -Wextra
-fblocks` against stubbed darwin headers. Behaviour on 26.4+ is
unchanged: `dlsym` finds the symbol there and the same synthesize path
runs as before.

## Why this is the right fix

The alternative is gating shim emission on host macOS version in
`shims.ts`, but the runtime check is strictly better: one binary works
on both, and it matches upstream exactly. The shim is temporary
regardless (self-obsoletes via `workarounds.ts` once LLVM 22.1.4+ is the
floor; #34299 deletes it as part of the LLVM 22 bump), so the goal here
is just to stop breaking pre-26.4 macOS until that lands.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 0 · build/CI scripts only; test-proof not
applicable

<!-- robobun:evidence:end -->
helly25 added a commit to bazel-contrib/toolchains_llvm that referenced this pull request Aug 16, 2026
Fixes #826.

## Summary

Wire rules_cc's stock `lsan` feature into the same augmentation path as
asan, ubsan, and tsan:

- add `//toolchain/config:use_lsan` and include it in
`use_common_sanitizer`
- add the Darwin `libclang_rt.lsan_osx_dynamic.dylib` filegroup and
route it through `dynamic_runtime_lib`
- add an end-to-end `//:lsan_test` and run it with the Linux sanitizer
tests
- add a focused macOS CI job using LLVM 22.1.8

The Darwin version pin is deliberate. On current macOS, LLVM 19.1.7,
20.1.8, and 21.1.8 all run `main` and then deadlock during LSan's exit
check. The leak-check thread calls `dyld_shared_cache_iterate_text`,
which lazily loads dyld introspection code and allocates while LSan
holds its allocator lock. LLVM compiler-rt #182943 switched this path to
`_dyld_get_dyld_header`; that fix was backported to LLVM 22, and LLVM
22.1.8 exits normally.

The configured action graph now contains a `SolibSymlink` whose declared
input is `libclang_rt.lsan_osx_dynamic.dylib`, so the runtime is
provisioned rather than merely found incidentally in the local execroot.

## Verification

On macOS arm64:

- LLVM 19.1.7: times out after completing `main`
- LLVM 20.1.8: times out after completing `main`
- LLVM 21.1.8: times out after completing `main`
- LLVM 22.1.8: `//:lsan_test` passes
- `//:sanitizer_combo_flags_test` passes
- `actionlint` and `git diff --check` pass

Upstream fix: llvm/llvm-project#182943
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

3 participants