Skip to content

Commit f082d91

Browse files
calixtemantstellar
authored andcommitted
[profile] Don't crash when forking in several threads
Summary: When forking in several threads, the counters were written out in using the same global static variables (see GCDAProfiling.c): that leads to crashes. So when there is a fork, the counters are resetted in the child process and they will be dumped at exit using the interprocess file locking. When there is an exec, the counters are written out and in case of failures they're resetted. Reviewers: jfb, vsk, marco-c, serge-sans-paille Reviewed By: marco-c, serge-sans-paille Subscribers: llvm-commits, serge-sans-paille, dmajor, cfe-commits, hiraditya, dexonsmith, #sanitizers, marco-c, sylvestre.ledru Tags: #sanitizers, #clang, #llvm Differential Revision: https://reviews.llvm.org/D78477 (cherry picked from commit bec223a)
1 parent baeb500 commit f082d91

File tree

5 files changed

+204
-37
lines changed

5 files changed

+204
-37
lines changed

clang/lib/Driver/ToolChains/Darwin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,7 @@ void Darwin::addProfileRTLibs(const ArgList &Args,
11461146
addExportedSymbol(CmdArgs, "___gcov_flush");
11471147
addExportedSymbol(CmdArgs, "_flush_fn_list");
11481148
addExportedSymbol(CmdArgs, "_writeout_fn_list");
1149+
addExportedSymbol(CmdArgs, "_reset_fn_list");
11491150
} else {
11501151
addExportedSymbol(CmdArgs, "___llvm_profile_filename");
11511152
addExportedSymbol(CmdArgs, "___llvm_profile_raw_version");

compiler-rt/lib/profile/GCDAProfiling.c

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
#include <windows.h>
3333
#include "WindowsMMap.h"
3434
#else
35-
#include <sys/mman.h>
3635
#include <sys/file.h>
36+
#include <sys/mman.h>
37+
#include <sys/types.h>
38+
#include <unistd.h>
3739
#endif
3840

3941
#if defined(__FreeBSD__) && defined(__i386__)
@@ -119,6 +121,11 @@ struct fn_list writeout_fn_list;
119121
*/
120122
struct fn_list flush_fn_list;
121123

124+
/*
125+
* A list of reset functions, shared between all dynamic objects.
126+
*/
127+
struct fn_list reset_fn_list;
128+
122129
static void fn_list_insert(struct fn_list* list, fn_ptr fn) {
123130
struct fn_node* new_node = malloc(sizeof(struct fn_node));
124131
new_node->fn = fn;
@@ -634,7 +641,46 @@ void llvm_delete_flush_function_list(void) {
634641
}
635642

636643
COMPILER_RT_VISIBILITY
637-
void llvm_gcov_init(fn_ptr wfn, fn_ptr ffn) {
644+
void llvm_register_reset_function(fn_ptr fn) {
645+
fn_list_insert(&reset_fn_list, fn);
646+
}
647+
648+
COMPILER_RT_VISIBILITY
649+
void llvm_delete_reset_function_list(void) { fn_list_remove(&reset_fn_list); }
650+
651+
COMPILER_RT_VISIBILITY
652+
void llvm_reset_counters(void) {
653+
struct fn_node *curr = reset_fn_list.head;
654+
655+
while (curr) {
656+
if (curr->id == CURRENT_ID) {
657+
curr->fn();
658+
}
659+
curr = curr->next;
660+
}
661+
}
662+
663+
#if !defined(_WIN32)
664+
COMPILER_RT_VISIBILITY
665+
pid_t __gcov_fork() {
666+
pid_t parent_pid = getpid();
667+
pid_t pid = fork();
668+
669+
if (pid == 0) {
670+
pid_t child_pid = getpid();
671+
if (child_pid != parent_pid) {
672+
// The pid changed so we've a fork (one could have its own fork function)
673+
// Just reset the counters for this child process
674+
// threads.
675+
llvm_reset_counters();
676+
}
677+
}
678+
return pid;
679+
}
680+
#endif
681+
682+
COMPILER_RT_VISIBILITY
683+
void llvm_gcov_init(fn_ptr wfn, fn_ptr ffn, fn_ptr rfn) {
638684
static int atexit_ran = 0;
639685

640686
if (wfn)
@@ -643,10 +689,14 @@ void llvm_gcov_init(fn_ptr wfn, fn_ptr ffn) {
643689
if (ffn)
644690
llvm_register_flush_function(ffn);
645691

692+
if (rfn)
693+
llvm_register_reset_function(rfn);
694+
646695
if (atexit_ran == 0) {
647696
atexit_ran = 1;
648697

649698
/* Make sure we write out the data and delete the data structures. */
699+
atexit(llvm_delete_reset_function_list);
650700
atexit(llvm_delete_flush_function_list);
651701
atexit(llvm_delete_writeout_function_list);
652702
atexit(llvm_writeout_files);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <sys/types.h>
2+
#include <thread>
3+
#include <unistd.h>
4+
#include <vector>
5+
6+
template <typename T>
7+
void launcher(T func) {
8+
std::vector<std::thread> pool;
9+
10+
for (int i = 0; i < 10; i++) {
11+
pool.emplace_back(std::thread(func));
12+
}
13+
14+
for (auto &t : pool) {
15+
t.join();
16+
}
17+
}
18+
19+
void h() {}
20+
21+
void g() {
22+
fork();
23+
launcher<>(h);
24+
}
25+
26+
void f() {
27+
fork();
28+
launcher<>(g);
29+
}
30+
31+
int main() {
32+
launcher<>(f);
33+
34+
return 0;
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
UNSUPPORTED: windows
2+
3+
RUN: mkdir -p %t.d
4+
RUN: cd %t.d
5+
6+
RUN: %clangxx --coverage -lpthread -o %t %S/Inputs/instrprof-gcov-multithread_fork.cpp
7+
RUN: test -f instrprof-gcov-multithread_fork.gcno
8+
9+
RUN: rm -f instrprof-gcov-multithread_fork.gcda
10+
RUN: %run %t
11+
RUN: llvm-cov gcov instrprof-gcov-multithread_fork.gcda

llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ class GCOVProfiler {
115115
// list.
116116
Function *
117117
insertCounterWriteout(ArrayRef<std::pair<GlobalVariable *, MDNode *>>);
118-
Function *insertFlush(ArrayRef<std::pair<GlobalVariable *, MDNode *>>);
118+
Function *insertReset(ArrayRef<std::pair<GlobalVariable *, MDNode *>>);
119+
Function *insertFlush(Function *ResetF);
119120

120121
void AddFlushBeforeForkAndExec();
121122

@@ -630,35 +631,76 @@ static bool shouldKeepInEntry(BasicBlock::iterator It) {
630631
}
631632

632633
void GCOVProfiler::AddFlushBeforeForkAndExec() {
633-
SmallVector<Instruction *, 2> ForkAndExecs;
634+
SmallVector<CallInst *, 2> Forks;
635+
SmallVector<CallInst *, 2> Execs;
634636
for (auto &F : M->functions()) {
635637
auto *TLI = &GetTLI(F);
636638
for (auto &I : instructions(F)) {
637639
if (CallInst *CI = dyn_cast<CallInst>(&I)) {
638640
if (Function *Callee = CI->getCalledFunction()) {
639641
LibFunc LF;
640-
if (TLI->getLibFunc(*Callee, LF) &&
641-
(LF == LibFunc_fork || LF == LibFunc_execl ||
642-
LF == LibFunc_execle || LF == LibFunc_execlp ||
643-
LF == LibFunc_execv || LF == LibFunc_execvp ||
644-
LF == LibFunc_execve || LF == LibFunc_execvpe ||
645-
LF == LibFunc_execvP)) {
646-
ForkAndExecs.push_back(&I);
642+
if (TLI->getLibFunc(*Callee, LF)) {
643+
if (LF == LibFunc_fork) {
644+
#if !defined(_WIN32)
645+
Forks.push_back(CI);
646+
#endif
647+
} else if (LF == LibFunc_execl || LF == LibFunc_execle ||
648+
LF == LibFunc_execlp || LF == LibFunc_execv ||
649+
LF == LibFunc_execvp || LF == LibFunc_execve ||
650+
LF == LibFunc_execvpe || LF == LibFunc_execvP) {
651+
Execs.push_back(CI);
652+
}
647653
}
648654
}
649655
}
650656
}
651657
}
652658

653-
// We need to split the block after the fork/exec call
654-
// because else the counters for the lines after will be
655-
// the same as before the call.
656-
for (auto I : ForkAndExecs) {
657-
IRBuilder<> Builder(I);
659+
for (auto F : Forks) {
660+
IRBuilder<> Builder(F);
661+
BasicBlock *Parent = F->getParent();
662+
auto NextInst = ++F->getIterator();
663+
664+
// We've a fork so just reset the counters in the child process
665+
FunctionType *FTy = FunctionType::get(Builder.getInt32Ty(), {}, false);
666+
FunctionCallee GCOVFork = M->getOrInsertFunction("__gcov_fork", FTy);
667+
F->setCalledFunction(GCOVFork);
668+
669+
// We split just after the fork to have a counter for the lines after
670+
// Anyway there's a bug:
671+
// void foo() { fork(); }
672+
// void bar() { foo(); blah(); }
673+
// then "blah();" will be called 2 times but showed as 1
674+
// because "blah()" belongs to the same block as "foo();"
675+
Parent->splitBasicBlock(NextInst);
676+
677+
// back() is a br instruction with a debug location
678+
// equals to the one from NextAfterFork
679+
// So to avoid to have two debug locs on two blocks just change it
680+
DebugLoc Loc = F->getDebugLoc();
681+
Parent->back().setDebugLoc(Loc);
682+
}
683+
684+
for (auto E : Execs) {
685+
IRBuilder<> Builder(E);
686+
BasicBlock *Parent = E->getParent();
687+
auto NextInst = ++E->getIterator();
688+
689+
// Since the process is replaced by a new one we need to write out gcdas
690+
// No need to reset the counters since they'll be lost after the exec**
658691
FunctionType *FTy = FunctionType::get(Builder.getVoidTy(), {}, false);
659-
FunctionCallee GCOVFlush = M->getOrInsertFunction("__gcov_flush", FTy);
660-
Builder.CreateCall(GCOVFlush);
661-
I->getParent()->splitBasicBlock(I);
692+
FunctionCallee WriteoutF =
693+
M->getOrInsertFunction("llvm_writeout_files", FTy);
694+
Builder.CreateCall(WriteoutF);
695+
696+
DebugLoc Loc = E->getDebugLoc();
697+
Builder.SetInsertPoint(&*NextInst);
698+
// If the exec** fails we must reset the counters since they've been
699+
// dumped
700+
FunctionCallee ResetF = M->getOrInsertFunction("llvm_reset_counters", FTy);
701+
Builder.CreateCall(ResetF)->setDebugLoc(Loc);
702+
Parent->splitBasicBlock(NextInst);
703+
Parent->back().setDebugLoc(Loc);
662704
}
663705
}
664706

@@ -850,7 +892,8 @@ bool GCOVProfiler::emitProfileArcs() {
850892
}
851893

852894
Function *WriteoutF = insertCounterWriteout(CountersBySP);
853-
Function *FlushF = insertFlush(CountersBySP);
895+
Function *ResetF = insertReset(CountersBySP);
896+
Function *FlushF = insertFlush(ResetF);
854897

855898
// Create a small bit of code that registers the "__llvm_gcov_writeout" to
856899
// be executed at exit and the "__llvm_gcov_flush" function to be executed
@@ -868,16 +911,14 @@ bool GCOVProfiler::emitProfileArcs() {
868911
IRBuilder<> Builder(BB);
869912

870913
FTy = FunctionType::get(Type::getVoidTy(*Ctx), false);
871-
Type *Params[] = {
872-
PointerType::get(FTy, 0),
873-
PointerType::get(FTy, 0)
874-
};
914+
Type *Params[] = {PointerType::get(FTy, 0), PointerType::get(FTy, 0),
915+
PointerType::get(FTy, 0)};
875916
FTy = FunctionType::get(Builder.getVoidTy(), Params, false);
876917

877-
// Initialize the environment and register the local writeout and flush
878-
// functions.
918+
// Initialize the environment and register the local writeout, flush and
919+
// reset functions.
879920
FunctionCallee GCOVInit = M->getOrInsertFunction("llvm_gcov_init", FTy);
880-
Builder.CreateCall(GCOVInit, {WriteoutF, FlushF});
921+
Builder.CreateCall(GCOVInit, {WriteoutF, FlushF, ResetF});
881922
Builder.CreateRetVoid();
882923

883924
appendToGlobalCtors(*M, F, 0);
@@ -1190,8 +1231,43 @@ Function *GCOVProfiler::insertCounterWriteout(
11901231
return WriteoutF;
11911232
}
11921233

1193-
Function *GCOVProfiler::
1194-
insertFlush(ArrayRef<std::pair<GlobalVariable*, MDNode*> > CountersBySP) {
1234+
Function *GCOVProfiler::insertReset(
1235+
ArrayRef<std::pair<GlobalVariable *, MDNode *>> CountersBySP) {
1236+
FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx), false);
1237+
Function *ResetF = M->getFunction("__llvm_gcov_reset");
1238+
if (!ResetF)
1239+
ResetF = Function::Create(FTy, GlobalValue::InternalLinkage,
1240+
"__llvm_gcov_reset", M);
1241+
else
1242+
ResetF->setLinkage(GlobalValue::InternalLinkage);
1243+
ResetF->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
1244+
ResetF->addFnAttr(Attribute::NoInline);
1245+
if (Options.NoRedZone)
1246+
ResetF->addFnAttr(Attribute::NoRedZone);
1247+
1248+
BasicBlock *Entry = BasicBlock::Create(*Ctx, "entry", ResetF);
1249+
IRBuilder<> Builder(Entry);
1250+
1251+
// Zero out the counters.
1252+
for (const auto &I : CountersBySP) {
1253+
GlobalVariable *GV = I.first;
1254+
Constant *Null = Constant::getNullValue(GV->getValueType());
1255+
Builder.CreateStore(Null, GV);
1256+
}
1257+
1258+
Type *RetTy = ResetF->getReturnType();
1259+
if (RetTy->isVoidTy())
1260+
Builder.CreateRetVoid();
1261+
else if (RetTy->isIntegerTy())
1262+
// Used if __llvm_gcov_reset was implicitly declared.
1263+
Builder.CreateRet(ConstantInt::get(RetTy, 0));
1264+
else
1265+
report_fatal_error("invalid return type for __llvm_gcov_reset");
1266+
1267+
return ResetF;
1268+
}
1269+
1270+
Function *GCOVProfiler::insertFlush(Function *ResetF) {
11951271
FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx), false);
11961272
Function *FlushF = M->getFunction("__llvm_gcov_flush");
11971273
if (!FlushF)
@@ -1212,16 +1288,10 @@ insertFlush(ArrayRef<std::pair<GlobalVariable*, MDNode*> > CountersBySP) {
12121288

12131289
IRBuilder<> Builder(Entry);
12141290
Builder.CreateCall(WriteoutF, {});
1215-
1216-
// Zero out the counters.
1217-
for (const auto &I : CountersBySP) {
1218-
GlobalVariable *GV = I.first;
1219-
Constant *Null = Constant::getNullValue(GV->getValueType());
1220-
Builder.CreateStore(Null, GV);
1221-
}
1291+
Builder.CreateCall(ResetF, {});
12221292

12231293
Type *RetTy = FlushF->getReturnType();
1224-
if (RetTy == Type::getVoidTy(*Ctx))
1294+
if (RetTy->isVoidTy())
12251295
Builder.CreateRetVoid();
12261296
else if (RetTy->isIntegerTy())
12271297
// Used if __llvm_gcov_flush was implicitly declared.

0 commit comments

Comments
 (0)