aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile19
-rw-r--r--bt.c206
-rw-r--r--example/raise1.c17
-rw-r--r--example/raise2.c25
-rw-r--r--example/recurse1.c17
-rw-r--r--example/recurse2.c26
7 files changed, 312 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c27884
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+libbt.so
+out/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bd97388
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+all: $(patsubst example/%.c, out/%, $(wildcard example/*.c))
+all: $(patsubst example/%.c, run/%, $(wildcard example/*.c))
+
+CFLAGS := -Wall -Wextra -g -O2
+CFLAGS_recurse2 := -lpthread
+
+run/%:
+ LD_PRELOAD=$(PWD)/libbt.so out/$* || test $$? -eq 42 && exit 0
+
+out/%: example/%.c libbt.so
+ @mkdir -p out
+ $(CC) -o $@ $< -rdynamic $(FLAGS) $(CFLAGS_$*)
+
+libbt.so: bt.c
+ $(CC) -o $@ $^ -shared -fPIC -static-libgcc -ldl $(FLAGS)
+
+clean:
+ $(RM) -r out
+ $(RM) libbt.so
diff --git a/bt.c b/bt.c
new file mode 100644
index 0000000..4a2c743
--- /dev/null
+++ b/bt.c
@@ -0,0 +1,206 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2024 Johannes Stölp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <signal.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unwind.h> // from gcc, clang
+
+#define LOG(fmt, ...) \
+ do { \
+ char buf[1024]; \
+ int cnt = snprintf(buf, sizeof(buf), fmt "\n", ##__VA_ARGS__); \
+ write(STDERR_FILENO, buf, cnt); \
+ } while (0)
+
+#define INFO(fmt, ...) LOG("[\e[0;32mINFO:BT\e[0m]: " fmt, ##__VA_ARGS__)
+#define WARN(fmt, ...) LOG("[\e[0;33mWARN:BT\e[0m]: " fmt, ##__VA_ARGS__)
+#define FAIL(fmt, ...) LOG("[\e[0;31mFAIL:BT\e[0m]: " fmt, ##__VA_ARGS__)
+
+// -- UNWIND HANDLER -----------------------------------------------------------
+
+struct unwind_data {
+ int num_frames;
+ int frame;
+ int skip;
+};
+
+static _Unwind_Reason_Code libbt_handle_unwind_frame(
+ struct _Unwind_Context* uctx,
+ void* arg) {
+ struct unwind_data* data = (struct unwind_data*)arg;
+ void* pc = (void*)_Unwind_GetIP(uctx);
+
+ if (pc == NULL) {
+ return _URC_END_OF_STACK;
+ }
+
+ if (data->skip > 0) {
+ data->skip -= 1;
+ return _URC_NO_REASON;
+ }
+
+ Dl_info info;
+ memset(&info, 0, sizeof(info));
+ if (dladdr(pc, &info) == 0) {
+ FAIL("dladdr call failed pc=%p stop unwinding", pc);
+ return _URC_END_OF_STACK;
+ }
+
+ if (info.dli_saddr) {
+ LOG("#%2d [%16p] %s+0x%lx %s+0x%lx", data->frame, pc, info.dli_sname,
+ pc - info.dli_saddr, info.dli_fname, info.dli_saddr - info.dli_fbase);
+ } else {
+ LOG("#%2d [%16p] %s+0x%lx", data->frame, pc, info.dli_fname,
+ pc - info.dli_fbase);
+ }
+
+ if (data->frame++ < data->num_frames) {
+ return _URC_NO_REASON;
+ }
+ return _URC_END_OF_STACK;
+}
+
+// -- SIGNAL HANDLING ----------------------------------------------------------
+
+static struct sigaction OLD;
+static struct sigaction NEW;
+
+static void libbt_report(int sig, siginfo_t* info, void* ucontext) {
+ INFO("caught signal %d", sig);
+
+ struct unwind_data ctx = {
+ .num_frames = 10,
+ .frame = 0,
+ .skip = 2,
+ };
+ _Unwind_Backtrace(libbt_handle_unwind_frame, &ctx);
+
+ if (OLD.sa_flags & SA_SIGINFO && OLD.sa_sigaction) {
+ OLD.sa_sigaction(sig, info, ucontext);
+ } else if (OLD.sa_handler) {
+ OLD.sa_handler(sig);
+ }
+
+ const char* noexit = getenv("BT_NOEXIT");
+ if (noexit && noexit[0] == '1') {
+ return;
+ }
+ INFO("exit after catching signal, set BT_NOEXIT=1 to keep running");
+ exit(42);
+}
+
+static void* libbt_install_sigstack() {
+ const size_t STACK_SIZE = 8192;
+
+ void* sp = malloc(STACK_SIZE);
+ if (sp == NULL) {
+ WARN("failed to allocate alternative signal stack");
+ return NULL;
+ }
+
+ stack_t stack = {
+ .ss_sp = sp,
+ .ss_flags = 0,
+ .ss_size = STACK_SIZE,
+ };
+ if (sigaltstack(&stack, NULL) != 0) {
+ WARN("failed to setup alternative signal stack");
+ }
+ return sp;
+}
+
+static int libbt_install_sighandler() {
+ memset(&OLD, 0, sizeof(OLD));
+ memset(&NEW, 0, sizeof(NEW));
+
+ NEW.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ NEW.sa_sigaction = libbt_report;
+
+ return sigaction(SIGSEGV, &NEW, &OLD);
+}
+
+// -- THREAD HANDLING ----------------------------------------------------------
+
+int (*NEXT_PTHREAD_CREATE)(pthread_t*,
+ const pthread_attr_t*,
+ void* (*)(void*),
+ void*);
+
+static int THREAD_CREATE_SIGSTACK = 0;
+
+struct thread_arg {
+ void* (*start_routine)(void*);
+ void* restrict arg;
+};
+
+static void* thread_entry(void* th_arg) {
+ struct thread_arg arg = *(struct thread_arg*)th_arg;
+ free(th_arg);
+
+ void* sp = THREAD_CREATE_SIGSTACK ? libbt_install_sigstack() : NULL;
+ void* ret = arg.start_routine(arg.arg);
+ free(sp);
+ return ret;
+}
+
+int pthread_create(pthread_t* thread,
+ const pthread_attr_t* attr,
+ void* (*start_routine)(void*),
+ void* arg) {
+ assert(NEXT_PTHREAD_CREATE);
+
+ struct thread_arg* th_arg = malloc(sizeof(struct thread_arg));
+ th_arg->start_routine = start_routine;
+ th_arg->arg = arg;
+ return NEXT_PTHREAD_CREATE(thread, attr, thread_entry, th_arg);
+}
+
+// -- GLOBAL INITIALIZATION ----------------------------------------------------
+
+static void __attribute__((constructor)) init() {
+ NEXT_PTHREAD_CREATE = dlsym(RTLD_NEXT, "pthread_create");
+ if (NEXT_PTHREAD_CREATE == NULL) {
+ FAIL("failed to get pthread_create");
+ exit(1);
+ }
+
+ if (libbt_install_sighandler() != 0) {
+ FAIL("failed to install signal handler");
+ return;
+ }
+ INFO("installed signal handler");
+
+ // Install signal stack for main thread, and leak it leak.
+ libbt_install_sigstack();
+
+ // Only create signal stacks in threads if the signal handler was installed.
+ THREAD_CREATE_SIGSTACK = 1;
+}
diff --git a/example/raise1.c b/example/raise1.c
new file mode 100644
index 0000000..a0be4f5
--- /dev/null
+++ b/example/raise1.c
@@ -0,0 +1,17 @@
+#include <signal.h>
+
+// Raise from main thread.
+
+void foo() {
+ raise(SIGSEGV);
+}
+void bar() {
+ foo();
+}
+void qux() {
+ bar();
+}
+
+int main() {
+ qux();
+}
diff --git a/example/raise2.c b/example/raise2.c
new file mode 100644
index 0000000..056a341
--- /dev/null
+++ b/example/raise2.c
@@ -0,0 +1,25 @@
+#include <pthread.h>
+#include <signal.h>
+
+// Raise from different thread.
+
+void foo() {
+ raise(SIGSEGV);
+}
+void bar() {
+ foo();
+}
+void qux() {
+ bar();
+}
+
+void* thread(void*) {
+ qux();
+ return 0;
+}
+
+int main() {
+ pthread_t th;
+ pthread_create(&th, NULL, thread, NULL);
+ pthread_join(th, NULL);
+}
diff --git a/example/recurse1.c b/example/recurse1.c
new file mode 100644
index 0000000..e5a86bc
--- /dev/null
+++ b/example/recurse1.c
@@ -0,0 +1,17 @@
+// Stack overflow on main thread.
+
+void qux();
+
+void foo() {
+ qux();
+}
+void bar() {
+ foo();
+}
+void qux() {
+ bar();
+}
+
+int main() {
+ foo();
+}
diff --git a/example/recurse2.c b/example/recurse2.c
new file mode 100644
index 0000000..1fe1c4d
--- /dev/null
+++ b/example/recurse2.c
@@ -0,0 +1,26 @@
+#include <pthread.h>
+
+// Stack overflow on different thread.
+
+void qux();
+
+void foo() {
+ qux();
+}
+void bar() {
+ foo();
+}
+void qux() {
+ bar();
+}
+
+void* thread(void*) {
+ foo();
+ return 0;
+}
+
+int main() {
+ pthread_t th;
+ pthread_create(&th, NULL, thread, NULL);
+ pthread_join(th, NULL);
+}