Thread: Instant call stack

  1. #1
    Registered User jeffcobb's Avatar
    Join Date
    Dec 2009
    Location
    Henderson, NV
    Posts
    875

    Instant call stack

    This is less of a question and more of a tip I am passing along. This probably won't do much for 90% of the readers since most are working on standard PC dev stations and such but if you work with GCC in any kind of non-standard environment where use of GDB is problematic, this might be useful to you. The problem is sometimes in a non-trivial application with many concurrently running threads of logic/program flow, you can find yourself in a core low level method being passed dodgy/bad data and you want to know who called it and how it got there. Without GDB et al the only way to view the call stack (or at least the more common way) is to trigger a core dump and examine the stack, cross-referencing that with the output of nm. One of the key defects with this approach is once you trigger the core dump you (the app) are done executing and if that particular iteration or call to your suspect function isn't the right one, you are wasting your time. This is exactly the situation I found myself in this week and frustrated with the lather-rinse-repeat approach of dropping core dump triggers here and there ( and logic to make them happen at the right time), recompile, run->dump->stack examination, repeat process until you get the right one drove me nuts. Making our problem a little more difficult, I could not change the make process to include any additional libs or toolsets. Some research later I came up with the following header file that so far has been really useful:
    Code:
    //******************************************************************
    //* File:bt.hpp
    //*
    //* Description:spits out backtrace
    //*
    //* Date:2010-05-14
    //*
    //* Author:jeff cobb
    //******************************************************************
    //* R E V S I O N S
    //*
    //******************************************************************
    #ifndef __BT_HPP
    #define __BT_HPP
    
    #include<execinfo.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include <cxxabi.h>
    #include <string.h>
    #define BT_SIZE 100
    
    void dumpBT(const char *tag)
    {
       printf("\n******** %s **********\n", tag);
       int j, nptrs;
       void *buffer[BT_SIZE];
       char **strings;
    
       nptrs = backtrace(buffer, BT_SIZE);
       printf("backtrace() returned %d addresses\n", nptrs);
       strings = backtrace_symbols(buffer, nptrs);
       if (strings == NULL)
       {
          perror("backtrace_symbols\n");
          exit(EXIT_FAILURE);
       } else
       {
          for (j = 0; j < nptrs;j++)
          {
    	 printf("%s\n", strings[j]);
          }
          free(strings);
       }
       printf("**************************\n");
    
    };
    
    void dumpBTDlx(const char *tag)
    {
       printf("\n******** %s **********\n", tag);
    
       const size_t max_depth = 100;
       size_t stack_depth;
       void *stack_addrs[max_depth];
       char **stack_strings;
    
       stack_depth = backtrace(stack_addrs, max_depth);
       stack_strings = backtrace_symbols(stack_addrs, stack_depth);
     
       for (size_t i = 1; i < stack_depth; i++)
       {
          size_t sz = 200; // just a guess, template names will go much wider
          char *function = static_cast<char*>(malloc(sz));
          char *begin = 0, *end = 0;
          // find the parentheses and address offset surrounding the mangled name
          for (char *j = stack_strings[i]; *j; ++j)
          {
    	 if (*j == '(')
    	 {
    	    begin = j;
    	 } else if (*j == '+')
    	 {
    	    end = j;
    	 }
          }
          if (begin && end)
          {
    	 *begin++ = NULL;
    	 *end = NULL;
    	 // found our mangled name, now in [begin, end)
    
    	 int status;
    	 char *ret = abi::__cxa_demangle(begin, function, &sz, &status);
    	 if (ret)
    	 {
    	    // return value may be a realloc() of the input
    	    function = ret;
    	 } else
    	 {
    	    // demangling failed, just pretend it's a C function with no args
    	    strncpy(function, begin, sz);
    	    strncat(function, "()", sz);
    	    function[sz-1] = NULL;
    	 }
    	 printf("    %s:%s\n", stack_strings[i], function);
          } else
          {
    	 // didn't find the mangled name, just print the whole line
    	 printf("    %s\n", stack_strings[i]);
          }
          free(function);
    
       }
    }
    #endif
    What this code does is provide two different functions for dynamically dumping call stack information at runtime to stdout. If you needed this to go to a file or other target, they would be easy to mod but in my situation where printf() is the only way to debug stuff, stdout works just fine.

    So in any code that I want to trigger a call stack dump and then (more importantly) keep going, I just stick something like:
    Code:
         dumpBT("some text msg");
         // or
         dumpBTDlx("some text msg);
    The first one works better for C code and the second works better for C++ because the names in the callstack are demangled on the fly.

    I know most will look at this and roll their eyes but for those who have been in this situation it is a life-saver. Or at least a sanity saver. In the situation above where the low-level function was getting called thousands of times before it was passed the dodgy data, it was simplicity in itself to add:
    Code:
    #include "bt.hpp"
    
    void functionC(int keyValue)
    {
    	if(keyValue == knownBadValue)
    	{
                  dumpBTDlx("Bad data passed, here is who did it:");
    	}
    }
    Which when run outputs:
    Code:
    jeff@jeff-gate:~/dev/backtrace$ ./backtrace_test 
    backtrace_test 1.0
    
    ******** Bad data passed, here is who did it: **********
        /home/jeff/dev/backtrace/libbacktrace.so:functionC(int)
        /home/jeff/dev/backtrace/libbacktrace.so:functionB(int)
        /home/jeff/dev/backtrace/libbacktrace.so:functionA(int)
        /home/jeff/dev/backtrace/libbacktrace.so:getLibName()
        ./backtrace_test:main()
        /lib/tls/i686/cmov/libc.so.6:__libc_start_main()
        ./backtrace_test [0x8048c01]
    Testing: backtrace
    jeff@jeff-gate:~/dev/backtrace$

    So for the cost of including a single simple header I can get multiple call stack dumps at runtime without artificially stopping the program. Another area where this could be useful is in a heavily multithreaded application where attaching GDB will artificially munge the thread switching order and have the program execute differently.

    Two situations where this is really useful is coding for the PS3 where the debugging tools are iffy and currently on slot machines where the customized Linux environment prevents standard troubleshooting tools from working smoothly.

    Now one caveat: if your environment allows this to run but only outputs function addresses for a callstack I have another related trick that can easily convert that into a usable callstack, right down to the file and line numbers of where the calls originated. PM me if this is of interest as well. I have written some Python (though it could be done in any language) that, when combined with some nm and stdin tricks can fairly easily generate a *human-readable* callstack from any information like this.

    Hope this helps someone; it has sure helped me.
    Last edited by jeffcobb; 05-15-2010 at 01:16 PM. Reason: Dang Gedit
    C/C++ Environment: GNU CC/Emacs
    Make system: CMake
    Debuggers: Valgrind/GDB

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Recursive function
    By WatchTower in forum C Programming
    Replies: 11
    Last Post: 07-15-2009, 07:42 AM
  2. How to get RSSI value, send to sensor, sensor receive package, repackage it?
    By techissue2008 in forum Networking/Device Communication
    Replies: 1
    Last Post: 03-04-2009, 10:13 AM
  3. Fixing my program
    By Mcwaffle in forum C Programming
    Replies: 5
    Last Post: 11-05-2008, 03:55 AM
  4. Stack manipulation
    By JJD in forum C Programming
    Replies: 1
    Last Post: 10-08-2002, 11:44 AM
  5. Assembly example
    By Lynux-Penguin in forum C Programming
    Replies: 6
    Last Post: 04-24-2002, 07:45 PM