diff options
Diffstat (limited to 'libgfortran/runtime/backtrace.c')
-rw-r--r-- | libgfortran/runtime/backtrace.c | 297 |
1 files changed, 85 insertions, 212 deletions
diff --git a/libgfortran/runtime/backtrace.c b/libgfortran/runtime/backtrace.c index 317da1f4931..0d7c1fcea22 100644 --- a/libgfortran/runtime/backtrace.c +++ b/libgfortran/runtime/backtrace.c @@ -31,249 +31,122 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #include <unistd.h> #endif -#ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> -#endif - -#include <limits.h> - -#include "unwind.h" - +#include "backtrace-supported.h" +#include "backtrace.h" -/* Macros for common sets of capabilities: can we fork and exec, and - can we use pipes to communicate with the subprocess. */ -#define CAN_FORK (defined(HAVE_FORK) && defined(HAVE_EXECVE) \ - && defined(HAVE_WAIT)) -#define CAN_PIPE (CAN_FORK && defined(HAVE_PIPE) \ - && defined(HAVE_DUP2) && defined(HAVE_CLOSE)) -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif +/* Store our own state while backtracing. */ +struct mystate +{ + int try_simple; + int frame; +}; -/* GDB style #NUM index for each stack frame. */ +/* Does a function name have "_gfortran_" or "_gfortrani_" prefix, possibly + with additional underscore(s) at the beginning? Cannot use strncmp() + because we might be called from a signal handler. */ -static void -bt_header (int num) +static int +has_gfortran_prefix (const char *s) { - st_printf ("#%d ", num); -} + if (!s) + return 0; + while (*s == '_') + s++; -/* fgets()-like function that reads a line from a fd, without - needing to malloc() a buffer, and does not use locks, hence should - be async-signal-safe. */ + return (s[0] == 'g' && s[1] == 'f' && s[2] == 'o' && s[3] == 'r' + && s[4] == 't' && s[5] == 'r' && s[6] == 'a' && s[7] == 'n' + && (s[8] == '_' || (s[8] == 'i' && s[9] == '_'))); +} -static char * -fd_gets (char *s, int size, int fd) +static void +error_callback (void *data, const char *msg, int errnum) { - for (int i = 0; i < size; i++) + struct mystate *state = (struct mystate *) data; + if (errnum < 0) { - char c; - ssize_t nread = read (fd, &c, 1); - if (nread == 1) - { - s[i] = c; - if (c == '\n') - { - if (i + 1 < size) - s[i+1] = '\0'; - else - s[i] = '\0'; - break; - } - } - else - { - s[i] = '\0'; - if (i == 0) - return NULL; - break; - } + state->try_simple = 1; + return; } - return s; -} + estr_write ("\nSomething went wrong while printing the backtrace: "); + estr_write (msg); + estr_write ("\n"); +} -extern char *addr2line_path; +static int +simple_callback (void *data, uintptr_t pc) +{ + struct mystate *state = (struct mystate *) data; + st_printf ("#%d 0x%lx\n", state->frame, (unsigned long) pc); + (state->frame)++; + return 0; +} -/* Struct containing backtrace state. */ -typedef struct +static int +full_callback (void *data, uintptr_t pc, const char *filename, + int lineno, const char *function) { - int frame_number; - int direct_output; - int outfd; - int infd; - int error; + struct mystate *state = (struct mystate *) data; + + if (has_gfortran_prefix (function)) + return 0; + + st_printf ("#%d 0x%lx in %s\n", state->frame, + (unsigned long) pc, function == NULL ? "???" : function); + if (filename || lineno != 0) + st_printf ("\tat %s:%d\n", filename == NULL ? "???" : filename, lineno); + (state->frame)++; + + if (function != NULL && strcmp (function, "main") == 0) + return 1; + + return 0; } -bt_state; -static _Unwind_Reason_Code -trace_function (struct _Unwind_Context *context, void *state_ptr) + +/* Display the backtrace. */ + +void +show_backtrace (int in_signal_handler) { - bt_state* state = (bt_state*) state_ptr; - _Unwind_Ptr ip; -#ifdef HAVE_GETIPINFO - int ip_before_insn = 0; - ip = _Unwind_GetIPInfo (context, &ip_before_insn); - - /* If the unwinder gave us a 'return' address, roll it back a little - to ensure we get the correct line number for the call itself. */ - if (! ip_before_insn) - --ip; -#else - ip = _Unwind_GetIP (context); -#endif + struct backtrace_state *lbstate; + struct mystate state = { 0, 0 }; + + lbstate = backtrace_create_state (NULL, 1, error_callback, NULL); - if (state->direct_output) + if (!BACKTRACE_SUPPORTED || (in_signal_handler && BACKTRACE_USES_MALLOC)) { - bt_header(state->frame_number); - st_printf ("%p\n", (void*) ip); + /* If symbolic backtrace is not supported on this target, or would + require malloc() and we are in a signal handler, go with a + simple backtrace. */ + + backtrace_simple (lbstate, 0, simple_callback, error_callback, &state); } else { - char addr_buf[GFC_XTOA_BUF_SIZE], func[1024], file[PATH_MAX]; - char *p; - const char* addr = gfc_xtoa (ip, addr_buf, sizeof (addr_buf)); - write (state->outfd, addr, strlen (addr)); - write (state->outfd, "\n", 1); - - if (! fd_gets (func, sizeof(func), state->infd)) - { - state->error = 1; - goto done; - } - if (! fd_gets (file, sizeof(file), state->infd)) - { - state->error = 1; - goto done; - } - - for (p = func; *p != '\n' && *p != '\r'; p++) - ; - *p = '\0'; - - /* _start is a setup routine that calls main(), and main() is - the frontend routine that calls some setup stuff and then - calls MAIN__, so at this point we should stop. */ - if (strcmp (func, "_start") == 0 || strcmp (func, "main") == 0) - return _URC_END_OF_STACK; - - bt_header (state->frame_number); - estr_write ("0x"); - estr_write (addr); - - if (func[0] != '?' && func[1] != '?') - { - estr_write (" in "); - estr_write (func); - } - - if (strncmp (file, "??", 2) == 0) - estr_write ("\n"); - else - { - estr_write (" at "); - estr_write (file); - } + /* libbacktrace uses mmap, which is safe to call from a signal handler + (in practice, if not in theory). Thus we can generate a symbolic + backtrace, if debug symbols are available. */ + + backtrace_full (lbstate, 0, full_callback, error_callback, &state); + if (state.try_simple) + backtrace_simple (lbstate, 0, simple_callback, error_callback, &state); } +} - done: - state->frame_number++; - - return _URC_NO_REASON; -} +/* Function called by the front-end translating the BACKTRACE intrinsic. */ -/* Display the backtrace. */ +extern void backtrace (void); +export_proto (backtrace); void backtrace (void) { - bt_state state; - state.frame_number = 0; - state.error = 0; - -#if CAN_PIPE - - if (addr2line_path == NULL) - goto fallback_noerr; - - /* We attempt to extract file and line information from addr2line. */ - do - { - /* Local variables. */ - int f[2], pid, inp[2]; - - /* Don't output an error message if something goes wrong, we'll simply - fall back to printing the addresses. */ - if (pipe (f) != 0) - break; - if (pipe (inp) != 0) - break; - if ((pid = fork ()) == -1) - break; - - if (pid == 0) - { - /* Child process. */ -#define NUM_FIXEDARGS 7 - char *arg[NUM_FIXEDARGS]; - char *newenv[] = { NULL }; - - close (f[0]); - - close (inp[1]); - if (dup2 (inp[0], STDIN_FILENO) == -1) - _exit (1); - close (inp[0]); - - close (STDERR_FILENO); - - if (dup2 (f[1], STDOUT_FILENO) == -1) - _exit (1); - close (f[1]); - - arg[0] = addr2line_path; - arg[1] = (char *) "-e"; - arg[2] = full_exe_path (); - arg[3] = (char *) "-f"; - arg[4] = (char *) "-s"; - arg[5] = (char *) "-C"; - arg[6] = NULL; - execve (addr2line_path, arg, newenv); - _exit (1); -#undef NUM_FIXEDARGS - } - - /* Father process. */ - close (f[1]); - close (inp[0]); - - state.outfd = inp[1]; - state.infd = f[0]; - state.direct_output = 0; - _Unwind_Backtrace (trace_function, &state); - if (state.error) - goto fallback; - close (inp[1]); - close (f[0]); - wait (NULL); - return; - -fallback: - estr_write ("** Something went wrong while running addr2line. **\n" - "** Falling back to a simpler backtrace scheme. **\n"); - } - while (0); - -fallback_noerr: -#endif /* CAN_PIPE */ - - /* Fallback to the simple backtrace without addr2line. */ - state.direct_output = 1; - _Unwind_Backtrace (trace_function, &state); + show_backtrace (0); } -iexport(backtrace); + |