File: stack_trace.cc

package info (click to toggle)
rocksdb 9.11.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 46,252 kB
  • sloc: cpp: 503,390; java: 43,039; ansic: 9,834; python: 8,381; perl: 5,822; sh: 4,921; makefile: 2,386; asm: 550; xml: 342
file content (419 lines) | stat: -rw-r--r-- 13,488 bytes parent folder | download
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//
#include "port/stack_trace.h"

#if !(defined(ROCKSDB_BACKTRACE) || defined(OS_MACOSX)) || defined(CYGWIN) || \
    defined(OS_SOLARIS) || defined(OS_WIN)

// noop

namespace ROCKSDB_NAMESPACE {
namespace port {
void InstallStackTraceHandler() {}
void PrintStack(int /*first_frames_to_skip*/) {}
void PrintAndFreeStack(void* /*callstack*/, int /*num_frames*/) {}
void* SaveStack(int* /*num_frames*/, int /*first_frames_to_skip*/) {
  return nullptr;
}
}  // namespace port
}  // namespace ROCKSDB_NAMESPACE

#else

#include <cxxabi.h>
#include <execinfo.h>
#include <pthread.h>
#include <unistd.h>

#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#ifdef OS_OPENBSD
#include <sys/sysctl.h>
#include <sys/wait.h>
#endif  // OS_OPENBSD
#ifdef OS_FREEBSD
#include <sys/sysctl.h>
#include <sys/wait.h>
#endif  // OS_FREEBSD
#ifdef OS_LINUX
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#if __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 30)
#include <sys/syscall.h>
#define gettid() syscall(SYS_gettid)
#endif  // GLIBC version
#endif  // OS_LINUX

#include <algorithm>
#include <atomic>

#include "port/lang.h"

namespace ROCKSDB_NAMESPACE::port {

namespace {

#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_OPENBSD) || \
    defined(OS_GNU_KFREEBSD)
const char* GetExecutableName() {
  static char name[1024];

#if defined(OS_FREEBSD)
  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
  size_t namesz = sizeof(name);

  auto ret = sysctl(mib, 4, name, &namesz, nullptr, 0);
  if (-1 == ret) {
    return nullptr;
  } else {
    return name;
  }
#elif defined(OS_OPENBSD)
  int mib[4] = {CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV};
  size_t namesz = sizeof(name);
  char* bin[namesz];

  auto ret = sysctl(mib, 4, bin, &namesz, nullptr, 0);
  if (-1 == ret) {
    return nullptr;
  } else {
    return bin[0];
  }
#else
  char link[1024];
  snprintf(link, sizeof(link), "/proc/%d/exe", getpid());
  auto read = readlink(link, name, sizeof(name) - 1);
  if (-1 == read) {
    return nullptr;
  } else {
    name[read] = 0;
    return name;
  }
#endif
}

void PrintStackTraceLine(const char* symbol, void* frame) {
  static const char* executable = GetExecutableName();
  if (symbol) {
    fprintf(stderr, "%s ", symbol);
  }
  if (executable) {
    // out source to addr2line, for the address translation
    const int kLineMax = 256;
    char cmd[kLineMax];
    snprintf(cmd, kLineMax, "addr2line %p -e %s -f -C 2>&1", frame, executable);
    auto f = popen(cmd, "r");
    if (f) {
      char line[kLineMax];
      while (fgets(line, sizeof(line), f)) {
        line[strlen(line) - 1] = 0;  // remove newline
        fprintf(stderr, "%s\t", line);
      }
      pclose(f);
    }
  } else {
    fprintf(stderr, " %p", frame);
  }

  fprintf(stderr, "\n");
}
#elif defined(OS_MACOSX)

void PrintStackTraceLine(const char* symbol, void* frame) {
  static int pid = getpid();
  // out source to atos, for the address translation
  const int kLineMax = 256;
  char cmd[kLineMax];
  snprintf(cmd, kLineMax, "xcrun atos %p -p %d  2>&1", frame, pid);
  auto f = popen(cmd, "r");
  if (f) {
    char line[kLineMax];
    while (fgets(line, sizeof(line), f)) {
      line[strlen(line) - 1] = 0;  // remove newline
      fprintf(stderr, "%s\t", line);
    }
    pclose(f);
  } else if (symbol) {
    fprintf(stderr, "%s ", symbol);
  }

  fprintf(stderr, "\n");
}

#endif

const char* GetLldbScriptSelectThread(long long tid) {
  // NOTE: called from a signal handler, so no heap allocation
  static char script[80];
  snprintf(script, sizeof(script),
           "script -l python -- lldb.process.SetSelectedThreadByID(%lld)", tid);
  return script;
}

}  // namespace

void PrintStack(void* frames[], int num_frames) {
  auto symbols = backtrace_symbols(frames, num_frames);

  for (int i = 0; i < num_frames; ++i) {
    fprintf(stderr, "#%-2d  ", i);
    PrintStackTraceLine((symbols != nullptr) ? symbols[i] : nullptr, frames[i]);
  }
  free(symbols);
}

void PrintStack(int first_frames_to_skip) {
  // Default to getting stack traces with GDB, at least on Linux where we
  // know how to attach to a particular thread.
  //
  // * Address space layout randomization (ASLR) interferes with getting good
  //   stack information from backtrace+addr2line. This is more likely to show
  //   up with LIB_MODE=shared builds (when kernel.randomize_va_space >= 1)
  //   but can also show up with LIB_MODE=static builds ((when
  //   kernel.randomize_va_space == 2).
  // * It doesn't appear easy to detect when ASLR is in use.
  // * With DEBUG_LEVEL < 2, backtrace() can skip frames that are not skipped
  //   in GDB.
  //
  // LLDB also available as an option
  bool lldb_stack_trace = getenv("ROCKSDB_LLDB_STACK") != nullptr;
#if defined(OS_LINUX)
  // Default true, override with ROCKSDB_BACKTRACE_STACK=1
  bool gdb_stack_trace =
      !lldb_stack_trace && getenv("ROCKSDB_BACKTRACE_STACK") == nullptr;
#else
  // Default false, override with ROCKSDB_GDB_STACK=1
  bool gdb_stack_trace = getenv("ROCKSDB_GDB_STACK") != nullptr;
#endif
  // Also support invoking interactive debugger on stack trace, with this
  // envvar set to non-empty
  char* debug_env = getenv("ROCKSDB_DEBUG");
  bool debug = debug_env != nullptr && strlen(debug_env) > 0;

  if (!debug && getenv("ROCKSDB_NO_STACK") != nullptr) {
    // Skip stack trace
    return;
  }

  if (lldb_stack_trace || gdb_stack_trace || debug) {
    // Allow ouside debugger to attach, even with Yama security restrictions
#ifdef PR_SET_PTRACER_ANY
    (void)prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
#endif
    // Try to invoke GDB, either for stack trace or debugging.
    long long attach_pid = getpid();
    // NOTE: we're in a signal handler, so no heap allocation
    char attach_pid_str[20];
    snprintf(attach_pid_str, sizeof(attach_pid_str), "%lld", attach_pid);

    // `gdb -p PID` seems to always attach to main thread, but `gdb -p TID`
    // seems to be able to attach to a particular thread in a process, which
    // makes sense as the main thread TID == PID of the process.
    // But I haven't found that gdb capability documented anywhere, so leave
    // a back door to attach to main thread.
    long long gdb_attach_id = attach_pid;
    // Save current thread id before fork
    long long attach_tid = 0;
#ifdef OS_LINUX
    attach_tid = gettid();
    if (getenv("ROCKSDB_DEBUG_USE_PID") == nullptr) {
      gdb_attach_id = attach_tid;
    }
#endif

    char gdb_attach_id_str[20];
    snprintf(gdb_attach_id_str, sizeof(gdb_attach_id_str), "%lld",
             gdb_attach_id);

    pid_t child_pid = fork();
    if (child_pid == 0) {
      // child process
      if (debug) {
        if (strcmp(debug_env, "lldb") == 0) {
          fprintf(stderr, "Invoking LLDB for debugging (ROCKSDB_DEBUG=%s)...\n",
                  debug_env);
          execlp(/*cmd in PATH*/ "lldb", /*arg0*/ "lldb", "-p", attach_pid_str,
                 /*"-Q",*/ "-o", GetLldbScriptSelectThread(attach_tid),
                 (char*)nullptr);
          return;
        } else {
          fprintf(stderr, "Invoking GDB for debugging (ROCKSDB_DEBUG=%s)...\n",
                  debug_env);
          execlp(/*cmd in PATH*/ "gdb", /*arg0*/ "gdb", "-p", gdb_attach_id_str,
                 (char*)nullptr);
          return;
        }
      } else {
        // Redirect child stdout to original stderr
        dup2(2, 1);
        // No child stdin (don't use pager)
        close(0);
        if (lldb_stack_trace) {
          fprintf(stderr, "Invoking LLDB for stack trace...\n");

          // Skip top ~4 frames here in PrintStack
          auto bt_in_lldb =
              "script -l python -- for f in lldb.thread.frames[4:]: print(f)";
          execlp(/*cmd in PATH*/ "lldb", /*arg0*/ "lldb", "-p", attach_pid_str,
                 "-b", "-Q", "-o", GetLldbScriptSelectThread(attach_tid), "-o",
                 bt_in_lldb, (char*)nullptr);
        } else {
          // gdb_stack_trace
          fprintf(stderr, "Invoking GDB for stack trace...\n");

          // Skip top ~4 frames here in PrintStack
          // See https://stackoverflow.com/q/40991943/454544
          auto bt_in_gdb =
              "frame apply level 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 -q frame";
          // -n : Loading config files can apparently cause failures with the
          // other options here.
          // -batch : non-interactive; suppress banners as much as possible
          execlp(/*cmd in PATH*/ "gdb", /*arg0*/ "gdb", "-n", "-batch", "-p",
                 gdb_attach_id_str, "-ex", bt_in_gdb, (char*)nullptr);
        }
        return;
      }
    } else {
      // parent process; wait for child
      int wstatus;
      waitpid(child_pid, &wstatus, 0);
      if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
        // Good
        return;
      }
    }
    fprintf(stderr, "GDB failed; falling back on backtrace+addr2line...\n");
  }

  const int kMaxFrames = 100;
  void* frames[kMaxFrames];

  int num_frames = (int)backtrace(frames, kMaxFrames);
  PrintStack(&frames[first_frames_to_skip], num_frames - first_frames_to_skip);
}

void PrintAndFreeStack(void* callstack, int num_frames) {
  PrintStack(static_cast<void**>(callstack), num_frames);
  free(callstack);
}

void* SaveStack(int* num_frames, int first_frames_to_skip) {
  const int kMaxFrames = 100;
  void* frames[kMaxFrames];

  int count = (int)backtrace(frames, kMaxFrames);
  *num_frames = count - first_frames_to_skip;
  void* callstack = malloc(sizeof(void*) * *num_frames);
  memcpy(callstack, &frames[first_frames_to_skip], sizeof(void*) * *num_frames);
  return callstack;
}

static std::atomic<uint64_t> g_thread_handling_stack_trace{0};
static int g_recursion_count = 0;
static std::atomic<bool> g_at_exit_called{false};

static void StackTraceHandler(int sig) {
  fprintf(stderr, "Received signal %d (%s)\n", sig, strsignal(sig));
  // Crude recursive mutex with no signal-unsafe system calls, to avoid
  // re-entrance from multiple threads and avoid core dumping while trying
  // to print the stack trace.
  uint64_t tid = 0;
  {
    const auto ptid = pthread_self();
    // pthread_t is an opaque type
    memcpy(&tid, &ptid, std::min(sizeof(tid), sizeof(ptid)));
    // Essentially ensure non-zero
    ++tid;
  }
  for (;;) {
    uint64_t expected = 0;
    if (g_thread_handling_stack_trace.compare_exchange_strong(expected, tid)) {
      // Acquired mutex
      g_recursion_count = 0;
      break;
    }
    if (expected == tid) {
      ++g_recursion_count;
      fprintf(stderr, "Recursive call to stack trace handler (%d)\n",
              g_recursion_count);
      break;
    }
    // Sleep before trying again
    usleep(1000);
  }

  if (g_recursion_count > 2) {
    // Give up after too many recursions
    fprintf(stderr, "Too many recursive calls to stack trace handler (%d)\n",
            g_recursion_count);
  } else {
    if (g_at_exit_called.load(std::memory_order_acquire)) {
      fprintf(stderr, "In a race with process already exiting...\n");
    }

    // skip the top three signal handler related frames
    PrintStack(3);

    // Efforts to fix or suppress TSAN warnings "signal-unsafe call inside of
    // a signal" have failed, so just warn the user about them.
#ifdef __SANITIZE_THREAD__
    fprintf(stderr,
            "==> NOTE: any above warnings about \"signal-unsafe call\" are\n"
            "==> ignorable, as they are expected when generating a stack\n"
            "==> trace because of a signal under TSAN. Consider why the\n"
            "==> signal was generated to begin with, and the stack trace\n"
            "==> in the TSAN warning can be useful for that. (The stack\n"
            "==> trace printed by the signal handler is likely obscured\n"
            "==> by TSAN output.)\n");
#endif
  }

  // reset to default handler
  signal(sig, SIG_DFL);
  // re-signal to default handler (so we still get core dump if needed...)
  raise(sig);

  // release the mutex, in case this is somehow recoverable
  if (g_recursion_count > 0) {
    --g_recursion_count;
  } else {
    g_thread_handling_stack_trace.store(0, std::memory_order_release);
  }
}

static void AtExit() {
  // wait for stack trace handler to finish, if needed
  while (g_thread_handling_stack_trace.load(std::memory_order_acquire)) {
    usleep(1000);
  }
  g_at_exit_called.store(true, std::memory_order_release);
}

void InstallStackTraceHandler() {
  // just use the plain old signal as it's simple and sufficient
  // for this use case
  signal(SIGILL, StackTraceHandler);
  signal(SIGSEGV, StackTraceHandler);
  signal(SIGBUS, StackTraceHandler);
  signal(SIGABRT, StackTraceHandler);
  atexit(AtExit);
  // Allow ouside debugger to attach, even with Yama security restrictions.
  // This is needed even outside of PrintStack() so that external mechanisms
  // can dump stacks if they suspect that a test has hung.
#ifdef PR_SET_PTRACER_ANY
  (void)prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
#endif
}

}  // namespace ROCKSDB_NAMESPACE::port

#endif