diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-04-12 13:44:04 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-04-12 13:44:04 -0400 |
| commit | 90a323e3b34cfecdf9df55d8a03d03649609f162 (patch) | |
| tree | c9dcf3b40e6b0de736f0c38864383a01b369439c | |
| parent | ba555a8aca47a78acb173780fd252b19d3cdd36e (diff) | |
Replace addr2line with libbacktrace to get improved stack traces
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rwxr-xr-x | install_dependencies.sh | 26 | ||||
| -rw-r--r-- | src/environment.h | 5 | ||||
| -rw-r--r-- | src/parse.c | 3 | ||||
| -rw-r--r-- | src/stdlib/stacktrace.c | 126 | ||||
| -rw-r--r-- | src/stdlib/stacktrace.h | 5 | ||||
| -rw-r--r-- | src/stdlib/stdlib.c | 78 | ||||
| -rw-r--r-- | src/stdlib/stdlib.h | 18 | ||||
| -rw-r--r-- | src/stdlib/tomo.h | 3 | ||||
| -rw-r--r-- | src/tomo.c | 4 |
11 files changed, 176 insertions, 104 deletions
@@ -43,7 +43,7 @@ G=-ggdb O=-Og CFLAGS=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) CFLAGS_PLACEHOLDER="$$(printf '\033[2m<flags...>\033[m\n')" -LDLIBS=-lgc -lcord -lm -lunistring -lgmp +LDLIBS=-lgc -lcord -lm -lunistring -lgmp -lbacktrace LIBTOMO_FLAGS=-shared ifeq ($(OS),OpenBSD) @@ -123,7 +123,7 @@ check-gcc: exit 1; \ fi -install: build/tomo build/$(LIB_FILE) +install-files: build/tomo build/$(LIB_FILE) @if ! echo "$$PATH" | tr ':' '\n' | grep -qx "$(PREFIX)/bin"; then \ printf "\033[31;1mError: '$(PREFIX)' is not in your \$$PATH variable!\033[m\n" >&2; \ printf "\033[31;1mSpecify a different prefix with 'make PREFIX=... install'\033[m\n" >&2; \ @@ -137,10 +137,14 @@ install: build/tomo build/$(LIB_FILE) rm -f "$(PREFIX)/bin/tomo" cp -v build/tomo "$(PREFIX)/bin/" cp -v docs/tomo.1 "$(PREFIX)/man/man1/" + +install-libs: build/tomo ./build/tomo -qIL lib/patterns lib/time lib/commands lib/shell lib/random lib/base64 lib/pthreads lib/uuid lib/core +install: install-files install-libs + uninstall: rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/include/tomo" "$(PREFIX)/lib/$(LIB_FILE) "$(PREFIX)/share/tomo"; \ .SUFFIXES: -.PHONY: all clean install uninstall test tags examples deps check-gcc +.PHONY: all clean install install-files install-libs uninstall test tags examples deps check-gcc @@ -92,7 +92,7 @@ Tomo has a very small set of dependencies: - [GNU multiple precision arithmetic library](https://gmplib.org/manual/index) for arbitrary precision integer math (version 6.2.1 or higher) - [Patchelf](https://github.com/NixOS/patchelf) for building tomo libraries -- [Binutils](https://www.gnu.org/software/binutils/) for stack traces. +- [libbacktrace](https://github.com/ianlancetaylor/libbacktrace) for stack traces. - and libc/libm, which should definitely already be installed. If you're feeling incautious, you can run `make deps` or diff --git a/install_dependencies.sh b/install_dependencies.sh index 27d04416..96dfe404 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -48,19 +48,19 @@ fi # Install packages case "$PKG_MGR" in - apt) $SUDO apt install libgc-dev libunistring-dev binutils patchelf libgmp-dev ;; - dnf) $SUDO dnf install gc-devel libunistring-devel binutils patchelf gmp-devel ;; - pacman) $SUDO pacman -S gc libunistring binutils patchelf gmp ;; - yay|paru) $PKG_MGR -S gc libunistring binutils patchelf gmp ;; - xbps) $SUDO xbps-install -S gc libunistring binutils patchelf gmp ;; - pkg_add) $SUDO pkg_add boehm-gc libunistring binutils patchelf gmp ;; - freebsd-pkg) $SUDO pkg install boehm-gc libunistring binutils patchelf gmp ;; - brew) brew install bdw-gc libunistring binutils patchelf gmp ;; - macports) $SUDO port install boehm-gc libunistring binutils patchelf gmp ;; - zypper) $SUDO zypper install gc-devel libunistring-devel binutils patchelf gmp-devel ;; - nix) nix-env -iA nixpkgs.boehm-gc nixpkgs.libunistring nixpkgs.binutils nixpkgs.patchelf nixpkgs.gmp ;; - spack) spack install boehm-gc libunistring binutils patchelf gmp ;; - conda) conda install boehm-gc libunistring binutils patchelf gmp ;; + apt) $SUDO apt install libgc-dev libunistring-dev libbacktrace patchelf libgmp-dev ;; + dnf) $SUDO dnf install gc-devel libunistring-devel libbacktrace patchelf gmp-devel ;; + pacman) $SUDO pacman -S gc libunistring libbacktrace patchelf gmp ;; + yay|paru) $PKG_MGR -S gc libunistring libbacktrace patchelf gmp ;; + xbps) $SUDO xbps-install -S gc libunistring libbacktrace patchelf gmp ;; + pkg_add) $SUDO pkg_add boehm-gc libunistring libbacktrace patchelf gmp ;; + freebsd-pkg) $SUDO pkg install boehm-gc libunistring libbacktrace patchelf gmp ;; + brew) brew install bdw-gc libunistring libbacktrace patchelf gmp ;; + macports) $SUDO port install boehm-gc libunistring libbacktrace patchelf gmp ;; + zypper) $SUDO zypper install gc-devel libunistring-devel libbacktrace patchelf gmp-devel ;; + nix) nix-env -iA nixpkgs.boehm-gc nixpkgs.libunistring nixpkgs.libbacktrace nixpkgs.patchelf nixpkgs.gmp ;; + spack) spack install boehm-gc libunistring libbacktrace patchelf gmp ;; + conda) conda install boehm-gc libunistring libbacktrace patchelf gmp ;; *) echo "Unknown package manager: $PKG_MGR" >&2 exit 1 diff --git a/src/environment.h b/src/environment.h index 1484a4c4..f9ddd6b0 100644 --- a/src/environment.h +++ b/src/environment.h @@ -4,10 +4,11 @@ #include <gc/cord.h> -#include "types.h" #include "stdlib/print.h" +#include "stdlib/stacktrace.h" #include "stdlib/stdlib.h" #include "stdlib/tables.h" +#include "types.h" typedef struct { CORD local_typedefs; @@ -79,7 +80,7 @@ env_t *namespace_env(env_t *env, const char *namespace_name); if (_f && start && end) \ highlight_error(_f, start, end, "\x1b[31;1m", 2, USE_COLOR); \ if (getenv("TOMO_STACKTRACE")) \ - print_stack_trace(stderr, 1, 3); \ + print_stacktrace(stderr, 1); \ raise(SIGABRT); \ exit(1); \ }) diff --git a/src/parse.c b/src/parse.c index fd37fec7..d6ff4e5a 100644 --- a/src/parse.c +++ b/src/parse.c @@ -24,6 +24,7 @@ #include "stdlib/integers.h" #include "stdlib/paths.h" #include "stdlib/print.h" +#include "stdlib/stacktrace.h" #include "stdlib/stdlib.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -163,7 +164,7 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, ch highlight_error((ctx)->file, (start), (end), "\x1b[31;1;7m", 2, USE_COLOR); \ fputs("\n", stderr); \ if (getenv("TOMO_STACKTRACE")) \ - print_stack_trace(stderr, 1, 3); \ + print_stacktrace(stderr, 1); \ if ((ctx)->on_err) \ longjmp(*((ctx)->on_err), 1); \ raise(SIGABRT); \ diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c new file mode 100644 index 00000000..bd867c7b --- /dev/null +++ b/src/stdlib/stacktrace.c @@ -0,0 +1,126 @@ +#include <backtrace.h> +#include <err.h> +#include <gc.h> +#include <signal.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +extern bool USE_COLOR; + +static struct backtrace_state *bt_state = NULL; + +static void fprint_context(FILE *out, const char *filename, int lineno, int context_before, int context_after) +{ + FILE *f = fopen(filename, "r"); + if (!f) return; + char *line = NULL; + size_t size = 0; + ssize_t nread; + int64_t cur_line = 1; + + int num_width = 1; + for (int n = lineno + context_after; n >= 10; n /= 10) + num_width += 1; + + while ((nread = getline(&line, &size, f)) != -1) { + if (line[strlen(line)-1] == '\n') + line[strlen(line)-1] = '\0'; + + if (cur_line >= lineno - context_before) + fprintf(out, USE_COLOR ? "%s\033[2m%*d\033(0\x78\x1b(B%s%s\033[m\n" : "%s%*d| %s%s\n", + cur_line == lineno ? (USE_COLOR ? "\033[31;1m>\033[m " : "> ") : " ", + num_width, cur_line, USE_COLOR ? (cur_line == lineno ? "\033[0;31;1m" : "\033[0m") : "", line); + + cur_line += 1; + if (cur_line > lineno + context_after) + break; + } + if (line) free(line); + fclose(f); +} + +typedef struct stack_info_s { + const char *function, *filename; + int lineno; + struct stack_info_s *next; +} stack_info_t; + +// Simple callback to print each frame +static int print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) +{ + (void)pc; + stack_info_t *info = GC_MALLOC(sizeof(stack_info_t)); + info->next = *(stack_info_t**)data; + info->function = function; + info->filename = filename; + info->lineno = lineno; + *(stack_info_t**)data = info; + return 0; +} + +public void print_stacktrace(FILE *out, int offset) +{ + stack_info_t *backwards = NULL; + + backtrace_full(bt_state, offset, print_callback, NULL, &backwards); + + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == NULL) + errx(1, "Path too large!"); + size_t cwd_len = strlen(cwd); + if (cwd_len + 2 > sizeof(cwd)) + errx(1, "Path too large!"); + cwd[cwd_len++] = '/'; + cwd[cwd_len] = '\0'; + + // Skip C entrypoint stuff: + while (backwards && backwards->function == NULL) + backwards = backwards->next; + + if (backwards && strstr(backwards->function, "$parse_and_run")) + backwards = backwards->next; + + for (stack_info_t *frame = backwards; frame; frame = frame->next) { + while (frame && frame->function == NULL) { + fprintf(out, USE_COLOR ? "\033[2m... unknown function ...\033[m\n" : "... unknown function ...\n"); + if (frame->next) + fprintf(out, "\n"); + frame = frame->next; + } + if (frame == NULL) break; + + char *function_display = GC_MALLOC_ATOMIC(strlen(frame->function)); + memcpy(function_display, frame->function, strlen(frame->function)+1); + function_display += strcspn(function_display, "$"); + function_display += strspn(function_display, "$"); + function_display += strcspn(function_display, "$"); + function_display += strspn(function_display, "$"); + for (char *p = function_display; *p; p++) { + if (*p == '$') *p = '.'; + } + + if (strncmp(frame->filename, cwd, cwd_len) == 0) + frame->filename += cwd_len; + + fprintf(out, USE_COLOR ? "\033[1mIn \033[33m%s()\033[37m" : "In %s()", function_display); + if (frame->filename) + fprintf(out, USE_COLOR ? " in \033[35m%s:%d" : " in %s:%d", frame->filename, frame->lineno); + fprintf(out, USE_COLOR ? "\033[m\n" : "\n"); + if (frame->filename) + fprint_context(out, frame->filename, frame->lineno, 3, 1); + if (frame->next) + fprintf(out, "\n"); + } +} + +public void initialize_stacktrace(const char *program) +{ + bt_state = backtrace_create_state(program, 1, NULL, NULL); + if (!bt_state) + errx(1, "Failed to create stacktrace state"); +} diff --git a/src/stdlib/stacktrace.h b/src/stdlib/stacktrace.h new file mode 100644 index 00000000..e3e04814 --- /dev/null +++ b/src/stdlib/stacktrace.h @@ -0,0 +1,5 @@ +#pragma once +#include <stdio.h> + +void initialize_stacktrace(const char *program); +void print_stacktrace(FILE *out, int offset); diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index e8cfe06a..6332c69d 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -12,16 +12,17 @@ #include <sys/param.h> #include <time.h> -#include "print.h" #include "bools.h" #include "files.h" #include "functiontype.h" #include "integers.h" -#include "optionals.h" #include "metamethods.h" #include "nums.h" +#include "optionals.h" #include "paths.h" +#include "print.h" #include "siphash.h" +#include "stacktrace.h" #include "stdlib.h" #include "tables.h" #include "text.h" @@ -48,7 +49,7 @@ static _Noreturn void signal_handler(int sig, siginfo_t *, void *) fflush(stdout); if (USE_COLOR) fputs("\x1b[31;7m ===== ILLEGAL INSTRUCTION ===== \n\n\x1b[m", stderr); else fputs("===== ILLEGAL INSTRUCTION =====\n\n", stderr); - print_stack_trace(stderr, 3, 4); + print_stacktrace(stderr, 3); fflush(stderr); raise(SIGABRT); _exit(1); @@ -441,75 +442,6 @@ public void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, #pragma GCC diagnostic pop #endif -static void print_stack_line(FILE *out, OptionalText_t fn_name, const char *filename, int64_t line_num) -{ - // NOTE: this function is a bit inefficient. Each time we print a line, we - // do a linear scan through the whole file. However, performance shouldn't - // really matter if we only print stack lines when there's a crash. - if (filename) { - fprint_inline(out, "\033[34mFile\033[m \033[35;1m", filename, "\033[m"); - if (line_num >= 1) - fprint_inline(out, "\033[34m line\033[m \033[35;1m", line_num, "\033[m"); - } - if (fn_name.length > 0) { - if (filename) - fprint_inline(out, "\033[34m, in \033[m \033[36;1m", fn_name, "\033[m"); - else - fprint_inline(out, "\033[36;1m", fn_name, "\033[m"); - } - fprint_inline(out, "\n"); - - FILE *f = fopen(filename, "r"); - if (!f) return; - char *line = NULL; - size_t size = 0; - ssize_t nread; - int64_t cur_line = 1; - while ((nread = getline(&line, &size, f)) != -1) { - if (line[strlen(line)-1] == '\n') - line[strlen(line)-1] = '\0'; - - if (cur_line >= line_num) - fprint(out, "\033[33;1m", line, "\033[m"); - - cur_line += 1; - if (cur_line > line_num) - break; - } - if (line) free(line); - fclose(f); -} - -public void print_stack_trace(FILE *out, int start, int stop) -{ - // Print stack trace: - void *stack[1024]; - int64_t size = (int64_t)backtrace(stack, sizeof(stack)/sizeof(stack[0])); - char **strings = strings = backtrace_symbols(stack, size); - for (int64_t i = start; i < size - stop; i++) { - char *filename = strings[i]; - char *paren = strings[i] + strcspn(strings[i], "("); - char *addr_end = paren + 1 + strcspn(paren + 1, ")"); - ptrdiff_t offset = strtol(paren + 1, &addr_end, 16) - 1; - const char *cmd = String("addr2line -e ", string_slice(filename, strcspn(filename, "(")), " -is +", hex((uint64_t)offset)); - FILE *fp = popen(cmd, "r"); - OptionalText_t fn_name = get_function_name(stack[i]); - const char *src_filename = NULL; - int64_t line_number = 0; - if (fp) { - char buf[PATH_MAX + 10] = {}; - if (fgets(buf, sizeof(buf), fp)) { - char *saveptr, *line_num_str; - if ((src_filename=strtok_r(buf, ":", &saveptr)) - && (line_num_str=strtok_r(NULL, ":", &saveptr))) - line_number = atoi(line_num_str); - } - pclose(fp); - } - print_stack_line(out, fn_name, src_filename, line_number); - } -} - public _Noreturn void fail_text(Text_t message) { fail(message); @@ -577,7 +509,7 @@ public void test_value(const char *filename, int64_t start, int64_t end, const v bool success = Text$equal_values(expr_text, expected_text); if (!success) { - print_stack_trace(stderr, 2, 4); + print_stacktrace(stderr, 2); fprint(stderr, ""); fflush(stderr); diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index aaa4a1d6..51983bc8 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -10,6 +10,7 @@ #include "datatypes.h" #include "files.h" #include "print.h" +#include "stacktrace.h" #include "types.h" #include "util.h" @@ -29,12 +30,14 @@ void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, int spe #define fail(...) ({ \ fflush(stdout); \ - if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); \ + if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \ else fputs("==================== ERROR ====================\n\n", stderr); \ - fprint_inline(stderr, __VA_ARGS__); \ - if (USE_COLOR) fputs("\x1b[m", stderr); \ - fputs("\n\n", stderr); \ - print_stack_trace(stderr, 2, 4); \ + print_stacktrace(stderr, 2); \ + if (USE_COLOR) fputs("\n\x1b[31;1m", stderr); \ + else fputs("\n", stderr); \ + fprint_inline(stderr, "Error: ", __VA_ARGS__); \ + if (USE_COLOR) fputs("\x1b[m\n", stderr); \ + else fputs("\n", stderr); \ fflush(stderr); \ raise(SIGABRT); \ _exit(1); \ @@ -53,9 +56,7 @@ void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, int spe fputs("\n", stderr); \ } \ if (USE_COLOR) fputs("\x1b[m", stderr); \ - print_stack_trace(stderr, 2, 4); \ - fputs("\n\n", stderr); \ - print_stack_trace(stderr, 2, 4); \ + print_stacktrace(stderr, 2); \ fflush(stderr); \ raise(SIGABRT); \ _exit(1); \ @@ -86,7 +87,6 @@ _Noreturn void tomo_exit(Text_t text, int32_t status); Closure_t spawn(Closure_t fn); bool pop_flag(char **argv, int *i, const char *flag, Text_t *result); -void print_stack_trace(FILE *out, int start, int stop); void sleep_num(double seconds); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h index 2d8d9908..63abd2d6 100644 --- a/src/stdlib/tomo.h +++ b/src/stdlib/tomo.h @@ -7,7 +7,6 @@ #include <stdint.h> #include <sys/param.h> -#include "lists.h" #include "bools.h" #include "bytes.h" #include "c_strings.h" @@ -15,6 +14,7 @@ #include "enums.h" #include "functiontype.h" #include "integers.h" +#include "lists.h" #include "memory.h" #include "metamethods.h" #include "nums.h" @@ -23,6 +23,7 @@ #include "pointers.h" #include "print.h" #include "siphash.h" +#include "stacktrace.h" #include "structs.h" #include "tables.h" #include "text.h" @@ -64,7 +64,7 @@ static OptionalText_t " -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE -fPIC -ggdb" " -DGC_THREADS" " -I$HOME/.local/include -I$HOME/.local/share/tomo/installed -I/usr/local/include"), - ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo"), + ldlibs = Text("-lgc -lm -lgmp -lunistring -lbacktrace -ltomo"), ldflags = Text("-Wl,-rpath,'$ORIGIN',-rpath,$HOME/.local/share/tomo/lib,-rpath,$HOME/.local/lib,-rpath,/usr/local/lib " "-L$HOME/.local/lib -L$HOME/.local/share/tomo/lib -L/usr/local/lib"), optimization = Text("2"), @@ -759,7 +759,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t paths_str(object_files), " -x c - -o ", exe_path); CORD program = CORD_all( "extern int ", main_binding->code, "$parse_and_run(int argc, char *argv[]);\n" + "extern void initialize_stacktrace(const char *program);\n" "int main(int argc, char *argv[]) {\n" + "\tinitialize_stacktrace(argv[0]);\n" "\treturn ", main_binding->code, "$parse_and_run(argc, argv);\n" "}\n" ); |
