Skip to content

Commit ffa9ca8

Browse files
committed
[BoundsSafety] Implement compiler instrumentation for a soft trap mode
This patch implements a "soft trap mode" for `-fbounds-safety` where hard traps are replaced with calls to a runtime function. It is assumed the runtime function can return so code is generated so that execution can proceed past the "soft trap" (i.e. as if the bounds check passed even though it didn't). It is expected implementations of the runtime function log the issue in some way. The motivation behind this is to provide a smoother path to adopting `-fbounds-safety` in existing codebases. Using soft trap mode with an appropriate runtime allows: * Improved stability of the program because bounds safety violations are no longer fatal because execution continues. * The ability to collect all the soft traps that occurred to get an overview of how many issues there are. Note this patch does not implement the actual runtime itself. This will be done in a separate patch. This patch introduces the `-fbounds-safety-soft-traps=` driver and CC1 flag. This allows enabling the soft trap compilation mode. It currently has two different modes which offer different trade-offs. * `call_with_str` - This generates calls that pass a constant C string describing the reason for trapping. This greatly simplifies logging but at the cost of increasing code size because a global string needs to be emitted for every "trap reason". * `call_with_code` - This generates calls that pass an integer constant to identify the reason for trapping. This produces better codesize than `call_with_str` but the reason for trapping is much less descriptive. Currently the integer constant is hard coded to zero because we don't currently have a good stable integer representation of trap reasons. This will be fixed in rdar://162824128. We may want to add a third `soft_trap_instruction` option one day that emits a special trap instruction that uses its immediate to identify itself as a trap that should be treated as soft by program host (e.g. the kernel). Implementing that is out-of-scope for this patch because we'd need a new LLVM IR intrinsic and backend support. This patch also introduces a new `-fbounds-safety-soft-trap-function=` CC1 flag. It is not intended users use this flag. This flag is for Clang driver to communicate to CC1 what the function name should be. Currently the driver doesn't pass anything so CC1 picks these function names: * `__bounds_safety_soft_trap_s` for `call_with_str` * `__bounds_safety_soft_trap_c` for `call_with_code` As implementations of Bounds Safety soft trap runtimes are brought up the driver logic will need add support for them. We will likely need a new driver flag to control which implementation to use (if any). The soft trap function names and API are defined in a new header (`bounds_safety_soft_traps.h`) that ships with the compiler. Having the interface defined in header provides several advantages: * Runtime implementations can include the header to check they conform to the current interface which might evolve over time. * It provides an explicit interface that runtime implementers can implement. In contrast `-ftrap-function` has an implicit interface that basically can't be evolved without causing a silent ABI break. Note this implementation deliberately doesn't re-use `-ftrap-function` and `-ftrap-function-returns` so that these features can be used independently. In particular it allows using `-fbounds-safety` in soft trap mode with trapping UBSan in any mode (see test cases). Note this implementation continues to deliberately use "trap reasons". This is the compiler feature where artificial inline frames are added to debug info on traps to describe the reason for trapping. This means the string representation for reason from trapping is still available in the `call_with_code` case provided debug information is available. This can be seen the in the LLDB tests added. This does mean that technically there is some redundancy here (trap reasons passed as arguments and in the debug info) but this is intentional because it means in the `call_with_str` case without debug info can still do a good job of logging problems. rdar://158088757 (cherry picked from commit c49eb6e)
1 parent 3c09dcd commit ffa9ca8

24 files changed

+2281
-2
lines changed

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ CODEGENOPT(BoundsSafetyUniqueTraps, 1, 0, Benign) ///< When true, merging of
339339
/// prevented.
340340

341341
ENUM_CODEGENOPT(BoundsSafetyDebugTrapReasons, SanitizeDebugTrapReasonKind, 2, SanitizeDebugTrapReasonKind::Detailed, Benign) ///< Control how "trap reasons" are emitted in debug info
342+
343+
ENUM_CODEGENOPT(BoundsSafetyTrapMode, BoundsSafetyTrapModeKind, 2, BoundsSafetyTrapModeKind::Hard, Benign) ///< Control how BoundsSafety traps are emitted
342344
/* TO_UPSTREAM(BoundsSafety) OFF*/
343345

344346
/// Treat loops as finite: language, always, never.

clang/include/clang/Basic/CodeGenOptions.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,24 @@ class CodeGenOptions : public CodeGenOptionsBase {
188188
///< larger debug info than `Basic`.
189189
};
190190

191+
/* TO_UPSTREAM(BoundsSafety) ON*/
192+
enum class BoundsSafetyTrapModeKind {
193+
Hard, ///< Emit a fatal trap instruction (default).
194+
SoftCallWithTrapString, ///< Emit a non-fatal call. The call
195+
///< is passed a string description of the failed
196+
///< bounds check.
197+
SoftCallWithTrapCode, ///< Emit a non-fatal call. The call
198+
///< is passed an integer describing the failed
199+
///< bounds check.
200+
};
201+
202+
/// The name of the function to call for BoundsSafety soft traps. This is used
203+
/// with `BoundsSafetyTrapModeKind::SoftCallWithTrapString` and
204+
// `BoundsSafetyTrapModeKind::SoftCallWithTrapCode`.
205+
std::string BoundsSafetySoftTrapFuncName;
206+
207+
/* TO_UPSTREAM(BoundsSafety) OFF*/
208+
191209
/// The code model to use (-mcmodel).
192210
std::string CodeModel;
193211

clang/include/clang/Driver/Options.td

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,32 @@ def fbounds_safety_debug_trap_reasons_EQ
20592059
NormalizedValues<["None", "Basic", "Detailed"]>,
20602060
MarshallingInfoEnum<CodeGenOpts<"BoundsSafetyDebugTrapReasons">, "Detailed">;
20612061

2062+
def fbounds_safety_soft_traps_EQ
2063+
: Joined<["-"], "fbounds-safety-soft-traps=">, Group<f_Group>,
2064+
Visibility<[ClangOption, CC1Option]>,
2065+
HelpText<"Enables soft traps which causes the compiler to emit a non-fatal "
2066+
"instruction sequence when a bounds check fails. After this instruction "
2067+
"sequence execution resumes as if the bounds check had succeeded."
2068+
"For `call-with-*` modes see 'bounds_safety_soft_traps.h' for the runtime"
2069+
" function interface. This option supports the following modes: "
2070+
"`none` - No soft traps, i.e. use hard traps (default)."
2071+
"`call-with-str` - Emit a call to a runtime function that receives the "
2072+
"trap reason as a string."
2073+
"`call-with-code` - Emit a call to a runtime function that receives an "
2074+
"integer describing the trap reason. This is better for codesize than "
2075+
"`call-with-str` but is harder to debug if debug info is missing.">,
2076+
Values<"none,call-with-str,call-with-code">,
2077+
NormalizedValuesScope<"CodeGenOptions::BoundsSafetyTrapModeKind">,
2078+
NormalizedValues<["Hard", "SoftCallWithTrapString", "SoftCallWithTrapCode"]>,
2079+
MarshallingInfoEnum<CodeGenOpts<"BoundsSafetyTrapMode">, "Hard">;
2080+
2081+
def fbounds_safety_soft_trap_function_EQ : Joined<["-"],
2082+
"fbounds-safety-soft-trap-function=">,
2083+
Group<f_Group>, Visibility<[CC1Option]>,
2084+
HelpText<"Set the name of the BoundsSafety soft trap function. See the "
2085+
"'bounds_safety_soft_traps.h' file for the API of this function.">,
2086+
MarshallingInfoString<CodeGenOpts<"BoundsSafetySoftTrapFuncName">>;
2087+
20622088
// TO_UPSTREAM(BoundsSafety) OFF
20632089

20642090
defm lifetime_safety : BoolFOption<

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4658,7 +4658,9 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
46584658
llvm::MDBuilder MDHelper(getLLVMContext());
46594659

46604660
/*TO_UPSTREAM(BoundsSafety) ON*/
4661-
NoMerge |= CGM.getCodeGenOpts().TrapFuncReturns;
4661+
NoMerge |= CGM.getCodeGenOpts().TrapFuncReturns ||
4662+
CGM.getCodeGenOpts().getBoundsSafetyTrapMode() !=
4663+
CodeGenOptions::BoundsSafetyTrapModeKind::Hard;
46624664
/*TO_UPSTREAM(BoundsSafety) OFF*/
46634665

46644666
if (TrapBB && !NoMerge) {
@@ -4700,7 +4702,45 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked,
47004702
/*TO_UPSTREAM(BoundsSafety) ON*/
47014703
ApplyDebugLocation applyTrapDI(*this, TrapLocation);
47024704
llvm::CallInst *TrapCall = nullptr;
4703-
if (CGM.getCodeGenOpts().TrapFuncReturns) {
4705+
if (CGM.getCodeGenOpts().getBoundsSafetyTrapMode() !=
4706+
CodeGenOptions::BoundsSafetyTrapModeKind::Hard &&
4707+
CheckHandlerID == SanitizerHandler::BoundsSafety) {
4708+
// BoundsSafety soft-trap mode. This takes prescendence over
4709+
// `-ftrap-function-returns`. Note: Make sure to bump
4710+
// `__CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION` and adjust the interface
4711+
// in `bounds_safety_soft_traps.h` if the API is changed.
4712+
llvm::FunctionType *FnType = nullptr;
4713+
llvm::Constant *SoftTrapCallArg = nullptr;
4714+
// Emit calls to one of the interfaces defined in
4715+
// `bounds_safety_soft_traps.h`
4716+
switch (CGM.getCodeGenOpts().getBoundsSafetyTrapMode()) {
4717+
case CodeGenOptions::BoundsSafetyTrapModeKind::SoftCallWithTrapCode: {
4718+
// void __bounds_safety_soft_trap_c(uint16_t);
4719+
// TODO: Set correct value from the `TrapReason` object
4720+
// (rdar://162824128).
4721+
SoftTrapCallArg = llvm::ConstantInt::get(CGM.Int16Ty, 0);
4722+
break;
4723+
}
4724+
case CodeGenOptions::BoundsSafetyTrapModeKind::SoftCallWithTrapString: {
4725+
// void __bounds_safety_soft_trap_s(const char*);
4726+
if (!TrapMessage.empty()) {
4727+
SoftTrapCallArg =
4728+
CGM.GetOrCreateGlobalStr(TrapMessage, Builder, "trap.reason");
4729+
} else {
4730+
SoftTrapCallArg = llvm::Constant::getNullValue(CGM.Int8PtrTy);
4731+
}
4732+
break;
4733+
}
4734+
default:
4735+
llvm_unreachable("Unhandled BoundsSafetyTrapMode");
4736+
}
4737+
FnType = llvm::FunctionType::get(CGM.VoidTy, {SoftTrapCallArg->getType()},
4738+
false);
4739+
auto TrapFunc = CGM.CreateRuntimeFunction(
4740+
FnType, CGM.getCodeGenOpts().BoundsSafetySoftTrapFuncName);
4741+
TrapCall = EmitNounwindRuntimeCall(TrapFunc, {SoftTrapCallArg});
4742+
Builder.CreateBr(Cont);
4743+
} else if (CGM.getCodeGenOpts().TrapFuncReturns) {
47044744
auto *TrapID = llvm::ConstantInt::get(CGM.Int8Ty, CheckHandlerID);
47054745
llvm::FunctionType *FnType =
47064746
llvm::FunctionType::get(CGM.VoidTy, {TrapID->getType()}, false);

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5413,6 +5413,23 @@ CodeGenModule::GetAddrOfGlobal(GlobalDecl GD, ForDefinition_t IsForDefinition) {
54135413
return GetAddrOfGlobalVar(cast<VarDecl>(D), /*Ty=*/nullptr, IsForDefinition);
54145414
}
54155415

5416+
/*TO_UPSTREAM(BoundsSafety) ON*/
5417+
llvm::Constant *CodeGenModule::GetOrCreateGlobalStr(StringRef Value,
5418+
CGBuilderTy &Builder,
5419+
const Twine &Name) {
5420+
auto globalStrIt = CachedGlobalStrings.find(Value);
5421+
if (globalStrIt != CachedGlobalStrings.end()) {
5422+
return globalStrIt->second;
5423+
}
5424+
5425+
llvm::GlobalVariable *GlobalStringArr =
5426+
Builder.CreateGlobalString(Value, Name);
5427+
5428+
CachedGlobalStrings[Value] = GlobalStringArr;
5429+
return GlobalStringArr;
5430+
}
5431+
/*TO_UPSTREAM(BoundsSafety) OFF*/
5432+
54165433
llvm::GlobalVariable *CodeGenModule::CreateOrReplaceCXXRuntimeVariable(
54175434
StringRef Name, llvm::Type *Ty, llvm::GlobalValue::LinkageTypes Linkage,
54185435
llvm::Align Alignment) {

clang/lib/CodeGen/CodeGenModule.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,10 @@ class CodeGenModule : public CodeGenTypeCache {
689689
// The list is sorted for binary-searching.
690690
std::vector<std::string> MSHotPatchFunctions;
691691

692+
/* TO_UPSTREAM(BoundsSafety) ON*/
693+
llvm::StringMap<llvm::Constant *> CachedGlobalStrings;
694+
/* TO_UPSTREAM(BoundsSafety) OFF*/
695+
692696
public:
693697
CodeGenModule(ASTContext &C, IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
694698
const HeaderSearchOptions &headersearchopts,
@@ -1754,6 +1758,11 @@ class CodeGenModule : public CodeGenTypeCache {
17541758
const VarDecl *D,
17551759
ForDefinition_t IsForDefinition = NotForDefinition);
17561760

1761+
/* TO_UPSTREAM(BoundsSafety) ON*/
1762+
llvm::Constant *GetOrCreateGlobalStr(StringRef Value, CGBuilderTy &Builder,
1763+
const Twine &Name = "");
1764+
/* TO_UPSTREAM(BoundsSafety) OFF*/
1765+
17571766
// FIXME: Hardcoding priority here is gross.
17581767
void AddGlobalCtor(llvm::Function *Ctor, int Priority = 65535,
17591768
unsigned LexOrder = ~0U,

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7138,6 +7138,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &Job,
71387138
}
71397139

71407140
Args.AddLastArg(CmdArgs, options::OPT_fbounds_safety_debug_trap_reasons_EQ);
7141+
Args.AddLastArg(CmdArgs, options::OPT_fbounds_safety_soft_traps_EQ);
71417142
/* TO_UPSTREAM(BoundsSafety) OFF*/
71427143

71437144
// Handle -f[no-]wrapv and -f[no-]strict-overflow, which are used by both

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,24 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
25982598

25992599
Opts.StaticClosure = Args.hasArg(options::OPT_static_libclosure);
26002600

2601+
/* TO_UPSTREAM(BoundsSafety) ON*/
2602+
if (LangOpts->hasBoundsSafety() &&
2603+
Opts.BoundsSafetySoftTrapFuncName.empty()) {
2604+
// Set the default function name if the driver didn't provide one.
2605+
// Different function names are used for the different ABIs.
2606+
switch (Opts.getBoundsSafetyTrapMode()) {
2607+
case CodeGenOptions::BoundsSafetyTrapModeKind::SoftCallWithTrapString:
2608+
Opts.BoundsSafetySoftTrapFuncName = "__bounds_safety_soft_trap_s";
2609+
break;
2610+
case CodeGenOptions::BoundsSafetyTrapModeKind::SoftCallWithTrapCode:
2611+
Opts.BoundsSafetySoftTrapFuncName = "__bounds_safety_soft_trap_c";
2612+
break;
2613+
case CodeGenOptions::BoundsSafetyTrapModeKind::Hard:
2614+
break;
2615+
}
2616+
}
2617+
/* TO_UPSTREAM(BoundsSafety) OFF*/
2618+
26012619
return Diags.getNumErrors() == NumErrorsBefore;
26022620
}
26032621

clang/lib/Headers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ set(files
337337

338338
# TO_UPSTREAM(BoundsSafety)
339339
list(APPEND files ptrcheck.h)
340+
list(APPEND files bounds_safety_soft_traps.h)
340341

341342
# TO_UPSTREAM(Lifetimebound)
342343
list(APPEND files lifetimebound.h)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*===---- bounds_safety_soft_traps.h ----------------------------------------===
2+
*
3+
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
* See https://llvm.org/LICENSE.txt for license information.
5+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
*
7+
*===------------------------------------------------------------------------===
8+
This file defines the interface used by `-fbounds-safety`'s trap mode. Note
9+
this interface isn't yet considered stable
10+
*===------------------------------------------------------------------------===
11+
*/
12+
13+
#ifndef __CLANG_BOUNDS_SAFETY_SOFT_TRAPS_H
14+
#define __CLANG_BOUNDS_SAFETY_SOFT_TRAPS_H
15+
#include <stdint.h>
16+
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
/// Macro that defines the current API version. This value can be queried at
22+
/// compile time to know which interface version the compiler uses.
23+
#define __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION 0
24+
25+
/// Called when a `-fbounds-safety` bounds check fails when building with
26+
/// `-fbounds-safety-soft-traps=call-with-str`. This function is allowed to
27+
/// to return.
28+
///
29+
/// \param reason A string constant describing the reason for trapping or
30+
/// NULL.
31+
void __bounds_safety_soft_trap_s(const char *reason);
32+
33+
// TODO(dliew): Document the `reason_code` values (rdar://162824128)
34+
35+
/// Called when a `-fbounds-safety` bounds check fails when building with
36+
/// `-fbounds-safety-soft-traps=call-with-code`. This function is allowed to
37+
/// to return.
38+
///
39+
/// \param reason_code. An integer the represents the reason for trapping.
40+
/// The values are currently not documented but will be in the future.
41+
///
42+
void __bounds_safety_soft_trap_c(uint16_t reason_code);
43+
44+
#ifdef __cplusplus
45+
}
46+
#endif
47+
48+
#endif

0 commit comments

Comments
 (0)