aboutsummaryrefslogtreecommitdiff
path: root/src/stdlib/stacktrace.c
blob: 2a54125c0895ff6606eaee24144f5c08392cddf5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <dlfcn.h>
#include <err.h>
#include <execinfo.h>
#include <gc.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../config.h"
#include "print.h"
#include "simpleparse.h"
#include "util.h"

extern bool USE_COLOR;

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) {
            int w = 1;
            for (int n = cur_line; n >= 10; n /= 10)
                w += 1;

            if (USE_COLOR) {
                fprint(out, cur_line == lineno ? "\033[31;1m>\033[m " : "  ", "\033[2m",
                       repeated_char(' ', num_width - w), cur_line, "\033(0\x78\033(B",
                       cur_line == lineno ? "\033[0;31;1m" : "\033[0m", line, "\033[m");
            } else {
                fprint(out, cur_line == lineno ? "> " : "  ", repeated_char(' ', num_width - w), cur_line, "| ", line);
            }
        }

        cur_line += 1;
        if (cur_line > lineno + context_after) break;
    }
    if (line) free(line);
    fclose(f);
}

static void _print_stack_frame(FILE *out, const char *cwd, const char *install_dir, const char *function,
                               const char *filename, int lineno) {
    if (function == NULL) {
        fprint(out, USE_COLOR ? "\033[2m...unknown function...\033[m" : "...unknown function...");
        return;
    }

    function = String(string_slice(function, strcspn(function, "+")));
    if (function[0] == '\0') function = "???";

    char *function_display = GC_MALLOC_ATOMIC(strlen(function));
    memcpy(function_display, function, strlen(function) + 1);
    char *last_dollar = strrchr(function_display, '$');
    if (last_dollar) *last_dollar = '\0';
    for (char *p = function_display; *p; p++) {
        if (*p == '$') *p = '.';
    }

    if (filename) {
        if (strncmp(filename, cwd, strlen(cwd)) == 0) filename += strlen(cwd);

        fprint_inline(out, USE_COLOR ? "\033[1mIn \033[33m" : "In ", function_display, USE_COLOR ? "()\033[37m" : "()");
        if (install_dir[0] && strncmp(filename, install_dir, strlen(install_dir)) == 0)
            fprint_inline(out, USE_COLOR ? " in library \033[35m" : " in library ", filename, ":", lineno);
        else fprint(out, USE_COLOR ? " in \033[35m" : " in ", filename, ":", lineno);
        fprint(out, USE_COLOR ? "\033[m" : "");
        fprint_context(out, filename, lineno, 3, 1);
    } else {
        fprint(out, "LINE: ", function);
    }
}

__attribute__((noinline)) public
void print_stacktrace(FILE *out, int offset) {
    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';

    const char *install_dir = TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/";

    static void *stack[1024];
    int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0]));
    char **strings = backtrace_symbols(stack, size);
    bool main_func_onwards = false;
    for (int64_t i = size - 1; i > offset; i--) {
        Dl_info info;
        void *call_address = stack[i] - 1;
        if (dladdr(call_address, &info) && info.dli_fname) {
            const char *file = info.dli_fname;
            uintptr_t frame_offset = (uintptr_t)call_address - (uintptr_t)info.dli_fbase;
            FILE *fp = popen(String("addr2line -f -e '", file, "' ", (void *)frame_offset, " 2>/dev/null"), "r");
            if (fp) {
                const char *function = NULL, *filename = NULL;
                long line_num = 0;
                if (fparse(fp, &function, "\n", &filename, ":", &line_num) == NULL) {
                    if (starts_with(function, "main$")) main_func_onwards = true;
                    if (main_func_onwards) _print_stack_frame(out, cwd, install_dir, function, filename, line_num);
                } else {
                    if (main_func_onwards) _print_stack_frame(out, cwd, install_dir, NULL, NULL, line_num);
                }
                pclose(fp);
            }
        } else {
            if (main_func_onwards) _print_stack_frame(out, cwd, install_dir, NULL, NULL, 0);
        }
        if (main_func_onwards && i - 1 > offset) fputs("\n", out);
    }
    free(strings);
}