Skip to content

Commit d010ec6

Browse files
authored
[clang][rtsan] Introduce realtime sanitizer codegen and driver (llvm#102622)
Introduce the `-fsanitize=realtime` flag in clang driver Plug in the RealtimeSanitizer PassManager pass in Codegen, and attribute a function based on if it has the `[[clang::nonblocking]]` function effect.
1 parent 172c4a4 commit d010ec6

16 files changed

+208
-5
lines changed

clang/docs/RealtimeSanitizer.rst

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
=================
2+
RealtimeSanitizer
3+
=================
4+
5+
.. contents::
6+
:local:
7+
8+
Introduction
9+
============
10+
RealtimeSanitizer (a.k.a. RTSan) is a real-time safety testing tool for C and C++
11+
projects. RTSan can be used to detect real-time violations, i.e. calls to methods
12+
that are not safe for use in functions with deterministic runtime requirements.
13+
RTSan considers any function marked with the ``[[clang::nonblocking]]`` attribute
14+
to be a real-time function. If RTSan detects a call to ``malloc``, ``free``,
15+
``pthread_mutex_lock``, or anything else that could have a non-deterministic
16+
execution time in a function marked ``[[clang::nonblocking]]``
17+
RTSan raises an error.
18+
19+
The runtime slowdown introduced by RealtimeSanitizer is negligible.
20+
21+
How to build
22+
============
23+
24+
Build LLVM/Clang with `CMake <https://llvm.org/docs/CMake.html>` and enable the
25+
``compiler-rt`` runtime. An example CMake configuration that will allow for the
26+
use/testing of RealtimeSanitizer:
27+
28+
.. code-block:: console
29+
30+
$ cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" <path to source>/llvm
31+
32+
Usage
33+
=====
34+
35+
There are two requirements:
36+
37+
1. The code must be compiled with the ``-fsanitize=realtime`` flag.
38+
2. Functions that are subject to real-time constraints must be marked
39+
with the ``[[clang::nonblocking]]`` attribute.
40+
41+
Typically, these attributes should be added onto the functions that are entry
42+
points for threads with real-time priority. These threads are subject to a fixed
43+
callback time, such as audio callback threads or rendering loops in video game
44+
code.
45+
46+
.. code-block:: console
47+
48+
% cat example_realtime_violation.cpp
49+
#include <vector>
50+
51+
void violation() [[clang::nonblocking]]{
52+
std::vector<float> v;
53+
v.resize(100);
54+
}
55+
56+
int main() {
57+
violation();
58+
return 0;
59+
}
60+
# Compile and link
61+
% clang++ -fsanitize=realtime -g example_realtime_violation.cpp
62+
63+
If a real-time safety violation is detected in a ``[[clang::nonblocking]]``
64+
context, or any function invoked by that function, the program will exit with a
65+
non-zero exit code.
66+
67+
.. code-block:: console
68+
69+
% clang++ -fsanitize=realtime -g example_realtime_violation.cpp
70+
% ./a.out
71+
Real-time violation: intercepted call to real-time unsafe function `malloc` in real-time context! Stack trace:
72+
#0 0x000102893034 in __rtsan::PrintStackTrace() rtsan_stack.cpp:45
73+
#1 0x000102892e64 in __rtsan::Context::ExpectNotRealtime(char const*) rtsan_context.cpp:78
74+
#2 0x00010289397c in malloc rtsan_interceptors.cpp:286
75+
#3 0x000195bd7bd0 in operator new(unsigned long)+0x1c (libc++abi.dylib:arm64+0x16bd0)
76+
#4 0x5c7f00010230f07c (<unknown module>)
77+
#5 0x00010230f058 in std::__1::__libcpp_allocate[abi:ue170006](unsigned long, unsigned long) new:324
78+
#6 0x00010230effc in std::__1::allocator<float>::allocate[abi:ue170006](unsigned long) allocator.h:114
79+
... snip ...
80+
#10 0x00010230e4bc in std::__1::vector<float, std::__1::allocator<float>>::__append(unsigned long) vector:1162
81+
#11 0x00010230dcdc in std::__1::vector<float, std::__1::allocator<float>>::resize(unsigned long) vector:1981
82+
#12 0x00010230dc28 in violation() main.cpp:5
83+
#13 0x00010230dd64 in main main.cpp:9
84+
#14 0x0001958960dc (<unknown module>)
85+
#15 0x2f557ffffffffffc (<unknown module>)

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,11 @@ Moved checkers
452452

453453
Sanitizers
454454
----------
455+
- Introduced Realtime Sanitizer, activated by using the -fsanitize=realtime
456+
flag. This sanitizer detects unsafe system library calls, such as memory
457+
allocations and mutex locks. If any such function is called during invocation
458+
of a function marked with the ``[[clang::nonblocking]]`` attribute, an error
459+
is printed to the console and the process exits non-zero.
455460

456461
- Added the ``-fsanitize-undefined-ignore-overflow-pattern`` flag which can be
457462
used to disable specific overflow-dependent code patterns. The supported

clang/docs/UsersManual.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,8 @@ are listed below.
20682068
integrity.
20692069
- ``-fsanitize=safe-stack``: :doc:`safe stack <SafeStack>`
20702070
protection against stack-based memory corruption errors.
2071+
- ``-fsanitize=realtime``: :doc:`RealtimeSanitizer`,
2072+
a real-time safety checker.
20712073

20722074
There are more fine-grained checks available: see
20732075
the :ref:`list <ubsan-checks>` of specific kinds of

clang/docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Using Clang as a Compiler
3232
UndefinedBehaviorSanitizer
3333
DataFlowSanitizer
3434
LeakSanitizer
35+
RealtimeSanitizer
3536
SanitizerCoverage
3637
SanitizerStats
3738
SanitizerSpecialCaseList

clang/include/clang/Basic/Sanitizers.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ SANITIZER("thread", Thread)
7979
// Numerical stability sanitizer.
8080
SANITIZER("numerical", NumericalStability)
8181

82+
// RealtimeSanitizer
83+
SANITIZER("realtime", Realtime)
84+
8285
// LeakSanitizer
8386
SANITIZER("leak", Leak)
8487

clang/include/clang/Driver/SanitizerArgs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class SanitizerArgs {
107107
bool needsNsanRt() const {
108108
return Sanitizers.has(SanitizerKind::NumericalStability);
109109
}
110+
bool needsRtsanRt() const { return Sanitizers.has(SanitizerKind::Realtime); }
110111

111112
bool hasMemTag() const {
112113
return hasMemtagHeap() || hasMemtagStack() || hasMemtagGlobals();

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
#include "llvm/Transforms/Instrumentation/MemorySanitizer.h"
7979
#include "llvm/Transforms/Instrumentation/NumericalStabilitySanitizer.h"
8080
#include "llvm/Transforms/Instrumentation/PGOInstrumentation.h"
81+
#include "llvm/Transforms/Instrumentation/RealtimeSanitizer.h"
8182
#include "llvm/Transforms/Instrumentation/SanitizerBinaryMetadata.h"
8283
#include "llvm/Transforms/Instrumentation/SanitizerCoverage.h"
8384
#include "llvm/Transforms/Instrumentation/ThreadSanitizer.h"
@@ -990,6 +991,13 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
990991
FPM.addPass(BoundsCheckingPass());
991992
});
992993

994+
if (LangOpts.Sanitize.has(SanitizerKind::Realtime))
995+
PB.registerScalarOptimizerLateEPCallback(
996+
[](FunctionPassManager &FPM, OptimizationLevel Level) {
997+
RealtimeSanitizerOptions Opts;
998+
FPM.addPass(RealtimeSanitizerPass(Opts));
999+
});
1000+
9931001
// Don't add sanitizers if we are here from ThinLTO PostLink. That already
9941002
// done on PreLink stage.
9951003
if (!IsThinLTOPostLink) {

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,13 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
845845
if (SanOpts.has(SanitizerKind::ShadowCallStack))
846846
Fn->addFnAttr(llvm::Attribute::ShadowCallStack);
847847

848+
if (SanOpts.has(SanitizerKind::Realtime))
849+
if (FD && FD->getASTContext().hasAnyFunctionEffects())
850+
for (const FunctionEffectWithCondition &Fe : FD->getFunctionEffects()) {
851+
if (Fe.Effect.kind() == FunctionEffect::Kind::NonBlocking)
852+
Fn->addFnAttr(llvm::Attribute::SanitizeRealtime);
853+
}
854+
848855
// Apply fuzzing attribute to the function.
849856
if (SanOpts.hasOneOf(SanitizerKind::Fuzzer | SanitizerKind::FuzzerNoLink))
850857
Fn->addFnAttr(llvm::Attribute::OptForFuzzing);

clang/lib/Driver/SanitizerArgs.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,15 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
558558
SanitizerKind::Leak | SanitizerKind::Thread |
559559
SanitizerKind::Memory | SanitizerKind::KernelAddress |
560560
SanitizerKind::Scudo | SanitizerKind::SafeStack),
561-
std::make_pair(SanitizerKind::MemTag,
562-
SanitizerKind::Address | SanitizerKind::KernelAddress |
563-
SanitizerKind::HWAddress |
564-
SanitizerKind::KernelHWAddress),
565-
std::make_pair(SanitizerKind::KCFI, SanitizerKind::Function)};
561+
std::make_pair(SanitizerKind::MemTag, SanitizerKind::Address |
562+
SanitizerKind::KernelAddress |
563+
SanitizerKind::HWAddress |
564+
SanitizerKind::KernelHWAddress),
565+
std::make_pair(SanitizerKind::KCFI, SanitizerKind::Function),
566+
std::make_pair(SanitizerKind::Realtime,
567+
SanitizerKind::Address | SanitizerKind::Thread |
568+
SanitizerKind::Undefined | SanitizerKind::Memory)};
569+
566570
// Enable toolchain specific default sanitizers if not explicitly disabled.
567571
SanitizerMask Default = TC.getDefaultSanitizers() & ~AllRemove;
568572

clang/lib/Driver/ToolChains/CommonArgs.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
14561456
if (!Args.hasArg(options::OPT_shared))
14571457
HelperStaticRuntimes.push_back("hwasan-preinit");
14581458
}
1459+
if (SanArgs.needsRtsanRt() && SanArgs.linkRuntimes())
1460+
SharedRuntimes.push_back("rtsan");
14591461
}
14601462

14611463
// The stats_client library is also statically linked into DSOs.
@@ -1481,6 +1483,10 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
14811483
StaticRuntimes.push_back("asan_cxx");
14821484
}
14831485

1486+
if (!SanArgs.needsSharedRt() && SanArgs.needsRtsanRt() &&
1487+
SanArgs.linkRuntimes())
1488+
StaticRuntimes.push_back("rtsan");
1489+
14841490
if (!SanArgs.needsSharedRt() && SanArgs.needsMemProfRt()) {
14851491
StaticRuntimes.push_back("memprof");
14861492
if (SanArgs.linkCXXRuntimes())

0 commit comments

Comments
 (0)