Dynamic library to catch run-time safety violations heavily inspired by RADSan
git submodule add -b main https://github.com/Tracktion/rtcheck rtcheck
To update down the road:
git submodule update --remote --merge rtcheck
add_subdirectory(rtcheck)
Include(FetchContent)
FetchContent_Declare(rtcheck
GIT_REPOSITORY https://github.com/Tracktion/rtcheck.git
GIT_TAG origin/main
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/rtcheck)
FetchContent_MakeAvailable(rtcheck)
target_link_libraries("YourProject" PRIVATE rtcheck)
Then include rtcheck in your source files:
#include <rtcheck.h>
Simply add an instance of rtc::realtime_context
at the start of the scope you want to check for real-time safety violations like this:
int main()
{
std::thread t ([]
{
rtc::realtime_context rc;
malloc (1024); // Allocating memory is a real-time safety violation!
});
t.join();
return 0;
}
The call to malloc in the above code will then be caught and logged as so:
Real-time violation: intercepted call to real-time unsafe function malloc in real-time context! Stack trace:
0 librt_check.dylib 0x000000010543f7d4 _ZN3rtc14get_stacktraceEv + 84
1 librt_check.dylib 0x000000010543f450 _ZN3rtc32log_function_if_realtime_contextEPKc + 300
2 librt_check.dylib 0x000000010543fe00 _Z44log_function_if_realtime_context_and_enabledN3rtc11check_flagsEPKc + 64
3 librt_check.dylib 0x000000010543fe30 wrap_malloc + 32
4 fail_malloc 0x0000000104fcff14 main + 32
5 dyld 0x00000001901760e0 start + 2360
There are two ways to disable checks. You can either disable all checks, which can be useful if you know you'll be calling a potentially unsafe function but in a safe way (e.g. an un-contented lock), or you want to log something:
{
rtc::non_realtime_context nrc;
std::cout << "need to get this message on the screen!";
}
Or you can selectively disable checks:
{
rtc::disable_checks_for_thread (check_flags::threads);
mutex.lock(); // I know this is uncontended, don't for get to unlock!
}
If you have some code which you know is non-real-time safe e.g. an unbounded distribution function or some other async call, you can opt-in to let rtcheck catch it by calling the following function:
void my_unsafe_function()
{
log_function_if_realtime_context (__func__);
}
This will then get logged if called whilst a rtc::realtime_context
is alive.
There are two currently supported error modes
- Exit with error code 1 (default)
- Log and continue
Exiting it useful for CI runs where you want the program to terminate in an obvious way (non-zero exit code) to fail the run. In normal use though, you may just want to log the violation and continue.
You can change between these globally using the following function:
/** Sets the global error more to determine the behaviour when a real-time
violation is detected.
*/
void set_error_mode (error_mode);
- Enable in scope
- Disable in scope
- Symbolicated stack-trace
- Run-time options for checks
- Opt-in for own code
- linux
- macOS (malloc unsupported)
- Add option to realtime_context constructor to disable checks for that scope
- Delay time
- Time
- sleep ✔
- nanosleep ✔
- usleep ✔
- Memory
- malloc ✔
- calloc ✔
- realloc ✔
- free ✔
- reallocf (macOS) ✔
- valloc ✔
- posix_memalign ✔
- mmap ✔
- munmap ✔
- Threads
- pthread_create ✔
- pthread_mutex_lock ✔
- pthread_mutex_unlock ✔
- pthread_join ✔
- pthread_cond_signal
- pthread_cond_broadcast
- pthread_cond_wait
- pthread_cond_timedwait
- pthread_rwlock_rdlock ✔
- pthread_rwlock_unlock ✔
- pthread_rwlock_wrlock ✔
- pthread_spin_lock (linux)
- Files
- open ✔
- openat
- close
- fopen
- fread
- fwrite
- fclose
- fcntl ✔
- creat
- puts
- fputs
- stat ✔
- stat64
- fstat
- fstat64
- IO
- socket
- send
- sendmsg
- sendto
- recv
- recvmsg
- recvfrom
- shutdown
- System calls
- syscall ✔
- schedule
- context_switch
- Failures
- Throwing exceptions
- Large std::function
- Atomic 4*ptr size
- Dynamic loading of a library
- Passes
- Atomic double
- Small std::function
- thread_local? (pass on linux, fail on macOS)
- Running on CI Linux
- Running on CI macOS
- Tests via CTest (can catch failues)
- cpack
- conan
- vcpkg