Skip to content

[clang][rtsan] Introduce realtime sanitizer clang codegen and -fsanitize flag #100192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3527,6 +3527,11 @@ def NoSanitize : InheritableAttr {
bool hasCoverage() const {
return llvm::is_contained(sanitizers(), "coverage");
}

bool hasRealtime() const {
return llvm::is_contained(sanitizers(), "realtime");
}

}];
}

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/Sanitizers.def
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
#endif


// RealtimeSanitizer
SANITIZER("realtime", Realtime)

// AddressSanitizer
SANITIZER("address", Address)

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Driver/SanitizerArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class SanitizerArgs {
bool needsStableAbi() const { return StableABI; }

bool needsMemProfRt() const { return NeedsMemProfRt; }
bool needsRtsanRt() const { return Sanitizers.has(SanitizerKind::Realtime); }
bool needsAsanRt() const { return Sanitizers.has(SanitizerKind::Address); }
bool needsHwasanRt() const {
return Sanitizers.has(SanitizerKind::HWAddress);
Expand Down
51 changes: 51 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "clang/AST/StmtObjC.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/Sanitizers.h"
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CodeGen/CGFunctionInfo.h"
Expand All @@ -40,6 +41,9 @@
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/FPEnv.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/MDBuilder.h"
Expand Down Expand Up @@ -1410,6 +1414,35 @@ QualType CodeGenFunction::BuildFunctionArgList(GlobalDecl GD,
return ResTy;
}

void InsertCallBeforeInstruction(llvm::Function *Fn,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file require a test in llvm-project/clang/test/CodeGen/

Copy link
Collaborator

@vitalybuka vitalybuka Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe I missed some discussion.
these changes looks like better be done by llvm pass.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style wise: https://llvm.org/docs/CodingStandards.html#anonymous-namespaces Use static for internal linkage functions. LLVM code is more consistent in that most functions use functionName while clang is more inconsistent (a lot of FunctionName). When moved to LLVM, make sure that the case is changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe I missed some discussion. these changes looks like better be done by llvm pass.

Nope, this is just us not knowing what we don't know!

Any thoughts as to where this may live in that step? I'm unfamiliar with where it might be appropriate. Pointing to a general file or something to grep for would be greatly appreciated! Thanks Vitaly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add a bit of context here on some attempts we made implementing the insertion in codegen vs. in a pass.

In the very first implementation of rtsan, we did actually implement the insertion of __rtsan_realtime_enter() and __rtsan_realtime_exit() in a pass. We thought at the time that this insertion was only possible in a pass if the nonblocking attribute was added to the function in its IR. This method seemed to work fine until we activated optimisation level -O1, at which point we found that our attribute in the IR was disappearing, presumably from some sort of DCE (but we weren't sure), before we had a chance to inject our function calls. This, of course, broke our code injection. Someone on the LLVM Discord server pointed out that it was possible to do what we wanted to do entirely at CodeGen time, so we pivoted our approach towards doing so.

@vitalybuka and @MaskRay, what would you recommend here? Are the problems that we experienced originally with the pass implementation surmountable? We also questioned whether we should be injecting early (before optimisations) or late (after optimisations). Our first assumption was that early was better (less risk of inlining optimisations breaking our realtime depth tracking?), but we're very keen to learn from your expert opinions here.

llvm::Instruction &Instruction,
const char *FunctionName) {
llvm::LLVMContext &context = Fn->getContext();
llvm::FunctionType *FuncType =
llvm::FunctionType::get(llvm::Type::getVoidTy(context), false);
llvm::FunctionCallee Func =
Fn->getParent()->getOrInsertFunction(FunctionName, FuncType);
llvm::IRBuilder<> builder{&Instruction};
builder.CreateCall(Func, {});
}

void InsertCallAtFunctionEntryPoint(llvm::Function *Fn,
const char *InsertFnName) {

InsertCallBeforeInstruction(Fn, Fn->front().front(), InsertFnName);
}

void InsertCallAtAllFunctionExitPoints(llvm::Function *Fn,
const char *InsertFnName) {
for (auto &BB : *Fn) {
for (auto &I : BB) {
if (auto *RI = dyn_cast<llvm::ReturnInst>(&I)) {
InsertCallBeforeInstruction(Fn, I, InsertFnName);
}
}
}
}

void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
const CGFunctionInfo &FnInfo) {
assert(Fn && "generating code for null Function");
Expand Down Expand Up @@ -1578,9 +1611,27 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
}
}

if (SanOpts.has(SanitizerKind::Realtime)) {
for (const FunctionEffectWithCondition &Fe : FD->getFunctionEffects()) {
if (Fe.Effect.kind() == FunctionEffect::Kind::NonBlocking) {
InsertCallAtFunctionEntryPoint(Fn, "__rtsan_realtime_enter");
break;
}
}
}

// Emit the standard function epilogue.
FinishFunction(BodyRange.getEnd());

if (SanOpts.has(SanitizerKind::Realtime)) {
for (const FunctionEffectWithCondition &Fe : FD->getFunctionEffects()) {
if (Fe.Effect.kind() == FunctionEffect::Kind::NonBlocking) {
InsertCallAtAllFunctionExitPoints(Fn, "__rtsan_realtime_exit");
break;
}
}
}

// If we haven't marked the function nothrow through other means, do
// a quick pass now to see if we can.
if (!CurFn->doesNotThrow())
Expand Down
14 changes: 9 additions & 5 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,15 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
SanitizerKind::Leak | SanitizerKind::Thread |
SanitizerKind::Memory | SanitizerKind::KernelAddress |
SanitizerKind::Scudo | SanitizerKind::SafeStack),
std::make_pair(SanitizerKind::MemTag,
SanitizerKind::Address | SanitizerKind::KernelAddress |
SanitizerKind::HWAddress |
SanitizerKind::KernelHWAddress),
std::make_pair(SanitizerKind::KCFI, SanitizerKind::Function)};
std::make_pair(SanitizerKind::MemTag, SanitizerKind::Address |
SanitizerKind::KernelAddress |
SanitizerKind::HWAddress |
SanitizerKind::KernelHWAddress),
std::make_pair(SanitizerKind::KCFI, SanitizerKind::Function),
std::make_pair(SanitizerKind::Realtime,
SanitizerKind::Address | SanitizerKind::Thread |
SanitizerKind::Undefined | SanitizerKind::Memory)};

// Enable toolchain specific default sanitizers if not explicitly disabled.
SanitizerMask Default = TC.getDefaultSanitizers() & ~AllRemove;

Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Driver/ToolChains/CommonArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
if (!Args.hasArg(options::OPT_shared))
HelperStaticRuntimes.push_back("hwasan-preinit");
}
if (SanArgs.needsRtsanRt() && SanArgs.linkRuntimes())
SharedRuntimes.push_back("rtsan");
}

// The stats_client library is also statically linked into DSOs.
Expand All @@ -1455,6 +1457,11 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
StaticRuntimes.push_back("asan_cxx");
}

if (!SanArgs.needsSharedRt() && SanArgs.needsRtsanRt() &&
SanArgs.linkRuntimes()) {
StaticRuntimes.push_back("rtsan");
}

if (!SanArgs.needsSharedRt() && SanArgs.needsMemProfRt()) {
StaticRuntimes.push_back("memprof");
if (SanArgs.linkCXXRuntimes())
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/Driver/ToolChains/Darwin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,8 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
const char *sanitizer = nullptr;
if (Sanitize.needsUbsanRt()) {
sanitizer = "UndefinedBehaviorSanitizer";
} else if (Sanitize.needsRtsanRt()) {
sanitizer = "RealtimeSanitizer";
} else if (Sanitize.needsAsanRt()) {
sanitizer = "AddressSanitizer";
} else if (Sanitize.needsTsanRt()) {
Expand All @@ -1541,6 +1543,11 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
AddLinkSanitizerLibArgs(Args, CmdArgs, "asan");
}
}
if (Sanitize.needsRtsanRt()) {
assert(Sanitize.needsSharedRt() &&
"Static sanitizer runtimes not supported");
AddLinkSanitizerLibArgs(Args, CmdArgs, "rtsan");
}
if (Sanitize.needsLsanRt())
AddLinkSanitizerLibArgs(Args, CmdArgs, "lsan");
if (Sanitize.needsUbsanRt()) {
Expand Down Expand Up @@ -3477,6 +3484,7 @@ SanitizerMask Darwin::getSupportedSanitizers() const {
const bool IsAArch64 = getTriple().getArch() == llvm::Triple::aarch64;
SanitizerMask Res = ToolChain::getSupportedSanitizers();
Res |= SanitizerKind::Address;
Res |= SanitizerKind::Realtime;
Res |= SanitizerKind::PointerCompare;
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Leak;
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Driver/ToolChains/Linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ SanitizerMask Linux::getSupportedSanitizers() const {
const bool IsHexagon = getTriple().getArch() == llvm::Triple::hexagon;
SanitizerMask Res = ToolChain::getSupportedSanitizers();
Res |= SanitizerKind::Address;
Res |= SanitizerKind::Realtime;
Res |= SanitizerKind::PointerCompare;
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Fuzzer;
Expand Down
8 changes: 8 additions & 0 deletions clang/test/CodeGen/rtsan_insert_at_entry.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %clang -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s

// The first instruction after the function is entred should be a call to
// enable the realtime sanitizer stack

int foo(int *a) [[clang::nonblocking]] { return *a; }
// CHECK: define{{.*}}foo
// CHECK-NEXT: __rtsan_realtime_enter
9 changes: 9 additions & 0 deletions clang/test/CodeGen/rtsan_insert_at_exit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %clang -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s

// __rtsan_realtime_exit should be inserted at all function returns

int bar(int* x) [[clang::nonblocking]] {
return *x;
}
// CHECK: __rtsan_realtime_exit
// CHECK-NEXT: ret
48 changes: 48 additions & 0 deletions clang/test/Driver/fsanitize.c
Original file line number Diff line number Diff line change
Expand Up @@ -1038,3 +1038,51 @@
// RUN: not %clang --target=aarch64-none-elf -fsanitize=dataflow %s -### 2>&1 | FileCheck %s -check-prefix=UNSUPPORTED-BAREMETAL
// RUN: not %clang --target=arm-arm-none-eabi -fsanitize=shadow-call-stack %s -### 2>&1 | FileCheck %s -check-prefix=UNSUPPORTED-BAREMETAL
// UNSUPPORTED-BAREMETAL: unsupported option '-fsanitize={{.*}}' for target

// RUN: %clang --target=x86_64-apple-darwin -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-DARWIN
// CHECK-RTSAN-X86-64-DARWIN-NOT: unsupported option

// RUN: %clang --target=x86_64-apple-darwin -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-DARWIN
// CHECK-RTSAN-X86-64-DARWIN-NOT: unsupported option
// RUN: %clang --target=x86_64-apple-macos -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-MACOS
// CHECK-RTSAN-X86-64-MACOS-NOT: unsupported option
// RUN: %clang --target=arm64-apple-macos -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-ARM64-MACOS
// CHECK-RTSAN-ARM64-MACOS-NOT: unsupported option

// RUN: %clang --target=arm64-apple-ios-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-ARM64-IOSSIMULATOR
// CHECK-RTSAN-ARM64-IOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=arm64-apple-watchos-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-ARM64-WATCHOSSIMULATOR
// CHECK-RTSAN-ARM64-WATCHOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=arm64-apple-tvos-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-ARM64-TVOSSIMULATOR
// CHECK-RTSAN-ARM64-TVOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=x86_64-apple-ios-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-IOSSIMULATOR
// CHECK-RTSAN-X86-64-IOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=x86_64-apple-watchos-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-WATCHOSSIMULATOR
// CHECK-RTSAN-X86-64-WATCHOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=x86_64-apple-tvos-simulator -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-TVOSSIMULATOR
// CHECK-RTSAN-X86-64-TVOSSIMULATOR-NOT: unsupported option

// RUN: %clang --target=x86_64-linux-gnu -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-X86-64-LINUX
// CHECK-RTSAN-X86-64-LINUX-NOT: unsupported option

// RUN: not %clang --target=i386-pc-openbsd -fsanitize=realtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-RTSAN-OPENBSD
// CHECK-RTSAN-OPENBSD: unsupported option '-fsanitize=realtime' for target 'i386-pc-openbsd'

// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=realtime,thread %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-REALTIME-TSAN
// CHECK-REALTIME-TSAN: error: invalid argument '-fsanitize=realtime' not allowed with '-fsanitize=thread'

// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=realtime,address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-REALTIME-ASAN
// CHECK-REALTIME-ASAN: error: invalid argument '-fsanitize=realtime' not allowed with '-fsanitize=address'

// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=realtime,memory %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-REALTIME-MSAN
// CHECK-REALTIME-MSAN: error: invalid argument '-fsanitize=realtime' not allowed with '-fsanitize=memory'

// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=realtime,undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-REALTIME-UBSAN
// CHECK-REALTIME-UBSAN: error: invalid argument '-fsanitize=realtime' not allowed with '-fsanitize=undefined'


12 changes: 12 additions & 0 deletions clang/test/Driver/rtsan.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %clang -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s
// RUN: %clang -O1 -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s
// RUN: %clang -O2 -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s
// RUN: %clang -O3 -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -o - | FileCheck %s
// RUN: %clang -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -flto=thin -o - | FileCheck %s
// RUN: %clang -O2 -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -flto=thin -o - | FileCheck %s
// RUN: %clang -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -flto -o - | FileCheck %s
// RUN: %clang -O2 -target x86_64-unknown-linux -fsanitize=realtime %s -S -emit-llvm -flto -o - | FileCheck %s

int foo(int *a) [[clang::nonblocking]] { return *a; }
// CHECK: __rtsan_realtime_enter
// CHECK: __rtsan_realtime_exit
Loading