blob: 31d6d3032ebe25930828fce1c21e43b17bcd23ba [file] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Watchpoint-based host data race detector inspired by KCSAN. The host and GPU
/// runtimes share a configurable packed-watchpoint implementation.
///
//===----------------------------------------------------------------------===//
#include "csan.h"
#include "csan_watch.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_flag_parser.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_symbolizer.h"
using namespace __sanitizer;
#define INTERFACE extern "C" SANITIZER_INTERFACE_ATTRIBUTE
static constexpr u32 kHostWatchpointEntries = 64;
static constexpr u32 kHostCheckAdjacent = 1;
static constexpr uptr kHostSlotRange = 4096;
static constexpr uptr kHostMaxAccessSize =
kHostSlotRange * (1 + kHostCheckAdjacent);
static_assert(__atomic_always_lock_free(sizeof(u64), nullptr),
"host watchpoints must be lock-free");
using HostWatchpointTable =
__csan::WatchpointTable<kHostMaxAccessSize, kHostCheckAdjacent>;
static u64 HostWatchpoints[kHostWatchpointEntries +
HostWatchpointTable::OverflowEntries];
static_assert(HostWatchpointTable::AddressBits == 48,
"host watchpoints require 48-bit pointers");
static HostWatchpointTable GetHostWatchpoints() {
return HostWatchpointTable(HostWatchpoints);
}
static u32 HostWatchpointSlot(uptr Address) {
return Address / kHostSlotRange % kHostWatchpointEntries;
}
namespace __csan {
Flags flags_data;
static void Initialize();
static atomic_uint64_t NumDataRaces;
static THREADLOCAL u32 DisableCount;
static THREADLOCAL s32 Skip;
static THREADLOCAL u32 RandState;
namespace {
struct ScopedDisable {
ScopedDisable() { ++DisableCount; }
~ScopedDisable() { --DisableCount; }
};
} // namespace
void RecordDataRace() {
atomic_fetch_add(&NumDataRaces, 1, memory_order_relaxed);
}
void Flags::SetDefaults() {
#define CSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
#include "csan_flags.inc"
#undef CSAN_FLAG
}
static void RegisterCsanFlags(FlagParser *Parser, Flags *F) {
#define CSAN_FLAG(Type, Name, DefaultValue, Description) \
RegisterFlag(Parser, #Name, Description, &F->Name);
#include "csan_flags.inc"
#undef CSAN_FLAG
}
void InitializeFlags() {
SetCommonFlagsDefaults();
{
CommonFlags CF;
CF.CopyFrom(*common_flags());
CF.external_symbolizer_path = GetEnv("CSAN_SYMBOLIZER_PATH");
OverrideCommonFlags(CF);
}
flags()->SetDefaults();
FlagParser Parser;
RegisterCommonFlags(&Parser);
RegisterCsanFlags(&Parser, flags());
Parser.ParseString(__csan_default_options());
Parser.ParseStringFromEnv("CSAN_OPTIONS");
InitializeCommonFlags();
if (Verbosity())
ReportUnrecognizedFlags();
if (common_flags()->help)
Parser.PrintFlagDescriptions();
}
static u32 Random(u32 EpRo) {
if (EpRo <= 1)
return 0;
u32 State = RandState;
if (!State)
State = (u32)__builtin_readcyclecounter() | 1u;
State = 1664525u * State + 1013904223u;
RandState = State;
return State % EpRo;
}
static void ResetSkip() {
s32 Count = flags()->skip_watch;
if (Count < 0)
Count = 0;
if (Count)
Count -= (s32)Random((u32)Count);
Skip = Count;
}
static bool ShouldWatch(int Type) {
if (Type & CSAN_ACCESS_ATOMIC)
return false;
if (--Skip >= 0)
return false;
return true;
}
static void DelayAccess(int Type) {
s32 Delay = flags()->udelay;
if (Delay < 0)
Delay = 0;
if (Delay) {
u32 Skew = (Type & CSAN_ACCESS_COMPOUND) ? 1u : 0u;
u32 Span = (u32)Delay >> Skew;
if (!Span)
Span = (u32)Delay;
Delay -= (s32)Random(Span);
}
if (Delay)
internal_usleep((u64)Delay);
}
static u64 ReadRange(const volatile u8 *Bytes, uptr Size) {
u64 Sum = 0xcbf29ce484222325ull;
uptr I = 0;
for (; I < Size && ((uptr)(Bytes + I) & 7u); ++I)
Sum = (Sum ^ Bytes[I]) * 0x100000001b3ull;
for (; I + 8 <= Size; I += 8)
Sum = (Sum ^ *(const volatile u64 *)(Bytes + I)) * 0x100000001b3ull;
for (; I < Size; ++I)
Sum = (Sum ^ Bytes[I]) * 0x100000001b3ull;
return Sum;
}
static u64 ReadInstrumented(const volatile void *Ptr, uptr Size) {
switch (Size) {
case 1:
return *(const volatile u8 *)Ptr;
case 2:
return *(const volatile uu16 *)Ptr;
case 4:
return *(const volatile uu32 *)Ptr;
case 8:
return *(const volatile uu64 *)Ptr;
default:
return ReadRange((const volatile u8 *)Ptr, Size);
}
}
static AccessInfo MakeAccessInfo(const volatile void *Ptr, uptr Size, int Type,
uptr PC, uptr BP) {
AccessInfo AI;
AI.ptr = Ptr;
AI.size = Size;
AI.access_type = Type;
AI.tid = (u32)GetTid();
AI.pc = PC;
AI.bp = BP;
return AI;
}
NOINLINE static void FoundWatchpoint(const volatile void *Ptr, uptr Size,
int Type, uptr PC, uptr, u64 *WP,
u64 Encoded) {
ScopedDisable Disable;
GetHostWatchpoints().TryConsume(WP, Encoded, PC,
(Type & CSAN_ACCESS_WRITE) != 0, Size);
}
NOINLINE static void SetupWatchpoint(const volatile void *Ptr, uptr Size,
int Type, uptr PC, uptr BP) {
ScopedDisable Disable;
ResetSkip();
if ((uptr)Ptr < GetPageSizeCached())
return;
u64 *WP = GetHostWatchpoints().Insert((uptr)Ptr, Size,
(Type & CSAN_ACCESS_WRITE) != 0,
HostWatchpointSlot((uptr)Ptr));
if (!WP)
return;
const u64 Old = ReadInstrumented(Ptr, Size);
DelayAccess(Type);
const u64 New = ReadInstrumented(Ptr, Size);
ValueChange VC = Old != New ? kValueChangeTrue : kValueChangeMaybe;
const AccessInfo AI = MakeAccessInfo(Ptr, Size, Type, PC, BP);
void *Peer;
int PeerAccess;
u32 PeerSize;
if (!GetHostWatchpoints().Consume(WP, Peer, PeerAccess, PeerSize)) {
ReportKnownOrigin(AI, VC, (uptr)Peer, PeerAccess, PeerSize, Old, New);
} else if (VC == kValueChangeTrue) {
ReportUnknownOrigin(AI, Old, New);
}
GetHostWatchpoints().Remove(WP);
}
ALWAYS_INLINE static void CheckAccess(const volatile void *Ptr, uptr Size,
int Type, uptr PC, uptr BP) {
if (UNLIKELY(!Ptr || !Size))
return;
if (Size > kHostMaxAccessSize)
Size = kHostMaxAccessSize;
if (UNLIKELY(uptr(Ptr) & ~HostWatchpointTable::AddressMask))
return;
Initialize();
if (UNLIKELY(DisableCount))
return;
u64 Encoded;
u64 *WP =
GetHostWatchpoints().Find((uptr)Ptr, Size, !(Type & CSAN_ACCESS_WRITE),
HostWatchpointSlot((uptr)Ptr), Encoded);
if (UNLIKELY(WP != nullptr))
FoundWatchpoint(Ptr, Size, Type, PC, BP, WP, Encoded);
else if (UNLIKELY(ShouldWatch(Type)))
SetupWatchpoint(Ptr, Size, Type, PC, BP);
}
static StaticSpinMutex InitMutex;
static atomic_uint8_t Initialized;
void Initialize() {
if (LIKELY(atomic_load(&Initialized, memory_order_acquire)))
return;
SpinMutexLock L(&InitMutex);
if (atomic_load(&Initialized, memory_order_relaxed))
return;
SanitizerToolName = "ConcurrencySanitizer";
CacheBinaryName();
InitializeFlags();
atomic_store(&Initialized, 1, memory_order_release);
Symbolizer::LateInitialize();
}
} // namespace __csan
SANITIZER_INTERFACE_WEAK_DEF(const char *, __csan_default_options, void) {
return "";
}
INTERFACE u64 __csan_get_num_data_races() {
return atomic_load(&__csan::NumDataRaces, memory_order_relaxed);
}
INTERFACE void __csan_init() { __csan::Initialize(); }
INTERFACE void __csan_func_entry(void *) {}
INTERFACE void __csan_func_exit() {}
INTERFACE void __csan_ignore_thread_begin() { ++__csan::DisableCount; }
INTERFACE void __csan_ignore_thread_end() {
if (__csan::DisableCount)
--__csan::DisableCount;
}
static int AccessFlags(int Flags, bool IsWrite) {
return Flags | (IsWrite ? CSAN_ACCESS_WRITE : 0);
}
#define CSAN_PROBE(name, N, IsWrite) \
INTERFACE void name(void *Addr, int Flags) { \
GET_CALLER_PC_BP; \
__csan::CheckAccess(Addr, N, AccessFlags(Flags, IsWrite), pc, bp); \
}
#define CSAN_ACCESS(N) \
CSAN_PROBE(__csan_read##N, N, false) \
CSAN_PROBE(__csan_unaligned_read##N, N, false) \
CSAN_PROBE(__csan_write##N, N, true) \
CSAN_PROBE(__csan_unaligned_write##N, N, true) \
CSAN_PROBE(__csan_read_write##N, N, true) \
CSAN_PROBE(__csan_unaligned_read_write##N, N, true)
CSAN_ACCESS(1)
CSAN_ACCESS(2)
CSAN_ACCESS(4)
CSAN_ACCESS(8)
CSAN_ACCESS(16)
INTERFACE void __csan_read_range(void *Addr, uptr Size, int Flags) {
GET_CALLER_PC_BP;
__csan::CheckAccess(Addr, Size, AccessFlags(Flags, false), pc, bp);
}
INTERFACE void __csan_write_range(void *Addr, uptr Size, int Flags) {
GET_CALLER_PC_BP;
__csan::CheckAccess(Addr, Size, AccessFlags(Flags, true), pc, bp);
}
// TODO: Handle thread reordering checks like KCSAN.
INTERFACE void __csan_atomic_thread_fence(int) {}
INTERFACE void __csan_atomic_signal_fence(int) {}