Skip to content

Commit c7b2d98

Browse files
authored
[sancov] Introduce optional callback for stack-depth tracking (#138323)
Normally -fsanitize-coverage=stack-depth inserts inline arithmetic to update thread_local __sancov_lowest_stack. To support stack depth tracking in the Linux kernel, which does not implement traditional thread_local storage, provide the option to call a function instead. This matches the existing "stackleak" implementation that is supported in Linux via a GCC plugin. To make this coverage more performant, a minimum estimated stack depth can be chosen to enable the callback mode, skipping instrumentation of functions with smaller stacks. With -fsanitize-coverage-stack-depth-callback-min set greater than 0, the __sanitize_cov_stack_depth() callback will be injected when the estimated stack depth is greater than or equal to the given minimum.
1 parent 17b2b6d commit c7b2d98

File tree

10 files changed

+409
-15
lines changed

10 files changed

+409
-15
lines changed

clang/docs/SanitizerCoverage.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,50 @@ Users need to implement a single function to capture the CF table at startup:
385385
// the collected control flow.
386386
}
387387
388+
Tracing Stack Depth
389+
===================
390+
391+
With ``-fsanitize-coverage=stack-depth`` the compiler will track how much
392+
stack space has been used for a function call chain. Leaf functions are
393+
not included in this tracing.
394+
395+
The maximum depth of a function call graph is stored in the thread-local
396+
``__sancov_lowest_stack`` variable. Instrumentation is inserted in every
397+
non-leaf function to check the frame pointer against this variable,
398+
and if it is lower, store the current frame pointer. This effectively
399+
inserts the following:
400+
401+
.. code-block:: c++
402+
403+
extern thread_local uintptr_t __sancov_lowest_stack;
404+
405+
uintptr_t stack = (uintptr_t)__builtin_frame_address(0);
406+
if (stack < __sancov_lowest_stack)
407+
__sancov_lowest_stack = stack;
408+
409+
If ``-fsanitize-coverage-stack-depth-callback-min=N`` (where
410+
``N > 0``) is also used, the tracking is delegated to a callback,
411+
``__sanitizer_cov_stack_depth``, instead of adding instrumentation to
412+
update ``__sancov_lowest_stack``. The ``N`` of the argument is used
413+
to determine which functions to instrument. Only functions estimated
414+
to be using ``N`` bytes or more of stack space will be instrumented to
415+
call the tracing callback. In the case of a dynamically sized stack,
416+
the callback is unconditionally added.
417+
418+
The callback takes no arguments and is responsible for determining
419+
the stack usage and doing any needed comparisons and storage. A roughly
420+
equivalent implementation of ``__sancov_lowest_stack`` using the callback
421+
would look like this:
422+
423+
.. code-block:: c++
424+
425+
void __sanitizer_cov_stack_depth(void) {
426+
uintptr_t stack = (uintptr_t)__builtin_frame_address(0);
427+
428+
if (stack < __sancov_lowest_stack)
429+
__sancov_lowest_stack = stack;
430+
}
431+
388432
Gated Trace Callbacks
389433
=====================
390434

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ CODEGENOPT(SanitizeCoveragePCTable, 1, 0) ///< Create a PC Table.
305305
CODEGENOPT(SanitizeCoverageControlFlow, 1, 0) ///< Collect control flow
306306
CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning.
307307
CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing
308+
VALUE_CODEGENOPT(SanitizeCoverageStackDepthCallbackMin , 32, 0) ///< Enable stack depth tracing callbacks.
308309
CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0) ///< Enable tracing of loads.
309310
CODEGENOPT(SanitizeCoverageTraceStores, 1, 0) ///< Enable tracing of stores.
310311
CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0) ///< Emit PCs for covered functions.

clang/include/clang/Driver/Options.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2365,6 +2365,13 @@ def fsanitize_coverage_ignorelist : Joined<["-"], "fsanitize-coverage-ignorelist
23652365
HelpText<"Disable sanitizer coverage instrumentation for modules and functions "
23662366
"that match the provided special case list, even the allowed ones">,
23672367
MarshallingInfoStringVector<CodeGenOpts<"SanitizeCoverageIgnorelistFiles">>;
2368+
def fsanitize_coverage_stack_depth_callback_min_EQ
2369+
: Joined<["-"], "fsanitize-coverage-stack-depth-callback-min=">,
2370+
Group<f_clang_Group>,
2371+
MetaVarName<"<M>">,
2372+
HelpText<"Use callback for max stack depth tracing with minimum stack "
2373+
"depth M">,
2374+
MarshallingInfoInt<CodeGenOpts<"SanitizeCoverageStackDepthCallbackMin">>;
23682375
def fexperimental_sanitize_metadata_EQ : CommaJoined<["-"], "fexperimental-sanitize-metadata=">,
23692376
Group<f_Group>,
23702377
HelpText<"Specify the type of metadata to emit for binary analysis sanitizers">;

clang/include/clang/Driver/SanitizerArgs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SanitizerArgs {
3434
std::vector<std::string> CoverageIgnorelistFiles;
3535
std::vector<std::string> BinaryMetadataIgnorelistFiles;
3636
int CoverageFeatures = 0;
37+
int CoverageStackDepthCallbackMin = 0;
3738
int BinaryMetadataFeatures = 0;
3839
int OverflowPatternExclusions = 0;
3940
int MsanTrackOrigins = 0;

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) {
255255
Opts.InlineBoolFlag = CGOpts.SanitizeCoverageInlineBoolFlag;
256256
Opts.PCTable = CGOpts.SanitizeCoveragePCTable;
257257
Opts.StackDepth = CGOpts.SanitizeCoverageStackDepth;
258+
Opts.StackDepthCallbackMin = CGOpts.SanitizeCoverageStackDepthCallbackMin;
258259
Opts.TraceLoads = CGOpts.SanitizeCoverageTraceLoads;
259260
Opts.TraceStores = CGOpts.SanitizeCoverageTraceStores;
260261
Opts.CollectControlFlow = CGOpts.SanitizeCoverageControlFlow;

clang/lib/Driver/SanitizerArgs.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,17 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
751751
options::OPT_fno_sanitize_ignorelist,
752752
clang::diag::err_drv_malformed_sanitizer_ignorelist, DiagnoseErrors);
753753

754+
// Verify that -fsanitize-coverage-stack-depth-callback-min is >= 0.
755+
if (Arg *A = Args.getLastArg(
756+
options::OPT_fsanitize_coverage_stack_depth_callback_min_EQ)) {
757+
StringRef S = A->getValue();
758+
if (S.getAsInteger(0, CoverageStackDepthCallbackMin) ||
759+
CoverageStackDepthCallbackMin < 0) {
760+
if (DiagnoseErrors)
761+
D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S;
762+
}
763+
}
764+
754765
// Parse -f[no-]sanitize-memory-track-origins[=level] options.
755766
if (AllAddedKinds & SanitizerKind::Memory) {
756767
if (Arg *A =
@@ -1269,6 +1280,11 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
12691280
addSpecialCaseListOpt(Args, CmdArgs, "-fsanitize-coverage-ignorelist=",
12701281
CoverageIgnorelistFiles);
12711282

1283+
if (CoverageStackDepthCallbackMin)
1284+
CmdArgs.push_back(
1285+
Args.MakeArgString("-fsanitize-coverage-stack-depth-callback-min=" +
1286+
Twine(CoverageStackDepthCallbackMin)));
1287+
12721288
if (!GPUSanitize) {
12731289
// Translate available BinaryMetadataFeatures to corresponding clang-cc1
12741290
// flags. Does not depend on any other sanitizers. Unsupported on GPUs.

clang/test/Driver/fsanitize-coverage.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@
9393
// CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-trace-pc-guard
9494
// CHECK-STACK-DEPTH-PC-GUARD: -fsanitize-coverage-stack-depth
9595

96+
// RUN: %clang --target=x86_64-linux-gnu \
97+
// RUN: -fsanitize-coverage-stack-depth-callback-min=100 %s -### 2>&1 | \
98+
// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK
99+
// RUN: %clang --target=x86_64-linux-gnu \
100+
// RUN: -fsanitize-coverage-stack-depth-callback-min=0 %s -### 2>&1 | \
101+
// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK-ZERO
102+
// RUN: not %clang --target=x86_64-linux-gnu \
103+
// RUN: -fsanitize-coverage-stack-depth-callback-min=-10 %s -### 2>&1 | \
104+
// RUN: FileCheck %s --check-prefix=CHECK-STACK-DEPTH-CALLBACK-NEGATIVE
105+
// CHECK-STACK-DEPTH-CALLBACK-NOT: error:
106+
// CHECK-STACK-DEPTH-CALLBACK: -fsanitize-coverage-stack-depth-callback-min=100
107+
// CHECK-STACK-DEPTH-CALLBACK-ZERO-NOT: -fsanitize-coverage-stack-depth-callback-min=0
108+
// CHECK-STACK-DEPTH-CALLBACK-NEGATIVE: error: invalid value '-10' in '-fsanitize-coverage-stack-depth-callback-min=-10'
109+
96110
// RUN: %clang --target=x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=trace-cmp,indirect-calls %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TYPE-NECESSARY
97111
// CHECK-NO-TYPE-NECESSARY-NOT: error:
98112
// CHECK-NO-TYPE-NECESSARY: -fsanitize-coverage-indirect-calls

llvm/include/llvm/Transforms/Utils/Instrumentation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ struct SanitizerCoverageOptions {
162162
bool TraceStores = false;
163163
bool CollectControlFlow = false;
164164
bool GatedCallbacks = false;
165+
int StackDepthCallbackMin = 0;
165166

166167
SanitizerCoverageOptions() = default;
167168
};

llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const char SanCovPCsSectionName[] = "sancov_pcs";
8686
const char SanCovCFsSectionName[] = "sancov_cfs";
8787
const char SanCovCallbackGateSectionName[] = "sancov_gate";
8888

89+
const char SanCovStackDepthCallbackName[] = "__sanitizer_cov_stack_depth";
8990
const char SanCovLowestStackName[] = "__sancov_lowest_stack";
9091
const char SanCovCallbackGateName[] = "__sancov_should_track";
9192

@@ -152,6 +153,12 @@ static cl::opt<bool> ClStackDepth("sanitizer-coverage-stack-depth",
152153
cl::desc("max stack depth tracing"),
153154
cl::Hidden);
154155

156+
static cl::opt<int> ClStackDepthCallbackMin(
157+
"sanitizer-coverage-stack-depth-callback-min",
158+
cl::desc("max stack depth tracing should use callback and only when "
159+
"stack depth more than specified"),
160+
cl::Hidden);
161+
155162
static cl::opt<bool>
156163
ClCollectCF("sanitizer-coverage-control-flow",
157164
cl::desc("collect control flow for each function"), cl::Hidden);
@@ -202,6 +209,8 @@ SanitizerCoverageOptions OverrideFromCL(SanitizerCoverageOptions Options) {
202209
Options.PCTable |= ClCreatePCTable;
203210
Options.NoPrune |= !ClPruneBlocks;
204211
Options.StackDepth |= ClStackDepth;
212+
Options.StackDepthCallbackMin = std::max(Options.StackDepthCallbackMin,
213+
ClStackDepthCallbackMin.getValue());
205214
Options.TraceLoads |= ClLoadTracing;
206215
Options.TraceStores |= ClStoreTracing;
207216
Options.GatedCallbacks |= ClGatedCallbacks;
@@ -271,6 +280,7 @@ class ModuleSanitizerCoverage {
271280
DomTreeCallback DTCallback;
272281
PostDomTreeCallback PDTCallback;
273282

283+
FunctionCallee SanCovStackDepthCallback;
274284
FunctionCallee SanCovTracePCIndir;
275285
FunctionCallee SanCovTracePC, SanCovTracePCGuard;
276286
std::array<FunctionCallee, 4> SanCovTraceCmpFunction;
@@ -514,6 +524,9 @@ bool ModuleSanitizerCoverage::instrumentModule() {
514524
SanCovTracePCGuard =
515525
M.getOrInsertFunction(SanCovTracePCGuardName, VoidTy, PtrTy);
516526

527+
SanCovStackDepthCallback =
528+
M.getOrInsertFunction(SanCovStackDepthCallbackName, VoidTy);
529+
517530
for (auto &F : M)
518531
instrumentFunction(F);
519532

@@ -1078,22 +1091,65 @@ void ModuleSanitizerCoverage::InjectCoverageAtBlock(Function &F, BasicBlock &BB,
10781091
Store->setNoSanitizeMetadata();
10791092
}
10801093
if (Options.StackDepth && IsEntryBB && !IsLeafFunc) {
1081-
// Check stack depth. If it's the deepest so far, record it.
10821094
Module *M = F.getParent();
1083-
auto FrameAddrPtr = IRB.CreateIntrinsic(
1084-
Intrinsic::frameaddress,
1085-
IRB.getPtrTy(M->getDataLayout().getAllocaAddrSpace()),
1086-
{Constant::getNullValue(Int32Ty)});
1087-
auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy);
1088-
auto LowestStack = IRB.CreateLoad(IntptrTy, SanCovLowestStack);
1089-
auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack);
1090-
auto ThenTerm = SplitBlockAndInsertIfThen(
1091-
IsStackLower, &*IP, false,
1092-
MDBuilder(IRB.getContext()).createUnlikelyBranchWeights());
1093-
IRBuilder<> ThenIRB(ThenTerm);
1094-
auto Store = ThenIRB.CreateStore(FrameAddrInt, SanCovLowestStack);
1095-
LowestStack->setNoSanitizeMetadata();
1096-
Store->setNoSanitizeMetadata();
1095+
const DataLayout &DL = M->getDataLayout();
1096+
1097+
if (Options.StackDepthCallbackMin) {
1098+
// In callback mode, only add call when stack depth reaches minimum.
1099+
uint32_t EstimatedStackSize = 0;
1100+
// If dynamic alloca found, always add call.
1101+
bool HasDynamicAlloc = false;
1102+
// Find an insertion point after last "alloca".
1103+
llvm::Instruction *InsertBefore = nullptr;
1104+
1105+
// Examine all allocas in the basic block. since we're too early
1106+
// to have results from Intrinsic::frameaddress, we have to manually
1107+
// estimate the stack size.
1108+
for (auto &I : BB) {
1109+
if (auto *AI = dyn_cast<AllocaInst>(&I)) {
1110+
// Move potential insertion point past the "alloca".
1111+
InsertBefore = AI->getNextNode();
1112+
1113+
// Make an estimate on the stack usage.
1114+
if (AI->isStaticAlloca()) {
1115+
uint32_t Bytes = DL.getTypeAllocSize(AI->getAllocatedType());
1116+
if (AI->isArrayAllocation()) {
1117+
if (const ConstantInt *arraySize =
1118+
dyn_cast<ConstantInt>(AI->getArraySize())) {
1119+
Bytes *= arraySize->getZExtValue();
1120+
} else {
1121+
HasDynamicAlloc = true;
1122+
}
1123+
}
1124+
EstimatedStackSize += Bytes;
1125+
} else {
1126+
HasDynamicAlloc = true;
1127+
}
1128+
}
1129+
}
1130+
1131+
if (HasDynamicAlloc ||
1132+
EstimatedStackSize >= Options.StackDepthCallbackMin) {
1133+
if (InsertBefore)
1134+
IRB.SetInsertPoint(InsertBefore);
1135+
IRB.CreateCall(SanCovStackDepthCallback)->setCannotMerge();
1136+
}
1137+
} else {
1138+
// Check stack depth. If it's the deepest so far, record it.
1139+
auto FrameAddrPtr = IRB.CreateIntrinsic(
1140+
Intrinsic::frameaddress, IRB.getPtrTy(DL.getAllocaAddrSpace()),
1141+
{Constant::getNullValue(Int32Ty)});
1142+
auto FrameAddrInt = IRB.CreatePtrToInt(FrameAddrPtr, IntptrTy);
1143+
auto LowestStack = IRB.CreateLoad(IntptrTy, SanCovLowestStack);
1144+
auto IsStackLower = IRB.CreateICmpULT(FrameAddrInt, LowestStack);
1145+
auto ThenTerm = SplitBlockAndInsertIfThen(
1146+
IsStackLower, &*IP, false,
1147+
MDBuilder(IRB.getContext()).createUnlikelyBranchWeights());
1148+
IRBuilder<> ThenIRB(ThenTerm);
1149+
auto Store = ThenIRB.CreateStore(FrameAddrInt, SanCovLowestStack);
1150+
LowestStack->setNoSanitizeMetadata();
1151+
Store->setNoSanitizeMetadata();
1152+
}
10971153
}
10981154
}
10991155

0 commit comments

Comments
 (0)