einpoklum
einpoklum

Reputation: 131626

In C++, how can I get the current thread's call stack?

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:

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.

Upvotes: 2

Views: 5376

Answers (3)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726599

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.

Upvotes: 1

Mats Petersson
Mats Petersson

Reputation: 129374

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].

Upvotes: 0

user405725
user405725

Reputation:

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).

Upvotes: 3

Related Questions