llvm / llvm/llvm-project

[BOLT] --split-strategy=cdsplit --frame-opt=hot generates invalid unwind information for warm fragments, causing backtrace() to segfault

Open
#211,392 1 comment 0 reactions 0 assignees View on GitHub
BOLT
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

BOLT can generate incorrect `.eh_frame` information when function splitting with `--split-strategy=cdsplit` is combined with shrink-wrapping through `--frame-opt=hot`.

When a function is split into hot, warm, and cold fragments, the warm fragment receives its own frame description entry. However, BOLT fails to initialize that entry with the correct canonical frame address offset. Consequently, unwinding through the warm fragment interprets a stack slot as the return address and can crash inside `backtrace()` or `_Unwind_Backtrace`.

**Example**:

``` C
#include
#include

volatile unsigned gate = 1;

long trace(long value) {
void *frames[8];
printf("backtrace: %d frames\n", backtrace(frames, 8));
return value;
}

long (*callback)(long) = trace;

long dispatch(long value, unsigned warm) {
// Place invalid return addresses on the stack to make bad unwinding crash.
volatile long sentinel[2] = {0x14, 0x14};

if (warm) {
if (!gate)
return 0; // Cold path

// Warm path
long saved = gate;
value = callback(value) + saved; // Unwinds through the warm fragment.
}

return value + sentinel[0] + sentinel[1]; // Hot path.
}

int main(void) {
for (unsigned i = 0; i < 100; ++i)
dispatch(i, i == 99);
}
```

```
# clang-22 -O3 -fno-inline repro.c -Wl,-q -o repro
# llvm-bolt-22 repro --instrument --instrumentation-file=repro.fdata -o repro.instrumented
...
# ./repro.instrumented
backtrace: 6 frames
# llvm-bolt-22 repro repro --data=repro.fdata --reorder-blocks=ext-tsp --split-functions --split-strategy=cdsplit --frame-opt=hot -o repro.bolt
...
# nm -n repro.bolt | grep -E ' dispatch'
0000000000400140 T dispatch
00000000004001c0 t dispatch.warm
0000000000400200 t dispatch.cold
# ./repro.bolt
Segmentation fault
```

The debug frame for the dispatch.warn fragment is missing the CFA offset causing the stack slot (`0x14`) to be used as the return address:
```
# readelf --debug-dump=frames repro.bolt | grep -A3 'pc=00000000004001c0'
00000084 0000000000000010 00000088 FDE cie=00000000 pc=00000000004001c0..00000000004001ee
DW_CFA_advance_loc: 18 to 00000000004001d2
DW_CFA_offset: r3 (rbx) at cfa-16
```

This is the likely cause of #185112 which was used as a starting point to create this smaller reproducer.

**AI disclosure**
AI assistance (OpenAI Codex) was used to develop and minimize the reproducer as well as the analysis.
The reproducer and analysis were reviewed and verified by @jjhelmus and the example run without AI assistance.

Contributor guide

Open the contributing guide

Research direction

Start with the repro.c example and run the listed clang, llvm-bolt, nm, and readelf commands to reproduce the crash. Trace BOLT's frame and unwind handling for the dispatch.warm fragment and verify that the generated .eh_frame entry has the correct CFA offset and that ./repro.bolt completes without a backtrace() segmentation fault.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.