Monday, November 29, 2004

Printing stack trace

Printing stack trace

I got the idea of printing stack trace when I saw JAVA exception mechanism. In JAVA it allows u to catch an exception and print stack trace i.e., get to know the point from where it is thrown.

Basically I don’t like JAVA as it’s principles doesn’t coincide with what ever I have learnt. But I adore the programming ease it provides to developers i.e., interface provided.

Most of the times I will be working on Linux and gcc. So I thought how to do it on Linux. While searching I stumbled across a libc function back trace. Voilla!!! I thought I got what I was searching for.

void *traces[MAX_STACK_DEPTH] ;
backtrace(traces, MAX_STACK_DEPTH) ;

backtrace gives u the traces once u specify MAX_STACK_DEPTH. These traces are nothing but pointers in ur code segment.

But what will I do with these pointers? I need clean stack trace saying line no, source file and function name where I am.

OK fine. libc developers thought about that and provided one more function backtrace_symbols. It accepts those traces and returns function name and offset within the function.

char **function_names ;
function_names = (char **)backtrace_symbols(traces, trace_size) ;

Now let us reiterate what we have discussed. Basically I want to find stack trace of the function I am in. Following code snippet illustrates that.


void fn(int ) ;
void fn(double , char ) ;
void show_stack_frame(int, double ) ;

char *pszProgramName ;

int main(int argc, char *argv[])
{
pszProgramName = argv[0] ;
fn(5) ;
return 0 ;
}

void fn(int x)
{
fn(6.78, 'a') ;
}

void fn(double y, char c)
{
show_stack_frame(5, 8.9) ;
}

void show_stack_frame(int x,double y)
{
void *traces[10] ;
char **function_names ;
int i ;

int trace_size = backtrace(traces, 10) ;
function_names = (char **)backtrace_symbols(traces, trace_size) ;

for(i = 0 ; i < style="">printf("%d: %s\n", i, function_names[i]) ;
}

Compile this program using –rdynamic flag. This is needed to get appropriate function names.

$ g++ -g -rdynamic main.cc –o main

Once u run this program u get following ouput.

$ ./main
0: ./main(_Z16show_stack_frameid+0x20) [0x8048938]
1: ./main(_Z3fn1dc+0x2c) [0x8048912]
2: ./main(_Z3fn1i+0x1a) [0x80488e0]
3: ./main(main+0x24) [0x80488bc]
4: /lib/tls/libc.so.6(__libc_start_main+0xe4) [0x42015574]
5: ./main(_ZNSt8ios_base4InitD1Ev+0x31) [0x8048809] <>

Looks good. But still function names and prefixed and appended with ugly characters. What’s that? I have never given such names to my function in my life.

This is called mangling. One of the biggest grieves for novices when they come to C++.

C++ allows u to overload functions. But when it comes to linker only thing it knows is the function name and its relocatable address. So how would compiler notify linker that there exists two fn functions accepting different set of parameters.

Simple generate a function name based on parameters it accepts and use it for internal purposes. That’s exactly is being done as shown in output.

If there exists a function fn with signature:

void fn(int ) ;

then mangled name will be:

_Z3fni.

(1) Function name: fn

(2) Parameter it accepts: i stands for int.

GCC site will give u all the data types and equivalent mangled names.

Ok I understood that. But what r last two functions. I never called them. Why they are appearing the stack trace? When u type in an executable name on the shell it does some initialization before calling main of ur program. We will discuss later in another article what exactly initialization goes behind scenes before calling main. <>

Now what do u expect me to do? Get a list of all the mangled equivalents and translate into equivalent ones? No. don’t worry. There is an utility program available to do for exactly that purpose. c++filt which takes a mangled name and gives equivalent demangled one.

$ c++filt _Z3fni
fn1(int)

Gr8. Now what do u expect? Invoke c++filt on each of these mangled name and get the demangled name. Also I need source file and line no. info. Where do I get them from?

The address u r seeing at the end withing [] is the key. That address is enough to give u everything u want with the addr2line.

addr2line is a bin utility program which accepts an executable and address and gives demangled function name, source file and line no. info.

$ addr2line -e main -fC 0x8048938
show_stack_frame(int, double)
/home/prasad/StackTrace/main.cc:40

Gr8. Now u want me to replace c++filt with addr2line to get this extra info. No. Don’t worry. We will discuss how to get the required stack info. programmatically in next section.

Till that happy hacking into bin util programs. Have fun!!.

1 Comments:

Blogger ChozenOne said...

I think this is a very good feature, but arn't you copying "java". I'm damm sure u must be a big fan of java...U should change ur name to Cool-Java-Coder.

On a serous note, is this stack tracing portable to other platforms??

July 12, 2005 at 3:39 AM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home