PR 48931 Make backtrace async-signal-safe, reformat output
authorJanne Blomqvist <jb@gcc.gnu.org>
Sun, 22 May 2011 16:38:05 +0000 (19:38 +0300)
committerJanne Blomqvist <jb@gcc.gnu.org>
Sun, 22 May 2011 16:38:05 +0000 (19:38 +0300)
From-SVN: r174030

libgfortran/ChangeLog
libgfortran/config.h.in
libgfortran/configure
libgfortran/configure.ac
libgfortran/runtime/backtrace.c
libgfortran/runtime/main.c

index d0effacaa22389907e67bf54b4663efdba0fdb6e..6f5b0581b5a54bc8ef149afffaba6bf9c70b7c9f 100644 (file)
@@ -1,3 +1,20 @@
+2011-05-22  Janne Blomqvist  <jb@gcc.gnu.org>
+
+        PR libfortran/48931
+        * configure.ac: Check for backtrace_symbols_fd instead of
+       backtrace_symbols, check for readlink.
+        * config.h.in: Regenerated.
+        * configure: Regenerated.
+        * runtime/backtrace.c (local_strcasestr): Remove.
+        (bt_header): New function.
+        (dump_glibc_backtrace): Remove.
+        (fd_gets): New function.
+        (show_backtrace): Rework to use backtrace_symbols_fd and pipes
+       avoiding functions that are not async-signal-safe, reformat
+       output.
+        * runtime/main.c (store_exe_path): Try to check /proc/self/exe
+       first.
+
 2011-05-20  Janne Blomqvist  <jb@gcc.gnu.org>
         Uros Bizjak  <ubizjak@gmail.com>
 
index 30da5fa7e214801111ce323cce139b40be8a73c4..57f66b51ab99319ef2dba3a4f916eb0ebdd99829 100644 (file)
@@ -84,8 +84,8 @@
 /* Define to 1 if you have the `backtrace' function. */
 #undef HAVE_BACKTRACE
 
-/* Define to 1 if you have the `backtrace_symbols' function. */
-#undef HAVE_BACKTRACE_SYMBOLS
+/* Define to 1 if you have the `backtrace_symbols_fd' function. */
+#undef HAVE_BACKTRACE_SYMBOLS_FD
 
 /* Define if powf is broken. */
 #undef HAVE_BROKEN_POWF
 /* Define to 1 if you have the <pwd.h> header file. */
 #undef HAVE_PWD_H
 
+/* Define to 1 if you have the `readlink' function. */
+#undef HAVE_READLINK
+
 /* libm includes round */
 #undef HAVE_ROUND
 
index 833a0e1c6a7ee93e3e4ec8221649d9155e0efbab..a716ff37fafa6379f3c20f12603d63f095c6208b 100755 (executable)
@@ -16458,7 +16458,7 @@ _ACEOF
 fi
 done
 
-for ac_func in clock_gettime strftime
+for ac_func in clock_gettime strftime readlink
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -16473,7 +16473,7 @@ done
 
 
 # Check for glibc backtrace functions
-for ac_func in backtrace backtrace_symbols
+for ac_func in backtrace backtrace_symbols_fd
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
index cf38fb024ea51e9bdf14f535348a4478b169c917..74cfe441a7518b9223a3770b51cc0b7ba04d4065 100644 (file)
@@ -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])])
index 10917d3e0c22fb5a106a495009cd787768daf550..dff4466aa56d46bc00f2df32952bcd892d380f94 100644 (file)
@@ -54,59 +54,57 @@ 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)
-{
-#ifdef HAVE_STRCASESTR
-  return strcasestr (s1, s2);
-#else
+/* GDB style #NUM index for each stack frame.  */
 
-  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
+static void 
+bt_header (int num)
+{
+  st_printf (" #%d  ", num);
 }
-#endif
 
 
-#if GLIBC_BACKTRACE
-static void
-dump_glibc_backtrace (int depth, char *str[])
-{
-  int i;
+/* 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.  */
 
-  for (i = 0; i < depth; i++)
+static char *
+fd_gets (char *s, int size, int fd)
+{
+  for (int i = 0; i < size; i++)
     {
-      estr_write ("  + ");
-      estr_write (str[i]);
-      estr_write ("\n");
+      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';
+         break;
+       }
     }
-
-  free (str);
+  return s;
 }
-#endif
+
 
 /* show_backtrace displays the backtrace, currently obtained by means of
    the glibc backtrace* functions.  */
+
 void
 show_backtrace (void)
 {
@@ -116,63 +114,48 @@ 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;
-    FILE *output;
-    char addr_buf[DEPTH][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]));
+    int f[2], pid, bt[2], inp[2];
+    char addr_buf[GFC_XTOA_BUF_SIZE], func[BUFSIZE], file[BUFSIZE];
+    char *p;
 
     /* 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";
@@ -180,112 +163,111 @@ show_backtrace (void)
        arg[2] = full_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);
-    output = fdopen (f[0], "r");
-    i = -1;
+    close (inp[0]);
+    if (pipe (bt) != 0)
+      break;
+    backtrace_symbols_fd (trace, depth, bt[1]);
+    close (bt[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 (! fd_gets (func, sizeof(func), f[0]))
+         goto fallback;
+       if (! fd_gets (file, sizeof(file), f[0]))
+         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)
-             {
-               *end = '\0';
-               line = atoi (++end);
-             }
-           else
-             line = -1;
-
-           if (strcmp (func, "MAIN__") == 0)
-             estr_write ("  + in the main program\n");
-           else
+           bt_header (j);
+           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;
+               write (STDERR_FILENO, &bc, 1);
              }
-
-           if (line <= 0 && strcmp (file, "??") == 0)
-             continue;
-
-           if (line <= 0)
+           estr_write ("\n");
+           continue;
+         }
+       else
+         {
+           /* Forward to the next entry in the backtrace. */
+           while (1)
              {
-               estr_write ("    from file ");
-               estr_write (file);
-               estr_write ("\n");
+               char bc;
+               ssize_t nread = read (bt[0], &bc, 1);
+               if (nread != 1 || bc == '\n')
+                 break;
              }
-           else
-             st_printf ("    at line %d of file %s\n", line, file);
          }
-       while (fgets (func, sizeof(func), output));
 
-       free (str);
-       return;
+       /* _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;
+       
+       bt_header (j);
+       estr_write (full_exe_path ());
+       estr_write ("[0x");
+       estr_write (addr);
+       estr_write ("] in ");
+       estr_write (func);
+       
+       if (strncmp (file, "??", 2) == 0)
+         estr_write ("\n");
+       else
+         {
+           estr_write (" at ");
+           estr_write (file);
+         }
+      } /* 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 */
+
+  /* Fallback to the glibc backtrace.  */
+  estr_write ("\nBacktrace for this error:\n");
+  backtrace_symbols_fd (trace, depth, STDERR_FILENO);
+  return;
 
-#if CAN_FORK && defined(HAVE_GETPPID)
+#elif defined(CAN_FORK) && defined(HAVE_GETPPID)
   /* Try to call pstack.  */
   do
   {
@@ -312,15 +294,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 +304,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
 }
index f5d4721283e16c837d97114fff56b83fe5c37716..54d9e091ce2413d2770e813867c420057204a25a 100644 (file)
@@ -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);