blob: 3a641c307f38719aed9b0a45b15dd507d5678d22 [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
/// Host ConcurrencySanitizer report generation.
///
//===----------------------------------------------------------------------===//
#include "csan.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_report_decorator.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_symbolizer.h"
using namespace __sanitizer;
namespace __sanitizer {
void BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context,
bool request_fast, u32 max_depth) {
uptr top = 0;
uptr bottom = 0;
GetThreadStackTopAndBottom(false, &top, &bottom);
bool fast = StackTrace::WillUseFastUnwind(request_fast);
Unwind(max_depth, pc, bp, context, top, bottom, fast);
}
} // namespace __sanitizer
namespace __csan {
namespace {
class Decorator : public SanitizerCommonDecorator {
public:
const char *Access() { return Blue(); }
const char *Location() { return Green(); }
};
// A pair of racing program counter values to deduplicate and check.
struct RacyPcs {
uptr pc[2];
bool operator==(const RacyPcs &Other) const {
if (pc[0] == Other.pc[0] && pc[1] == Other.pc[1])
return true;
return pc[0] == Other.pc[1] && pc[1] == Other.pc[0];
}
};
InternalMmapVectorNoCtor<RacyPcs> RacyPcsSeen;
struct PeerInfo {
uptr pc;
uptr size;
int access_type;
};
bool HandleRacyPcs(uptr PC, uptr PeerPC) {
const RacyPcs Racy = {{PC, PeerPC}};
for (uptr I = 0; I < RacyPcsSeen.size(); ++I) {
if (Racy == RacyPcsSeen[I]) {
VReport(2, "%s: suppressing report as doubled\n", SanitizerToolName);
return true;
}
}
RacyPcsSeen.push_back(Racy);
return false;
}
const char *AccessKind(int Type) {
const bool Write = Type & CSAN_ACCESS_WRITE;
const bool Atomic = Type & CSAN_ACCESS_ATOMIC;
const bool Compound = Type & CSAN_ACCESS_COMPOUND;
if (Compound && Write)
return Atomic ? "read-write (atomic)" : "read-write";
if (Write)
return Atomic ? "write (atomic)" : "write";
return Atomic ? "read (atomic)" : "read";
}
const char *MemOpDesc(bool First, int Type) {
const bool Write = Type & CSAN_ACCESS_WRITE;
const bool Atomic = Type & CSAN_ACCESS_ATOMIC;
const bool Compound = Type & CSAN_ACCESS_COMPOUND;
if (Compound && Write)
return Atomic ? (First ? "Read-write (atomic)"
: "Previous read-write (atomic)")
: (First ? "Read-write" : "Previous read-write");
if (Write)
return Atomic ? (First ? "Write (atomic)" : "Previous write (atomic)")
: (First ? "Write" : "Previous write");
return Atomic ? (First ? "Read (atomic)" : "Previous read (atomic)")
: (First ? "Read" : "Previous read");
}
void CaptureStack(uptr PC, uptr BP, uptr *Out, u32 *N) {
UNINITIALIZED BufferedStackTrace Stack;
Stack.Unwind(PC, BP, nullptr, common_flags()->fast_unwind_on_fatal,
kMaxStackFrames);
*N = Stack.size > kMaxStackFrames ? kMaxStackFrames : Stack.size;
if (*N)
internal_memcpy(Out, Stack.trace, *N * sizeof(uptr));
}
SymbolizedStack *SymbolizeFrame(uptr PC) {
return Symbolizer::GetOrInit()->SymbolizePC(
StackTrace::GetPreviousInstructionPc(PC));
}
u32 SkipRuntimeFrames(const uptr *PCs, u32 N) {
for (u32 I = 0; I < N; ++I) {
SymbolizedStack *Frames = SymbolizeFrame(PCs[I]);
const bool User = Frames && SkipInternalFrames(Frames);
if (Frames)
Frames->ClearAll();
if (User)
return I;
}
return 0;
}
void CopyFuncName(uptr PC, InternalScopedString *Out) {
SymbolizedStack *Frames = SymbolizeFrame(PC);
const SymbolizedStack *User = Frames ? SkipInternalFrames(Frames) : nullptr;
if (!User)
User = Frames;
if (User && User->info.function && User->info.function[0])
Out->Append(User->info.function);
else
Out->AppendF("%p", (void *)PC);
if (Frames)
Frames->ClearAll();
}
void PrintFrames(const uptr *PCs, u32 N) {
if (!N) {
Printf(" [failed to restore the stack]\n\n");
return;
}
const u32 Skip = SkipRuntimeFrames(PCs, N);
StackTrace Trace(PCs + Skip, N - Skip);
Trace.Print();
}
void PrintLocation(uptr Addr) {
DataInfo Loc;
if (!Symbolizer::GetOrInit()->SymbolizeData(Addr, &Loc) || !Loc.name ||
!Loc.name[0] || Loc.name[0] == '?') {
Loc.Clear();
return;
}
Decorator D;
Printf("%s", D.Location());
if (Loc.size)
Printf(" Location is global '%s' of size %zu at %p\n", Loc.name, Loc.size,
(void *)Addr);
else
Printf(" Location is global '%s' at %p\n", Loc.name, (void *)Addr);
Printf("%s", D.Default());
Loc.Clear();
}
void PrintHexValue(u64 V, uptr Size) {
switch (Size) {
case 1:
Printf("0x%02llx", (unsigned long long)V);
break;
case 2:
Printf("0x%04llx", (unsigned long long)V);
break;
case 4:
Printf("0x%08llx", (unsigned long long)V);
break;
default:
Printf("0x%016llx", (unsigned long long)V);
break;
}
}
void PrintValueChange(uptr Size, u64 Old, u64 New) {
if (Size == 0 || Size > 8 || Old == New)
return;
Printf(" value changed: ");
PrintHexValue(Old, Size);
Printf(" -> ");
PrintHexValue(New, Size);
Printf("\n");
}
void PrintReport(const AccessInfo &AI, const PeerInfo *Other, u64 Old,
u64 New) {
if (HandleRacyPcs(AI.pc, Other ? Other->pc : 0))
return;
UNINITIALIZED uptr ThisStack[kMaxStackFrames];
u32 ThisN = 0;
CaptureStack(AI.pc, AI.bp, ThisStack, &ThisN);
const u32 ThisSkip = SkipRuntimeFrames(ThisStack, ThisN);
const uptr ThisFrame = ThisN ? ThisStack[ThisSkip] : AI.pc;
const uptr OtherFrame = Other ? Other->pc : 0;
RecordDataRace();
Decorator D;
InternalScopedString ThisFn;
CopyFuncName(ThisFrame, &ThisFn);
Printf("==================\n");
Printf("%s", D.Warning());
if (Other) {
InternalScopedString OtherFn;
CopyFuncName(OtherFrame, &OtherFn);
const int Cmp = internal_strcmp(OtherFn.data(), ThisFn.data());
Printf("WARNING: ConcurrencySanitizer: data race in %s / %s\n",
Cmp < 0 ? OtherFn.data() : ThisFn.data(),
Cmp < 0 ? ThisFn.data() : OtherFn.data());
} else {
Printf("WARNING: ConcurrencySanitizer: data race of unknown origin in %s\n",
ThisFn.data());
}
Printf("%s", D.Default());
Printf("%s", D.Access());
if (Other) {
Printf(" %s of size %zu at %p by thread %u:\n",
MemOpDesc(true, AI.access_type), AI.size, AI.ptr, AI.tid);
Printf("%s", D.Default());
PrintFrames(ThisStack, ThisN);
Printf("%s", D.Access());
Printf(" %s of size %zu at %p:\n", MemOpDesc(false, Other->access_type),
Other->size, AI.ptr);
Printf("%s", D.Default());
PrintFrames(&Other->pc, 1);
} else {
Printf(
" race at unknown origin, with %s of size %zu at %p by thread %u:\n",
AccessKind(AI.access_type), AI.size, AI.ptr, AI.tid);
Printf("%s", D.Default());
PrintFrames(ThisStack, ThisN);
}
PrintValueChange(AI.size, Old, New);
PrintLocation((uptr)AI.ptr);
if (ThisN) {
StackTrace Summary(ThisStack + ThisSkip, ThisN - ThisSkip);
ReportErrorSummary(Other ? "data race" : "data race of unknown origin",
&Summary);
}
Printf("==================\n");
if (flags()->halt_on_error)
Die();
}
} // namespace
void ReportKnownOrigin(const AccessInfo &AI, ValueChange VC, uptr PeerPC,
int PeerAccess, uptr PeerSize, u64 Old, u64 New) {
ScopedErrorReportLock L;
if (VC == kValueChangeFalse)
return;
const PeerInfo Peer = {PeerPC, PeerSize, PeerAccess};
PrintReport(AI, &Peer, Old, New);
}
void ReportUnknownOrigin(const AccessInfo &AI, u64 Old, u64 New) {
ScopedErrorReportLock L;
PrintReport(AI, nullptr, Old, New);
}
} // namespace __csan