blob: 27b40d720dca7d180a7124706d85e6af9fb2f78c [file]
/** @file
backtrace.cc
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/****************************************************************************
backtrace.cc
Functions to generate backtrace from a TS process.
****************************************************************************/
#include "tscore/ink_config.h"
#include <sys/types.h>
#if TS_USE_REMOTE_UNWINDING
#include "tscore/Diags.h"
#include "tscore/TextBuffer.h"
#include <string.h>
#include <libunwind.h>
#include <libunwind-ptrace.h>
#if defined(__FreeBSD__)
#define __WALL P_ALL
#define PTRACE_ATTACH PT_ATTACH
#define PTRACE_DETACH PT_DETACH
#define DATA_NULL 0
#else
#define DATA_NULL NULL
#endif
#include <sys/ptrace.h>
#include <cxxabi.h>
#include <vector>
namespace
{
using threadlist = std::vector<pid_t>;
DbgCtl dbg_ctl_backtrace{"backtrace"};
/** Enumerate all threads for a given process by reading /proc/<pid>/task. */
static threadlist
threads_for_process(pid_t proc)
{
DIR *dir = nullptr;
struct dirent *entry = nullptr;
char path[64];
threadlist threads;
if (snprintf(path, sizeof(path), "/proc/%ld/task", static_cast<long>(proc)) >= static_cast<int>(sizeof(path))) {
goto done;
}
dir = opendir(path);
if (dir == nullptr) {
goto done;
}
while ((entry = readdir(dir))) {
pid_t threadid;
if (isdot(entry->d_name) || isdotdot(entry->d_name)) {
continue;
}
threadid = strtol(entry->d_name, nullptr, 10);
if (threadid > 0) {
threads.push_back(threadid);
Dbg(dbg_ctl_backtrace, "found thread %ld\n", (long)threadid);
}
}
done:
if (dir) {
closedir(dir);
}
return threads;
}
static void
backtrace_for_thread(pid_t threadid, TextBuffer &text)
{
int status;
unw_addr_space_t addr_space = nullptr;
unw_cursor_t cursor;
void *ap = nullptr;
pid_t target = -1;
bool attached{false};
unsigned level = 0;
int step_result;
// First, attach to the child, causing it to stop.
status = ptrace(PTRACE_ATTACH, threadid, 0, 0);
if (status < 0) {
text.format(" [ptrace ATTACH failed: %s (%d)]\n", strerror(errno), errno);
Dbg(dbg_ctl_backtrace, "ptrace(ATTACH, %ld) -> %s (%d)\n", (long)threadid, strerror(errno), errno);
return;
}
attached = true;
// Wait for it to stop. The caller uses alarm() to enforce a timeout.
target = waitpid(threadid, &status, __WALL | WUNTRACED);
Dbg(dbg_ctl_backtrace, "waited for target %ld, found PID %ld, %s\n", (long)threadid, (long)target,
WIFSTOPPED(status) ? "STOPPED" : "???");
if (target < 0) {
text.format(" [waitpid failed: %s (%d)]\n", strerror(errno), errno);
goto done;
}
ap = _UPT_create(threadid);
Dbg(dbg_ctl_backtrace, "created UPT %p", ap);
if (ap == nullptr) {
text.format(" [_UPT_create failed]\n");
goto done;
}
addr_space = unw_create_addr_space(&_UPT_accessors, 0 /* byteorder */);
Dbg(dbg_ctl_backtrace, "created address space %p\n", addr_space);
if (addr_space == nullptr) {
text.format(" [unw_create_addr_space failed]\n");
goto done;
}
status = unw_init_remote(&cursor, addr_space, ap);
Dbg(dbg_ctl_backtrace, "unw_init_remote(...) -> %d\n", status);
if (status != 0) {
text.format(" [unw_init_remote failed: %d]\n", status);
goto done;
}
step_result = unw_step(&cursor);
if (step_result <= 0) {
text.format(" [unw_step returned %d on first call]\n", step_result);
}
while (step_result > 0) {
unw_word_t ip;
unw_word_t offset = 0;
char buf[256];
unw_get_reg(&cursor, UNW_REG_IP, &ip);
if (unw_get_proc_name(&cursor, buf, sizeof(buf), &offset) == 0) {
int demangle_status;
char *name = abi::__cxa_demangle(buf, nullptr, nullptr, &demangle_status);
text.format("%-4u 0x%016llx %s + 0x%lx\n", level, static_cast<unsigned long long>(ip), name ? name : buf,
static_cast<unsigned long>(offset));
free(name);
} else {
text.format("%-4u 0x%016llx <unknown>\n", level, static_cast<unsigned long long>(ip));
}
++level;
step_result = unw_step(&cursor);
}
done:
if (addr_space) {
unw_destroy_addr_space(addr_space);
}
if (ap) {
_UPT_destroy(ap);
}
if (attached) {
status = ptrace(PTRACE_DETACH, threadid, nullptr, DATA_NULL);
Dbg(dbg_ctl_backtrace, "ptrace(DETACH, %ld) -> %d (errno %d)\n", (long)threadid, status, errno);
}
}
/** Format a thread header with the thread name from /proc. */
static void
format_thread_header(pid_t threadid, const char *prefix, TextBuffer &text)
{
ats_scoped_fd fd;
char path[128];
snprintf(path, sizeof(path), "/proc/%ld/comm", static_cast<long>(threadid));
fd = open(path, O_RDONLY);
if (fd >= 0) {
text.format("%s (TID %ld, ", prefix, static_cast<long>(threadid));
text.readFromFD(fd);
text.chomp();
text.format("):\n");
} else {
text.format("%s (TID %ld):\n", prefix, static_cast<long>(threadid));
}
}
} // namespace
int
ServerBacktrace(unsigned /* options */, pid_t pid, pid_t crashing_tid, char **trace)
{
*trace = nullptr;
threadlist threads(threads_for_process(pid));
TextBuffer text(0);
Dbg(dbg_ctl_backtrace, "tracing %zd threads for traffic_server PID %ld, crashing TID %ld\n", threads.size(),
static_cast<long>(pid), static_cast<long>(crashing_tid));
// First, trace the crashing thread.
if (crashing_tid > 0) {
Dbg(dbg_ctl_backtrace, "tracing crashing thread %ld\n", static_cast<long>(crashing_tid));
format_thread_header(crashing_tid, "Crashing Thread", text);
backtrace_for_thread(crashing_tid, text);
text.format("\n");
}
// Then trace all other threads.
bool printed_header = false;
for (auto threadid : threads) {
if (threadid == crashing_tid) {
continue;
}
if (!printed_header) {
text.format("Other Non-Crashing Threads:\n\n");
printed_header = true;
}
Dbg(dbg_ctl_backtrace, "tracing thread %ld\n", static_cast<long>(threadid));
format_thread_header(threadid, "Thread", text);
backtrace_for_thread(threadid, text);
text.format("\n");
}
*trace = text.release();
return 0;
}
#else /* TS_USE_REMOTE_UNWINDING */
int
ServerBacktrace([[maybe_unused]] unsigned options, [[maybe_unused]] pid_t pid, [[maybe_unused]] pid_t crashing_tid, char **trace)
{
*trace = nullptr;
return -1;
}
#endif