//===-- 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 #include #include #include #include #include #include namespace __esan { static const int SigAltStackSize = 4*1024; static const int SidelineStackSize = 4*1024; // 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(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 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(MmapOrDie(SidelineStackSize, "SidelineStack")); // 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) { CHECK(internal_getpid() == SidelineId); 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