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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
#include <execinfo.h>
#include <regex.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <caml/mlvalues.h>
#define ARR_SIZE(a) (sizeof(a) / sizeof(*(a)))
#if defined(__APPLE__)
#define RE_FUNC_NAME "^[[:digit:]]+[[:space:]]+[[:alnum:]_\\.]+[[:space:]]+0x[[:xdigit:]]+[[:space:]]([[:alnum:]_\\.]+).*$"
#else
#define RE_FUNC_NAME "^.*\\((.+)\\+0x[[:xdigit:]]+\\) \\[0x[[:xdigit:]]+\\]$"
#endif
#define RE_TRIM_FUNC "(caml.*)_[[:digit:]]+"
#define CAML_ENTRY "caml_program"
typedef struct frame_info
{
struct frame_info* prev; /* rbp */
void* retaddr; /* rip */
} frame_info;
/*
* A backtrace symbol looks like this on Linux:
* ./path/to/binary(camlModule_fn_123+0xAABBCC) [0xAABBCCDDEE]
*
* or this on macOS:
* 0 c_call.opt 0x000000010e621079 camlC_call.entry + 57
*
*/
static const char* backtrace_symbol(const struct frame_info* fi)
{
char** symbols = backtrace_symbols(&fi->retaddr, 1);
if (!symbols) {
perror("backtrace_symbols");
return NULL;
}
const char* symbol = strdup(symbols[0]);
free(symbols);
return symbol;
}
static regmatch_t func_name_from_symbol(const char* symbol)
{
regex_t regex;
regmatch_t match[2] = { {-1, -1}, {-1, -1}};
char errbuf[128];
int err;
err = regcomp(®ex, RE_FUNC_NAME, REG_EXTENDED);
if (err) {
regerror(err, ®ex, errbuf, ARR_SIZE(errbuf));
fprintf(stderr, "regcomp: %s\n", errbuf);
return match[0];
}
err = regexec(®ex, symbol, ARR_SIZE(match), match, 0);
if (err == REG_NOMATCH)
return match[0];
return match[1];
}
static bool is_caml_entry(const char* symbol, const regmatch_t* funcname)
{
size_t len = funcname->rm_eo - funcname->rm_so;
return strncmp(symbol + funcname->rm_so, CAML_ENTRY, len) == 0;
}
static regmatch_t trim_func_name(const char* symbol, const regmatch_t* funcname)
{
regex_t regex;
regmatch_t match[2] = { {-1, -1}, {-1, -1}};
char errbuf[128];
int err;
err = regcomp(®ex, RE_TRIM_FUNC, REG_EXTENDED);
if (err) {
regerror(err, ®ex, errbuf, ARR_SIZE(errbuf));
fprintf(stderr, "regcomp: %s\n", errbuf);
return match[0];
}
match[0] = *funcname;
err = regexec(®ex, symbol, ARR_SIZE(match), match, REG_STARTEND);
if (err == REG_NOMATCH) {
/* match[0] has already been overwritten to hold the function full name for
regexec */
return match[1];
}
return match[1];
}
static void print_symbol(const char* symbol, const regmatch_t* match)
{
regoff_t off = match->rm_so;
regoff_t len = match->rm_eo - match->rm_so;
fprintf(stdout, "%.*s\n", (int)len, symbol + off);
fflush(stdout);
}
void fp_backtrace(value argv0)
{
const char* execname = String_val(argv0);
const char* symbol = NULL;
for (struct frame_info *fi = __builtin_frame_address(0), *next = NULL;
fi;
fi = next) {
next = fi->prev;
/* Detect the simplest kind of infinite loop */
if (fi == next) {
fprintf(stderr, "fp_backtrace: loop detected\n");
break;
}
symbol = backtrace_symbol(fi);
if (!symbol)
continue;
/* Extract the full function name */
regmatch_t funcname = func_name_from_symbol(symbol);
if (funcname.rm_so == -1)
goto skip;
/* Trim numeric suffix from caml functions */
regmatch_t functrimmed = trim_func_name(symbol, &funcname);
/* Use the trimmed caml name if available, otherwise use the full function
name */
const regmatch_t* match = (functrimmed.rm_so != -1) ?
&functrimmed : &funcname;
print_symbol(symbol, match);
/* Stop the backtrace at caml_program */
if (is_caml_entry(symbol, &funcname))
break;
skip:
free((void*)symbol);
symbol = NULL;
}
if (symbol)
free((void*)symbol);
}
|