//
// 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 : Mon Feb  5 23:40:04 2018
// Update Count     : 17
//

#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"

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 ) {
				abortf( "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 ) abortf( "interpose_symbol : internal error, %s\n", error );

	return originalFunc.fptr;
}


__typeof__( exit ) libc_exit __attribute__(( noreturn ));
__typeof__( abort ) libc_abort __attribute__(( noreturn ));

forall(dtype T)
static inline void assign_ptr( 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 INIT_REALRTN( x, ver ) assign_ptr( (void**)&libc_##x, #x, ver)

void sigHandler_segv ( __CFA_SIGPARMS__ );
void sigHandler_abort( __CFA_SIGPARMS__ );

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

		INIT_REALRTN( abort, version );
		INIT_REALRTN( exit, version );

		__kernel_sigaction( SIGSEGV, sigHandler_segv , SA_SIGINFO ); // Failure handler
		__kernel_sigaction( SIGBUS , sigHandler_segv , SA_SIGINFO ); // Failure handler
		__kernel_sigaction( SIGABRT, sigHandler_abort, SA_SIGINFO ); // Failure handler
	}
}

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

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

	void exit( int __status ) __attribute__ ((__nothrow__, __leaf__, __noreturn__)) {
		libc_exit(__status);
	}
}

void abort( const char *fmt, va_list argp ) __attribute__ ((__nothrow__, __leaf__, __noreturn__)) {
	abortf( fmt, argp );
}

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

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

extern "C" {
	void abortf( const char fmt[], ... ) __attribute__ ((__nothrow__, __leaf__, __noreturn__)) {
		void * kernel_data = kernel_abort();

		int 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 );
			//__cfaabi_dbg_bits_write( "\n", 1 );
		}

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


		kernel_abort_msg( kernel_data, abort_text, abort_text_size );

		libc_abort();
	}
}

// skip first 6 stack frames by default
static void __kernel_backtrace() {
	// skip first N stack frames
	int start = 6;

	enum { Frames = 50 };
	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]);

	// skip last 2 stack frames after main
	for ( int i = start; i < size && messages != NULL; i += 1 ) {
		char * name = NULL;
		char * offset_begin = NULL;
		char * 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__ ) {
	// skip first only 1 stack frames in case of segfault.
	abortf( "*CFA runtime error* program cfa-cpp terminated with %s\n", sig == SIGSEGV ? "segment fault." : "bus error." );
}

void sigHandler_abort( __CFA_SIGPARMS__ ) {
	__kernel_backtrace();

	// reset default signal handler
	__kernel_sigdefault( SIGABRT );

	raise( SIGABRT );
}

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