Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

anthonyhatran
Copy link

@anthonyhatran anthonyhatran commented Aug 31, 2025

For the project "Usability Improvements for trapping Undefined Behavior Sanitizer"

@anthonyhatran
Copy link
Author

cc: @delcypher @Michael137 @asl

TODO: Show example of normal userspace UBSan and how it outputs the problem
TODO: show example of trapping UBSan. Both with and without the debugger. This will let you illustrate the problem of it being hard to understand what happened, even in the debugger. This gives you the motivation for your work which is currently missing.

## Human readable descriptions of UBSan traps in LLDB
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fold this into the Background section as a sub-section? And call it: "Improving trap debuggability: Prior Art"? Or something like that?

This is where you can describe the artificial frame stuff. And at the end of this section you can foreshadow that this prior work lend itself well to be extended to UBSan traps, which is what you implemented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was satisfied with Dan's ported changes.

-0.0% -4 -0.0% -4 ,__const
-0.0% -1.27Ki -0.0% -1.27Ki ,__cstring
+0.2% +11.5Mi +0.1% +9.19Mi TOTAL
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets provide some discussion of the numbers


The diagnostics extension for trap messages has been recently upstreamed by Dan [6]. As of right now, only signed and unsigned overflow for addition, subtraction, and multiplication is being used by this system. I've investigated some use cases outside of signed and unsigned overflow, and I plan to implement that within the next week(s). [stub, since I'll probably upstream my changes soon so this part will change]

There is also an issue [8] where trap messages are not emitted in cases where they should be due to a null check. The purpose of the null check was to prevent a nullptr dereference that occurred in debug-info prologue. This is a known issue to which there isn't a concrete solution as of current.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best to mention this in the implementation section

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about this, but I feel like having a dedicated section to the future scope is good. I ended just putting it in both spots (I go less into detail on the second mention though).


An alternative to this approach would be to teach debuggers (e.g. LLDB) to decode the trap reason encoded in trap instructions in the debugger. However, this approach wasn’t taken for several reasons:


Copy link
Member

@Michael137 Michael137 Sep 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make these a bullet point? The previous sentence makes it sound like a list of reasons is to follow

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like that was the case. I split it into bulletpoints, let me know if it still looks correct.

During my GSoC project I implemented support for displaying human readable descriptions of UBSan traps in LLDB to improve the debugging experience.


The approach used is based on how `__builtin_verbose_trap` is currently implemented inside Clang [11] [12]. `__builtin_verbose_trap` was implemented in the past for [libc++ hardening](https://discourse.llvm.org/t/rfc-hardening-in-libc/73925). At a high-level this works by encoding the reason for trapping as a string on the trap instruction in the debug info for the program being compiled. Then when a trap is hit in the debugger, the debugger retrieves this string and shows it as the reason for trapping.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The approach used is based on how `__builtin_verbose_trap` is currently implemented inside Clang [11] [12]. `__builtin_verbose_trap` was implemented in the past for [libc++ hardening](https://discourse.llvm.org/t/rfc-hardening-in-libc/73925). At a high-level this works by encoding the reason for trapping as a string on the trap instruction in the debug info for the program being compiled. Then when a trap is hit in the debugger, the debugger retrieves this string and shows it as the reason for trapping.
The approach used is based on how `__builtin_verbose_trap` is currently implemented inside Clang [11] [12]. `__builtin_verbose_trap` was implemented in the past for [libc++ hardening](https://discourse.llvm.org/t/rfc-hardening-in-libc/73925). At a high-level this works by encoding the reason for trapping on the trap's debug info. When a trap is hit in the debugger, the debugger retrieves this string and shows it as the reason for trapping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mention that you are going to expand on this in an upcoming section? Currently it sounds like you are glossing over the implementation details, although you do explain them later on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, can we just remove this entire paragraph (or move the important bits into the Encoding the trap reason in the debug info subsection below?

I feel like gives us following clear structure:

  1. "We want to make the trap reasons human readable in the debugger"
  2. "We could make the debugger handle this without compiler support"
  • "These are the reasons it didn't work"
  1. "Encode reason in debug info"
  • "This is the approach we took"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it to its relevant section.

### Encoding the trap reason in the debug info


As previously mentioned the approach I took is based on how `__builtin_verbose_trap` encodes its message into debug info. This is done by pretending in the debug info that the trap instruction was inlined from another function, where that function is artificially generated and its name is of the form `__clang_trap_msg$<Category>$<TrapMessage>`, where <Category> and <TrapMessage> are the trap category and message to display when trapping respectively. This function does not actually exist in the compiled program. It only exists in the debug info as a convenient (albeit hacky) way to describe the reason for trapping.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done by pretending in the debug info that the trap instruction was inlined from another function

I wouldn't lead with this. I feel like this just opens a bunch of questions.

Can we just say that the trap reason string gets encoded on the trap's debug info. Then show the IR and point out that the trap's debug info metadata is scoped by the DISubprogram %32, which is not actually a real function, but we needed to stuff the string somewhere in the DWARF DIE tree and using a DW_TAG_subprogram was deemed the most straightforward (and most space efficient) place to put it. We could've put the string elsewhere but for reasons outside the scope of this blog pots it lives on a fake function DIE. The trap reason is encoded in the DW_TAG_subprograms name. You can point them to the discussion start at llvm/llvm-project#145967 (comment) for more info on this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made an attempt at addressing this in a way that made sense without stripping away too much of what was previously written. Let me know if it still makes sense post-changes.

```


Note it is likely the code size difference is negligible because because in optimized builds trap instructions in a function get merged together which causes the additional debug info my patch adds to be dropped.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would point out where the debug info size impact would mainly come from: llvm/llvm-project#145967 (comment)

And also mention, the size increase is tied to how many traps get emitted (since we emit a new DW_TAG_subprogram DIE for each trap). I would also link to the comment here: llvm/llvm-project#154618 (comment), which shows that in production Google saw a pretty large size increase in debug-info with this (~15% total binary size increase IIRC). So this could be a potential future thing to look into. Why does the binary size increase by that much and what could we do about it? As it stands, they had to disable the trap reason because of that, which isn't great.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, though I'm not sure if the reviewer had ran it with -fsanitize-debug-trap-reasons=basic or -fsanitize-debug-trap-reasons=detailed? I'm not sure how big of a difference it would make.

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes

Looking better. I'd give @delcypher some time to have a look before merging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants