[lld][MachO] Exceptions become uncatchable when linking with -fsanitize=address,function on arm64 macOS
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Linking with `ld64.lld` while **both** `-fsanitize=address` and `-fsanitize=function` are enabled produces a binary where C++ exceptions are never caught — the handler is skipped and `libc++abi` calls `terminate`. Apple's `ld` links the same objects correctly. Either sanitizer alone is fine; only the combination fails.
### Reproducer
```cpp
#include
#include
__attribute__((noinline)) void boom() { throw std::string_view("payload"); }
int main()
{
try {
boom();
} catch (std::string_view const msg) {
std::printf("CAUGHT: %.*s\n", static_cast(msg.size()), msg.data());
return 0;
}
return 2; // handler never ran
}
```
```console
$ clang++ -std=c++17 -arch arm64 -O0 -g -fsanitize=address,function -fuse-ld=lld repro.cpp -o bad && ./bad
libc++abi: terminating due to uncaught exception of type std::__1::basic_string_view>
Abort trap: 6 # exit 134
$ clang++ -std=c++17 -arch arm64 -O0 -g -fsanitize=address,function -fuse-ld=ld repro.cpp -o good && ./good
CAUGHT: payload
```
**Expected:** `CAUGHT: payload`, exit 0.
**Actual:** the `catch` clause never matches; `std::terminate` is called.
| sanitizers | Apple ld | ld64.lld |
|---|---|---|
| `-fsanitize=function` | PASS | PASS |
| `-fsanitize=address` | PASS | PASS |
| `-fsanitize=address,function` | PASS | **FAIL** |
### Observation
The code is identical; only the linker-synthesized compact unwind table differs:
| | `__text` | `__unwind_info` |
|---|---|---|
| Apple ld | 0x628 | 0x00e8 (232 B) |
| ld64.lld | 0x628 | 0x1084 (4228 B) |
`__gcc_except_tab` and `__eh_frame` are byte-identical in size, and there is exactly one `typeinfo for std::string_view` in each binary, non-external, so this does not look like a typeinfo-identity problem. `__unwind_info` is 18x larger under lld even for this trivial program, which suggests the compact-unwind entries are not being merged or attributed correctly.
A likely factor: `-fsanitize=function` emits an 8-byte prologue signature (`0xc105cafe` + type hash) *before* each function's entry symbol, so the function's atom starts ahead of its symbol address. If lld derives function extents from symbol addresses, the resulting unwind entries would be misattributed and `_Unwind_RaiseException` would fail to locate the frame containing the `try`. This part is a hypothesis — the prologue bytes and section sizes are verified, the extent-computation mechanism is not.
### Versions
Reproduces identically on LLVM/LLD **22.1.2** and **23.1.1**, arm64 macOS 14 target. Compared against Apple `ld-1167.5` and `ld-1267`, both of which work.
Contributor guide
Research direction
Reproduce the arm64 macOS case with ld64.lld using the provided C++ program and both sanitizers. Inspect compact-unwind synthesis and how function extents are derived when function-s sanitizer places its 8-byte signature before the entry symbol, comparing __unwind_info with Apple ld. Done means the combined-sanitizer binary catches the exception and exits 0, while the single-sanitizer cases remain passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, macos
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100