Last active
March 31, 2022 01:10
-
-
Save homuler/9fdfc483aef64dcc69d542669f1bc2cf to your computer and use it in GitHub Desktop.
PoC code to show how to print stack traces in C with line numbers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a PoC code to show how to print stack traces in C with line numbers. | |
// This code is based on [glibc/debug/backtracesymsfd.c](https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=debug/backtracesymsfd.c;hb=244b415d386487521882debb845a040a4758cb18) | |
/* Copyright (C) 1998-2022 Free Software Foundation, Inc. | |
This file is part of the GNU C Library. | |
The GNU C Library is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Lesser General Public | |
License as published by the Free Software Foundation; either | |
version 2.1 of the License, or (at your option) any later version. | |
The GNU C Library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public | |
License along with the GNU C Library; if not, see | |
<https://www.gnu.org/licenses/>. */ | |
/* | |
Usage: | |
gcc -g -ldl -ldw backtrace.c | |
./a.out | |
Sample Output: | |
./a.out(baz+0x1ea4)[0x55bf9cb66ea4] (at /path/to/backtrace.c:216,3) | |
./a.out(bar+0x1ecd)[0x55bf9cb66ecd] (at /path/to/backtrace.c:220,3) | |
./a.out(foo+0x1ede)[0x55bf9cb66ede] (at /path/to/backtrace.c:224,3) | |
./a.out(main+0x1f03)[0x55bf9cb66f03] (at /path/to/backtrace.c:230,3) | |
/usr/lib/libc.so.6(__libc_start_call_main+0x2d310)[0x7f34384c9310] | |
/usr/lib/libc.so.6(__libc_start_main+0x81)[0x7f34384c93c1] | |
./a.out(_start+0x1185)[0x55bf9cb66185] | |
Segmentation fault (core dumped) | |
*/ | |
#define _GNU_SOURCE | |
#include <execinfo.h> | |
#include <dlfcn.h> | |
#include <link.h> | |
#include <string.h> | |
#include <sys/uio.h> | |
#include <elfutils/libdwfl.h> | |
#include <signal.h> | |
#include <stdio.h> | |
#define WORD_WIDTH 16 | |
#define MAX_DIGITS 10 | |
static const char* _digits = "0123456789abcdef"; | |
static inline char* __attribute__ ((unused, always_inline)) _itoa_word(unsigned long int value, char* bufend, unsigned int base) { | |
do { | |
*--bufend = _digits[value % base]; | |
} while ((value /= base) != 0); | |
return bufend; | |
} | |
static Dwfl* init_dwfl() { | |
static char *debuginfo_path; | |
static const Dwfl_Callbacks proc_callbacks = { | |
.debuginfo_path = &debuginfo_path, | |
.find_debuginfo = dwfl_standard_find_debuginfo, | |
.find_elf = dwfl_linux_proc_find_elf, | |
}; | |
Dwfl *dwfl = dwfl_begin(&proc_callbacks); | |
if (!dwfl || dwfl_linux_proc_report(dwfl, getpid()) != 0 || dwfl_report_end(dwfl, NULL, NULL) != 0) { | |
return NULL; | |
} | |
return dwfl; | |
} | |
static void print_frame(Dwfl* dwfl, Dwarf_Addr addr) { | |
struct iovec iov[20]; | |
int cnt = 0; | |
char diffbuf[WORD_WIDTH]; | |
char addrbuf[WORD_WIDTH]; | |
char linebuf[MAX_DIGITS]; | |
char colbuf[MAX_DIGITS]; | |
Dl_info info; | |
struct link_map* map; | |
// TODO: probably we should read DWARF sections. | |
if (dladdr1((void *)addr, &info, (void *)&map, RTLD_DL_LINKMAP) && info.dli_fname != NULL && info.dli_fname[0] != '\0') { | |
/* Name of the file. */ | |
iov[cnt].iov_base = (void *)info.dli_fname; | |
iov[cnt++].iov_len = strlen(info.dli_fname); | |
if (info.dli_sname != NULL || map->l_addr != 0) { | |
size_t diff; | |
iov[cnt].iov_base = (void *) "("; | |
iov[cnt++].iov_len = 1; | |
if (info.dli_sname != NULL) { | |
/* We have a symbol name. */ | |
iov[cnt].iov_base = (void *)info.dli_sname; | |
iov[cnt++].iov_len = strlen(info.dli_sname); | |
} else { | |
if (dwfl) { | |
Dwfl_Module* module = dwfl_addrmodule(dwfl, addr); | |
const char* symbol = dwfl_module_addrname(module, addr); | |
if (symbol) { | |
iov[cnt].iov_base = (void *)symbol; | |
iov[cnt++].iov_len = strlen(symbol); | |
} | |
} | |
/* We have no symbol */ | |
info.dli_saddr = (void *)map->l_addr; | |
} | |
if ((void *)addr >= (void *)info.dli_saddr) { | |
iov[cnt].iov_base = (void *)"+0x"; | |
diff = (void *)addr - info.dli_saddr; | |
} else { | |
iov[cnt].iov_base = (void *)"-0x"; | |
diff = info.dli_saddr - (void *)addr; | |
} | |
iov[cnt++].iov_len = 3; | |
iov[cnt].iov_base = _itoa_word((unsigned long int) diff, &diffbuf[WORD_WIDTH], 16); | |
iov[cnt].iov_len = (&diffbuf[WORD_WIDTH] - (char *)iov[cnt].iov_base); | |
++cnt; | |
iov[cnt].iov_base = (void *)")"; | |
iov[cnt++].iov_len = 1; | |
} | |
} | |
iov[cnt].iov_base = (void *)"[0x"; | |
iov[cnt++].iov_len = 3; | |
iov[cnt].iov_base = _itoa_word((unsigned long int)addr, &addrbuf[WORD_WIDTH], 16); | |
iov[cnt].iov_len = &addrbuf[WORD_WIDTH] - (char *)iov[cnt].iov_base; | |
++cnt; | |
iov[cnt].iov_base = (void *)"]"; | |
iov[cnt++].iov_len = 1; | |
if (dwfl) { | |
Dwfl_Line* line = dwfl_getsrc(dwfl, addr - 1); | |
if (line) { | |
int linep; | |
int colp; | |
const char* filename = dwfl_lineinfo(line, NULL, &linep, &colp, NULL, NULL); | |
if (filename) { | |
iov[cnt].iov_base = (void *)" (at "; | |
iov[cnt++].iov_len = 5; | |
iov[cnt].iov_base = (void *)filename; | |
iov[cnt++].iov_len = strlen(filename); | |
iov[cnt].iov_base = (void *)":"; | |
iov[cnt++].iov_len = 1; | |
iov[cnt].iov_base = _itoa_word(linep, &linebuf[MAX_DIGITS], 10); | |
iov[cnt].iov_len = &linebuf[MAX_DIGITS] - (char *)iov[cnt].iov_base; | |
++cnt; | |
iov[cnt].iov_base = (void *)","; | |
iov[cnt++].iov_len = 1; | |
iov[cnt].iov_base = _itoa_word(colp, &colbuf[MAX_DIGITS], 10); | |
iov[cnt].iov_len = &colbuf[MAX_DIGITS] - (char *)iov[cnt].iov_base; | |
++cnt; | |
iov[cnt].iov_base = (void *)")"; | |
iov[cnt++].iov_len = 1; | |
} | |
} | |
} | |
iov[cnt].iov_base = (void *)"\n"; | |
iov[cnt++].iov_len = 1; | |
writev(STDERR_FILENO, iov, cnt); | |
} | |
void invoke_default_handler(int sig) { | |
struct sigaction sig_action; | |
memset(&sig_action, 0, sizeof(sig_action)); | |
sigemptyset(&sig_action.sa_mask); | |
sig_action.sa_handler = SIG_DFL; | |
sigaction(sig, &sig_action, NULL); | |
kill(getpid(), sig); | |
} | |
void handler(int sig) { | |
void *array[30]; | |
size_t size; | |
size = backtrace(array, 30); | |
Dwfl* dwfl = init_dwfl(); | |
int cnt; | |
// skip signal handler's stack frames | |
for (cnt = 2; cnt < size; ++cnt) { | |
print_frame(dwfl, (Dwarf_Addr)array[cnt]); | |
} | |
invoke_default_handler(sig); | |
} | |
// The following code is for testing purposes. | |
void baz() { | |
int *foo = (int*)-1; | |
printf("%d\n", *foo); // SIGSEGV | |
} | |
void bar() { | |
baz(); | |
} | |
void foo() { | |
bar(); | |
} | |
int main() { | |
signal(SIGSEGV, handler); | |
foo(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment