RealtimeSanitizer

Introduction

RealtimeSanitizer (a.k.a. RTSan) is a real-time safety testing tool for C and C++ projects. RTSan can be used to detect real-time violations, i.e. calls to methods that are not safe for use in functions with deterministic runtime requirements. RTSan considers any function marked with the [[clang::nonblocking]] attribute to be a real-time function. If RTSan detects a call to malloc, free, pthread_mutex_lock, or anything else that could have a non-deterministic execution time in a function marked [[clang::nonblocking]] RTSan raises an error.

The runtime slowdown introduced by RealtimeSanitizer is negligible.

How to build

Build LLVM/Clang with CMake <https://llvm.org/docs/CMake.html> and enable the compiler-rt runtime. An example CMake configuration that will allow for the use/testing of RealtimeSanitizer:

$ cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" <path to source>/llvm

Usage

There are two requirements:

  1. The code must be compiled with the -fsanitize=realtime flag.

  2. Functions that are subject to real-time constraints must be marked with the [[clang::nonblocking]] attribute.

Typically, these attributes should be added onto the functions that are entry points for threads with real-time priority. These threads are subject to a fixed callback time, such as audio callback threads or rendering loops in video game code.

% cat example_realtime_violation.cpp
#include <vector>

void violation() [[clang::nonblocking]]{
  std::vector<float> v;
  v.resize(100);
}

int main() {
  violation();
  return 0;
}
# Compile and link
% clang++ -fsanitize=realtime -g example_realtime_violation.cpp

If a real-time safety violation is detected in a [[clang::nonblocking]] context, or any function invoked by that function, the program will exit with a non-zero exit code.

% clang++ -fsanitize=realtime -g example_realtime_violation.cpp
% ./a.out
Real-time violation: intercepted call to real-time unsafe function `malloc` in real-time context! Stack trace:
 #0 0x000102893034 in __rtsan::PrintStackTrace() rtsan_stack.cpp:45
 #1 0x000102892e64 in __rtsan::Context::ExpectNotRealtime(char const*) rtsan_context.cpp:78
 #2 0x00010289397c in malloc rtsan_interceptors.cpp:286
 #3 0x000195bd7bd0 in operator new(unsigned long)+0x1c (libc++abi.dylib:arm64+0x16bd0)
 #4 0x5c7f00010230f07c  (<unknown module>)
 #5 0x00010230f058 in std::__1::__libcpp_allocate[abi:ue170006](unsigned long, unsigned long) new:324
 #6 0x00010230effc in std::__1::allocator<float>::allocate[abi:ue170006](unsigned long) allocator.h:114
 ... snip ...
 #10 0x00010230e4bc in std::__1::vector<float, std::__1::allocator<float>>::__append(unsigned long) vector:1162
 #11 0x00010230dcdc in std::__1::vector<float, std::__1::allocator<float>>::resize(unsigned long) vector:1981
 #12 0x00010230dc28 in violation() main.cpp:5
 #13 0x00010230dd64 in main main.cpp:9
 #14 0x0001958960dc  (<unknown module>)
 #15 0x2f557ffffffffffc  (<unknown module>)

Disabling

In some circumstances, you may want to suppress error reporting in a specific scope.

In C++, this is achieved via __rtsan::ScopedDisabler. Within the scope where the ScopedDisabler object is instantiated, all sanitizer error reports are suppressed. This suppression applies to the current scope as well as all invoked functions, including any functions called transitively.

#include <sanitizer/rtsan_interface.h>

void process(const std::vector<float>& buffer) [[clang::nonblocking]] {
    {
        __rtsan::ScopedDisabler d;
        ...
    }
}

If RealtimeSanitizer is not enabled at compile time (i.e., the code is not compiled with the -fsanitize=realtime flag), the ScopedDisabler is compiled as a no-op.

In C, you can use the __rtsan_disable() and rtsan_enable() functions to manually disable and re-enable RealtimeSanitizer checks.

#include <sanitizer/rtsan_interface.h>

int process(const float* buffer) [[clang::nonblocking]]
{
    {
        __rtsan_disable();

        ...

        __rtsan_enable();
    }
}

Each call to __rtsan_disable() must be paired with a subsequent call to __rtsan_enable() to restore normal sanitizer functionality. If a corresponding rtsan_enable() call is not made, the behavior is undefined.

Compile-time sanitizer detection

Clang provides the pre-processor macro __has_feature which may be used to detect if RealtimeSanitizer is enabled at compile-time.

#if defined(__has_feature) && __has_feature(realtime_sanitizer)
...
#endif