1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
//===-- esan_sideline_linux.cpp ---------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of EfficiencySanitizer, a family of performance tuners.
//
// Support for a separate or "sideline" tool thread on Linux.
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_LINUX
#include "esan_sideline.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_linux.h"
#include <errno.h>
#include <sched.h>
#include <sys/prctl.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
namespace __esan {
static const int SigAltStackSize = 4*1024;
static const int SidelineStackSize = 4*1024;
static const uptr SidelineIdUninitialized = 1;
// FIXME: we'll need some kind of TLS (can we trust that a pthread key will
// work in our non-POSIX thread?) to access our data in our signal handler
// with multiple sideline threads. For now we assume there is only one
// sideline thread and we use a dirty solution of a global var.
static SidelineThread *TheThread;
// We aren't passing SA_NODEFER so the same signal is blocked while here.
void SidelineThread::handleSidelineSignal(int SigNum, void *SigInfo,
void *Ctx) {
VPrintf(3, "Sideline signal %d\n", SigNum);
CHECK_EQ(SigNum, SIGALRM);
// See above about needing TLS to avoid this global var.
SidelineThread *Thread = TheThread;
if (atomic_load(&Thread->SidelineExit, memory_order_relaxed) != 0)
return;
Thread->sampleFunc(Thread->FuncArg);
}
void SidelineThread::registerSignal(int SigNum) {
__sanitizer_sigaction SigAct;
internal_memset(&SigAct, 0, sizeof(SigAct));
SigAct.sigaction = handleSidelineSignal;
// We do not pass SA_NODEFER as we want to block the same signal.
SigAct.sa_flags = SA_ONSTACK | SA_SIGINFO;
int Res = internal_sigaction(SigNum, &SigAct, nullptr);
CHECK_EQ(Res, 0);
}
int SidelineThread::runSideline(void *Arg) {
VPrintf(1, "Sideline thread starting\n");
SidelineThread *Thread = static_cast<SidelineThread*>(Arg);
// If the parent dies, we want to exit also.
internal_prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
// Set up a signal handler on an alternate stack for safety.
InternalScopedBuffer<char> StackMap(SigAltStackSize);
struct sigaltstack SigAltStack;
SigAltStack.ss_sp = StackMap.data();
SigAltStack.ss_size = SigAltStackSize;
SigAltStack.ss_flags = 0;
internal_sigaltstack(&SigAltStack, nullptr);
// We inherit the signal mask from the app thread. In case
// we weren't created at init time, we ensure the mask is empty.
__sanitizer_sigset_t SigSet;
internal_sigfillset(&SigSet);
int Res = internal_sigprocmask(SIG_UNBLOCK, &SigSet, nullptr);
CHECK_EQ(Res, 0);
registerSignal(SIGALRM);
bool TimerSuccess = Thread->adjustTimer(Thread->Freq);
CHECK(TimerSuccess);
// We loop, doing nothing but handling itimer signals.
while (atomic_load(&TheThread->SidelineExit, memory_order_relaxed) == 0)
sched_yield();
if (!Thread->adjustTimer(0))
VPrintf(1, "Failed to disable timer\n");
VPrintf(1, "Sideline thread exiting\n");
return 0;
}
bool SidelineThread::launchThread(SidelineFunc takeSample, void *Arg,
u32 FreqMilliSec) {
// This can only be called once. However, we can't clear a field in
// the constructor and check for that here as the constructor for
// a static instance is called *after* our module_ctor and thus after
// this routine! Thus we rely on the TheThread check below.
CHECK(TheThread == nullptr); // Only one sideline thread is supported.
TheThread = this;
sampleFunc = takeSample;
FuncArg = Arg;
Freq = FreqMilliSec;
atomic_store(&SidelineExit, 0, memory_order_relaxed);
// We do without a guard page.
Stack = static_cast<char*>(MmapOrDie(SidelineStackSize, "SidelineStack"));
// We need to handle the return value from internal_clone() not having been
// assigned yet (for our CHECK in adjustTimer()) so we ensure this has a
// sentinel value.
SidelineId = SidelineIdUninitialized;
// By omitting CLONE_THREAD, the child is in its own thread group and will not
// receive any of the application's signals.
SidelineId = internal_clone(
runSideline, Stack + SidelineStackSize,
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED,
this, nullptr /* parent_tidptr */,
nullptr /* newtls */, nullptr /* child_tidptr */);
int ErrCode;
if (internal_iserror(SidelineId, &ErrCode)) {
Printf("FATAL: EfficiencySanitizer failed to spawn a thread (code %d).\n",
ErrCode);
Die();
return false; // Not reached.
}
return true;
}
bool SidelineThread::joinThread() {
VPrintf(1, "Joining sideline thread\n");
bool Res = true;
atomic_store(&SidelineExit, 1, memory_order_relaxed);
while (true) {
uptr Status = internal_waitpid(SidelineId, nullptr, __WALL);
int ErrCode;
if (!internal_iserror(Status, &ErrCode))
break;
if (ErrCode == EINTR)
continue;
VPrintf(1, "Failed to join sideline thread (errno %d)\n", ErrCode);
Res = false;
break;
}
UnmapOrDie(Stack, SidelineStackSize);
return Res;
}
// Must be called from the sideline thread itself.
bool SidelineThread::adjustTimer(u32 FreqMilliSec) {
// The return value of internal_clone() may not have been assigned yet:
CHECK(internal_getpid() == SidelineId ||
SidelineId == SidelineIdUninitialized);
Freq = FreqMilliSec;
struct itimerval TimerVal;
TimerVal.it_interval.tv_sec = (time_t) Freq / 1000;
TimerVal.it_interval.tv_usec = (time_t) (Freq % 1000) * 1000;
TimerVal.it_value.tv_sec = (time_t) Freq / 1000;
TimerVal.it_value.tv_usec = (time_t) (Freq % 1000) * 1000;
// As we're in a different thread group, we cannot use either
// ITIMER_PROF or ITIMER_VIRTUAL without taking up scheduled
// time ourselves: thus we must use real time.
int Res = setitimer(ITIMER_REAL, &TimerVal, nullptr);
return (Res == 0);
}
} // namespace __esan
#endif // SANITIZER_LINUX
|