//
// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// interpose.c --
//
// Author           : Thierry Delisle
// Created On       : Wed Mar 29 16:10:31 2017
// Last Modified By : Peter A. Buhr
// Last Modified On : Sat May  5 11:37:35 2018
// Update Count     : 111
//

#include <stdarg.h>										// va_start, va_end
#include <string.h>										// strlen
#include <unistd.h>										// _exit, getpid
#define __USE_GNU
#include <signal.h>
#undef __USE_GNU
extern "C" {
#include <dlfcn.h>										// dlopen, dlsym
#include <execinfo.h>									// backtrace, messages
}

#include "bits/debug.h"
#include "bits/defs.h"
#include "bits/signal.h"								// sigHandler_?
#include "startup.h"									// STARTUP_PRIORITY_CORE

//=============================================================================================
// Interposing helpers
//=============================================================================================

void preload_libgcc(void) {
	dlopen( "libgcc_s.so.1", RTLD_NOW );
	if ( const char * error = dlerror() ) abort( "interpose_symbol : internal error pre-loading libgcc, %s\n", error );
}

typedef void (* generic_fptr_t)(void);
generic_fptr_t interpose_symbol( const char * symbol, const char * version ) {
	const char * error;

	static void * library;
	if ( ! library ) {
		#if defined( RTLD_NEXT )
			library = RTLD_NEXT;
		#else
			// missing RTLD_NEXT => must hard-code library name, assuming libstdc++
			library = dlopen( "libc.so.6", RTLD_LAZY );
			error = dlerror();
			if ( error ) {
				abort( "interpose_symbol : failed to open libc, %s\n", error );
			}
		#endif
	} // if

	union { generic_fptr_t fptr; void * ptr; } originalFunc;

	#if defined( _GNU_SOURCE )
		if ( version ) {
			originalFunc.ptr = dlvsym( library, symbol, version );
		} else {
			originalFunc.ptr = dlsym( library, symbol );
		}
	#else
		originalFunc.ptr = dlsym( library, symbol );
	#endif // _GNU_SOURCE

	error = dlerror();
	if ( error ) abort( "interpose_symbol : internal error, %s\n", error );

	return originalFunc.fptr;
}

#define INTERPOSE_LIBC( x, ver ) __cabi_libc.x = (typeof(__cabi_libc.x))interpose_symbol( #x, ver )

//=============================================================================================
// Interposition Startup logic
//=============================================================================================

void sigHandler_segv ( __CFA_SIGPARMS__ );
void sigHandler_ill  ( __CFA_SIGPARMS__ );
void sigHandler_fpe  ( __CFA_SIGPARMS__ );
void sigHandler_abort( __CFA_SIGPARMS__ );
void sigHandler_term ( __CFA_SIGPARMS__ );

struct {
	void (* exit)( int ) __attribute__(( __noreturn__ ));
	void (* abort)( void ) __attribute__(( __noreturn__ ));
} __cabi_libc;

extern "C" {
	void __cfaabi_interpose_startup(void)  __attribute__(( constructor( STARTUP_PRIORITY_CORE ) ));
	void __cfaabi_interpose_startup( void ) {
		const char *version = NULL;

		preload_libgcc();

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
		INTERPOSE_LIBC( abort, version );
		INTERPOSE_LIBC( exit , version );
#pragma GCC diagnostic pop

		// Failure handler
		__cfaabi_sigaction( SIGSEGV, sigHandler_segv , SA_SIGINFO );
		__cfaabi_sigaction( SIGBUS , sigHandler_segv , SA_SIGINFO );
		__cfaabi_sigaction( SIGILL , sigHandler_ill  , SA_SIGINFO );
		__cfaabi_sigaction( SIGFPE , sigHandler_fpe  , SA_SIGINFO );
		__cfaabi_sigaction( SIGABRT, sigHandler_abort, SA_SIGINFO | SA_RESETHAND);
		__cfaabi_sigaction( SIGTERM, sigHandler_term , SA_SIGINFO );
		__cfaabi_sigaction( SIGINT , sigHandler_term , SA_SIGINFO );
	}
}

//=============================================================================================
// Terminating Signals logic
//=============================================================================================

// Forward declare abort after the __typeof__ call to avoid ambiguities
void exit( int status, const char fmt[], ... ) __attribute__(( format(printf, 2, 3), __nothrow__, __leaf__, __noreturn__ ));
void abort( const char fmt[], ... ) __attribute__(( format(printf, 1, 2), __nothrow__, __leaf__, __noreturn__ ));

extern "C" {
	void abort( void ) __attribute__(( __nothrow__, __leaf__, __noreturn__ )) {
		abort( NULL );
	}

	void __cabi_abort( const char fmt[], ... ) __attribute__(( format(printf, 1, 2), __nothrow__, __leaf__, __noreturn__ )) {
		va_list argp;
		va_start( argp, fmt );
		abort( fmt, argp );
		va_end( argp );
	}

	void exit( int status ) __attribute__(( __nothrow__, __leaf__, __noreturn__ )) {
		__cabi_libc.exit( status );
	}
}

void * kernel_abort    ( void ) __attribute__(( __nothrow__, __leaf__, __weak__ )) { return NULL; }
void   kernel_abort_msg( void * data, char * buffer, int size ) __attribute__(( __nothrow__, __leaf__, __weak__ )) {}
int kernel_abort_lastframe( void ) __attribute__(( __nothrow__, __leaf__, __weak__ )) { return 4; }

enum { abort_text_size = 1024 };
static char abort_text[ abort_text_size ];
static int abort_lastframe;

void exit( int status, const char fmt[], ... ) __attribute__(( format(printf, 2, 3), __nothrow__, __leaf__, __noreturn__ )) {
    va_list args;
    va_start( args, fmt );
    vfprintf( stderr, fmt, args );
    va_end( args );
	__cabi_libc.exit( status );
}

void abort( const char fmt[], ... ) __attribute__(( format(printf, 1, 2), __nothrow__, __leaf__, __noreturn__ )) {
	void * kernel_data = kernel_abort();			// must be done here to lock down kernel
	int len;

	abort_lastframe = kernel_abort_lastframe();
	len = snprintf( abort_text, abort_text_size, "Cforall Runtime error (UNIX pid:%ld) ", (long int)getpid() ); // use UNIX pid (versus getPid)
	__cfaabi_dbg_bits_write( abort_text, len );

	if ( fmt ) {
		va_list args;
		va_start( args, fmt );

		len = vsnprintf( abort_text, abort_text_size, fmt, args );
		va_end( args );
		__cfaabi_dbg_bits_write( abort_text, len );

		if ( fmt[strlen( fmt ) - 1] != '\n' ) {		// add optional newline if missing at the end of the format text
			__cfaabi_dbg_bits_write( "\n", 1 );
		}
	}

	kernel_abort_msg( kernel_data, abort_text, abort_text_size );
	__cabi_libc.abort();
}

static void __cfaabi_backtrace() {
	enum {
		Frames = 50,									// maximum number of stack frames
		Start = 8,										// skip first N stack frames
	};

	void * array[Frames];
	size_t size = backtrace( array, Frames );
	char ** messages = backtrace_symbols( array, size );

	// find executable name
	*index( messages[0], '(' ) = '\0';
	__cfaabi_dbg_bits_print_nolock( "Stack back trace for: %s\n", messages[0]);

	for ( int i = Start; i < size - abort_lastframe && messages != NULL; i += 1 ) {
		char * name = NULL, * offset_begin = NULL, * offset_end = NULL;

		for ( char * p = messages[i]; *p; ++p ) {
			//__cfaabi_dbg_bits_print_nolock( "X %s\n", p);
			// find parantheses and +offset
			if ( *p == '(' ) {
				name = p;
			}
			else if ( *p == '+' ) {
				offset_begin = p;
			}
			else if ( *p == ')' ) {
				offset_end = p;
				break;
			}
		}

		// if line contains symbol print it
		int frameNo = i - Start;
		if ( name && offset_begin && offset_end && name < offset_begin ) {
			// delimit strings
			*name++ = '\0';
			*offset_begin++ = '\0';
			*offset_end++ = '\0';

			__cfaabi_dbg_bits_print_nolock( "(%i) %s : %s + %s %s\n", frameNo, messages[i], name, offset_begin, offset_end);
		}
		// otherwise, print the whole line
		else {
			__cfaabi_dbg_bits_print_nolock( "(%i) %s\n", frameNo, messages[i] );
		}
	}
	free( messages );
}

void sigHandler_segv( __CFA_SIGPARMS__ ) {
	abort( "Addressing invalid memory at location %p\n"
			"Possible cause is reading outside the address space or writing to a protected area within the address space with an invalid pointer or subscript.\n",
			sfp->si_addr );
}

void sigHandler_ill( __CFA_SIGPARMS__ ) {
	abort( "Executing illegal instruction at location %p.\n"
			"Possible cause is stack corruption.\n",
			sfp->si_addr );
}

void sigHandler_fpe( __CFA_SIGPARMS__ ) {
	const char * msg;

	choose( sfp->si_code ) {
	  case FPE_INTDIV, FPE_FLTDIV: msg = "divide by zero";
	  case FPE_FLTOVF: msg = "overflow";
	  case FPE_FLTUND: msg = "underflow";
	  case FPE_FLTRES: msg = "inexact result";
	  case FPE_FLTINV: msg = "invalid operation";
	  default: msg = "unknown";
	} // choose
	abort( "Computation error %s at location %p.\n", msg, sfp->si_addr );
}

void sigHandler_abort( __CFA_SIGPARMS__ ) {
	__cfaabi_backtrace();

	// reset default signal handler
	__cfaabi_sigdefault( SIGABRT );

	raise( SIGABRT );
}

void sigHandler_term( __CFA_SIGPARMS__ ) {
	abort( "Application stopped by %s signal.", sig == SIGINT ? "an interrupt (SIGINT)" : "a terminate (SIGTERM)" );
}

// Local Variables: //
// mode: c //
// tab-width: 4 //
// End: //
