forked from OSchip/llvm-project
178 lines
6.4 KiB
C++
178 lines
6.4 KiB
C++
//===-- esan_sideline_linux.cpp ---------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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,
|
|
__sanitizer_siginfo *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.
|
|
InternalMmapVector<char> StackMap(SigAltStackSize);
|
|
stack_t 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
|