2013-01-18 09:00:23 +01:00
/*
2016-04-19 14:48:14 +02:00
This file implements the fully fledged util_abort ( ) function which
2013-01-18 09:00:23 +01:00
assumes that the current build has the following features :
2013-04-04 08:24:40 +02:00
fork ( ) : To support calling external program addr2line ( ) .
2016-04-19 14:48:14 +02:00
pthread : To serialize the use of util_abort ( ) - not very important .
execinfo . h : The backtrace functions backtrace ( ) and backtrace_symbols ( ) .
2013-04-04 08:24:40 +02:00
_GNU_SOURCE : To get the dladdr ( ) function .
2013-01-18 09:00:23 +01:00
If not all these features are availbale the simpler version in
2016-04-19 14:48:14 +02:00
util_abort_simple . c is built instead .
2013-01-18 09:00:23 +01:00
*/
/**
This function uses the external program addr2line to convert the
hexadecimal adress given by the libc function backtrace ( ) into a
function name and file : line .
Observe that the function is quite involved , so if util_abort ( ) is
called because something is seriously broken , it might very well fail .
The executable should be found from one line in the backtrace with
the function util_bt_alloc_current_executable ( ) , the argument
bt_symbol is the lines generated by the bt_symbols ( ) function .
This function is purely a helper function for util_abort ( ) .
*/
2017-06-22 11:51:46 +02:00
# define __USE_GNU // Must be defined to get access to the dladdr() function; Man page says the symbol should be: _GNU_SOURCE but that does not seem to work?
# define _GNU_SOURCE // Must be defined _before_ #include <errno.h> to get the symbol 'program_invocation_name'.
# include <errno.h>
# include <stdlib.h>
# include <string.h>
# include <ert/util/util.h>
2018-08-13 10:20:34 +02:00
# include <ert/util/test_util.hpp>
2017-06-22 11:51:46 +02:00
# include <stdbool.h>
# include <dlfcn.h>
# include <execinfo.h>
# include <pthread.h>
2016-04-19 14:48:14 +02:00
# include <pwd.h>
2017-06-22 11:51:46 +02:00
# include <signal.h>
# include <unistd.h>
2016-04-19 14:48:14 +02:00
2013-10-07 13:31:13 +02:00
# if !defined(__GLIBC__) /* note: not same as __GNUC__ */
# if defined (__APPLE__)
# include <stdlib.h> /* alloca */
# include <sys / syslimits.h> /* PATH_MAX */
# include <mach-o / dyld.h> /* _NSGetExecutablePath */
# elif defined (__LINUX__)
# include <stdlib.h> /* alloca */
# include <limits.h> /* PATH_MAX */
# include <unistd.h> /* readlink */
# else
# error No known program_invocation_name in runtime library
# endif
# endif
2013-04-04 08:24:40 +02:00
# define UNDEFINED_FUNCTION "??"
static bool util_addr2line_lookup__ ( const void * bt_addr , char * * func_name , char * * file_name , int * line_nr , bool subtract_base_adress ) {
* func_name = NULL ; // If dladdr() succeeds, but addr2line fails the func_name pointer will be set, but the function will return false anyway.
* file_name = NULL ;
* line_nr = 0 ;
2013-01-18 09:00:23 +01:00
{
2013-04-04 08:24:40 +02:00
bool address_found = false ;
2014-10-09 20:13:04 +02:00
# if defined(__APPLE__)
2017-08-29 07:48:38 +02:00
return address_found ;
2014-10-09 20:13:04 +02:00
# else
2017-08-29 07:48:38 +02:00
Dl_info dl_info ;
2013-04-04 08:24:40 +02:00
if ( dladdr ( bt_addr , & dl_info ) ) {
const char * executable = dl_info . dli_fname ;
* func_name = util_alloc_string_copy ( dl_info . dli_sname ) ;
if ( util_file_exists ( executable ) ) {
char * stdout_file = util_alloc_tmp_file ( " /tmp " , " addr2line " , true ) ;
/* 1: Run addr2line application */
{
2017-08-29 07:48:38 +02:00
char * * argv = ( char * * ) util_calloc ( 3 , sizeof * argv ) ;
2013-04-04 08:24:40 +02:00
argv [ 0 ] = util_alloc_string_copy ( " --functions " ) ;
argv [ 1 ] = util_alloc_sprintf ( " --exe=%s " , executable ) ;
{
char * rel_address = ( char * ) bt_addr ;
if ( subtract_base_adress )
rel_address - = ( size_t ) dl_info . dli_fbase ;
argv [ 2 ] = util_alloc_sprintf ( " %p " , ( void * ) rel_address ) ;
}
2016-04-19 14:48:14 +02:00
util_spawn_blocking ( " addr2line " , 3 , ( const char * * ) argv , stdout_file , NULL ) ;
2013-04-04 08:24:40 +02:00
util_free_stringlist ( argv , 3 ) ;
}
2016-04-19 14:48:14 +02:00
2013-04-04 08:24:40 +02:00
/* 2: Parse stdout output */
2017-04-24 21:53:45 +02:00
if ( util_file_exists ( stdout_file ) ) {
2013-04-04 08:24:40 +02:00
bool at_eof ;
FILE * stream = util_fopen ( stdout_file , " r " ) ;
char * tmp_fname = util_fscanf_alloc_line ( stream , & at_eof ) ;
if ( strcmp ( tmp_fname , UNDEFINED_FUNCTION ) ! = 0 ) {
char * stdout_file_name = util_fscanf_alloc_line ( stream , & at_eof ) ;
char * line_string = NULL ;
util_binary_split_string ( stdout_file_name , " : " , false , file_name , & line_string ) ;
2016-04-19 14:48:14 +02:00
if ( line_string & & util_sscanf_int ( line_string , line_nr ) )
2013-04-04 08:24:40 +02:00
address_found = true ;
2016-04-19 14:48:14 +02:00
2013-04-04 08:24:40 +02:00
free ( stdout_file_name ) ;
2018-08-13 10:20:34 +02:00
free ( line_string ) ;
2013-04-04 08:24:40 +02:00
}
free ( tmp_fname ) ;
fclose ( stream ) ;
}
util_unlink_existing ( stdout_file ) ;
free ( stdout_file ) ;
2016-04-19 14:48:14 +02:00
}
}
2013-04-04 08:24:40 +02:00
return address_found ;
2014-10-09 20:13:04 +02:00
# endif
2013-01-18 09:00:23 +01:00
}
}
2013-04-04 08:24:40 +02:00
bool util_addr2line_lookup ( const void * bt_addr , char * * func_name , char * * file_name , int * line_nr ) {
if ( util_addr2line_lookup__ ( bt_addr , func_name , file_name , line_nr , false ) )
return true ;
else
return util_addr2line_lookup__ ( bt_addr , func_name , file_name , line_nr , true ) ;
}
2013-01-18 09:00:23 +01:00
/**
2013-04-04 08:24:40 +02:00
This function prints a message to stream and aborts . The function is
2013-01-18 09:00:23 +01:00
implemented with the help of a variable length argument list - just
like printf ( fmt , arg1 , arg2 , arg3 . . . ) ;
Observe that it is __VERY__ important that the arguments and the
format string match up , otherwise the util_abort ( ) routine will hang
2013-04-04 08:24:40 +02:00
indefinetely ; without printing anything to stream .
2013-01-18 09:00:23 +01:00
A backtrace is also included , with the help of the exernal utility
addr2line , this backtrace is converted into usable
function / file / line information ( provided the required debugging
information is compiled in ) .
*/
2016-04-19 14:48:14 +02:00
2013-01-18 09:00:23 +01:00
static pthread_mutex_t __abort_mutex = PTHREAD_MUTEX_INITIALIZER ; /* Used purely to serialize the util_abort() routine. */
2013-04-04 08:24:40 +02:00
static char * realloc_padding ( char * pad_ptr , int pad_length ) {
int i ;
2017-08-29 07:48:38 +02:00
pad_ptr = ( char * ) util_realloc ( pad_ptr , ( pad_length + 1 ) * sizeof * pad_ptr ) ;
2013-04-04 08:24:40 +02:00
for ( i = 0 ; i < pad_length ; i + + )
pad_ptr [ i ] = ' ' ;
pad_ptr [ pad_length ] = ' \0 ' ;
return pad_ptr ;
}
static void util_fprintf_backtrace ( FILE * stream ) {
const char * with_linenr_format = " #%02d %s(..) %s in %s:%d \n " ;
const char * func_format = " #%02d %s(..) %s in ??? \n " ;
const char * unknown_format = " #%02d ???? \n " ;
2016-04-19 14:48:14 +02:00
const int max_bt = 100 ;
const int max_func_length = 70 ;
2013-04-04 08:24:40 +02:00
void * bt_addr [ max_bt ] ;
int size , i ;
size = backtrace ( bt_addr , max_bt ) ;
fprintf ( stream , " -------------------------------------------------------------------------------- \n " ) ;
for ( i = 0 ; i < size ; i + + ) {
int line_nr ;
char * func_name ;
char * file_name ;
char * padding = NULL ;
2016-04-19 14:48:14 +02:00
if ( util_addr2line_lookup ( bt_addr [ i ] , & func_name , & file_name , & line_nr ) ) {
2013-04-04 08:24:40 +02:00
int pad_length ;
2018-05-04 14:06:42 +02:00
const char * function ;
2013-04-04 08:24:40 +02:00
// Seems it can return true - but with func_name == NULL?! Static/inlinded functions?
if ( func_name )
function = func_name ;
else
function = " ??? " ;
2013-12-09 15:19:06 +01:00
pad_length = util_int_max ( 2 , 2 + max_func_length - strlen ( function ) ) ;
2013-04-04 08:24:40 +02:00
padding = realloc_padding ( padding , pad_length ) ;
fprintf ( stream , with_linenr_format , i , function , padding , file_name , line_nr ) ;
} else {
if ( func_name ! = NULL ) {
2016-04-19 14:48:14 +02:00
int pad_length = util_int_max ( 2 , 2 + max_func_length - strlen ( func_name ) ) ;
2013-04-04 08:24:40 +02:00
padding = realloc_padding ( padding , pad_length ) ;
fprintf ( stream , func_format , i , func_name , padding ) ;
} else {
padding = realloc_padding ( padding , 2 + max_func_length ) ;
fprintf ( stream , unknown_format , i , padding ) ;
2013-01-18 09:00:23 +01:00
}
2013-04-04 08:24:40 +02:00
}
2016-04-19 14:48:14 +02:00
2018-08-13 10:20:34 +02:00
free ( func_name ) ;
free ( file_name ) ;
free ( padding ) ;
2013-01-18 09:00:23 +01:00
}
2013-04-04 08:24:40 +02:00
fprintf ( stream , " -------------------------------------------------------------------------------- \n " ) ;
2013-01-18 09:00:23 +01:00
}
2013-09-09 12:13:33 +02:00
char * util_alloc_dump_filename ( ) {
time_t timestamp = time ( NULL ) ;
char day [ 32 ] ;
2014-10-09 20:13:04 +02:00
strftime ( day , 32 , " %Y%m%d-%H%M%S " , localtime ( & timestamp ) ) ;
{
uid_t uid = getuid ( ) ;
struct passwd * pwd = getpwuid ( uid ) ;
char * filename ;
2016-04-19 14:48:14 +02:00
2014-10-09 20:13:04 +02:00
if ( pwd )
filename = util_alloc_sprintf ( " /tmp/ert_abort_dump.%s.%s.log " , pwd - > pw_name , day ) ;
else
filename = util_alloc_sprintf ( " /tmp/ert_abort_dump.%d.%s.log " , uid , day ) ;
2013-09-09 12:13:33 +02:00
2014-10-09 20:13:04 +02:00
return filename ;
}
2013-09-09 12:13:33 +02:00
}
2016-04-19 14:48:14 +02:00
# include <setjmp.h>
static jmp_buf jump_buffer ;
static char * intercept_function = NULL ;
static void util_abort_test_intercept ( const char * function ) {
if ( intercept_function & & ( strcmp ( function , intercept_function ) = = 0 ) ) {
longjmp ( jump_buffer , 0 ) ;
}
}
jmp_buf * util_abort_test_jump_buffer ( ) {
return & jump_buffer ;
}
void util_abort_test_set_intercept_function ( const char * function ) {
intercept_function = util_realloc_string_copy ( intercept_function , function ) ;
}
2017-08-29 07:48:38 +02:00
static char * __abort_program_message ;
2014-10-09 20:13:04 +02:00
void util_abort__ ( const char * file , const char * function , int line , const char * fmt , . . . ) {
util_abort_test_intercept ( function ) ;
2013-01-18 09:00:23 +01:00
pthread_mutex_lock ( & __abort_mutex ) ; /* Abort before unlock() */
{
2014-10-09 20:13:04 +02:00
char * filename = NULL ;
2013-12-09 15:19:06 +01:00
FILE * abort_dump = NULL ;
2013-09-09 12:13:33 +02:00
2016-04-19 14:48:14 +02:00
if ( ! getenv ( " ERT_SHOW_BACKTRACE " ) )
2014-10-09 20:13:04 +02:00
filename = util_alloc_dump_filename ( ) ;
if ( filename )
2013-12-09 15:19:06 +01:00
abort_dump = fopen ( filename , " w " ) ;
2016-04-19 14:48:14 +02:00
if ( abort_dump = = NULL )
2013-12-09 15:19:06 +01:00
abort_dump = stderr ;
2016-04-19 14:48:14 +02:00
2013-01-18 09:00:23 +01:00
va_list ap ;
va_start ( ap , fmt ) ;
2013-09-09 12:13:33 +02:00
fprintf ( abort_dump , " \n \n " ) ;
2015-04-14 15:47:22 +02:00
fprintf ( abort_dump , " Abort called from: %s (%s:%d) \n \n " , function , file , line ) ;
fprintf ( abort_dump , " Error message: " ) ;
2013-09-09 12:13:33 +02:00
vfprintf ( abort_dump , fmt , ap ) ;
fprintf ( abort_dump , " \n \n " ) ;
2013-01-18 09:00:23 +01:00
va_end ( ap ) ;
/*
The backtrace is based on calling the external program
2016-04-19 14:48:14 +02:00
addr2line ; the call is based on util_spawn ( ) which is
2013-01-18 09:00:23 +01:00
currently only available on POSIX .
*/
const bool include_backtrace = true ;
if ( include_backtrace ) {
if ( __abort_program_message ! = NULL ) {
2013-10-07 13:31:13 +02:00
# if !defined(__GLIBC__)
/* allocate a temporary buffer to hold the path */
2018-05-04 14:06:42 +02:00
char * program_invocation_name = ( char * ) alloca ( PATH_MAX ) ;
2013-10-07 13:31:13 +02:00
# if defined(__APPLE__)
uint32_t buflen = PATH_MAX ;
_NSGetExecutablePath ( program_invocation_name , & buflen ) ;
# elif defined(__LINUX__)
readlink ( " /proc/self/exe " , program_invocation_name , PATH_MAX ) ;
# endif
# endif /* !defined(__GLIBC__) */
2013-09-09 12:13:33 +02:00
fprintf ( abort_dump , " -------------------------------------------------------------------------------- \n " ) ;
fprintf ( abort_dump , " %s " , __abort_program_message ) ;
fprintf ( abort_dump , " Current executable ..: %s \n " , program_invocation_name ) ;
fprintf ( abort_dump , " -------------------------------------------------------------------------------- \n " ) ;
2013-01-18 09:00:23 +01:00
}
2013-09-09 12:13:33 +02:00
fprintf ( abort_dump , " \n " ) ;
util_fprintf_backtrace ( abort_dump ) ;
2013-01-18 09:00:23 +01:00
}
2013-04-04 08:24:40 +02:00
2013-12-09 15:19:06 +01:00
if ( abort_dump ! = stderr ) {
2018-08-13 10:20:34 +02:00
fclose ( abort_dump ) ;
2015-04-14 15:47:22 +02:00
fprintf ( stderr , " \n Error message: " ) ;
{
va_list args ;
va_start ( args , fmt ) ;
vfprintf ( stderr , fmt , args ) ;
va_end ( args ) ;
}
fprintf ( stderr , " \n See file: %s for more details of the crash. \n Setting the environment variable \" ERT_SHOW_BACKTRACE \" will show the backtrace on stderr. \n " , filename ) ;
2013-09-09 12:13:33 +02:00
}
2017-04-24 21:53:45 +02:00
chmod ( filename , 00644 ) ; // -rw-r--r--
2013-09-09 12:13:33 +02:00
free ( filename ) ;
}
2016-04-19 14:48:14 +02:00
2013-09-09 12:13:33 +02:00
pthread_mutex_unlock ( & __abort_mutex ) ;
signal ( SIGABRT , SIG_DFL ) ;
2013-04-04 08:24:40 +02:00
abort ( ) ;
2013-01-18 09:00:23 +01:00
}
2013-04-04 08:24:40 +02:00
/*****************************************************************/