forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			178 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			178 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- 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
 |