diff --git a/libgfortran/configure.ac b/libgfortran/configure.ac index cf38fb0..74cfe44 100644 --- a/libgfortran/configure.ac +++ b/libgfortran/configure.ac @@ -264,10 +264,10 @@ AC_CHECK_FUNCS(sleep time ttyname signal alarm clock access fork execl) AC_CHECK_FUNCS(wait setmode execvp pipe dup2 close fdopen strcasestr getrlimit) AC_CHECK_FUNCS(gettimeofday stat fstat lstat getpwuid vsnprintf dup getcwd) AC_CHECK_FUNCS(localtime_r gmtime_r strerror_r getpwuid_r ttyname_r) -AC_CHECK_FUNCS(clock_gettime strftime) +AC_CHECK_FUNCS(clock_gettime strftime readlink) # Check for glibc backtrace functions -AC_CHECK_FUNCS(backtrace backtrace_symbols) +AC_CHECK_FUNCS(backtrace backtrace_symbols_fd) # Check libc for getgid, getpid, getuid AC_CHECK_LIB([c],[getgid],[AC_DEFINE([HAVE_GETGID],[1],[libc includes getgid])]) diff --git a/libgfortran/runtime/backtrace.c b/libgfortran/runtime/backtrace.c index 10917d3..c591b01 100644 --- a/libgfortran/runtime/backtrace.c +++ b/libgfortran/runtime/backtrace.c @@ -54,57 +54,20 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #define CAN_FORK (defined(HAVE_FORK) && defined(HAVE_EXECVP) \ && defined(HAVE_WAIT)) #define GLIBC_BACKTRACE (defined(HAVE_BACKTRACE) \ - && defined(HAVE_BACKTRACE_SYMBOLS)) + && defined(HAVE_BACKTRACE_SYMBOLS_FD)) #define CAN_PIPE (CAN_FORK && defined(HAVE_PIPE) \ && defined(HAVE_DUP2) && defined(HAVE_FDOPEN) \ && defined(HAVE_CLOSE)) -#if GLIBC_BACKTRACE && CAN_PIPE -static char * -local_strcasestr (const char *s1, const char *s2) +/* GDB style #NUM index for each stack frame. */ +static void +bt_header (int num) { -#ifdef HAVE_STRCASESTR - return strcasestr (s1, s2); -#else - - const char *p = s1; - const size_t len = strlen (s2); - const char u = *s2, v = isupper((int) *s2) ? tolower((int) *s2) - : (islower((int) *s2) ? toupper((int) *s2) - : *s2); - - while (1) - { - while (*p != u && *p != v && *p) - p++; - if (*p == 0) - return NULL; - if (strncasecmp (p, s2, len) == 0) - return (char *)p; - } -#endif + st_printf (" #%d ", num); } -#endif -#if GLIBC_BACKTRACE -static void -dump_glibc_backtrace (int depth, char *str[]) -{ - int i; - - for (i = 0; i < depth; i++) - { - estr_write (" + "); - estr_write (str[i]); - estr_write ("\n"); - } - - free (str); -} -#endif - /* show_backtrace displays the backtrace, currently obtained by means of the glibc backtrace* functions. */ void @@ -116,176 +79,186 @@ show_backtrace (void) #define BUFSIZE 1024 void *trace[DEPTH]; - char **str; int depth; depth = backtrace (trace, DEPTH); if (depth <= 0) return; - str = backtrace_symbols (trace, depth); - #if CAN_PIPE -#ifndef STDIN_FILENO -#define STDIN_FILENO 0 -#endif - -#ifndef STDOUT_FILENO -#define STDOUT_FILENO 1 -#endif - -#ifndef STDERR_FILENO -#define STDERR_FILENO 2 -#endif - /* We attempt to extract file and line information from addr2line. */ do { /* Local variables. */ - int f[2], pid, line, i; + int f[2], pid, line, bt[2], inp[2]; FILE *output; - char addr_buf[DEPTH][GFC_XTOA_BUF_SIZE], func[BUFSIZE], file[BUFSIZE]; + char addr_buf[GFC_XTOA_BUF_SIZE], func[BUFSIZE], file[BUFSIZE]; char *p, *end; - const char *addr[DEPTH]; - /* Write the list of addresses in hexadecimal format. */ - for (i = 0; i < depth; i++) - addr[i] = gfc_xtoa ((GFC_UINTEGER_LARGEST) (intptr_t) trace[i], addr_buf[i], - sizeof (addr_buf[i])); + char *exe_path = full_exe_path (); + if (exe_path == NULL) + exe_path = (char *) "a.out"; /* Don't output an error message if something goes wrong, we'll simply fall back to the pstack and glibc backtraces. */ if (pipe (f) != 0) break; + if (pipe (inp) != 0) + break; if ((pid = fork ()) == -1) break; if (pid == 0) { /* Child process. */ -#define NUM_FIXEDARGS 5 - char *arg[DEPTH+NUM_FIXEDARGS+1]; +#define NUM_FIXEDARGS 7 + char *arg[NUM_FIXEDARGS]; close (f[0]); - close (STDIN_FILENO); + + 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 (0); + _exit (1); close (f[1]); arg[0] = (char *) "addr2line"; arg[1] = (char *) "-e"; - arg[2] = full_exe_path (); + arg[2] = exe_path; arg[3] = (char *) "-f"; arg[4] = (char *) "-s"; - for (i = 0; i < depth; i++) - arg[NUM_FIXEDARGS+i] = (char *) addr[i]; - arg[NUM_FIXEDARGS+depth] = NULL; + arg[5] = (char *) "-C"; + arg[6] = NULL; execvp (arg[0], arg); - _exit (0); + _exit (1); #undef NUM_FIXEDARGS } /* Father process. */ close (f[1]); - wait (NULL); + close (inp[0]); + if (pipe (bt) != 0) + break; + backtrace_symbols_fd (trace, depth, bt[1]); + close (bt[1]); output = fdopen (f[0], "r"); - i = -1; - if (fgets (func, sizeof(func), output)) + estr_write ("\nBacktrace for this error:\n"); + for (int j = 0; j < depth; j++) { - estr_write ("\nBacktrace for this error:\n"); - - do + const char *addr = gfc_xtoa + ((GFC_UINTEGER_LARGEST) (intptr_t) trace[j], + addr_buf, sizeof (addr_buf)); + + write (inp[1], addr, strlen (addr)); + write (inp[1], "\n", 1); + + if (! fgets (func, sizeof(func), output)) + goto fallback; + if (! fgets (file, sizeof(file), output)) + goto fallback; + + for (p = func; *p != '\n' && *p != '\r'; p++) + ; + *p = '\0'; + + /* If we only have the address, use the glibc backtrace. */ + if (func[0] == '?' && func[1] == '?' && file[0] == '?' + && file[1] == '?') { - if (! fgets (file, sizeof(file), output)) - goto fallback; - - i++; - - for (p = func; *p != '\n' && *p != '\r'; p++) - ; - - *p = '\0'; - - /* Try to recognize the internal libgfortran functions. */ - if (strncasecmp (func, "*_gfortran", 10) == 0 - || strncasecmp (func, "_gfortran", 9) == 0 - || strcmp (func, "main") == 0 || strcmp (func, "_start") == 0 - || strcmp (func, "_gfortrani_backtrace_handler") == 0) - continue; - - if (local_strcasestr (str[i], "libgfortran.so") != NULL - || local_strcasestr (str[i], "libgfortran.dylib") != NULL - || local_strcasestr (str[i], "libgfortran.a") != NULL) - continue; - - /* If we only have the address, use the glibc backtrace. */ - if (func[0] == '?' && func[1] == '?' && file[0] == '?' - && file[1] == '?') - { - estr_write (" + "); - estr_write (str[i]); - estr_write ("\n"); - continue; - } - - /* Extract the line number. */ - for (end = NULL, p = file; *p; p++) - if (*p == ':') - end = p; - if (end != NULL) + bt_header (j); + while (1) { - *end = '\0'; - line = atoi (++end); + char bc; + ssize_t nread = read (bt[0], &bc, 1); + if (nread != 1 || bc == '\n') + break; + write (STDERR_FILENO, &bc, 1); } - else - line = -1; - - if (strcmp (func, "MAIN__") == 0) - estr_write (" + in the main program\n"); - else + estr_write ("\n"); + continue; + } + else + { + /* Forward to the next entry in the backtrace. */ + while (1) { - estr_write (" + function "); - estr_write (func); - estr_write (" (0x"); - estr_write (addr[i]); - estr_write (")\n"); + char bc; + ssize_t nread = read (bt[0], &bc, 1); + if (nread != 1 || bc == '\n') + break; } + } - if (line <= 0 && strcmp (file, "??") == 0) - continue; + /* _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) + break; + + /* Extract the line number. */ + for (end = NULL, p = file; *p; p++) + if (*p == ':') + end = p; + if (end != NULL) + { + *end = '\0'; + line = atoi (++end); + } + else + line = -1; + + bt_header (j); + estr_write (exe_path); + estr_write ("[0x"); + estr_write (addr); + estr_write ("] in "); + estr_write (func); + + if (line <= 0 && strcmp (file, "??") == 0) + { + estr_write ("\n"); + continue; + } - if (line <= 0) - { - estr_write (" from file "); - estr_write (file); - estr_write ("\n"); - } - else - st_printf (" at line %d of file %s\n", line, file); + if (line <= 0) + { + estr_write (" at "); + estr_write (file); + estr_write ("\n"); } - while (fgets (func, sizeof(func), output)); + else + st_printf (" at %s:%d\n", file, line); - free (str); - return; + } /* Loop over each hex address. */ + close (inp[1]); + close (bt[0]); + wait (NULL); + return; fallback: - estr_write ("** Something went wrong while running addr2line. **\n" - "** Falling back to a simpler backtrace scheme. **\n"); - } - } + estr_write ("** Something went wrong while running addr2line. **\n" + "** Falling back to a simpler backtrace scheme. **\n"); + } while (0); #undef DEPTH #undef BUFSIZE -#endif -#endif +#endif /* CAN_PIPE */ -#if CAN_FORK && defined(HAVE_GETPPID) + /* Fallback to the glibc backtrace. */ + estr_write ("\nBacktrace for this error:\n"); + backtrace_symbols_fd (trace, depth, STDERR_FILENO); + return; + +#elif defined(CAN_FORK) && defined(HAVE_GETPPID) /* Try to call pstack. */ do { @@ -312,15 +285,9 @@ fallback: execvp (arg[0], arg); #undef NUM_ARGS - /* pstack didn't work, so we fall back to dumping the glibc - backtrace if we can. */ -#if GLIBC_BACKTRACE - dump_glibc_backtrace (depth, str); -#else + /* pstack didn't work. */ estr_write (" unable to produce a backtrace, sorry!\n"); -#endif - - _exit (0); + _exit (1); } /* Father process. */ @@ -328,13 +295,7 @@ fallback: return; } while(0); -#endif - -#if GLIBC_BACKTRACE - /* Fallback to the glibc backtrace. */ - estr_write ("\nBacktrace for this error:\n"); - dump_glibc_backtrace (depth, str); - return; -#endif +#else estr_write ("\nBacktrace not yet available on this platform, sorry!\n"); +#endif } diff --git a/libgfortran/runtime/main.c b/libgfortran/runtime/main.c index f5d4721..402dca7 100644 --- a/libgfortran/runtime/main.c +++ b/libgfortran/runtime/main.c @@ -92,6 +92,19 @@ store_exe_path (const char * argv0) if (please_free_exe_path_when_done) free ((char *) exe_path); + /* Reading the /proc/self/exe symlink is Linux-specific(?), but if + it works it gives the correct answer. */ +#ifdef HAVE_READLINK + int len; + if ((len = readlink ("/proc/self/exe", buf, sizeof (buf) - 1)) != -1) + { + buf[len] = '\0'; + exe_path = strdup (buf); + please_free_exe_path_when_done = 1; + return; + } +#endif + /* On the simulator argv is not set. */ if (argv0 == NULL || argv0[0] == '/') { @@ -107,7 +120,9 @@ store_exe_path (const char * argv0) cwd = ""; #endif - /* exe_path will be cwd + "/" + argv[0] + "\0" */ + /* exe_path will be cwd + "/" + argv[0] + "\0". This will not work + if the executable is not in the cwd, but at this point we're out + of better ideas. */ size_t pathlen = strlen (cwd) + 1 + strlen (argv0) + 1; path = malloc (pathlen); snprintf (path, pathlen, "%s%c%s", cwd, DIR_SEPARATOR, argv0); @@ -120,6 +135,10 @@ store_exe_path (const char * argv0) char * full_exe_path (void) { + /* If a non-Fortran main program has not called set_args(), try to + figure out the executable path nonetheless. */ + if (exe_path == NULL) + store_exe_path (NULL); return (char *) exe_path; }