Commits

ssnyder  committed c07a766

Rewrite stack trace so that on linux it doesnt require dynamic memory allocation.

  • Participants
  • Parent commits 8159846
  • Tags CxxUtils-00-00-46

Comments (0)

Files changed (8)

+2010-01-09  scott snyder  <snyder@bnl.gov>
+
+	* Tagging CxxUtils-00-00-45.
+	* src/SealDebug.cxx: Rewrite stack trace so that on linux it
+	doesn't require dynamic memory allocation.
+	* CxxUtils/SealDebug.h: Include SealDebug.h.
+
+	* test/stacktrace_test.cxx: (new)
+	* share/stacktrace_test.ref: (new)
+	* cmt/requirements: Add it.
+
+	* test/exctrace1_test.cxx: Update comments.
+	* test/exctrace2_test.cxx: Update comments.
+
 2010-01-06  scott snyder  <snyder@bnl.gov>
 
 	* Tagging CxxUtils-00-00-44.

File CxxUtils/SealDebug.h

 
 //<<<<<< INCLUDES                                                       >>>>>>
 
+#include "CxxUtils/SealCommon.h"  // sss -- needed for IOFD
 //# include "SealBase/Macros.h"                   wlav
 //# include "SealBase/sysapi/IOTypes.h"           wlav
 # include <cstddef>

File cmt/requirements

 apply_pattern UnitTest_run unit_test=exctrace2
 apply_pattern UnitTest_run unit_test=pointer_list
 apply_pattern UnitTest_run unit_test=FloatPacker
+apply_pattern UnitTest_run unit_test=stacktrace
 
 end_private

File share/stacktrace_test.ref

+ 0xX Athena::DebugAids::stacktrace(int) /SealDebug.cxx:701 + 0xX [/libCxxUtils.so]
+ 0xX fromhere() /stacktrace_test.cxx:114 + 0xX [/stacktrace_test.exe]
+ 0xX main /stacktrace_test.cxx:124 + 0xX [/stacktrace_test.exe]
+ 0xX __libc_start_main + 0xX [/libc.so.6]
+ 0xX _start + 0xX [/stacktrace_test.exe]

File src/SealDebug.cxx

 #endif
 
 
+#if HAVE_BACKTRACE_SYMBOLS_FD && HAVE_DLADDR
+// sss
+#include <unistd.h>
+#include <malloc.h>
+#include <errno.h>
+namespace {
+
+
+struct BacktraceInit
+{
+  BacktraceInit()
+  {
+    // backtrace() has a one-time initialization that uses malloc().
+    // so call it once now.
+    void* trace[1];
+    backtrace (trace, 1);
+  }
+};
+BacktraceInit backtraceInit;
+
+
+// This is like popen, except that it returns a fd rather
+// than a FILE*.  The PID is returned in pid.
+// This is to avoid memory allocation.
+int stacktracePopenFD (const char* cmd, pid_t& child_pid)
+{
+  int stat;
+  int fds[2];
+
+  // The glibc popen() uses pipe2() here with O_CLOEXEC.
+  // pipe2() is linux-specific, though, so avoid it here.
+  stat = pipe (fds);
+  if (stat < 0) return stat;
+
+  int parent_end = fds[0];
+  int child_end = fds[1];
+
+  child_pid = fork();
+  if (child_pid == 0) {
+    int child_std_end = 1;
+    close (parent_end);
+    if (child_end != child_std_end) {
+      dup2 (child_end, child_std_end);
+      close (child_end);
+    }
+
+    /* POSIX.2:  "popen() shall ensure that any streams from previous
+       popen() calls that remain open in the parent process are closed
+       in the new child process."
+
+       For our specific case here, we ignore this. */
+    
+    execl ("/bin/sh", "sh", "-c", cmd, (char *) 0);
+    _exit (127);
+  }
+
+  close (child_end);
+  if (child_pid < 0) {
+    close (parent_end);
+    return child_pid;
+  }
+
+  return parent_end;
+}
+
+
+int stacktracePcloseFD (int fd, pid_t child_pid)
+{
+  int stat = close (fd);
+  if (stat < 0) return stat;
+
+  /* POSIX.2 Rationale:  "Some historical implementations either block
+     or ignore the signals SIGINT, SIGQUIT, and SIGHUP while waiting
+     for the child process to terminate.  Since this behavior is not
+     described in POSIX.2, such implementations are not conforming." */
+  pid_t wait_pid;
+  int wstatus;
+  do {
+    wait_pid = waitpid (child_pid, &wstatus, 0);
+  } while (wait_pid == -1 && errno == EINTR);
+
+  if (wait_pid == -1)
+    return -1;
+  return wstatus;
+}
+
+
+int stacktraceReadline (int fd, char* buf, int buflen)
+{
+  int len = 0;
+  while (len < buflen-1) {
+    int stat = read (fd, buf, 1);
+    if (stat < 0) return stat;
+    if (stat == 0) break;
+    if (*buf == '\n') break;
+    ++len;
+    ++buf;
+  }
+  *buf = '\0';
+  return len;
+}
+
+
+} // anonymous namespace
+// sss
+#endif
+
+
 //namespace seal {                                wlav
 namespace Athena {                             // wlav
 //<<<<<< PRIVATE DEFINES                                                >>>>>>
   iovec		bufs [6];
   int			nbufs = 0;
   char		addrbuf [5 + BitTraits<unsigned long>::HexDigits];
+
+#if HAVE_BACKTRACE_SYMBOLS_FD && HAVE_DLADDR
   char		diffbuf [7 + BitTraits<unsigned long>::HexDigits];
   static const char	trailer [] = "]\n";
-
   Dl_info		info;
 
   if (dladdr ((void*)addr, &info) && info.dli_fname && info.dli_fname[0])
   {
     const char *libname = info.dli_fname;
-    const char *symname = 0;
-
-    //	 const char *symname = (info.dli_sname && info.dli_sname[0]
-    //	 		       ? info.dli_sname : "?");
-
-    // Demangle symbol name
-    if (info.dli_sname && info.dli_sname[0]) {
-      int stat;
-      const char* dmg = abi::__cxa_demangle(info.dli_sname,0,0,&stat);
-      symname = (stat == 0) ? dmg : info.dli_sname;
-    }
-    else {
-      symname = "?";
-    }
-            
+           
     unsigned long	symaddr = (unsigned long) info.dli_saddr;
     bool		gte = (addr >= symaddr);
     unsigned long	diff = (gte ? addr - symaddr : symaddr - addr);
 	    
     // RS start
-    unsigned int length = 0;
+    int length = 0;
 
     // difference of two pointers
     unsigned long libaddr = (unsigned long) info.dli_fbase;
       relative_address = addr;
 
     // need popen for addr2line ...
-    FILE *p;
+    int pfd;
+    pid_t child_pid;
     char line[ LINE_MAX ];
+    char dembuf[ LINE_MAX ];
+    const char* symname = dembuf;
+    size_t demlen = 0;
 
     // did we find valid entry ?
     size_t len = strlen(info.dli_fname);
     {
       if (getenv ("LD_PRELOAD"))
         unsetenv ("LD_PRELOAD");
-      sprintf (line, "addr2line -e %s %p", info.dli_fname,
+      snprintf (line, LINE_MAX, "addr2line -f -C -e %s %p", info.dli_fname,
                (void*)relative_address);
 
-      p = popen( line, "r" );
+      pfd = stacktracePopenFD( line, child_pid );
 
       length = 1;
       line[0] = ' ';
+
       // did we succeed to open the pipe?
-      if ( p != NULL )
+      if ( pfd >= 0 )
       {
-        // read string from stream and close pipe
-        char* r = fgets( &line[1], LINE_MAX-2, p );
-        int stat = pclose(p);
-        if ( r && stat == 0 ) {
-          length = strlen(line);
+        demlen = stacktraceReadline (pfd, dembuf, sizeof(dembuf));
 
-          // remove this ugly newline at the end, if present
-          if ( length>1 && line[length-1]=='\n' ) {
-            line[length-1] = '\0';
-            --length;
-          }
-        }
+        length = stacktraceReadline (pfd, line+1, sizeof(line)-1);
+        if (length >= 0) ++length;
+
+        int stat = stacktracePcloseFD (pfd, child_pid);
 
         // don't print anything, if nothing is found
-        if ( ! r || stat || line[1] == '?' )
+        if ( stat || line[1] == '?' || length < 0)
         {
           line[1] = '\0';
           length = 0;
         }
 
+        if ( stat || demlen <= 0 || dembuf[0] == '?') {
+          symname = info.dli_sname;
+          demlen = strlen (symname);
+        }
+
       }
     }
     // RS end
     ++nbufs;
 
     bufs [nbufs].iov_base = (void *) symname; // discard const
-    bufs [nbufs].iov_len  = strlen (symname);
+    bufs [nbufs].iov_len  = demlen;
     ++nbufs;
 
     // RS start
 
   }
   else
+#endif
   {
     bufs [nbufs].iov_base = addrbuf;
     bufs [nbufs].iov_len  = sprintf (addrbuf, " 0x%08lx ", addr);
     void		*trace [MAX_BACKTRACE_DEPTH];
     int			depth = backtrace (trace, MAX_BACKTRACE_DEPTH);
 
-    iovec		bufs [6];
-    int			nbufs = 0;
-    char		addrbuf [5 + BitTraits<unsigned long>::HexDigits];
-    char		diffbuf [7 + BitTraits<unsigned long>::HexDigits];
-    static const char	trailer [] = "]\n";
-
-    for (int n = 0; n < depth; ++n, nbufs = 0)
+    for (int n = 0; n < depth; ++n/*, nbufs = 0*/)
     {
 	unsigned long	addr = (unsigned long) trace [n];
-	Dl_info		info;
-
-	if (dladdr (trace[n], &info) && info.dli_fname && info.dli_fname[0])
-	{
-	    const char *libname = info.dli_fname;
-            const char *symname = 0;
-
-            //	 const char *symname = (info.dli_sname && info.dli_sname[0]
-            //	 		       ? info.dli_sname : "?");
-
-            // Demangle symbol name
-            if (info.dli_sname && info.dli_sname[0]) {
-              int stat;
-              const char* dmg = abi::__cxa_demangle(info.dli_sname,0,0,&stat);
-              symname = (stat == 0) ? dmg : info.dli_sname;
-            }
-            else {
-              symname = "?";
-            }
-            
-	    unsigned long	symaddr = (unsigned long) info.dli_saddr;
-	    bool		gte = (addr >= symaddr);
-	    unsigned long	diff = (gte ? addr - symaddr : symaddr - addr);
-	    
-	    // RS start
-	    unsigned int length = 0;
-
-	    // difference of two pointers
-            unsigned long libaddr = (unsigned long) info.dli_fbase;
-	    unsigned long relative_address = (addr >= libaddr) ? addr - libaddr : libaddr - addr;
-
-	    // need popen for addr2line ...
-	    FILE *p;
- 	    char line[ LINE_MAX ];
-
-	    // did we find valid entry ?
-	    if ( strlen(info.dli_fname) > 0 )
-            {
-                std::ostringstream cmd;
-		cmd << "addr2line -e " << info.dli_fname << " " << (void*)relative_address << std::ends;
-
-		length = 1;
-		line[0] = ' ';
-
-		p = popen( cmd.str().c_str(), "r" );
-		// did we succeed to open the pipe?
-		if ( p != NULL )
-                {
-		    // read string from stream and close pipe
-		    char* r = fgets( &line[1], LINE_MAX-2, p );
-		    int stat = pclose(p);
-                    if ( r && stat == 0 ) {
-                        length = strlen(line);
-
-                        // remove this ugly newline at the end, if present
-	                if ( length>1 && line[length-1]=='\n' ) {
-                            line[length-1] = '\0';
-                            --length;
-                        }
-                    }
-
-                    // don't print anything, if nothing is found
-                    if ( ! r || stat || line[1] == '?' )
-                    {
-                       line[1] = '\0';
-                       length = 0;
-                    }
-
-                }
-            }
-	    // RS end
-
-	    bufs [nbufs].iov_base = addrbuf;
-	    bufs [nbufs].iov_len  = sprintf (addrbuf, " 0x%08lx ", addr);
-	    ++nbufs;
-
-	    bufs [nbufs].iov_base = (void *) symname; // discard const
-	    bufs [nbufs].iov_len  = strlen (symname);
-	    ++nbufs;
-
-	    // RS start
-	    bufs [nbufs].iov_base = line;
-	    bufs [nbufs].iov_len  = length;;
-	    ++nbufs;
-	    // RS end
-
-	    bufs [nbufs].iov_base = diffbuf;
-	    bufs [nbufs].iov_len  = sprintf (diffbuf, " %s 0x%lx [",
-					     gte ? "+" : "-", diff);
-	    ++nbufs;
-
-	    bufs [nbufs].iov_base = (void *) libname; // discard const
-	    bufs [nbufs].iov_len  = strlen (libname);
-	    ++nbufs;
-
-	    bufs [nbufs].iov_base = (void *) trailer; // discard const
-	    bufs [nbufs].iov_len  = 2;
-	    ++nbufs;
-
-	}
-	else
-	{
-	    bufs [nbufs].iov_base = addrbuf;
-	    bufs [nbufs].iov_len  = sprintf (addrbuf, " 0x%08lx ", addr);
-	    ++nbufs;
-
-	    bufs [nbufs].iov_base = (void *) "<unknown function>\n"; //no const
-	    bufs [nbufs].iov_len  = 19;
-	    ++nbufs;
-	}
-
-	writev (fd, bufs, nbufs);
+        stacktraceLine (fd, addr);
     }
 
 #elif HAVE_EXCPT_H && HAVE_PDSC_H && HAVE_RLD_INTERFACE_H // tru64

File test/exctrace1_test.cxx

+/**
+ * @file CxxUtils/test/exctrace2_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Oct, 2009
+ * @brief Test tracebacks for caught exceptions --- without the collector.
+ */
+
+
 #include <cstdio>
 #include <stdexcept>
 #include "CxxUtils/exctrace.h"

File test/exctrace2_test.cxx

+/**
+ * @file CxxUtils/test/exctrace2_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Oct, 2009
+ * @brief Test tracebacks for caught exceptions --- using the collector.
+ */
+
+
 #include <string>
 #include "stdlib.h"
 

File test/stacktrace_test.cxx

+/**
+ * @file CxxUtils/test/stacktrace_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Jan, 2010
+ * @brief Test stack trace printing.
+ */
+
+
+#undef NDEBUG
+
+#include "CxxUtils/SealDebug.h"
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <malloc.h>
+#include <cstdlib>
+
+
+char* snip (char* buf, char* p, char fill = '\0')
+{
+  if (p > buf) {
+    if (fill)
+      *buf++ = fill;
+    char* q = buf;
+    while (*p)
+      *q++ = *p++;
+    *q = 0;
+  }
+  return buf;
+}
+
+
+void filter (char* buf)
+{
+  char* sl = 0;
+  while (*buf) {
+    if (buf[0] == '0' && buf[1] == 'x') {
+      buf += 2;
+      char* p = buf;
+      while (isxdigit (*p))
+        ++p;
+      buf = snip (buf, p, 'X');
+    }
+
+    else if (buf[0] == '/') {
+      if (sl)
+        buf = snip (sl, buf);
+      sl = buf;
+      ++buf;
+    }
+
+    else if (buf[0] == ' ') {
+      ++buf;
+      sl = 0;
+    }
+
+    else if (buf[0] == '.' && buf[1] == '.') {
+      buf = snip (buf, buf+2);
+    }
+
+    else
+      ++buf;
+  }
+}
+
+
+void dumptrace (FILE* fp)
+{
+  fseek (fp, 0, SEEK_SET);
+  char buf[65536];
+  while (fgets (buf, sizeof (buf), fp)) {
+    filter (buf);
+    fputs (buf, stdout);
+  }
+}
+
+
+bool armed = true;
+
+
+void* malloc_hook (size_t /*sz*/, const void* /*ptr*/)
+{
+  printf ("malloc called\n");
+  std::abort();
+}
+
+
+void* realloc_hook (void* /*ptrin*/, size_t /*sz*/, const void* /*ptr*/)
+{
+  printf ("malloc called\n");
+  std::abort();
+}
+
+
+// Used to check that we don't call malloc during the stack trace.
+void sethooks()
+{
+  __malloc_hook = malloc_hook;
+  __realloc_hook = realloc_hook;
+}
+
+void resethooks()
+{
+  __malloc_hook = 0;
+  __realloc_hook = 0;
+}
+
+
+void fromhere()
+{
+  FILE* fp = tmpfile();
+  int fd = fileno (fp);
+  sethooks();
+  Athena::DebugAids::stacktrace (fd);
+  resethooks();
+  dumptrace (fp);
+  fclose (fp);
+}
+
+
+int main()
+{
+  fromhere();
+  return 0;
+}