buildat/src/impl/linux/debug.cpp
2014-10-30 06:36:31 +02:00

260 lines
6.8 KiB
C++

// http://www.apache.org/licenses/LICENSE-2.0
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#include "interface/debug.h"
#include "core/log.h"
#include <c55/string_util.h>
#include <stdexcept>
#include <iostream>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <execinfo.h>
#include <signal.h>
#include <sys/ucontext.h>
#include <unistd.h>
#define MODULE "debug"
namespace interface {
namespace debug {
// Execute a program and return whatever it writes to stdout
static ss_ exec_get_stdout(char *cmd)
{
FILE* pipe = popen(cmd, "r");
if(!pipe) return "ERROR";
char buffer[50];
ss_ result;
try{
while(!feof(pipe)){
if(fgets(buffer, 50, pipe) != NULL)
result += buffer; // Can cause std::bad_alloc
}
pclose(pipe);
return result;
}catch(std::bad_alloc){
return ss_();
}
}
// Execute a program and return whatever it writes to stdout, without trailing
// newline
static ss_ exec_get_stdout_without_newline(char *cmd)
{
ss_ s = exec_get_stdout(cmd);
if(!s.empty() && s[s.size()-1] == '\n')
s = s.substr(0, s.size()-1);
return s;
}
static void* get_library_executable_address(const ss_ &lib_path)
{
char cmdbuf[500];
snprintf(cmdbuf, sizeof cmdbuf,
"cat /proc/%i/maps | grep %s | grep ' ..x. ' | cut -d- -f1",
getpid(), cs(lib_path));
ss_ address_s = exec_get_stdout_without_newline(cmdbuf);
//log_v(MODULE, "address_s=\"%s\"", cs(address_s));
if(address_s.empty())
return (void*)0x0;
void *address = (void*)strtoul(address_s.c_str(), NULL, 16);
return address;
}
//#define __USE_GNU
#include <ucontext.h> // REG_EIP (or REG_RIP)
#if __WORDSIZE == 64 // REG_RIP replaces REG_EIP on x86_64
#define REG_EIP REG_RIP
#endif
static void log_backtrace(void* const *trace, int trace_size, const ss_ &title);
static void debug_sighandler(int sig, siginfo_t *info, void *secret)
{
ucontext_t *uc = (ucontext_t*)secret;
log_i(MODULE, " ");
if(sig == SIGSEGV)
log_w(MODULE, "Crash: SIGSEGV: Address %p (executing %p)",
info->si_addr, (void*)uc->uc_mcontext.gregs[REG_EIP]);
else
log_w(MODULE, "Crash: Signal %d", sig);
void *trace[16];
int trace_size = backtrace(trace, 16);
// Overwrite sigaction with caller's address
trace[1] = (void*) uc->uc_mcontext.gregs[REG_EIP];
log_backtrace(trace, trace_size, "Backtrace for signal:");
exit(1);
}
void init_signal_handlers(const SigConfig &config)
{
struct sigaction sa;
sa.sa_sigaction = debug_sighandler;
sigemptyset (&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO;
if(config.catch_segfault)
sigaction(SIGSEGV, &sa, NULL);
if(config.catch_abort)
sigaction(SIGABRT, &sa, NULL);
}
static std::atomic_int log_backtrace_spinlock(0);
static char backtrace_buffer[10000];
static size_t backtrace_buffer_len = 0;
static void bt_print(const char *fmt, ...)
{
va_list va_args;
va_start(va_args, fmt);
backtrace_buffer_len += vsnprintf(
backtrace_buffer + backtrace_buffer_len,
sizeof backtrace_buffer - backtrace_buffer_len,
fmt, va_args);
va_end(va_args);
backtrace_buffer_len += snprintf(
backtrace_buffer + backtrace_buffer_len,
sizeof backtrace_buffer - backtrace_buffer_len,
"\n");
}
static void log_backtrace(void* const *trace, int trace_size, const ss_ &title)
{
char **symbols = backtrace_symbols(trace, trace_size);
// Lock spinlock
for(;;){
int previous_value = log_backtrace_spinlock.fetch_add(1);
if(previous_value == 0)
break;
log_backtrace_spinlock--;
}
// The first stack frame points to this functiton
backtrace_buffer_len = 0;
bt_print("\n %s", cs(title));
int first_real_i = 1;
for(int i = 1; i < trace_size; i++){
char cmdbuf[500];
// Parse symbol to get file name
// Example: "../cache/rccpp_build/main.so(+0x14e2c) [0x7f7880d36e2c]"
//log_v(MODULE, "symbol: %s", symbols[i]);
c55::Strfnd f(symbols[i]);
ss_ file_path = f.next("(");
//log_v(MODULE, "file_path: %s", cs(file_path));
void *address = trace[i];
bool is_shared_lib = (file_path.find(".so") != ss_::npos);
//log_v(MODULE, "is_shared_lib=%s", is_shared_lib?"true":"false");
if(is_shared_lib){
void *base_addr = get_library_executable_address(file_path);
//log_v(MODULE, "base_addr=%p", base_addr);
address = (void*)((char*)trace[i] - (char*)base_addr);
//log_v(MODULE, "address=%p", address);
}
snprintf(cmdbuf, sizeof cmdbuf, "echo '%s' | c++filt", symbols[i]);
ss_ cppfilt_symbol = exec_get_stdout_without_newline(cmdbuf);
snprintf(cmdbuf, sizeof cmdbuf, "addr2line %p -e %s",
address, cs(file_path));
ss_ addr2line_output = exec_get_stdout_without_newline(cmdbuf);
// Clean up the beginning of the backtrace (for whatever reason there
// often seems to be two basic_string-related lines at the beginnong of
// the backtrace)
if(i <= 2 && i <= first_real_i &&
addr2line_output.find("/basic_string.h") != ss_::npos){
first_real_i = i + 1;
continue;
}
if(addr2line_output.size() > 4){
bt_print(" #%i %s", i-first_real_i, cs(addr2line_output));
//bt_print(" = %s", cs(cppfilt_symbol));
} else {
bt_print(" #%i %s", i-first_real_i, cs(cppfilt_symbol));
}
}
// Print to log
backtrace_buffer[sizeof backtrace_buffer - 1] = 0;
log_i(MODULE, "%s", backtrace_buffer);
// Unlock spinlock
log_backtrace_spinlock--;
}
void log_current_backtrace(const ss_ &title)
{
void *trace[16];
int trace_size = backtrace(trace, 16);
log_backtrace(trace, trace_size, title);
}
#include <cxxabi.h>
#include <dlfcn.h>
static void *last_exception_frames[16];
static int last_exception_num_frames = 0;
static ss_ last_exception_name;
// GCC-specific
static ss_ demangle(const char *name)
{
int status;
std::unique_ptr<char,void(*)(void*)>
realname(abi::__cxa_demangle(name, 0, 0, &status), &std::free);
return status ? "failed" : &*realname;
}
// GCC-specific
extern "C" {
void __cxa_throw(void *ex, void *info, void (*dest)(void *)) {
last_exception_name = demangle(
reinterpret_cast<const std::type_info*>(info)->name());
last_exception_num_frames = backtrace(last_exception_frames,
sizeof last_exception_frames / sizeof(void*));
static void (*const rethrow)(void*,void*,void(*)(void*))
__attribute__((noreturn)) = (void (*)(void*,void*,void(*)(void*)))
dlsym(RTLD_NEXT, "__cxa_throw");
rethrow(ex,info,dest);
}
}
void log_exception_backtrace(const ss_ &title)
{
log_backtrace(last_exception_frames, last_exception_num_frames, title);
}
void get_current_backtrace(StoredBacktrace &result)
{
result.exception_name.clear();
result.num_frames = backtrace(result.frames, 16);
}
void get_exception_backtrace(StoredBacktrace &result)
{
result.exception_name = last_exception_name;
result.num_frames = last_exception_num_frames;
for(int i = 0; i < result.num_frames; i++){
result.frames[i] = last_exception_frames[i];
}
}
void log_backtrace(const StoredBacktrace &result, const ss_ &title)
{
log_backtrace(result.frames, result.num_frames, title);
}
}
}
// vim: set noet ts=4 sw=4: