llvm / llvm/llvm-project

[clang-cl][ASan][Windows SEH] `catch(std::exception&)` binds to a stack slot holding stale bytes from an earlier call frame, under ASan instrumentation.

Open
#223,073 2 comments 0 reactions 0 assignees View on GitHub
clang-cl
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## TL;DR

A plain `catch (std::exception& e) { ... << e.what() ...; }` (no custom exception type, nothing unusual) crashes with an ASan access-violation reading address 0, in a RelWithDebInfo/ASan/UBSan clang-cl 21.1.5 Windows build. Live CDB debugging shows `e` is bound to a real, in-range stack address (not null) - but the first 8 bytes there (where the exception object's vtable pointer should be) are zero, and the surrounding bytes are stale leftovers from an earlier, already-returned call in the same function. UBSan is already enabled in this exact build and printed nothing before the crash, unlike the earlier, confirmed-UB issue #199115 from this same project - so this does not look like the same "it was UB, not a compiler bug" story. Asking whether this is a known clang-cl/ASan-on-Windows frame-layout issue, and what to capture next.

## Disclaimer

For analysis I partly used LLM.

## Concise Background

Same project as #199115 (Trilinos on Windows, clang-cl for the ASan/UBSan sanitizer builds). While debugging a *different*, already-understood application bug (a Belos solver-registry lookup failing across a DLL boundary, causing `Belos::SolverFactoryParent::create()` to throw `std::invalid_argument` - this part is a real Trilinos bug, not a compiler issue, and is being fixed separately, see "Related Issues" below), the test process itself crashes with an AddressSanitizer access-violation *while handling that exception*, inside completely ordinary, idiomatic code:

```cpp
// packages/ifpack2/test/belos/build_solver.hpp
Teuchos::RCP > solver;
Belos::SolverFactory factory;
try {
solver = factory.create(solver_type, rcpparams); // line 38 - throws here
} catch (std::exception& e) { // line 39
std::cout << "*** FAILED: Belos::SolverFactory::create threw an exception: "
<< e.what() << std::endl; // line 40 - crashes here
return Teuchos::null;
}
```

There is nothing unusual about this `try`/`catch` - no custom exception type, no multiple inheritance, no `noexcept` interaction, just `std::invalid_argument` (constructed via `Teuchos::TestForException.hpp`'s `throw Exception(omsgstr)`, `omsgstr` a `const std::string&` bound to a local `std::ostringstream::str()` result - a completely standard, standard-library-only throw).

## Related Issues

- https://github.com/llvm/llvm-project/issues/199115
- https://github.com/trilinos/Trilinos/pull/15699

## Environment

| Item | Value |
| --- | --- |
| OS | Windows 11 Pro x64, build 26100 |
| Build type | RelWithDebInfo, shared libraries, ASan + UBSan, C++20 |
|MSVC | 19.51.36246 for x64 (VS 2026 Community) |
|clang-cl | 21.1.5;x86_64-pc-windows-msvc;thread-model:posix |
| Target triple | `x86_64-pc-windows-msvc` |
| Key compile flags | `-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined /EHsc /Ob0 /Od /RTC1 -std:c++20 -MD -Zi /EHsc -mcx16` |

## Details

- [ifpack2_gmres_crash_ca0c_2026-09-11_18-46-11-988.log](https://github.com/user-attachments/files/32130689/ifpack2_gmres_crash_ca0c_2026-09-11_18-46-11-988.log)
- [ifpack2_gmres_crash2_9dc4_2026-09-11_18-51-32-349.log](https://github.com/user-attachments/files/32130695/ifpack2_gmres_crash2_9dc4_2026-09-11_18-51-32-349.log)
- [ifpack2_gmres_crash3_1d8c_2026-09-11_18-53-23-836.log](https://github.com/user-attachments/files/32130696/ifpack2_gmres_crash3_1d8c_2026-09-11_18-53-23-836.log)
- [ifpack2_gmres_crash4_b240_2026-09-11_18-55-47-291.log](https://github.com/user-attachments/files/32130697/ifpack2_gmres_crash4_b240_2026-09-11_18-55-47-291.log)
- [ifpack2_gmres_crash5_f88c_2026-09-11_18-58-56-008.log](https://github.com/user-attachments/files/32130698/ifpack2_gmres_crash5_f88c_2026-09-11_18-58-56-008.log)
- [ifpack2_gmres_crash6_a534_2026-09-11_19-08-03-630.log](https://github.com/user-attachments/files/32130700/ifpack2_gmres_crash6_a534_2026-09-11_19-08-03-630.log)

Full CDB session logs referenced here by local path and line number:

- `ifpack2_gmres_crash2_9dc4_2026-09-11_18-51-32-349.log` - first live catch of the *actual* reported crash (not a benign ASan-internal first-chance AV during process startup, which had to be explicitly suppressed with `sxd av` first). Lines 2378-2386:

```
ExceptionAddress: 00007ff68c435859 (Ifpack2_tif_belos!build_solver+0x0000000000001501)
ExceptionCode: c0000005 (Access violation)
...
Attempt to read from address 0000000000000000
```

This matches the original `ctest`/ASan report byte-for-byte:

`==110900==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000`

at `build_solver.hpp:40`, confirming the CDB repro is the same crash, not a different one.

- `ifpack2_gmres_crash4_b240_2026-09-11_18-55-47-291.log`, lines 1701-1716 - disassembly immediately before the fault:

```
00007ff6`8c435823 488b0d06490300 mov rcx,qword ptr [Ifpack2_tif_belos!_imp_?coutstd (00007ff6`8c46a130)]
00007ff6`8c43582a 488d15afa80400 lea rdx,[Ifpack2_tif_belos!`string' (00007ff6`8c4800e0)]
00007ff6`8c435831 e8929cfeff call Ifpack2_tif_belos!std::operator<< > (00007ff6`8c41f4c8)
00007ff6`8c435836 4883c420 add rsp,20h
00007ff6`8c43583a 4889c6 mov rsi,rax
00007ff6`8c43583d 488b4370 mov rax,qword ptr [rbx+70h]
00007ff6`8c435841 488b08 mov rcx,qword ptr [rax]
00007ff6`8c435844 4889c8 mov rax,rcx
00007ff6`8c435847 48c1e803 shr rax,3
00007ff6`8c43584b 488b5328 mov rdx,qword ptr [rbx+28h]
00007ff6`8c43584f 803c1000 cmp byte ptr [rax+rdx],0
00007ff6`8c435853 0f85ce000000 jne ...+0x15cf
00007ff6`8c435859 488b01 mov rax,qword ptr [rcx] <- faults here, rcx=0
```

The first `call` is the *literal-string* `operator<<` from `build_solver.hpp:40` (`std::cout << "*** FAILED: ..."`, `rdx` = address of the string literal, not
the exception message). The code right after it (`mov rax,[rbx+70h]` / `mov rcx,[rax]` / the shift+compare pair, which is ASan's inline shadow-byte
check) is preparing to stream `e.what()`'s `const char*` result through the *second* `operator<<` - `rbx+0x70` is the storage slot for the bound reference `e`, and dereferencing it (`mov rcx,[rax]`) is where the zero comes from.

- `ifpack2_gmres_crash6_a534_2026-09-11_19-08-03-630.log` - breaking one instruction *before* the fault (`bp 0x00007ff68c435841`) to capture live, non-post-mortem register/memory state. Lines 1673-1682:

```
rax=00000026caafe0f0 rbx=00000026caafe120 rcx=0000000000000000
...
Ifpack2_tif_belos!build_solver<...>+0x14e9:
00007ff6`8c435841 488b08 mov rcx,qword ptr [rax] ds:00000026`caafe0f0=0000000000000000
```

So `[rbx+0x70]` (line 1728/1784 in the same log: address `0x26caafe190` holds the value `0x26caafe0f0`) holds the pointer `0x26caafe0f0` -
`e`'s bound address, a completely ordinary, in-range stack address, **not null**. But the 8 bytes stored *at* that address (lines 1717-1718, `RAX_PTR` section) are:

```
00000026`caafe0f0 00000000`00000000 00007ffd`3cddbb1c
```

zero - where the exception object's vtable pointer should be. The `STACK_NEAR_RAX` dump (lines 1754-1786) shows this is not fresh/dedicated
storage: the qwords immediately following it are recognizable stale values from an earlier, already-returned call within `build_solver` itself:

```
00000026`caafe110 00007ffe`1755df56 ucrtbase!o_memset+0x1c6
00000026`caafe118 00007ff6`8c4343d0 Ifpack2_tif_belos!build_solver<...>+0x78
```

(`build_solver+0x78` is very early in the function - consistent with a saved return address from an earlier call such as `Ifpack2::getParameter(...)` or the `std::string solver_type` construction, both of which happen before the `try` block at line 37-38).

## How to Reproduce

Same repo/commit family as #199115 (Trilinos, `develop` branch, this checkout currently at commit `286936754e1`). Full reproduce, mirroring the format used in #199115:

1. Configure a shared, RelWithDebInfo, ASan+UBSan clang-cl build enabling `Ifpack2`, `Belos` (with `Belos_ENABLE_Tpetra=ON`), `Tpetra` - the exact compile command line for the relevant `.cpp`s is in `compilation-output-clangcl-21.1.5-ninja-serial-err.log` (see Environment below for the key flags extracted from it).
2. Build target `Ifpack2_tif_belos` (test executable at `packages/ifpack2/test/belos/Ifpack2_tif_belos.exe` once built).
3. Run (from `packages/ifpack2/test/belos/`, with every built `.dll`'s directory on `PATH`):

```
Ifpack2_tif_belos.exe --xml_file=test_gmres_small_sym_mm.xml
```

This crashes immediately (`test_gmres_small_sym_mm.xml` sets `solver_type = "Block Gmres"` - the specific solver name is not important here; what matters for reproducing *this* crash is only that `factory.create(...)` throws, which currently happens for an unrelated, already-diagnosed Trilinos-side reason - see Related Issues. Any input that makes `create()` throw inside `build_solver()` should reproduce the same crash in the `catch` block.)

## Minimal repro

Not attempted yet. Given the experience with #199115 (a hand-written, dependency-free repro consistently failed to reproduce that bug - only the real `.obj`/preprocessed source from the actual TU did), I expect the same difficulty here: this looks like it depends on the exact combination of a large, heavily-templated function's stack frame layout under `-Od` plus ASan's own stack instrumentation, not on anything expressible in a small file. Willing to attempt `-save-temps` / preprocessed source + the `.obj` for the real TU if a maintainer confirms that is the more useful next step (as happened in #199115).

> P.S. will attempt on MSVC later.

Contributor guide

Open the contributing guide

Research direction

Start at packages/ifpack2/test/belos/build_solver.hpp, especially the catch at lines 39-40, and run Ifpack2_tif_belos.exe --xml_file=test_gmres_small_sym_mm.xml in the described clang-cl ASan/UBSan build. Review the referenced CDB logs and disassembly, then determine whether a minimal repro or the real TU's preprocessed source and .obj are needed. Done means establishing a reproducible compiler or frame-layout issue with supporting artifacts.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers, operating-systems, testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.