aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/common.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common.cpp')
-rw-r--r--src/common.cpp112
1 files changed, 81 insertions, 31 deletions
diff --git a/src/common.cpp b/src/common.cpp
index 834f7b8b..aa27c537 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -2,6 +2,8 @@
#include "config.h"
#include <assert.h>
+#include <cxxabi.h>
+#include <dlfcn.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
@@ -49,12 +51,16 @@ static bool thread_assertions_configured_for_testing = false;
wchar_t ellipsis_char;
wchar_t omitted_newline_char;
-
bool g_profiling_active = false;
-
const wchar_t *program_name;
+int debug_level = 1; // default maximum debug output level (errors and warnings)
+int debug_stack_frames = 0; // default number of stack frames to show on debug() calls
+
+/// This allows us to notice when we've forked.
+static pid_t initial_pid = 0;
-int debug_level = 1;
+/// Be able to restore the term's foreground process group.
+static pid_t initial_foreground_process_group = -1;
/// This struct maintains the current state of the terminal size. It is updated on demand after
/// receiving a SIGWINCH. Do not touch this struct directly, it's managed with a rwlock. Use
@@ -65,18 +71,53 @@ static rwlock_t termsize_rwlock;
static char *wcs2str_internal(const wchar_t *in, char *out);
-void show_stackframe() {
+// This function produces a stack backtrace with demangled function & method names. It is based on
+// https://gist.github.com/fmela/591333 but adapted to the style of the fish project.
+static const wcstring_list_t __attribute__((noinline))
+demangled_backtrace(int max_frames, int skip_levels) {
+ void *callstack[128];
+ const int n_max_frames = sizeof(callstack) / sizeof(callstack[0]);
+ int n_frames = backtrace(callstack, n_max_frames);
+ char **symbols = backtrace_symbols(callstack, n_frames);
+ wchar_t text[1024];
+ std::vector<wcstring> backtrace_text;
+
+ if (skip_levels + max_frames < n_frames) n_frames = skip_levels + max_frames;
+
+ for (int i = skip_levels; i < n_frames; i++) {
+ Dl_info info;
+ if (dladdr(callstack[i], &info) && info.dli_sname) {
+ char *demangled = NULL;
+ int status = -1;
+ if (info.dli_sname[0] == '_')
+ demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
+ swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s + %td", i - skip_levels,
+ status == 0 ? demangled : info.dli_sname == 0 ? symbols[i] : info.dli_sname,
+ (char *)callstack[i] - (char *)info.dli_saddr);
+ free(demangled);
+ } else {
+ swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s", i - skip_levels, symbols[i]);
+ }
+ backtrace_text.push_back(text);
+ }
+ free(symbols);
+ return backtrace_text;
+}
+
+static void debug_shared(const wchar_t msg_level, const wcstring &msg);
+void __attribute__((noinline)) show_stackframe(const wchar_t msg_level, int frame_count,
+ int skip_levels) {
ASSERT_IS_NOT_FORKED_CHILD();
// Hack to avoid showing backtraces in the tester.
if (program_name && !wcscmp(program_name, L"(ignore)")) return;
- void *trace[32];
- int trace_size = 0;
-
- trace_size = backtrace(trace, 32);
- debug(0, L"Backtrace:");
- backtrace_symbols_fd(trace, trace_size, STDERR_FILENO);
+ if (frame_count < 1) frame_count = 999;
+ debug_shared(msg_level, L"Backtrace:");
+ std::vector<wcstring> bt = demangled_backtrace(frame_count, skip_levels + 2);
+ for (int i = 0; i < bt.size(); i++) {
+ debug_shared(msg_level, bt[i]);
+ }
}
int fgetws2(wcstring *s, FILE *f) {
@@ -493,23 +534,34 @@ static bool should_debug(int level) {
return true;
}
-static void debug_shared(const wcstring &msg) {
- const wcstring sb = wcstring(program_name) + L": " + msg;
- fwprintf(stderr, L"%ls", reformat_for_screen(sb).c_str());
+static void debug_shared(const wchar_t level, const wcstring &msg) {
+ pid_t current_pid = getpid();
+
+ if (current_pid == initial_pid) {
+ fwprintf(stderr, L"<%lc> %ls: %ls\n", (unsigned long)level, program_name, msg.c_str());
+ } else {
+ fwprintf(stderr, L"<%lc> %ls: %d: %ls\n", (unsigned long)level, program_name, current_pid,
+ msg.c_str());
+ }
}
-void debug(int level, const wchar_t *msg, ...) {
+static wchar_t level_char[] = {L'E', L'W', L'2', L'3', L'4', L'5'};
+void __attribute__((noinline)) debug(int level, const wchar_t *msg, ...) {
if (!should_debug(level)) return;
int errno_old = errno;
va_list va;
va_start(va, msg);
wcstring local_msg = vformat_string(msg, va);
va_end(va);
- debug_shared(local_msg);
+ const wchar_t msg_level = level <= 5 ? level_char[level] : L'9';
+ debug_shared(msg_level, local_msg);
+ if (debug_stack_frames > 0) {
+ show_stackframe(msg_level, debug_stack_frames, 1);
+ }
errno = errno_old;
}
-void debug(int level, const char *msg, ...) {
+void __attribute__((noinline)) debug(int level, const char *msg, ...) {
if (!should_debug(level)) return;
int errno_old = errno;
char local_msg[512];
@@ -517,7 +569,11 @@ void debug(int level, const char *msg, ...) {
va_start(va, msg);
vsnprintf(local_msg, sizeof local_msg, msg, va);
va_end(va);
- debug_shared(str2wcstring(local_msg));
+ const wchar_t msg_level = level <= 5 ? level_char[level] : L'9';
+ debug_shared(msg_level, str2wcstring(local_msg));
+ if (debug_stack_frames > 0) {
+ show_stackframe(msg_level, debug_stack_frames, 1);
+ }
errno = errno_old;
}
@@ -1470,7 +1526,7 @@ int create_directory(const wcstring &d) {
}
__attribute__((noinline)) void bugreport() {
- debug(1, _(L"This is a bug. Break on bugreport to debug."
+ debug(1, _(L"This is a bug. Break on bugreport to debug. "
L"If you can reproduce it, please send a bug report to %s."),
PACKAGE_BUGREPORT);
}
@@ -1630,12 +1686,6 @@ void configure_thread_assertions_for_testing(void) {
thread_assertions_configured_for_testing = true;
}
-/// Notice when we've forked.
-static pid_t initial_pid = 0;
-
-/// Be able to restore the term's foreground process group.
-static pid_t initial_foreground_process_group = -1;
-
bool is_forked_child(void) {
// Just bail if nobody's called setup_fork_guards, e.g. some of our tools.
if (!initial_pid) return false;
@@ -1712,14 +1762,14 @@ void assert_is_locked(void *vmutex, const char *who, const char *caller) {
void scoped_lock::lock(void) {
assert(!locked);
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_lock(lock_obj));
locked = true;
}
void scoped_lock::unlock(void) {
assert(locked);
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_unlock(lock_obj));
locked = false;
}
@@ -1736,35 +1786,35 @@ scoped_lock::~scoped_lock() {
void scoped_rwlock::lock(void) {
assert(!(locked || locked_shared));
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_rdlock(rwlock_obj));
locked = true;
}
void scoped_rwlock::unlock(void) {
assert(locked);
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj));
locked = false;
}
void scoped_rwlock::lock_shared(void) {
assert(!(locked || locked_shared));
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_wrlock(rwlock_obj));
locked_shared = true;
}
void scoped_rwlock::unlock_shared(void) {
assert(locked_shared);
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj));
locked_shared = false;
}
void scoped_rwlock::upgrade(void) {
assert(locked_shared);
- assert(!is_forked_child());
+ ASSERT_IS_NOT_FORKED_CHILD();
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj));
locked = false;
VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_wrlock(rwlock_obj));