mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-25 18:55:39 -06:00
325 lines
11 KiB
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(×tamp));
|
|
{
|
|
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();
|
|
}
|
|
|
|
|
|
/*****************************************************************/
|