Files
ResInsight/ThirdParty/Ert/lib/util/util_abort_gnu.c

325 lines
11 KiB
C

/*
This file implements the fully fledged util_abort() function which
assumes that the current build has the following features:
fork() : To support calling external program addr2line().
pthread : To serialize the use of util_abort() - not very important.
execinfo.h : The backtrace functions backtrace() and backtrace_symbols().
_GNU_SOURCE : To get the dladdr() function.
If not all these features are availbale the simpler version in
util_abort_simple.c is built instead.
*/
/**
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().
*/
#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>
#include <ert/util/test_util.hpp>
#include <stdbool.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <unistd.h>
#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
#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;
{
bool address_found = false;
#if defined(__APPLE__)
return address_found;
#else
Dl_info dl_info;
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 */
{
char ** argv = (char**)util_calloc(3 , sizeof * argv );
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);
}
util_spawn_blocking("addr2line", 3, (const char **) argv, stdout_file, NULL);
util_free_stringlist(argv , 3);
}
/* 2: Parse stdout output */
if (util_file_exists( stdout_file )) {
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);
if (line_string && util_sscanf_int( line_string , line_nr))
address_found = true;
free( stdout_file_name );
free( line_string );
}
free( tmp_fname );
fclose(stream);
}
util_unlink_existing(stdout_file);
free( stdout_file );
}
}
return address_found;
#endif
}
}
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);
}
/**
This function prints a message to stream and aborts. The function is
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
indefinetely; without printing anything to stream.
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).
*/
static pthread_mutex_t __abort_mutex = PTHREAD_MUTEX_INITIALIZER; /* Used purely to serialize the util_abort() routine. */
static char * realloc_padding(char * pad_ptr , int pad_length) {
int i;
pad_ptr = (char*)util_realloc( pad_ptr , (pad_length + 1) * sizeof * pad_ptr );
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";
const int max_bt = 100;
const int max_func_length = 70;
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;
if (util_addr2line_lookup(bt_addr[i], &func_name , &file_name , &line_nr)) {
int pad_length;
const char * function;
// Seems it can return true - but with func_name == NULL?! Static/inlinded functions?
if (func_name)
function = func_name;
else
function = "???";
pad_length = util_int_max (2, 2 + max_func_length - strlen(function));
padding = realloc_padding( padding , pad_length);
fprintf(stream , with_linenr_format , i , function , padding , file_name , line_nr);
} else {
if (func_name != NULL) {
int pad_length = util_int_max( 2 , 2 + max_func_length - strlen(func_name));
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);
}
}
free( func_name );
free( file_name );
free( padding );
}
fprintf(stream , "--------------------------------------------------------------------------------\n");
}
char * util_alloc_dump_filename() {
time_t timestamp = time(NULL);
char day[32];
strftime(day, 32, "%Y%m%d-%H%M%S", localtime(&timestamp));
{
uid_t uid = getuid();
struct passwd *pwd = getpwuid(uid);
char * filename;
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);
return filename;
}
}
#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 );
}
static char* __abort_program_message;
void util_abort__(const char * file , const char * function , int line , const char * fmt , ...) {
util_abort_test_intercept( function );
pthread_mutex_lock( &__abort_mutex ); /* Abort before unlock() */
{
char * filename = NULL;
FILE * abort_dump = NULL;
if (!getenv("ERT_SHOW_BACKTRACE"))
filename = util_alloc_dump_filename();
if (filename)
abort_dump = fopen(filename, "w");
if (abort_dump == NULL)
abort_dump = stderr;
va_list ap;
va_start(ap , fmt);
fprintf(abort_dump , "\n\n");
fprintf(abort_dump , "Abort called from: %s (%s:%d) \n\n",function , file , line);
fprintf(abort_dump , "Error message: ");
vfprintf(abort_dump , fmt , ap);
fprintf(abort_dump , "\n\n");
va_end(ap);
/*
The backtrace is based on calling the external program
addr2line; the call is based on util_spawn() which is
currently only available on POSIX.
*/
const bool include_backtrace = true;
if (include_backtrace) {
if (__abort_program_message != NULL) {
#if !defined(__GLIBC__)
/* allocate a temporary buffer to hold the path */
char* program_invocation_name = (char*)alloca (PATH_MAX);
# 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__) */
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");
}
fprintf(abort_dump,"\n");
util_fprintf_backtrace( abort_dump );
}
if (abort_dump != stderr) {
fclose(abort_dump);
fprintf(stderr, "\nError message: ");
{
va_list args;
va_start(args , fmt);
vfprintf(stderr , fmt , args);
va_end(args);
}
fprintf(stderr, "\nSee file: %s for more details of the crash.\nSetting the environment variable \"ERT_SHOW_BACKTRACE\" will show the backtrace on stderr.\n", filename);
}
chmod(filename, 00644); // -rw-r--r--
free(filename);
}
pthread_mutex_unlock(&__abort_mutex);
signal(SIGABRT, SIG_DFL);
abort();
}
/*****************************************************************/