2

I'm writing this error handler for some code I'm working in, in C++. I would like to be able to make some sort of reference to whatever I have on the stack, without it being explicitly passed to me. Specifically, let's say I want to print the names of the functions on the call stack, in order. This is trivial in managed runtime environments like the JVM, probably not so trivial with 'simple' compiled code. Can I do this?

Notes:

  • Assume for simplicity that I compile my code with debugging information and no optimization.
  • I want to write something that is either platform-independent or multi-platform. Much prefer the former.
  • If you think I'm trying to reinvent the wheel, just link to the source of the relevant wheel and I'll look there.

Update:

I can't believe how much you need to bend over backwards to do this... almost makes me pine for another language which shall not be mentioned.

Community
  • 1
  • 1
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • If anything, look in [``](http://man7.org/linux/man-pages/man3/backtrace.3.html). –  Aug 07 '13 at 15:06
  • Aside from using something like what H2CO3 suggested, it's largely platform-dependent and, in some (perhaps rare) cases, may be impossible. – Drew McGowen Aug 07 '13 at 15:08
  • It will heavily depend on debugging information and the degree of optimisation. Many optimisations will have a tendancy to cause some stack frames to not exist altogether, like inlining and tail-call optimisation. – SirDarius Aug 07 '13 at 15:11
  • @einpoklum this is probably one of the most annoying (and difficult) things to do in a native language. It's complicated by the fact that on some platforms (x86 Windows, don't know about linux) you can't preform [a proper stack walk](http://blogs.msdn.com/b/oldnewthing/archive/2013/03/20/10403718.aspx). – Mgetz Aug 07 '13 at 15:14
  • @SirDarius: Corrected my question, assume we have debug info and no optimization. – einpoklum Aug 07 '13 at 15:18

3 Answers3

3

There is a way to get a back-trace in C++, though it is not portable. I cannot speak for Windows, but on Unix-like systems there is a backtrace API that consists primarily of the following functions:

  • int backtrace(void** array, int size);
  • char** backtrace_symbols(void* const* array, int size);
  • void backtrace_symbols_fd(void* const* array, int size, int fd);

You can find up to date documentation and examples on GNU website here. There are other sources, like this manual page for OS X, etc.

Keep in mind that there are a few problems with getting backtrace using this API. Firstly, there no file names and no line numbers. Secondly, you cannot even get backtrace in certain situations like if the frame pointer is omitted entirely (default behavior of recent GCC compilers for x86_64 platforms). Or maybe the binary doesn't have any debug symbols whatsoever. On some systems, you also have to specify -rdynamic flag when compiling your binary (which has other, possible undesirable, effects).

  • Thanks, that's also what @H2CO3 suggested. But isn't there any code wrapping that which gets me a nice linked list with everything I need? – einpoklum Aug 07 '13 at 15:18
  • "the binary doesn't have any debug symbols whatsoever" - it's rather that it's stripped, no? I don't think debug symbols are needed for this, simply plain old (function) symbols suffice. –  Aug 07 '13 at 15:18
  • @einpoklum No. But this gives you an array, which is equivalent. –  Aug 07 '13 at 15:18
  • @H2CO3: Right, `strip -s` and the backtrace is gone :) –  Aug 07 '13 at 15:26
  • @einpoklum: Well, you can write a few lines of code around first two functions to have a vector, or anything that you like. When I need to debug and see backtraces, especially when dealing with exceptions, I usually automate `gdb` (see [here](http://lazarenko.me/2013/04/16/cpp-exceptions-and-stack-trace/)), it is a lot easier, more featured, and doesn't require any code changes. –  Aug 07 '13 at 15:29
1

Unfortunately, there is no built-in way of doing this with the standard C++. You can construct a system of classes to help you build a stack tracer utility, but you would need to put a special macro in each of the methods that you would like to trace.

I've seen it done (and even implemented parts of it) using the strategy outlined below:

  • Define your own class that stores the information about a stack frame. At the minimum, each node should contain the name of the function being called, file name / line number info being close second.
  • Stack frame nodes are stored in a linked list, which is reused if it exists, or created if it does not exist
  • A stack frame is created and added to the list by instantiating a special object. Object's constructor adds the frame node to the list; object's destructor deletes the node from the list.
  • The same constructor/destructor pair are responsible for creating the list of frames in thread local storage, and deleting the list that it creates
  • The construction of the special object is handled by a macro. The macro uses special preprocessor tokens to pass function identification and location information to the frame creator object.

Here is a rather skeletal proof-of-concept implementation of this approach:

#include <iostream>
#include <list>

using namespace std;

struct stack_frame {
    const char *funName;
    const char *fileName;
    int line;
    stack_frame(const char* func, const char* file, int ln)
    : funName(func), fileName(file), line(ln) {}
};

thread_local list<stack_frame> *frames = 0;

struct entry_exit {
    bool delFrames;
    entry_exit(const char* func, const char* file, int ln) {
        if (!frames) {
            frames = new list<stack_frame>();
            delFrames = true;
        } else {
            delFrames = false;
        }
        frames->push_back(stack_frame(func, file, ln));
    }
    ~entry_exit() {
        frames ->pop_back();
        if (delFrames) {
            delete frames;
            frames = 0;
        }
    }
};

void show_stack() {
    for (list<stack_frame>::const_iterator i = frames->begin() ; i != frames->end() ; ++i) {
        cerr << i->funName << " - " << i->fileName << " (" << i->line << ")" << endl;
    }
}

#define FUNCTION_ENTRY entry_exit _entry_exit_(__func__, __FILE__, __LINE__);

void foo() {
    FUNCTION_ENTRY;
    show_stack();
}
void bar() {
    FUNCTION_ENTRY;
    foo();
}
void baz() {
    FUNCTION_ENTRY;
    bar();
}

int main() {
        baz();
        return 0;
}

The above code compiles with C++11 and prints this:

baz - prog.cpp (52)
bar - prog.cpp (48)
foo - prog.cpp (44)

Functions that do not have that macro would be invisible on the stack. Performance-critical functions should not have such macros.

Here is a demo on ideone.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

It is not easy. The exact solution depends very much on the OS and Execution environment.

Printing the stack is usually not that difficult, but finding symbols can be quite tricky, since it usually means reading debug symbols.

An alternative is to use an intrusive approach and add some "where am I" type code to each function (presumably for "debug builds only"):

#ifdef DEBUG
struct StackEntry
{
   const char *file;
   const char *func;
   int         line;
   StackEntry(const char *f, const char *fn, int ln) : file(f), func(fn), line(ln) {}
};

std::stack<StackEntry> call_stack;

class FuncEntry
{
   public:
    FuncEntry(const char *file, const char *func, int line)
    {
       StackEntry se(file, func, line);
       call_stack.push_back(se);
    }
    ~FuncEntry()
    {
        call_stack.pop_back();
    }

    void DumpStack()
    {
         for(sp : call_stack)
         {
             cout << sp->file << ":" << sp->line << ": " << sp->func << "\n";
         }
    }
 };


 #define FUNC() FuncEntry(__FILE__, __func__, __LINE__); 
 #else
 #define FUNC()
 #endif


 void somefunction()
 {
     FUNC();
     ... more code here. 
 }

I have used this technique in the past, but I just typed this code in, it may not compile, but I think it's clear enough . One major benefit is that you don't HAVE to put it in every function - just "important ones". [You could even have different types of FUNC macros that are enabled or disabled based on different levels of debugging].

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227