/* -*- linux-c -*- */

/* Taken from
 * http://tlug.up.ac.za/wiki/index.php/Obtaining_a_stack_trace_in_C_upon_SIGSEGV
 * and tailored to Aqualung.
 */

/* $Id: segv.c 1067 2009-07-24 09:35:15Z peterszilagyi $ */

#include <config.h>

#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>

#ifdef DEBUG_BUILD

#include <ucontext.h>
#include <dlfcn.h>
#include <execinfo.h>

#include "version.h"

#endif /* DEBUG_BUILD */

#include "segv.h"

#ifdef DEBUG_BUILD

#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif

#if defined(__ppc__) || defined(__powerpc__)
#define UC_MCONTEXT_GREGS uc_mcontext.uc_regs->gregs
#else
#define UC_MCONTEXT_GREGS uc_mcontext.gregs
#endif


static void
signal_segv(int signum, siginfo_t * info, void * ptr) {

	static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};

	unsigned i;
	ucontext_t *ucontext = (ucontext_t*)ptr;

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
	int f = 0;
	Dl_info dlinfo;
	void **bp = 0;
	void *ip = 0;
#else
	void *bt[20];
	char **strings;
	size_t sz;
#endif

	fprintf(stderr, "===[ CRASH REPORT ]======\n");
	fprintf(stderr, "Please mail this to <aqualung-friends@lists.sourceforge.net>\n");
	fprintf(stderr, "along with a short description of what you were doing when\n");
	fprintf(stderr, "the program crashed. Please also send the output of `aqualung -v'.\n");
	fprintf(stderr, "Thank you in advance!\n\n");
	fprintf(stderr, "  Aqualung %s\n\n", AQUALUNG_VERSION);
	fprintf(stderr, "  si_signo = %d (%s)\n", signum, strsignal(signum));
	fprintf(stderr, "  si_errno = %d\n", info->si_errno);
	fprintf(stderr, "  si_code  = %d (%s)\n", info->si_code, si_codes[info->si_code]);
	fprintf(stderr, "  si_addr  = %p\n\n", info->si_addr);

	for (i = 0; i < NGREG; i++) {
		fprintf(stderr, "  R[%02u] = 0x" REGFORMAT,
			i, ucontext->UC_MCONTEXT_GREGS[i]);
		if ((i % 4) == 3) {
			fprintf(stderr, "\n");
		}
	}
	fprintf(stderr, "\n\n");

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
# if defined(SIGSEGV_STACK_IA64)
	ip = (void*)ucontext->uc_mcontext.gregs[REG_RIP];
	bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP];
# elif defined(SIGSEGV_STACK_X86)
	ip = (void*)ucontext->uc_mcontext.gregs[REG_EIP];
	bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP];
# endif

	while (bp && ip) {
		if (!dladdr(ip, &dlinfo))
			break;

		const char *symname = dlinfo.dli_sname;
		fprintf(stderr, "%3d: %12p <%s + %u> (%s)\n",
			++f,
			ip,
			symname,
			(unsigned)(ip - dlinfo.dli_saddr),
			dlinfo.dli_fname);

		if(dlinfo.dli_sname && !strcmp(dlinfo.dli_sname, "main"))
			break;

		ip = bp[1];
		bp = (void**)bp[0];
	}
#else
	fprintf(stderr, "  backtrace():\n");
	sz = backtrace(bt, 20);
	strings = backtrace_symbols(bt, sz);

	for(i = 0; i < sz; ++i)
		fprintf(stderr, "%s\n", strings[i]);
#endif
	fprintf(stderr, "===[ END OF CRASH REPORT ]======\n");
	exit (-1);
}

#endif /* DEBUG_BUILD */

#ifdef RELEASE_BUILD
static void
signal_segv(int signum, siginfo_t * info, void * ptr) {

	fprintf(stderr, "Aqualung received signal %d (%s).\n\n",
		signum, strsignal(signum));
#if defined(_WIN32) || defined (__FreeBSD__) || defined (__OpenBSD__)
	fprintf(stderr, "If you were running on Linux, you could help the\n");
	fprintf(stderr, "developers by sending them the backtrace you'd get\n");
	fprintf(stderr, "instead of this message...\n");
#else
	fprintf(stderr, "To help the developers fix the bug causing this crash,\n");
	fprintf(stderr, "please do the following:\n\n");
	fprintf(stderr, "1) configure & make Aqualung with --enable-debug\n");
	fprintf(stderr, "2) reproduce the crash\n");
	fprintf(stderr, "3) send the crash report to the developers\n\n");
	fprintf(stderr, "Thank you for supporting Aqualung!\n");
#endif /* _WIN32 || __FreeBSD__ || __OpenBSD__ */
	exit(-1);
}
#endif /* RELEASE_BUILD */

int
setup_sigsegv() {
	struct sigaction action;
	memset(&action, 0, sizeof(action));
	action.sa_sigaction = signal_segv;
	action.sa_flags = SA_SIGINFO;

	if (sigaction(SIGFPE, &action, NULL) < 0) {
		perror("sigaction");
		return 0;
	}
	if (sigaction(SIGILL, &action, NULL) < 0) {
		perror("sigaction");
		return 0;
	}
	if (sigaction(SIGSEGV, &action, NULL) < 0) {
		perror("sigaction");
		return 0;
	}
	if (sigaction(SIGBUS, &action, NULL) < 0) {
		perror("sigaction");
		return 0;
	}
	if (sigaction(SIGABRT, &action, NULL) < 0) {
		perror("sigaction");
		return 0;
	}
	return 1;
}

#ifndef SIGSEGV_NO_AUTO_INIT
static void
__attribute((constructor))
init(void) {
	setup_sigsegv();
}
#endif

// vim: shiftwidth=8:tabstop=8:softtabstop=8 :

