#include "port/stack_trace.h"
#if !(defined(ROCKSDB_BACKTRACE) || defined(OS_MACOSX)) || defined(CYGWIN) || \
defined(OS_SOLARIS) || defined(OS_WIN)
namespace ROCKSDB_NAMESPACE {
namespace port {
void InstallStackTraceHandler() {}
void PrintStack(int ) {}
void PrintAndFreeStack(void* , int ) {}
void* SaveStack(int* , int ) {
return nullptr;
}
} }
#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 #ifdef OS_FREEBSD
#include <sys/sysctl.h>
#include <sys/wait.h>
#endif #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 #endif
#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) {
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; 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();
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; fprintf(stderr, "%s\t", line);
}
pclose(f);
} else if (symbol) {
fprintf(stderr, "%s ", symbol);
}
fprintf(stderr, "\n");
}
#endif
const char* GetLldbScriptSelectThread(long long tid) {
static char script[80];
snprintf(script, sizeof(script),
"script -l python -- lldb.process.SetSelectedThreadByID(%lld)", tid);
return script;
}
}
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) {
bool lldb_stack_trace = getenv("ROCKSDB_LLDB_STACK") != nullptr;
#if defined(OS_LINUX)
bool gdb_stack_trace =
!lldb_stack_trace && getenv("ROCKSDB_BACKTRACE_STACK") == nullptr;
#else
bool gdb_stack_trace = getenv("ROCKSDB_GDB_STACK") != nullptr;
#endif
char* debug_env = getenv("ROCKSDB_DEBUG");
bool debug = debug_env != nullptr && strlen(debug_env) > 0;
if (!debug && getenv("ROCKSDB_NO_STACK") != nullptr) {
return;
}
if (lldb_stack_trace || gdb_stack_trace || debug) {
#ifdef PR_SET_PTRACER_ANY
(void)prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
#endif
long long attach_pid = getpid();
char attach_pid_str[20];
snprintf(attach_pid_str, sizeof(attach_pid_str), "%lld", attach_pid);
long long gdb_attach_id = attach_pid;
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) {
if (debug) {
if (strcmp(debug_env, "lldb") == 0) {
fprintf(stderr, "Invoking LLDB for debugging (ROCKSDB_DEBUG=%s)...\n",
debug_env);
execlp( "lldb", "lldb", "-p", attach_pid_str,
"-o", GetLldbScriptSelectThread(attach_tid),
(char*)nullptr);
return;
} else {
fprintf(stderr, "Invoking GDB for debugging (ROCKSDB_DEBUG=%s)...\n",
debug_env);
execlp( "gdb", "gdb", "-p", gdb_attach_id_str,
(char*)nullptr);
return;
}
} else {
dup2(2, 1);
close(0);
if (lldb_stack_trace) {
fprintf(stderr, "Invoking LLDB for stack trace...\n");
auto bt_in_lldb =
"script -l python -- for f in lldb.thread.frames[4:]: print(f)";
execlp( "lldb", "lldb", "-p", attach_pid_str,
"-b", "-Q", "-o", GetLldbScriptSelectThread(attach_tid), "-o",
bt_in_lldb, (char*)nullptr);
} else {
fprintf(stderr, "Invoking GDB for stack trace...\n");
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";
execlp( "gdb", "gdb", "-n", "-batch", "-p",
gdb_attach_id_str, "-ex", bt_in_gdb, (char*)nullptr);
}
return;
}
} else {
int wstatus;
waitpid(child_pid, &wstatus, 0);
if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
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));
uint64_t tid = 0;
{
const auto ptid = pthread_self();
memcpy(&tid, &ptid, std::min(sizeof(tid), sizeof(ptid)));
++tid;
}
for (;;) {
uint64_t expected = 0;
if (g_thread_handling_stack_trace.compare_exchange_strong(expected, tid)) {
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;
}
usleep(1000);
}
if (g_recursion_count > 2) {
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");
}
PrintStack(3);
#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
}
signal(sig, SIG_DFL);
raise(sig);
if (g_recursion_count > 0) {
--g_recursion_count;
} else {
g_thread_handling_stack_trace.store(0, std::memory_order_release);
}
}
static void AtExit() {
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() {
signal(SIGILL, StackTraceHandler);
signal(SIGSEGV, StackTraceHandler);
signal(SIGBUS, StackTraceHandler);
signal(SIGABRT, StackTraceHandler);
atexit(AtExit);
#ifdef PR_SET_PTRACER_ANY
(void)prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
#endif
}
}
#endif