Skip to content

Commit b66ea6b

Browse files
anthonyhatrandelcypher
authored andcommitted
[Clang][CodeGen] Emit “trap reasons” on UBSan traps (llvm#145967)
This patch adds a human readable trap category and message to UBSan traps. The category and message are encoded in a fake frame in the debug info where the function is a fake inline function where the name encodes the trap category and message. This is the same mechanism used by Clang’s `__builtin_verbose_trap()`. This change allows consumers of binaries built with trapping UBSan to more easily identify the reason for trapping. In particular LLDB already has a frame recognizer that recognizes the fake function names emitted in debug info by this patch. A patch testing this behavior in LLDB will be added in a separately. The human readable trap messages are based on the messages currently emitted by the userspace runtime for UBSan in compiler-rt. Note the wording is not identical because the userspace UBSan runtime has access to dynamic information that is not available during Clang’s codegen. Test cases for each UBSan trap kind are included. This complements the [`-fsanitize-annotate-debug-info` feature](llvm#141997). While `-fsanitize-annotate-debug-info` attempts to annotate all UBSan-added instructions, this feature (`-fsanitize-debug-trap-reasons`) only annotates the final trap instruction using SanitizerHandler information. This work is part of a GSoc 2025 project. (cherry picked from commit ba477b9) (cherry picked from commit 29992cf)
1 parent 0a3a8ce commit b66ea6b

38 files changed

+589
-103
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,18 @@ Non-comprehensive list of changes in this release
366366
correct method to check for these features is to test for the ``__PTRAUTH__``
367367
macro.
368368

369+
- Trapping UBSan (e.g. ``-fsanitize-trap=undefined``) now emits a string describing the reason for
370+
trapping into the generated debug info. This feature allows debuggers (e.g. LLDB) to display
371+
the reason for trapping if the trap is reached. The string is currently encoded in the debug
372+
info as an artificial frame that claims to be inlined at the trap location. The function used
373+
for the artificial frame is an artificial function whose name encodes the reason for trapping.
374+
The encoding used is currently the same as ``__builtin_verbose_trap`` but might change in the future.
375+
This feature is enabled by default but can be disabled by compiling with
376+
``-fno-sanitize-annotate-debug-info-traps``.
369377

370378
New Compiler Flags
371379
------------------
380+
- New option ``-fno-sanitize-annotate-debug-info-traps`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).
372381

373382
- New option ``-Wundef-true`` added and enabled by default to warn when `true` is used in the C preprocessor without being defined before C23.
374383

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0, Benign) ///< Emit PCs for atomic
306306
CODEGENOPT(SanitizeBinaryMetadataUAR, 1, 0, Benign) ///< Emit PCs for start of functions
307307
///< that are subject for use-after-return checking.
308308
CODEGENOPT(SanitizeStats , 1, 0, Benign) ///< Collect statistics for sanitizers.
309+
CODEGENOPT(SanitizeDebugTrapReasons, 1, 1 , Benign) ///< Enable UBSan trapping messages
309310
CODEGENOPT(SimplifyLibCalls , 1, 1, Benign) ///< Set when -fbuiltin is enabled.
310311
CODEGENOPT(SoftFloat , 1, 0, Benign) ///< -soft-float.
311312
CODEGENOPT(SpeculativeLoadHardening, 1, 0, Benign) ///< Enable speculative load hardening.

clang/include/clang/Driver/Options.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,16 @@ def fsanitize_undefined_trap_on_error
27172717
def fno_sanitize_undefined_trap_on_error
27182718
: Flag<["-"], "fno-sanitize-undefined-trap-on-error">, Group<f_clang_Group>,
27192719
Alias<fno_sanitize_trap_EQ>, AliasArgs<["undefined"]>;
2720+
defm sanitize_debug_trap_reasons
2721+
: BoolFOption<
2722+
"sanitize-debug-trap-reasons",
2723+
CodeGenOpts<"SanitizeDebugTrapReasons">, DefaultTrue,
2724+
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2725+
"Annotate trap blocks in debug info with UBSan trap reasons">,
2726+
NegFlag<SetFalse, [], [ClangOption, CC1Option],
2727+
"Do not annotate trap blocks in debug info with UBSan trap "
2728+
"reasons">>;
2729+
27202730
defm sanitize_minimal_runtime : BoolOption<"f", "sanitize-minimal-runtime",
27212731
CodeGenOpts<"SanitizeMinimalRuntime">, DefaultFalse,
27222732
PosFlag<SetTrue>,

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6535,7 +6535,7 @@ CodeGenFunction::LexicalScope::~LexicalScope() {
65356535
static std::string SanitizerHandlerToCheckLabel(SanitizerHandler Handler) {
65366536
std::string Label;
65376537
switch (Handler) {
6538-
#define SANITIZER_CHECK(Enum, Name, Version) \
6538+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
65396539
case Enum: \
65406540
Label = "__ubsan_check_" #Name; \
65416541
break;

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,17 @@ void CodeGenFunction::EmitValueTerminatedAssignmentCheck(
312312
EmitBoundsSafetyTrapCheck(Check, BNS_TRAP_TERMINATED_BY_TERM_ASSIGN);
313313
}
314314

315+
316+
static llvm::StringRef GetUBSanTrapForHandler(SanitizerHandler ID) {
317+
switch (ID) {
318+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
319+
case SanitizerHandler::Enum: \
320+
return Msg;
321+
LIST_SANITIZER_CHECKS
322+
#undef SANITIZER_CHECK
323+
}
324+
}
325+
315326
/// CreateTempAlloca - This creates a alloca and inserts it into the entry
316327
/// block.
317328
RawAddress
@@ -4169,7 +4180,7 @@ struct SanitizerHandlerInfo {
41694180
}
41704181

41714182
const SanitizerHandlerInfo SanitizerHandlers[] = {
4172-
#define SANITIZER_CHECK(Enum, Name, Version) {#Name, Version},
4183+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) {#Name, Version},
41734184
LIST_SANITIZER_CHECKS
41744185
#undef SANITIZER_CHECK
41754186
};
@@ -4474,6 +4485,8 @@ void CodeGenFunction::EmitCfiCheckFail() {
44744485
StartFunction(GlobalDecl(), CGM.getContext().VoidTy, F, FI, Args,
44754486
SourceLocation());
44764487

4488+
ApplyDebugLocation ADL = ApplyDebugLocation::CreateArtificial(*this);
4489+
44774490
// This function is not affected by NoSanitizeList. This function does
44784491
// not have a source location, but "src:*" would still apply. Revert any
44794492
// changes to SanOpts made in StartFunction.
@@ -4564,7 +4577,7 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
45644577
bool NoMerge,
45654578
/*TO_UPSTREAM(BoundsSafety) ON*/
45664579
StringRef Annotation,
4567-
StringRef TrapMessage) {
4580+
StringRef BoundsSafetyTrapMessage) {
45684581
/*TO_UPSTREAM(BoundsSafety) OFF*/
45694582
llvm::BasicBlock *Cont = createBasicBlock("cont");
45704583

@@ -4575,13 +4588,23 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
45754588

45764589
llvm::BasicBlock *&TrapBB = TrapBBs[CheckHandlerID];
45774590

4578-
/*TO_UPSTREAM(BoundsSafety) ON*/
45794591
llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
4592+
4593+
/*TO_UPSTREAM(BoundsSafety) ON*/
45804594
if (CheckHandlerID == SanitizerHandler::BoundsSafety && getDebugInfo()) {
45814595
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
4582-
TrapLocation, GetBoundsSafetyTrapMessagePrefix(), TrapMessage);
4596+
TrapLocation, GetBoundsSafetyTrapMessagePrefix(), BoundsSafetyTrapMessage);
45834597
}
45844598
/*TO_UPSTREAM(BoundsSafety) OFF*/
4599+
else {
4600+
llvm::StringRef TrapMessage = GetUBSanTrapForHandler(CheckHandlerID);
4601+
4602+
if (getDebugInfo() && !TrapMessage.empty() &&
4603+
CGM.getCodeGenOpts().SanitizeDebugTrapReasons && TrapLocation) {
4604+
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
4605+
TrapLocation, "Undefined Behavior Sanitizer", TrapMessage);
4606+
}
4607+
}
45854608

45864609
NoMerge = NoMerge || !CGM.getCodeGenOpts().OptimizationLevel ||
45874610
(CurCodeDecl && CurCodeDecl->hasAttr<OptimizeNoneAttr>());

clang/lib/CodeGen/SanitizerHandler.h

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,69 @@
1414
#define LLVM_CLANG_LIB_CODEGEN_SANITIZER_HANDLER_H
1515

1616
#define LIST_SANITIZER_CHECKS \
17-
SANITIZER_CHECK(AddOverflow, add_overflow, 0) \
18-
SANITIZER_CHECK(BuiltinUnreachable, builtin_unreachable, 0) \
19-
SANITIZER_CHECK(CFICheckFail, cfi_check_fail, 0) \
20-
SANITIZER_CHECK(DivremOverflow, divrem_overflow, 0) \
21-
SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0) \
22-
SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0) \
23-
SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0) \
24-
SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0) \
25-
SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0) \
26-
SANITIZER_CHECK(InvalidObjCCast, invalid_objc_cast, 0) \
27-
SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0) \
28-
SANITIZER_CHECK(MissingReturn, missing_return, 0) \
29-
SANITIZER_CHECK(MulOverflow, mul_overflow, 0) \
30-
SANITIZER_CHECK(NegateOverflow, negate_overflow, 0) \
31-
SANITIZER_CHECK(NullabilityArg, nullability_arg, 0) \
32-
SANITIZER_CHECK(NullabilityReturn, nullability_return, 1) \
33-
SANITIZER_CHECK(NonnullArg, nonnull_arg, 0) \
34-
SANITIZER_CHECK(NonnullReturn, nonnull_return, 1) \
35-
SANITIZER_CHECK(OutOfBounds, out_of_bounds, 0) \
36-
SANITIZER_CHECK(PointerOverflow, pointer_overflow, 0) \
37-
SANITIZER_CHECK(ShiftOutOfBounds, shift_out_of_bounds, 0) \
38-
SANITIZER_CHECK(SubOverflow, sub_overflow, 0) \
39-
SANITIZER_CHECK(TypeMismatch, type_mismatch, 1) \
40-
SANITIZER_CHECK(AlignmentAssumption, alignment_assumption, 0) \
41-
SANITIZER_CHECK(VLABoundNotPositive, vla_bound_not_positive, 0) \
42-
SANITIZER_CHECK(BoundsSafety, bounds_safety, 0)
17+
SANITIZER_CHECK(AddOverflow, add_overflow, 0, "Integer addition overflowed") \
18+
SANITIZER_CHECK(BuiltinUnreachable, builtin_unreachable, 0, \
19+
"_builtin_unreachable(), execution reached an unreachable " \
20+
"program point") \
21+
SANITIZER_CHECK(CFICheckFail, cfi_check_fail, 0, \
22+
"Control flow integrity check failed") \
23+
SANITIZER_CHECK(DivremOverflow, divrem_overflow, 0, \
24+
"Integer divide or remainder overflowed") \
25+
SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0, \
26+
"Dynamic type cache miss, member call made on an object " \
27+
"whose dynamic type differs from the expected type") \
28+
SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0, \
29+
"Floating-point to integer conversion overflowed") \
30+
SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0, \
31+
"Function called with mismatched signature") \
32+
SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0, \
33+
"Implicit integer conversion overflowed or lost data") \
34+
SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0, \
35+
"Invalid use of builtin function") \
36+
SANITIZER_CHECK(InvalidObjCCast, invalid_objc_cast, 0, \
37+
"Invalid Objective-C cast") \
38+
SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0, \
39+
"Loaded an invalid or uninitialized value for the type") \
40+
SANITIZER_CHECK(MissingReturn, missing_return, 0, \
41+
"Execution reached the end of a value-returning function " \
42+
"without returning a value") \
43+
SANITIZER_CHECK(MulOverflow, mul_overflow, 0, \
44+
"Integer multiplication overflowed") \
45+
SANITIZER_CHECK(NegateOverflow, negate_overflow, 0, \
46+
"Integer negation overflowed") \
47+
SANITIZER_CHECK( \
48+
NullabilityArg, nullability_arg, 0, \
49+
"Passing null as an argument which is annotated with _Nonnull") \
50+
SANITIZER_CHECK(NullabilityReturn, nullability_return, 1, \
51+
"Returning null from a function with a return type " \
52+
"annotated with _Nonnull") \
53+
SANITIZER_CHECK(NonnullArg, nonnull_arg, 0, \
54+
"Passing null pointer as an argument which is declared to " \
55+
"never be null") \
56+
SANITIZER_CHECK(NonnullReturn, nonnull_return, 1, \
57+
"Returning null pointer from a function which is declared " \
58+
"to never return null") \
59+
SANITIZER_CHECK(OutOfBounds, out_of_bounds, 0, "Array index out of bounds") \
60+
SANITIZER_CHECK(PointerOverflow, pointer_overflow, 0, \
61+
"Pointer arithmetic overflowed bounds") \
62+
SANITIZER_CHECK(ShiftOutOfBounds, shift_out_of_bounds, 0, \
63+
"Shift exponent is too large for the type") \
64+
SANITIZER_CHECK(SubOverflow, sub_overflow, 0, \
65+
"Integer subtraction overflowed") \
66+
SANITIZER_CHECK(TypeMismatch, type_mismatch, 1, \
67+
"Type mismatch in operation") \
68+
SANITIZER_CHECK(AlignmentAssumption, alignment_assumption, 0, \
69+
"Alignment assumption violated") \
70+
SANITIZER_CHECK( \
71+
VLABoundNotPositive, vla_bound_not_positive, 0, \
72+
"Variable length array bound evaluates to non-positive value") \
73+
SANITIZER_CHECK(BoundsSafety, bounds_safety, 0, \
74+
"") // BoundsSafety Msg is empty because it is not considered
75+
// part of UBSan; therefore, no trap reason is emitted for
76+
// this case.
4377

4478
enum SanitizerHandler {
45-
#define SANITIZER_CHECK(Enum, Name, Version) Enum,
79+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) Enum,
4680
LIST_SANITIZER_CHECKS
4781
#undef SANITIZER_CHECK
4882
};

clang/lib/Driver/SanitizerArgs.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,12 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
13851385
CmdArgs.push_back(Args.MakeArgString("-fsanitize-annotate-debug-info=" +
13861386
toString(AnnotateDebugInfo)));
13871387

1388+
if (const Arg *A =
1389+
Args.getLastArg(options::OPT_fsanitize_debug_trap_reasons,
1390+
options::OPT_fno_sanitize_debug_trap_reasons)) {
1391+
CmdArgs.push_back(Args.MakeArgString(A->getAsString(Args)));
1392+
}
1393+
13881394
addSpecialCaseListOpt(Args, CmdArgs,
13891395
"-fsanitize-ignorelist=", UserIgnorelistFiles);
13901396
addSpecialCaseListOpt(Args, CmdArgs,

clang/test/CodeGen/bounds-checking-debuginfo.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ void d(double*);
2525
// CHECK-TRAP-NEXT: [[TMP1:%.*]] = icmp ult i64 [[TMP0]], 10, !dbg [[DBG23]], !nosanitize [[META10]]
2626
// CHECK-TRAP-NEXT: br i1 [[TMP1]], label %[[CONT:.*]], label %[[TRAP:.*]], !dbg [[DBG23]], !prof [[PROF27:![0-9]+]], !nosanitize [[META10]]
2727
// CHECK-TRAP: [[TRAP]]:
28-
// CHECK-TRAP-NEXT: call void @llvm.ubsantrap(i8 18) #[[ATTR3:[0-9]+]], !dbg [[DBG23]], !nosanitize [[META10]]
29-
// CHECK-TRAP-NEXT: unreachable, !dbg [[DBG23]], !nosanitize [[META10]]
28+
// CHECK-TRAP-NEXT: call void @llvm.ubsantrap(i8 18) #[[ATTR3:[0-9]+]], !dbg [[DBGTRAP:![0-9]+]], !nosanitize [[META10]]
29+
// CHECK-TRAP-NEXT: unreachable, !dbg [[DBGTRAP]], !nosanitize [[META10]]
3030
// CHECK-TRAP: [[CONT]]:
3131
// CHECK-TRAP-NEXT: [[IDXPROM:%.*]] = sext i32 [[CALL]] to i64, !dbg [[DBG26:![0-9]+]]
3232
// CHECK-TRAP-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [10 x double], ptr [[A]], i64 0, i64 [[IDXPROM]], !dbg [[DBG26]]
3333
// CHECK-TRAP-NEXT: [[TMP2:%.*]] = load double, ptr [[ARRAYIDX]], align 8, !dbg [[DBG26]]
34-
// CHECK-TRAP-NEXT: ret double [[TMP2]], !dbg [[DBG28:![0-9]+]]
34+
// CHECK-TRAP-NEXT: ret double [[TMP2]], !dbg [[DBG30:![0-9]+]]
3535
//
3636
// CHECK-NOTRAP-LABEL: define dso_local double @f1(
3737
// CHECK-NOTRAP-SAME: i32 noundef [[B:%.*]], i32 noundef [[I:%.*]]) #[[ATTR0:[0-9]+]] !dbg [[DBG4:![0-9]+]] {
@@ -93,7 +93,9 @@ double f1(int b, int i) {
9393
// CHECK-TRAP: [[META25]] = !DISubroutineType(types: null)
9494
// CHECK-TRAP: [[DBG26]] = !DILocation(line: 66, column: 10, scope: [[DBG4]])
9595
// CHECK-TRAP: [[PROF27]] = !{!"branch_weights", i32 1048575, i32 1}
96-
// CHECK-TRAP: [[DBG28]] = !DILocation(line: 66, column: 3, scope: [[DBG4]])
96+
// CHECK-TRAP: [[DBGTRAP]] = !DILocation(line: 0, scope: [[TRAPMSG:![0-9]+]], inlinedAt: [[DBG23]])
97+
// CHECK-TRAP: [[TRAPMSG]] = distinct !DISubprogram(name: "__clang_trap_msg$Undefined Behavior Sanitizer$Array index out of bounds", scope: [[META5]], file: [[META5]], type: [[META25]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]])
98+
// CHECK-TRAP: [[DBG30]] = !DILocation(line: 66, column: 3, scope: [[DBG4]])
9799
//.
98100
// CHECK-NOTRAP: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
99101
// CHECK-NOTRAP: [[META1]] = !DIFile(filename: "<stdin>", directory: {{.*}})

0 commit comments

Comments
 (0)