Archive for ohbitchuary

Stacktraces for c++

I must be getting old. I felt the need for stacktraces in some c++ code I’m running that ehhh occasionally has trouble dealing with life and decides to blow up rather ignominiously in the depths of some 3rd party library. This is entirely un-portable and frankly, my dear, I don’t have a ham. Linux and g++. Deal with it. If it works anywhere else, its a miracle and you can send me a check. If it doesn’t work, you get to keep the pieces.

So, roughly speaking, when our test program blows up we want to see it dump a somewhat readable stacktrace with unmangled (demangled?!) c++ names.. not the blah blah that looks random junk but is in fact encoded facts about the name.. like so:

1
2
3
4
5
6
7
8
9
10
amandla> ./test 
stacktrace_exception::stacktrace_exception()
three()
foo::call_three()
two()
one()
main()
__libc_start_main()
./test [0x401619]
amandla>

where this is the test code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstdlib>
#include <stacktrace_exception.hpp>
 
void three() throw(stacktrace_exception)
{
        stacktrace_exception e;
        throw e;
}
 
struct foo 
{
        int call_three() 
        {
                three();
        }
};
 
void two() throw(stacktrace_exception)
{
        foo f;  
        f.call_three();
}
 
void one() throw(stacktrace_exception)
{
        two();
}
 
int main()
{
        using namespace std;
        try {
                one();
        }
        catch (stacktrace_exception& e) {
                cout << e.what() << endl;
        }
        return EXIT_SUCCESS;
}

On my Ubuntu system, there are some rather useful functions in /usr/include/c++/4.3/cxxabi.h that deal with demangling names. You have to love code that contains “magic placehoder classes”. But, I digress. Without further ado, here is the header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef _STACKTRACE_EXCEPTION_HPP
#define _STACKTRACE_EXCEPTION_HPP
 
#include <string>
#include <exception>
#include <execinfo.h>
#include <cxxabi.h>
 
static const int max_stacktrace_depth = 256;
 
class stacktrace_exception : public std::exception {
public:
        explicit stacktrace_exception();
        ~stacktrace_exception() throw();
        virtual const char* what() const throw();
 
private:
        std::string trace;
};
 
#endif

And the implementation.. with some comments to describe the more shameful aspects..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <cstdlib>
#include <cstring>
#include <stacktrace_exception.hpp>
 
stacktrace_exception::stacktrace_exception() 
{
    // Unfortunately we have to commit to a 
    // maximum stack trace depth. *sigh*
    void *buffer[max_stacktrace_depth];
 
    // Walk the stack and fill in a backtrace 
    // in our buffer.
    int number_of_addresses 
        = backtrace(buffer, max_stacktrace_depth);
 
    // Get the symbols for the stack trace.
    char **strings 
        = backtrace_symbols(buffer, number_of_addresses);
 
    // Since we're in c++ we should attempt to 
    // demangle the entries.
    for (int i = 0; i < number_of_addresses; ++i) {
	// Take a guess at the maximum width of a 
        // name - template names can be wide. 
	size_t max_name_width = 1024;
 
	// Malloc up some space; we use the C functions 
        // because of the code we're interfacing with. 
	char *demangled_name 
            = static_cast<char*>(malloc(max_name_width));
 
	// Begin and end of the mangled name.
	char *begin = 0, *end = 0;
 
        // Find the the parentheses and address offset 
        // surrounding the name.
	for (char *j = strings[i]; *j; ++j) {
	    if (*j == '(')
		begin = j;
	    else if (*j == '+') 
		end = j;
	}
 
	// If we found it one..
	if (0 != begin && 0 != end) {
	    // Isolate the name.
	    *begin++ = '\0'; *end = '\0';
 
	    int ignore;
	    char *returned_demangled_name 
            = abi::__cxa_demangle(begin, demangled_name, 
                                 &max_name_width, &ignore);
	    if (0 != returned_demangled_name){
		// We need to do this in case 
                // things got realloc()'d on us.
		demangled_name = returned_demangled_name;
	    }
	    else {
		// Demangling failed, just do our best 
                // with the existing name.
		std::strncpy(demangled_name, begin, max_name_width);
		std::strncat(demangled_name, "()", max_name_width);
		demangled_name[max_name_width-1] = '\0';
	    }
	    trace.append(demangled_name);
	}
	else
	    trace.append(strings[i]);
	trace.append("\n"); 
	free(demangled_name);
    }
    free(strings);
}
 
stacktrace_exception::~stacktrace_exception() throw ()
{
}
 
const char* stacktrace_exception::what() const throw()
{
    return trace.c_str();   
}

Now the astute reader will be cringing at the memory being allocated. We could be in a situation where the glorious edifice of our program is crumbling around our ears due to memory being exhausted, and we should dump the backtrace symbols to stderr via a call to backtrace_symbols_fd(). Such an extension is left as an exercise to the reader.

I’ve long maintained that c++ is a language for consenting adults; any time its used you know someone is going to get fucked. However, its hard to ignore, its generally useful and there are some fine libraries that are best dealt with from their native language. Such is life. Even if it does take “worse is better” to new and dizzying depths.

Leave a Comment