[BPF] Trailing DBG_VALUE suppresses exit after __bpf_trap
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
A trailing debug record changes the ordinary BPF instructions emitted for an
LLVM IR module accepted by `llvm-as` and LLVM's IR verifier. Without the
record, LLVM emits
`call __bpf_trap; exit`. With it, the record lowers to a trailing `DBG_VALUE`,
and LLVM omits the `exit` that `addExitAfterUnreachable()` is intended to add.
This reproduces with LLVM 24.0.0git at revision
`9e3a7335e77e92cf4d739d78592c6c31900c4e97` on x86-64 Linux, targeting
`bpfel` with `-mcpu=v3`.
## Reproducer
Attach these two files individually:
- `reduced.ll.txt` (the `.txt` suffix is only for GitHub uploads); and
- `reproduce.sh`.
[reduced.ll.txt](https://github.com/user-attachments/files/31823351/reduced.ll.txt)
[reproduce.sh](https://github.com/user-attachments/files/31823354/reproduce.sh)
Run:
```bash
bash reproduce.sh
```
The script checks the IR with `llvm-as` and `opt -passes=verify`, enables
`-verify-machineinstrs`, and compares the input with a copy that differs only
by deletion of the `#dbg_value` record.
## Actual behavior
```text
without #dbg_value:
call __bpf_trap
exit
with #dbg_value:
call __bpf_trap
without #dbg_value exit count: 1
with #dbg_value exit count: 0
```
The same difference is present in the encoded objects: the version without
the debug record contains a `call` relocation for `__bpf_trap` followed by an
`exit`, whereas the version with the record contains only the call.
Immediately before `bpf-mi-pre-emit-peephole`, the ordinary Machine IR is
identical. The debug variant has one additional `DBG_VALUE` after
`JAL @__bpf_trap`.
## Cause
`BPFMIPreEmitPeepholeImpl::addExitAfterUnreachable()` examines the physical
last instruction in the block:
```cpp
MachineInstr &MI = MBB.back();
if (MI.getOpcode() != BPF::JAL || !MI.getOperand(0).isGlobal() ||
MI.getOperand(0).getGlobal()->getName() != BPF_TRAP)
return false;
```
The trailing `DBG_VALUE` therefore hides the final ordinary
`JAL @__bpf_trap`, and the pass returns without inserting `BPF::RET`.
The exit-insertion logic was introduced in
https://github.com/llvm/llvm-project/pull/131731 to prevent fallthrough after a
terminal trap call and allow the kernel verifier to report the dedicated
`__bpf_trap` diagnostic.
## Expected behavior
Debug-only instructions should not affect exit insertion. Both variants
should emit `call __bpf_trap; exit`.
One focused fix is to inspect the last non-debug instruction, while retaining
pseudo probes as barriers, rather than using `MBB.back()`.
## Impact boundary
For a reachable `__bpf_trap`, the current Linux verifier rejects both
variants. With the exit, verification can reach the dedicated
`unexpected __bpf_trap() due to uninitialized variable?` diagnostic. Without
it, structural verification instead rejects the subprogram as
`last insn is not an exit or jmp`.
Contributor guide
Research direction
Start with BPFMIPreEmitPeepholeImpl::addExitAfterUnreachable() and inspect how it uses MBB.back() around trailing DBG_VALUE instructions. Run reproduce.sh with reduced.ll.txt to confirm the differing instruction and exit counts. Done means debug-only instructions no longer suppress the exit after __bpf_trap, while pseudo probes remain barriers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, linux
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100