Fix __builtin_verbose_trap support for GPU and CPU - #199
Conversation
998fd18 to
f65ff67
Compare
aktemur
left a comment
There was a problem hiding this comment.
Please squash the second patch to the second, so that the test accompanies the fix.
| # You should have received a copy of the GNU General Public License | ||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
|
||
| # Test that __builtin_trap PC is correctly adjusted |
There was a problem hiding this comment.
The test is now using __builtin_verbose_trap, this test is about that.
f65ff67 to
994a6fa
Compare
c9f8030 to
60cbb93
Compare
lancesix
left a comment
There was a problem hiding this comment.
The test should exercise the stepping as well. And since you add host support, I suppose this should have a test as well.
a36876d to
1b9efda
Compare
Ok. Added another inline function to the test, so the test steps over it and checks whether regular inline frames stay hidden. |
|
I'm not intimately familiar with gdb, but the |
|
|
||
| When dealing with a corefile, it is expected that the PC has | ||
| been adjusted before generating the corefile, so no need to | ||
| re-do it now. */ | ||
| if ((stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT) != 0 | ||
| if (((stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT) != 0 | ||
| || (stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_ASSERT_TRAP) != 0) |
There was a problem hiding this comment.
FYI, I am not sure this should be the way to go.
I have ROCm/rocm-systems#9776 to change the trap handler behaviour so this is not necessary. Such change would fix other issues reported independent to this PR.
There was a problem hiding this comment.
Integrated the changes with ROCm/rocm-systems#9776 and with https://sourceware.org/pipermail/gdb-patches/2026-August/229200.html
This means that this PR only needs to show inline frames when dealing with builtin_verbose_trap.
We can now also single-step into it without hanging the program.
There was a problem hiding this comment.
Re. the "gdb: Add gdbarch hook to show verbose trap inline frames" patch:
The commit log needs to be turned upside down.
This adds the gdbarch hook, yes, but it needs to begin by stating what the problem is, talk about __builtin_verbose_trap, show what goes wrong today without the fix.
I.e., establish rationale for why a change is needed.
Explain that this affects both CPU and GPU.
Then state how we're fixing this. The new gdbarch hook.
|
Dropping to a draft since no action in a while. |
Added single stepping to the test. |
3764561 to
83763c7
Compare
Squashed the test commit with the commit that introduces the changes, per Baris' suggestion. Dropped the PC adjust commit since Lancelots patch solves that. That leaves us with a single commit, and message for that commit has been changed according to your suggestion. |
4eee731 to
07fd530
Compare
| if {![runto test_trap_function]} { | ||
| return | ||
| } |
There was a problem hiding this comment.
Sorry for the back and forth, but here we shall use run to breakpoint. Running to the function may not necessarily stop at the "int x = 1;" line. It may stop as "{", depending on the line table. So, we should rather define a bp at "int x = 1;" and run to it, and then do "next".
There was a problem hiding this comment.
Fixed. Changed to:
- runto_main
- Set breakpoint at "int x = 1" line
- Continue to that breakpoint
- Proceed with stepping tests
This avoids ambiguity about which line in test_trap_function to stop at.
|
|
||
| gdb_test_multiple "next" "step to trap line" { | ||
| -re -wrap ".*SIGILL.*" { | ||
| xfail "$gdb_test_name (compiler line table issue)" |
There was a problem hiding this comment.
Ideally we should put the bug number.
There was a problem hiding this comment.
Response:
Fixed. Added comprehensive 7-line explanatory paragraph at the beginning of both test files explaining:
- What the compiler line table bug is
- How it manifests (trap instruction associated with wrong source line)
- Why it causes the observed test behavior
- How tests handle it (xfail)
The xfail now references "compiler line table bug" without a specific bug number, since the explanation paragraph
provides full context.
| return | ||
| } | ||
|
|
||
| gdb_test_no_output "set confirm off" |
There was a problem hiding this comment.
Fixed. Removed gdb_test_no_output
| # Test single stepping to the trap line. | ||
| # Expected: step should stop at __builtin_verbose_trap line, then | ||
| # next step triggers the trap. | ||
| # Due to a compiler line table issue, the trap may fire immediately. |
There was a problem hiding this comment.
We can remove "Due to a compiler ....". This is covered by xfail below.
There was a problem hiding this comment.
Fixed. Removed inline compiler bug comment from gdb.base test (covered by explanation paragraph).
| # Test single stepping to the trap line. | ||
| # Expected: step should stop at __builtin_verbose_trap line, then | ||
| # next step triggers the trap. | ||
| # Due to a compiler line table issue, the trap may fire immediately. |
There was a problem hiding this comment.
We can remove "Due to a compiler...". It's covered by the xfail below.
| both conditions indicate a verbose trap scenario. | ||
| """, | ||
| type="bool", | ||
| name="show_verbose_trap_inline_frame", |
There was a problem hiding this comment.
Nit: Based on how this gdbarch method is used and implemented, it makes more sense to me to name it "is_verbose_trap_frame".
There was a problem hiding this comment.
Fixed, but used Pedro's more general-purpose suggestion: renamed to should_show_inline_frame instead.
| # Test that __builtin_verbose_trap inline frames are shown when trap fires. | ||
| # __builtin_verbose_trap is a Clang-only builtin, added in Clang 17. | ||
|
|
||
| require {expr {[test_compiler_info clang*] && ![test_compiler_info {clang-1[0-6]-*}]}} |
There was a problem hiding this comment.
The comment says Clang 17, and compiler check matches (though it fails to consider clang 9 and lower), but I tried this on ubuntu 24.04, which has:
$ clang -v
Ubuntu clang version 18.1.3 (1ubuntu1)
And got:
$ make check RUNTESTFLAGS="CC_FOR_TARGET=clang CXX_FOR_TARGET=clang++" TESTS="gdb.base/builtin_verbose_trap.exp"
...
get_compiler_info: clang-18-1-3
gdb_do_cache: get_compiler_info_1 ( c++ )
get_compiler_info: clang-18-1-3
Executing on host: clang++ -fdiagnostics-color=never -Wno-unknown-warning-option -c -g -o /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/builtin_verbose_trap/builtin_verbose_trap0.o /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.cpp (timeout = 300)
builtin_spawn -ignore SIGHUP clang++ -fdiagnostics-color=never -Wno-unknown-warning-option -c -g -o /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/builtin_verbose_trap/builtin_verbose_trap0.o /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.cpp
/home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.cpp:21:3: error: use of undeclared identifier '__builtin_verbose_trap'
21 | __builtin_verbose_trap ("check verbose", "This is verbose trap!");
| ^
1 error generated.
compiler exited with status 1
gdb compile failed, /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.cpp:21:3: error: use of undeclared identifier '__builtin_verbose_trap'
21 | __builtin_verbose_trap ("check verbose", "This is verbose trap!");
| ^
1 error generated.
UNTESTED: gdb.base/builtin_verbose_trap.exp: failed to prepare
testcase /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.exp completed in 0 seconds
=== gdb Summary ===
# of untested testcases 1
There was a problem hiding this comment.
Test fails on Clang 18 - __builtin_verbose_trap doesn't exist until Clang 19. Fixed.
| # Continue to trigger the trap. | ||
| # CPU uses ud2 instruction which generates SIGILL (not SIGABRT like GPU). | ||
| gdb_test "continue" \ | ||
| ".*received signal SIGILL.*" \ |
There was a problem hiding this comment.
The leading ".*" in the regexp passed to gdb_test, and gdb_test_multiple, is unnecessary, it is implied. (Many places in the testcase have this, I won't point at them all.)
| Return true if the inline frame represented by FUNC should NOT be skipped | ||
| when stopped due to STOP_SIGNAL. This allows architectures to show | ||
| compiler-generated inline frames that contain verbose trap messages | ||
| (e.g., __builtin_verbose_trap). |
There was a problem hiding this comment.
It seems to me that the hook's name and description is a bit more specific than necessary.
The hook is called to decide whether to skip inline frames.
The implementation should then be able to use it for verbose trap. But it could be used for other things.
We can already see a little bit of awkwardness in the semantics, because the answer to "show verbose trap inline frame?" is always "YES!".
The real question the hook is asking is "show THIS inline frame?", and then the answer is "YES, IF it's a verbose trap frame."
There was a problem hiding this comment.
Fixed. Designed hook as fully general-purpose:
Hook name: should_show_inline_frame (generic, not verbose-trap-specific)
Documentation describes generic purpose: "Determine whether an inline frame should be shown to the user." Uses
verbose trap as example use case, not requirement.
Signature: (const struct symbol *func, enum gdb_signal stop_signal)
- Generic parameters suitable for other scenarios
- Not tied to verbose trap specifics
Default returns false (hide inline frames during stepping, existing behavior)
Architectures can override with their own visibility logic.
|
Please include this at the end of the commit message: |
efba53e to
eda2309
Compare
Fixed |
| # Test that __builtin_verbose_trap inline frames are shown when trap fires. | ||
| # __builtin_verbose_trap is a Clang-only builtin, added in Clang 19. | ||
|
|
||
| require {expr {[test_compiler_info clang*] && ![test_compiler_info {clang-1[0-8]-*}] && ![test_compiler_info {clang-[1-9]-*}]}} |
There was a problem hiding this comment.
Line too long. Break it:
require {expr {[test_compiler_info clang*]
&& ![test_compiler_info {clang-1[0-8]-*}]
&& ![test_compiler_info {clang-[1-9]-*}]}}
| } | ||
|
|
||
| # Continue to trigger the trap. | ||
| # CPU uses ud2 instruction which generates SIGILL (not SIGABRT like GPU). |
There was a problem hiding this comment.
The testcase should stand alone without GPU references. And it'll be run on all kinds of CPUs, don't assume CPU == x86-64.
Also, not all CPUs will generate SIGILL. I pointed Claude at the LLVM codebase, and asked it to build me a table with what instruction __builtin_verbose_trap uses and what signal does it raise on all the supported architectures. Here's what I got:
llvm.trap lowering
| Target | Instruction | Host signal |
|---|---|---|
| x86-64 / x86 | ud2 |
SIGILL |
| AArch64 | brk #1 |
SIGTRAP |
| ARM (A32/T32, v5T+) | trap (undefined-insn class) |
SIGTRAP |
| ARM (no v5T) | udf 254 |
SIGILL |
| RISC-V | unimp |
SIGILL |
| PowerPC | trap (tw 31,r0,r0) |
SIGTRAP |
| MIPS | break |
SIGTRAP |
| SPARC | ta 5 |
SIGTRAP |
| SystemZ | 4-byte trap alias | SIGILL |
| LoongArch | ud 0 (alias amswap.w) |
SIGILL |
| Hexagon | PS_crash pseudo |
— |
| WebAssembly | unreachable (0x00) |
trap (no POSIX signal) |
| AMDGPU | s_trap 2 |
queue error → SIGABRT |
So ignoring AMD GPU, it's either SIGILL or SIGTRAP.
For SIGTRAP archs, I think we'll need to teach GDB to distinguish between the trap actually executing vs a single step or other reasons for a SIGTRAP. I'll need to think about how best to make that work.
For now, in the interest of time, I'm OK with going as is. Just tweak the testcase comment.
There was a problem hiding this comment.
Fixed. Removed all architecture-specific references (ud2, x86-64, GPU, CPU) from comments in both CPU and GPU test files. Comments now use generic "trap instruction" terminology.
| set has_line_table_bug 0 | ||
|
|
||
| gdb_test_multiple "disassemble /m test_trap_function" "" { | ||
| -re "$line_x\[^\r\n\]*\r\n(\[^\r\n\]*\r\n)*?.*ud2" { |
There was a problem hiding this comment.
Consider what will need to happen to the testcase when someone registers the new gdbarch for another CPU. With this approach, they'll have to come here and hardcode some other instruction.
Did you try instead to ask GDB about the line that should contain the trap, like Baris mentioned in the call earlier today? E.g., I see this:
(gdb) info line 21
Line 21 of "/home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/builtin_verbose_trap.cpp"
is at address 0x55555555576d <_Z18test_trap_functionv+13> but contains no code.
The signal is "but contains no code". If the compiler put ud2 at line 21, we would not see that. That seems like a general way to test this without having to hardcode specific instructions.
There was a problem hiding this comment.
Fixed. Replaced architecture-specific disassembly checks (looking for ud2/s_trap instructions) with portable info line approach in both CPU and GPU tests. Now checks for "contains no code" message to detect the compiler line table bug.
|
Tests seem to be failing. |
eda2309 to
0e9bd3a
Compare
| on x86_64. */ | ||
|
|
||
| /* Determine whether to show an inline frame. | ||
| Show verbose trap frames (__clang_trap_msg$...) when SIGILL occurs. */ |
There was a problem hiding this comment.
Two comment blocks for the same function? Did you intend to drop the first one, or something like that?
| set_gdbarch_fetch_hiperr_info | ||
| (gdbarch, amd64_linux_fetch_hiperr_info); | ||
|
|
||
| /* Show verbose trap inline frames when stopped due to abort. */ |
There was a problem hiding this comment.
"stopped due to abort"
"Abort" doesn't apply for the CPU, does it?
There was a problem hiding this comment.
fixed, for cpu its "illegal instruction"
0e9bd3a to
7572ee2
Compare
Hi Joseph. I'm not familiar with "-fsanitize-runtime=trap", and a google search is finding no hits, and neither my system clang nor the one that comes with my rocm build is aware of it. Is that a new feature? I don't suppose it's the same as "-fsanitize-trap=undefined"? Does it make the ubsan runtime use __builtin_verbose_trap? __builtin_verbose_trap takes constant strings and embeds them in the generated inline function's name (i.e., compile time), so I'm curious to see how that works. From your output, lldb seems to be hiding frame #0 (it presents a stop at frame #1), and decoding the stop reason out of somewhere. If ubsan in that mode is really using __builtin_verbose_trap, then I guess lldb is probably extracting the arguments from the function name. |
|
I found this https://lists.llvm.org/pipermail/lldb-commits/Week-of-Mon-20251117/155573.html, and the code seems to be in upstream ldlb already (in lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp). That talks about a "-fbounds-safety" option, and I also don't have... swiftlang/llvm-project#11645 describes it. So does https://clang.llvm.org/docs/BoundsSafety.html, and then and https://clang.llvm.org/docs/BoundsSafetyImplPlans.html says that it's still not fully implemented. So is this all a swift-llvm-fork-only feature? But I'm confused since you used a different command line option. In any case, afaict from the lldb code, it is extracting the stop reason from the (runtime) argument passed to __bounds_safety_soft_trap, the magic function the ubsan runtime calls. This is looking like should be a completely separate discussion. |
7572ee2 to
16a2a54
Compare
| set has_line_table_bug 1 | ||
| exp_continue | ||
| } | ||
| -re -wrap "" { |
There was a problem hiding this comment.
This is missing a "pass $gdb_test_name" call. (check the CPU version too).
__builtin_verbose_trap is a Clang builtin that allows embedding custom
trap messages in the binary for better crash diagnostics. When called,
it emits a trap instruction and creates an artificial inline frame with
a specially-formatted name: __clang_trap_msg$<category>$<message>.
Currently, when a program hits a verbose trap, GDB hides this inline
frame by default (as it does for all inline frames during normal
stepping). This means the trap message is not visible in backtraces,
defeating the purpose of verbose traps.
Before this fix, a backtrace after hitting a verbose trap shows:
#0 test_trap_kernel () at test.cpp:24
After this fix:
#0 __clang_trap_msg$check verbose$This is verbose trap! ()
at test.cpp:23
#1 test_trap_kernel () at test.cpp:24
This affects both CPU (x86_64) and GPU (AMDGPU) targets, though with
different trap mechanisms:
- CPU: ud2 instruction generates SIGILL
- GPU: s_trap 2 instruction generates SIGABRT
To fix this, add a new gdbarch hook 'should_show_inline_frame' that
allows architecture-specific code to decide whether an inline frame
should be shown. The hook receives the symbol and the stop signal,
returning true if the frame should be displayed.
The hook is implemented for:
- amd64-linux: checks for SIGILL + __clang_trap_msg$ prefix
- amdgpu: checks for SIGABRT + __clang_trap_msg$ prefix
The signal check ensures the frame is only shown when the trap actually
fires, preserving normal stepping behavior where inline frames are
hidden.
Bug: AIROCGDB-558
16a2a54 to
088f46f
Compare
|
Labelling ci:skip here as we already had a green CI. We don't need to run it again for a trivial change. Once we have code/substantial changes, let's remove the ci:skip label and let it validate fully. |
Ticket: AIROCGDB-558
Summary
Implements complete support for
__builtin_verbose_trapon both AMD GPU and CPU (x86_64), making verbose trap diagnostic messages visible in backtraces.Problem
When
__builtin_verbose_trapfired, the diagnostic inline frames were not visible in backtraces, making it impossible to see the trap category and message. Two issues prevented this:skip_inline_frames()intentionally hides inline frames to improve stepping experience, but this also hid the verbose trap framesSolution
This PR provides a two-part fix:
1. PC Adjustment for s_trap 2 (GPU)
Rewind PC by 4 bytes when a wave stops with s_trap 2, similar to breakpoint handling. This ensures the PC remains in the inlined DWARF frame containing
__builtin_verbose_trapdebug info.Files changed:
gdb/amd-dbgapi-target.c- PC adjustment logicgdb/testsuite/gdb.rocm/builtin_verbose_trap.{cpp,exp}- Test coverage2. gdbarch Hook for Inline Frame Control (GPU + CPU)
Adds architecture-specific control over inline frame visibility using GDB's gdbarch pattern. Architectures can now prevent skipping of compiler-generated inline frames containing diagnostic information.
Changes:
gdb/gdbarch_components.py- Defineshow_verbose_trap_inline_framemethodgdb/arch-utils.{c,h}- Default implementation (preserves current behavior)gdb/amdgpu-tdep.c- AMD GPU implementation (checksSIGABRT+ name pattern)gdb/amd64-linux-tdep.c- CPU x86_64 implementation (checksSIGILL+ name pattern)gdb/inline-frame.c- Call gdbarch hook in skip loopgdb/gdbarch-gen.{c,h}- Auto-generatedDetection logic:
SIGABRTfor GPU s_trap 2,SIGILLfor CPU ud2)__clang_trap_msg$<category>$<message>Results
Before:
After (GPU):
After (CPU):