Skip to content

Commit 07773d5

Browse files
committed
Emit trap reasons for UBSan checks
1 parent 67b5195 commit 07773d5

38 files changed

+588
-102
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,18 @@ Non-comprehensive list of changes in this release
8989
-------------------------------------------------
9090
- Added ``__builtin_elementwise_minnumnum`` and ``__builtin_elementwise_maxnumnum``.
9191

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

93101
New Compiler Flags
94102
------------------
103+
- 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``).
95104

96105
Deprecated Compiler Flags
97106
-------------------------

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0, Benign) ///< Emit PCs for atomic
307307
CODEGENOPT(SanitizeBinaryMetadataUAR, 1, 0, Benign) ///< Emit PCs for start of functions
308308
///< that are subject for use-after-return checking.
309309
CODEGENOPT(SanitizeStats , 1, 0, Benign) ///< Collect statistics for sanitizers.
310+
CODEGENOPT(SanitizeDebugTrapReasons, 1, 1 , Benign) ///< Enable UBSan trapping messages
310311
CODEGENOPT(SimplifyLibCalls , 1, 1, Benign) ///< Set when -fbuiltin is enabled.
311312
CODEGENOPT(SoftFloat , 1, 0, Benign) ///< -soft-float.
312313
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
@@ -2597,6 +2597,16 @@ def fsanitize_undefined_trap_on_error
25972597
def fno_sanitize_undefined_trap_on_error
25982598
: Flag<["-"], "fno-sanitize-undefined-trap-on-error">, Group<f_clang_Group>,
25992599
Alias<fno_sanitize_trap_EQ>, AliasArgs<["undefined"]>;
2600+
defm sanitize_debug_trap_reasons
2601+
: BoolFOption<
2602+
"sanitize-debug-trap-reasons",
2603+
CodeGenOpts<"SanitizeDebugTrapReasons">, DefaultTrue,
2604+
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2605+
"Annotate trap blocks in debug info with UBSan trap reasons">,
2606+
NegFlag<SetFalse, [], [ClangOption, CC1Option],
2607+
"Do not annotate trap blocks in debug info with UBSan trap "
2608+
"reasons">>;
2609+
26002610
defm sanitize_minimal_runtime : BoolOption<"f", "sanitize-minimal-runtime",
26012611
CodeGenOpts<"SanitizeMinimalRuntime">, DefaultFalse,
26022612
PosFlag<SetTrue>,

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6435,7 +6435,7 @@ CodeGenFunction::LexicalScope::~LexicalScope() {
64356435
static std::string SanitizerHandlerToCheckLabel(SanitizerHandler Handler) {
64366436
std::string Label;
64376437
switch (Handler) {
6438-
#define SANITIZER_CHECK(Enum, Name, Version) \
6438+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
64396439
case Enum: \
64406440
Label = "__ubsan_check_" #Name; \
64416441
break;

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ enum VariableTypeDescriptorKind : uint16_t {
8585
// Miscellaneous Helper Methods
8686
//===--------------------------------------------------------------------===//
8787

88+
static llvm::StringRef GetUBSanTrapForHandler(SanitizerHandler ID) {
89+
switch (ID) {
90+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) \
91+
case SanitizerHandler::Enum: \
92+
return Msg;
93+
LIST_SANITIZER_CHECKS
94+
#undef SANITIZER_CHECK
95+
}
96+
}
97+
8898
/// CreateTempAlloca - This creates a alloca and inserts it into the entry
8999
/// block.
90100
RawAddress
@@ -3649,7 +3659,7 @@ struct SanitizerHandlerInfo {
36493659
}
36503660

36513661
const SanitizerHandlerInfo SanitizerHandlers[] = {
3652-
#define SANITIZER_CHECK(Enum, Name, Version) {#Name, Version},
3662+
#define SANITIZER_CHECK(Enum, Name, Version, Msg) {#Name, Version},
36533663
LIST_SANITIZER_CHECKS
36543664
#undef SANITIZER_CHECK
36553665
};
@@ -3954,6 +3964,8 @@ void CodeGenFunction::EmitCfiCheckFail() {
39543964
StartFunction(GlobalDecl(), CGM.getContext().VoidTy, F, FI, Args,
39553965
SourceLocation());
39563966

3967+
ApplyDebugLocation ADL = ApplyDebugLocation::CreateArtificial(*this);
3968+
39573969
// This function is not affected by NoSanitizeList. This function does
39583970
// not have a source location, but "src:*" would still apply. Revert any
39593971
// changes to SanOpts made in StartFunction.
@@ -4051,6 +4063,15 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
40514063

40524064
llvm::BasicBlock *&TrapBB = TrapBBs[CheckHandlerID];
40534065

4066+
llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
4067+
llvm::StringRef TrapMessage = GetUBSanTrapForHandler(CheckHandlerID);
4068+
4069+
if (getDebugInfo() && !TrapMessage.empty() &&
4070+
CGM.getCodeGenOpts().SanitizeDebugTrapReasons && TrapLocation) {
4071+
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
4072+
TrapLocation, "Undefined Behavior Sanitizer", TrapMessage);
4073+
}
4074+
40544075
NoMerge = NoMerge || !CGM.getCodeGenOpts().OptimizationLevel ||
40554076
(CurCodeDecl && CurCodeDecl->hasAttr<OptimizeNoneAttr>());
40564077

@@ -4059,8 +4080,8 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
40594080
auto Call = TrapBB->begin();
40604081
assert(isa<llvm::CallInst>(Call) && "Expected call in trap BB");
40614082

4062-
Call->applyMergedLocation(Call->getDebugLoc(),
4063-
Builder.getCurrentDebugLocation());
4083+
Call->applyMergedLocation(Call->getDebugLoc(), TrapLocation);
4084+
40644085
Builder.CreateCondBr(Checked, Cont, TrapBB,
40654086
MDHelper.createLikelyBranchWeights());
40664087
} else {
@@ -4069,6 +4090,8 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
40694090
MDHelper.createLikelyBranchWeights());
40704091
EmitBlock(TrapBB);
40714092

4093+
ApplyDebugLocation applyTrapDI(*this, TrapLocation);
4094+
40724095
llvm::CallInst *TrapCall =
40734096
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::ubsantrap),
40744097
llvm::ConstantInt::get(CGM.Int8Ty, CheckHandlerID));

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
@@ -1382,6 +1382,12 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
13821382
CmdArgs.push_back(Args.MakeArgString("-fsanitize-annotate-debug-info=" +
13831383
toString(AnnotateDebugInfo)));
13841384

1385+
if (const Arg *A =
1386+
Args.getLastArg(options::OPT_fsanitize_debug_trap_reasons,
1387+
options::OPT_fno_sanitize_debug_trap_reasons)) {
1388+
CmdArgs.push_back(Args.MakeArgString(A->getAsString(Args)));
1389+
}
1390+
13851391
addSpecialCaseListOpt(Args, CmdArgs,
13861392
"-fsanitize-ignorelist=", UserIgnorelistFiles);
13871393
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)