Skip to content

Commit bec223a

Browse files
committed
[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
1 parent ddfe588 commit bec223a

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
@@ -1173,6 +1173,7 @@ void Darwin::addProfileRTLibs(const ArgList &Args,
11731173
addExportedSymbol(CmdArgs, "___gcov_flush");
11741174
addExportedSymbol(CmdArgs, "_flush_fn_list");
11751175
addExportedSymbol(CmdArgs, "_writeout_fn_list");
1176+
addExportedSymbol(CmdArgs, "_reset_fn_list");
11761177
} else {
11771178
addExportedSymbol(CmdArgs, "___llvm_profile_filename");
11781179
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;
@@ -643,7 +650,46 @@ void llvm_delete_flush_function_list(void) {
643650
}
644651

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

649695
if (wfn)
@@ -652,10 +698,14 @@ void llvm_gcov_init(fn_ptr wfn, fn_ptr ffn) {
652698
if (ffn)
653699
llvm_register_flush_function(ffn);
654700

701+
if (rfn)
702+
llvm_register_reset_function(rfn);
703+
655704
if (atexit_ran == 0) {
656705
atexit_ran = 1;
657706

658707
/* Make sure we write out the data and delete the data structures. */
708+
atexit(llvm_delete_reset_function_list);
659709
atexit(llvm_delete_flush_function_list);
660710
atexit(llvm_delete_writeout_function_list);
661711
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

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

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

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

@@ -851,7 +893,8 @@ bool GCOVProfiler::emitProfileArcs() {
851893
}
852894

853895
Function *WriteoutF = insertCounterWriteout(CountersBySP);
854-
Function *FlushF = insertFlush(CountersBySP);
896+
Function *ResetF = insertReset(CountersBySP);
897+
Function *FlushF = insertFlush(ResetF);
855898

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

871914
FTy = FunctionType::get(Type::getVoidTy(*Ctx), false);
872-
Type *Params[] = {
873-
PointerType::get(FTy, 0),
874-
PointerType::get(FTy, 0)
875-
};
915+
Type *Params[] = {PointerType::get(FTy, 0), PointerType::get(FTy, 0),
916+
PointerType::get(FTy, 0)};
876917
FTy = FunctionType::get(Builder.getVoidTy(), Params, false);
877918

878-
// Initialize the environment and register the local writeout and flush
879-
// functions.
919+
// Initialize the environment and register the local writeout, flush and
920+
// reset functions.
880921
FunctionCallee GCOVInit = M->getOrInsertFunction("llvm_gcov_init", FTy);
881-
Builder.CreateCall(GCOVInit, {WriteoutF, FlushF});
922+
Builder.CreateCall(GCOVInit, {WriteoutF, FlushF, ResetF});
882923
Builder.CreateRetVoid();
883924

884925
appendToGlobalCtors(*M, F, 0);
@@ -1191,8 +1232,43 @@ Function *GCOVProfiler::insertCounterWriteout(
11911232
return WriteoutF;
11921233
}
11931234

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

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

12241294
Type *RetTy = FlushF->getReturnType();
1225-
if (RetTy == Type::getVoidTy(*Ctx))
1295+
if (RetTy->isVoidTy())
12261296
Builder.CreateRetVoid();
12271297
else if (RetTy->isIntegerTy())
12281298
// Used if __llvm_gcov_flush was implicitly declared.

0 commit comments

Comments
 (0)