aboutsummaryrefslogtreecommitdiffstats
path: root/lib/esan/esan_sideline_linux.cpp
blob: d04f5909d6a27d56b56d78390eab5fe2f7fcacfd (plain) (blame)
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