Skip to content

Commit 47bdea3

Browse files
committed
[ThreadSanitizer] Add fallback DebugLocation for instrumentation calls
When building with debug info enabled, some load/store instructions do not have a DebugLocation attached. When using the default IRBuilder, it attempts to copy the DebugLocation from the insertion-point instruction. When there's no DebugLocation, no attempt is made to add one. This is problematic for inserted calls, where the enclosing function has debug info but the call ends up without a DebugLocation in e.g. LTO builds that verify that both the enclosing function and calls to inlinable functions have debug info attached. This issue was noticed in Linux kernel KCSAN builds with LTO and debug info enabled: | ... | inlinable function call in a function with debug info must have a !dbg location | call void @__tsan_read8(i8* %432) | ... To fix, ensure that all calls to the runtime have a DebugLocation attached, where the possibility exists that the insertion-point might not have any DebugLocation attached to it. Reviewed By: nickdesaulniers Differential Revision: https://reviews.llvm.org/D124937
1 parent 40c1372 commit 47bdea3

File tree

2 files changed

+77
-10
lines changed

2 files changed

+77
-10
lines changed

llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ struct ThreadSanitizer {
133133
};
134134

135135
void initialize(Module &M);
136-
bool instrumentLoadOrStore(const InstructionInfo &II, const DataLayout &DL);
137-
bool instrumentAtomic(Instruction *I, const DataLayout &DL);
136+
bool instrumentLoadOrStore(const InstructionInfo &II, const DataLayout &DL,
137+
const Function &F);
138+
bool instrumentAtomic(Instruction *I, const DataLayout &DL,
139+
const Function &F);
138140
bool instrumentMemIntrinsic(Instruction *I);
139141
void chooseInstructionsToInstrument(SmallVectorImpl<Instruction *> &Local,
140142
SmallVectorImpl<InstructionInfo> &All,
@@ -181,6 +183,27 @@ void insertModuleCtor(Module &M) {
181183
[&](Function *Ctor, FunctionCallee) { appendToGlobalCtors(M, Ctor, 0); });
182184
}
183185

186+
// Use to ensure the inserted instrumentation has a DebugLocation; if none is
187+
// attached to the source instruction, try to use a DILocation with offset 0
188+
// scoped to surrounding function (if it has a DebugLocation).
189+
//
190+
// Some non-call instructions may be missing debug info, but when inserting
191+
// instrumentation calls, some builds (e.g. LTO) want calls to have debug info
192+
// if the enclosing function does.
193+
struct InstrumentationIRBuilder : IRBuilder<> {
194+
static void ensureDebugInfo(IRBuilder<> &IRB, const Function &F) {
195+
if (IRB.getCurrentDebugLocation())
196+
return;
197+
if (DISubprogram *SP = F.getSubprogram())
198+
IRB.SetCurrentDebugLocation(DILocation::get(SP->getContext(), 0, 0, SP));
199+
}
200+
201+
explicit InstrumentationIRBuilder(Instruction *I, const Function &F)
202+
: IRBuilder<>(I) {
203+
ensureDebugInfo(*this, F);
204+
}
205+
};
206+
184207
} // namespace
185208

186209
PreservedAnalyses ThreadSanitizerPass::run(Function &F,
@@ -491,10 +514,11 @@ static bool isTsanAtomic(const Instruction *I) {
491514
}
492515

493516
void ThreadSanitizer::InsertRuntimeIgnores(Function &F) {
494-
IRBuilder<> IRB(F.getEntryBlock().getFirstNonPHI());
517+
InstrumentationIRBuilder IRB(F.getEntryBlock().getFirstNonPHI(), F);
495518
IRB.CreateCall(TsanIgnoreBegin);
496519
EscapeEnumerator EE(F, "tsan_ignore_cleanup", ClHandleCxxExceptions);
497520
while (IRBuilder<> *AtExit = EE.Next()) {
521+
InstrumentationIRBuilder::ensureDebugInfo(*AtExit, F);
498522
AtExit->CreateCall(TsanIgnoreEnd);
499523
}
500524
}
@@ -554,14 +578,14 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
554578
// Instrument memory accesses only if we want to report bugs in the function.
555579
if (ClInstrumentMemoryAccesses && SanitizeFunction)
556580
for (const auto &II : AllLoadsAndStores) {
557-
Res |= instrumentLoadOrStore(II, DL);
581+
Res |= instrumentLoadOrStore(II, DL, F);
558582
}
559583

560584
// Instrument atomic memory accesses in any case (they can be used to
561585
// implement synchronization).
562586
if (ClInstrumentAtomics)
563587
for (auto Inst : AtomicAccesses) {
564-
Res |= instrumentAtomic(Inst, DL);
588+
Res |= instrumentAtomic(Inst, DL, F);
565589
}
566590

567591
if (ClInstrumentMemIntrinsics && SanitizeFunction)
@@ -577,14 +601,15 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
577601

578602
// Instrument function entry/exit points if there were instrumented accesses.
579603
if ((Res || HasCalls) && ClInstrumentFuncEntryExit) {
580-
IRBuilder<> IRB(F.getEntryBlock().getFirstNonPHI());
604+
InstrumentationIRBuilder IRB(F.getEntryBlock().getFirstNonPHI(), F);
581605
Value *ReturnAddress = IRB.CreateCall(
582606
Intrinsic::getDeclaration(F.getParent(), Intrinsic::returnaddress),
583607
IRB.getInt32(0));
584608
IRB.CreateCall(TsanFuncEntry, ReturnAddress);
585609

586610
EscapeEnumerator EE(F, "tsan_cleanup", ClHandleCxxExceptions);
587611
while (IRBuilder<> *AtExit = EE.Next()) {
612+
InstrumentationIRBuilder::ensureDebugInfo(*AtExit, F);
588613
AtExit->CreateCall(TsanFuncExit, {});
589614
}
590615
Res = true;
@@ -593,8 +618,9 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
593618
}
594619

595620
bool ThreadSanitizer::instrumentLoadOrStore(const InstructionInfo &II,
596-
const DataLayout &DL) {
597-
IRBuilder<> IRB(II.Inst);
621+
const DataLayout &DL,
622+
const Function &F) {
623+
InstrumentationIRBuilder IRB(II.Inst, F);
598624
const bool IsWrite = isa<StoreInst>(*II.Inst);
599625
Value *Addr = IsWrite ? cast<StoreInst>(II.Inst)->getPointerOperand()
600626
: cast<LoadInst>(II.Inst)->getPointerOperand();
@@ -722,8 +748,9 @@ bool ThreadSanitizer::instrumentMemIntrinsic(Instruction *I) {
722748
// The following page contains more background information:
723749
// http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/
724750

725-
bool ThreadSanitizer::instrumentAtomic(Instruction *I, const DataLayout &DL) {
726-
IRBuilder<> IRB(I);
751+
bool ThreadSanitizer::instrumentAtomic(Instruction *I, const DataLayout &DL,
752+
const Function &F) {
753+
InstrumentationIRBuilder IRB(I, F);
727754
if (LoadInst *LI = dyn_cast<LoadInst>(I)) {
728755
Value *Addr = LI->getPointerOperand();
729756
Type *OrigTy = LI->getType();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
; RUN: opt < %s -passes=tsan -S | FileCheck %s
2+
3+
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
4+
5+
define i32 @with_dbg(i32* %a) sanitize_thread !dbg !3 {
6+
entry:
7+
%tmp1 = load i32, i32* %a, align 4
8+
ret i32 %tmp1
9+
}
10+
; CHECK-LABEL: @with_dbg
11+
; CHECK-NEXT: entry:
12+
; CHECK: call void @__tsan_func_entry(i8* %0), !dbg [[DBG:![0-9]+]]
13+
; CHECK: call void @__tsan_read4(i8* %1), !dbg [[DBG]]
14+
; CHECK: call void @__tsan_func_exit(), !dbg [[DBG]]
15+
16+
define i32 @without_dbg(i32* %a) sanitize_thread {
17+
entry:
18+
%tmp1 = load i32, i32* %a, align 4
19+
ret i32 %tmp1
20+
}
21+
; CHECK-LABEL: @without_dbg
22+
; CHECK-NEXT: entry:
23+
; CHECK-NOT: call void @__tsan_func_entry(i8* %0), !dbg
24+
; CHECK-NOT: call void @__tsan_read4(i8* %1), !dbg
25+
; CHECK-NOT: call void @__tsan_func_exit(), !dbg
26+
; CHECK: call void @__tsan_func_entry(i8* %0)
27+
; CHECK: call void @__tsan_read4(i8* %1)
28+
; CHECK: call void @__tsan_func_exit()
29+
30+
!llvm.dbg.cu = !{!0}
31+
!llvm.module.flags = !{!2}
32+
33+
!0 = distinct !DICompileUnit(language: DW_LANG_C89, file: !1, producer: "", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
34+
!1 = !DIFile(filename: "foo.c", directory: "")
35+
!2 = !{i32 2, !"Debug Info Version", i32 3}
36+
!3 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 190, type: !4, scopeLine: 192, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0)
37+
!4 = !DISubroutineType(types: !5)
38+
!5 = !{}
39+
40+
; CHECK: [[DBG]] = !DILocation(line: 0, scope: !3)

0 commit comments

Comments
 (0)