/* Since our implementation relies on nonstandard functions and headers, we
* provide a dummy one below for compatibility purposes. The dummy version can
* be selected at build time using -DDUMMY_BACKTRACE. */
#ifndef DUMMY_BACKTRACE
/* This is needed for some of the functions below. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <execinfo.h>
#include <link.h>
#include <stdlib.h> /* NULL */
#include <sys/procfs.h>
int get_backtrace(void *buffer, int size)
{
return backtrace(buffer, size);
}
void *get_func_start(void *pc)
{
int r;
Dl_info info;
r = dladdr(pc, &info);
if (r == 0)
return NULL;
return info.dli_saddr;
}
void *get_func_end(void *func)
{
int r;
Dl_info dl_info;
ElfW(Sym) * elf_info;
r = dladdr1(func, &dl_info, (void **)&elf_info, RTLD_DL_SYMENT);
if (r == 0)
return NULL;
if (elf_info == NULL)
return NULL;
if (dl_info.dli_saddr == NULL)
return NULL;
return ((unsigned char *)func) + elf_info->st_size;
}
void *get_func_addr(const char *func_name)
{
return dlsym(RTLD_DEFAULT, func_name);
}
#else
/* Dummy versions */
#warning Using dummy versions of backtrace
#include <stddef.h> /* for NULL */
int get_backtrace(void *buffer, int size)
{
return 0;
}
void *get_func_end(void *pc)
{
return NULL;
}
void *get_func_start(void *pc)
{
return NULL;
}
void *get_func_addr(const char *func_name)
{
return NULL;
}
#endif // DUMMY_BACKTRACE
/* Ugly but useful conversion from function pointer to void *.
* This is not guaranteed by the standard, but has to work on all platforms
* where we support backtrace(), because that function assumes it so. */
static void *fp_to_voidp(void (*funcp)())
{
unsigned char **p;
p = (unsigned char **)&funcp;
return *p;
}
int backtrace_works(void (*caller)())
{
/* We remember the result so we don't have to compute it over an over
* again, we know it doesn't change. */
static int works = -1;
void *start = NULL;
void *end = NULL;
void *bt_buffer[100];
void *pc;
int nptrs, i;
/* Return the result if we know it. */
if (works >= 0)
return works;
nptrs = get_backtrace(bt_buffer, 100);
if (nptrs <= 0) {
works = 0;
return works;
}
/* We will detect if it works by looking for the caller in the
* backtrace. */
start = get_func_start(fp_to_voidp(caller));
end = get_func_end(fp_to_voidp(caller));
if (start == NULL && end == NULL) {
works = 0;
return works;
}
for (i = 0; i < nptrs; i++) {
pc = bt_buffer[i];
/* On some platforms, we have everything except
* get_func_end(), and that's ok. */
if (end) {
if (pc >= start && pc <= end) {
works = 1;
return works;
}
} else {
if (get_func_start(pc) == start) {
works = 1;
return works;
}
}
}
works = 0;
return works;
}