//
// 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 : Thu Feb  8 16:18:09 2018
// Update Count     : 75
//

#include <stdarg.h>
#include <stddef.h>

extern "C" {
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>
#define __USE_GNU
#include <signal.h>
#undef __USE_GNU
#include <execinfo.h>
}

#include "bits/debug.h"
#include "bits/defs.h"
#include "bits/signal.h"
#include "startup.h"

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

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;
}

forall(dtype T)
static inline void ptr_from_symbol( T** symbol_ptr, const char * symbol_name, const char * version) {
	union {
		generic_fptr_t gp;
		T* tp;
	} u;

	u.gp = interpose_symbol( symbol_name, version );

	*symbol_ptr = u.tp;
}

#define INTERPOSE_LIBC( x, ver ) ptr_from_symbol( (void**)&__cabi_libc.x, #x, ver)

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

void sigHandler_segv ( __CFA_SIGPARMS__ );
void sigHandler_ill  ( __CFA_SIGPARMS__ );
void sigHandler_fpe  ( __CFA_SIGPARMS__ );
void sigHandler_abort( __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;

		INTERPOSE_LIBC( abort, version );
		INTERPOSE_LIBC( exit , version );

		__cfaabi_sigaction( SIGSEGV, sigHandler_segv , SA_SIGINFO ); // Failure handler
		__cfaabi_sigaction( SIGBUS , sigHandler_segv , SA_SIGINFO ); // Failure handler
		__cfaabi_sigaction( SIGILL , sigHandler_ill  , SA_SIGINFO ); // Failure handler
		__cfaabi_sigaction( SIGFPE , sigHandler_fpe  , SA_SIGINFO ); // Failure handler
		__cfaabi_sigaction( SIGABRT, sigHandler_abort, SA_SIGINFO ); // Failure handler
	}
}

//=============================================================================================
// 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 );
}

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