This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[x86-64] An unreliable backtrace().
- From: PaweÅ Sikora <pluto at agmk dot net>
- To: libc-alpha at sourceware dot org
- Date: Sun, 6 Aug 2006 19:35:43 +0200
- Subject: [x86-64] An unreliable backtrace().
Hi,
In glibc-2.4 the x86-64/ia64 backtrace_helper() uses _Unwind_GetIP() to fill
the backtrace array. In fact _Unwind_GetIP retrieves the return address which
isn't a caller address. A backtrace is a summary of how your program got where
it is. Currently glibc-2.4 and gdb-6.5 (at least for x86-64) produces
an unwind path instead of true backtrace.
Please consider attached testcase.
$ gcc -Wall -ggdb -rdynamic --save-temps -O1 backTrace.c -o backTrace
Let's see a quick comparision.
(gdb) bt
#0 libcBackTrace () at backTrace.c:39
#1 0x0000000000400a7f in zoo () at backTrace.c:52
#2 0x00000000004009ce in bar (f=0x400a71 <zoo>) at backTrace.c:58
#3 0x00000000004009e1 in foo () at backTrace.c:63
#4 0x00000000004009f4 in main () at backTrace.c:68
(gdb) c
libc backtrace:
ip = 0x400a11
ip = 0x400a7f
ip = 0x4009ce
ip = 0x4009e1
ip = 0x4009f4
ip = 0x2b58886f1134
ip = 0x4008f9
(gdb) set listsize 1
(gdb) l *0x400a11
0x400a11 is in libcBackTrace (backTrace.c:41).
41 int n = backtrace( array, 64 ); <= wrong, 0x400a11 != call
(gdb) l *0x400a7f
0x400a7f is in zoo (backTrace.c:53).
53 myBackTrace(); <= wrong, 0x400a7f != call
(gdb) l *0x4009ce
0x4009ce is in bar (backTrace.c:59).
59 } <= wrong, 0x4009ce != ret
(gdb) l *0x4009e1
0x4009e1 is in foo (backTrace.c:64).
64 } <= wrong, 0x4009e1 != ret
(gdb) l *0x4009f4
0x4009f4 is in main (backTrace.c:70).
70 } <= wrong, 0x4009f4 != ret
We can see that libc and gdb print bogus backtrace :(
00000000004009c3 <bar>:
4009c3: 48 83 ec 08 sub $0x8,%rsp
4009c7: b8 00 00 00 00 mov $0x0,%eax
4009cc: ff d7 callq *%rdi
4009ce: 48 83 c4 08 add $0x8,%rsp
4009d2: c3 retq
00000000004009d3 <foo>:
4009d3: 48 83 ec 08 sub $0x8,%rsp
4009d7: bf 71 0a 40 00 mov $0x400a71,%edi
4009dc: e8 e2 ff ff ff callq 4009c3 <bar>
4009e1: 48 83 c4 08 add $0x8,%rsp
4009e5: c3 retq
00000000004009e6 <main>:
4009e6: 48 83 ec 08 sub $0x8,%rsp
4009ea: b8 00 00 00 00 mov $0x0,%eax
4009ef: e8 df ff ff ff callq 4009d3 <foo>
4009f4: b8 00 00 00 00 mov $0x0,%eax
4009f9: 48 83 c4 08 add $0x8,%rsp
4009fd: c3 retq
00000000004009fe <libcBackTrace>:
4009fe: 41 54 push %r12
400a00: 55 push %rbp
400a01: 53 push %rbx
400a02: be 40 00 00 00 mov $0x40,%esi
400a07: bf 60 12 60 00 mov $0x601260,%edi
400a0c: e8 67 fe ff ff callq 400878 <backtrace@plt>
400a11: 41 89 c4 mov %eax,%r12d
400a14: bf ac 0b 40 00 mov $0x400bac,%edi
400a19: e8 6a fe ff ff callq 400888 <puts@plt>
400a1e: 45 85 e4 test %r12d,%r12d
400a21: 7e 27 jle 400a4a <libcBackTrace+0x4c>
400a23: bd 00 00 00 00 mov $0x0,%ebp
400a28: bb 60 12 60 00 mov $0x601260,%ebx
400a2d: 48 8b 33 mov (%rbx),%rsi
400a30: bf bc 0b 40 00 mov $0x400bbc,%edi
400a35: b8 00 00 00 00 mov $0x0,%eax
400a3a: e8 29 fe ff ff callq 400868 <printf@plt>
400a3f: ff c5 inc %ebp
400a41: 48 83 c3 08 add $0x8,%rbx
400a45: 41 39 ec cmp %ebp,%r12d
400a48: 75 e3 jne 400a2d <libcBackTrace+0x2f>
400a4a: 5b pop %rbx
400a4b: 5d pop %rbp
400a4c: 41 5c pop %r12
400a4e: c3 retq
0000000000400a4f <myBackTrace>:
400a4f: 48 83 ec 08 sub $0x8,%rsp
400a53: bf c5 0b 40 00 mov $0x400bc5,%edi
400a58: e8 2b fe ff ff callq 400888 <puts@plt>
400a5d: be 00 00 00 00 mov $0x0,%esi
400a62: bf 8e 0a 40 00 mov $0x400a8e,%edi
400a67: e8 4c fe ff ff callq 4008b8 <_Unwind_Backtrace@plt>
400a6c: 48 83 c4 08 add $0x8,%rsp
400a70: c3 retq
0000000000400a71 <zoo>:
400a71: 48 83 ec 08 sub $0x8,%rsp
400a75: b8 00 00 00 00 mov $0x0,%eax
400a7a: e8 7f ff ff ff callq 4009fe <libcBackTrace>
400a7f: b8 00 00 00 00 mov $0x0,%eax
400a84: e8 c6 ff ff ff callq 400a4f <myBackTrace>
400a89: 48 83 c4 08 add $0x8,%rsp
400a8d: c3 retq
With a little hack in backtrace helper we can extract the true caller
address from return point address.
(gdb) c
my backtrace:
ip = 0x400a67
ip = 0x400a84
ip = 0x4009cc
ip = 0x4009dc
ip = 0x4009ef
ip = 0x2b58886f1134
ip = 0x4008f4
(gdb) l *0x400a67
0x400a67 is in myBackTrace (backTrace.c:35).
35 _Unwind_Backtrace( backTraceHelper, 0 );
(gdb) l *0x400a84
0x400a84 is in zoo (backTrace.c:53).
53 myBackTrace();
(gdb) l *0x4009cc
0x4009cc is in bar (backTrace.c:58).
58 f();
(gdb) l *0x4009dc
0x4009dc is in foo (backTrace.c:63).
63 bar( &zoo );
(gdb) l *0x4009ef
0x4009ef is in main (backTrace.c:68).
68 foo();
Yup, It looks better now.
BR,
PaweÅ.
CXX := gcc
CXXFLAGS := -Wall -ggdb -rdynamic --save-temps -O1
all: backTrace.c
$(CXX) $(CXXFLAGS) backTrace.c -o backTrace
clean:
rm -f *.s *.i* *.o backTrace
#include <execinfo.h>
#include <stdio.h>
#include <string.h>
#include <unwind.h>
long callerAddress( long ip )
{
#if defined( __x86_64__ )
unsigned char const* code = (unsigned char const*)ip;
// call, near, relative
if ( code[ -5 ] == 0xe8 )
return ( ip - 5 );
// call, near, absolute indirect
if ( code[ -2 ] == 0xff )
{
if ( ( code[ -1 ] & 0xf8 ) == 0xd0 ) // call *%reg
return ( ip - 2 );
if ( ( code[ -1 ] & 0xf8 ) == 0x10 ) // call *(%reg)
return ( ip - 2 );
}
#endif
return ip;
}
_Unwind_Reason_Code backTraceHelper( struct _Unwind_Context *ctx, void *traceArgs )
{
_Unwind_Ptr ip = _Unwind_GetIP( ctx );
printf( "ip = 0x%lx\n", callerAddress( (long)ip ) );
return _URC_NO_REASON;
}
void myBackTrace()
{
puts( "my backtrace:" );
_Unwind_Backtrace( backTraceHelper, 0 );
}
void libcBackTrace()
{
static void* array[ 64 ];
int n = backtrace( array, 64 );
int i = 0;
puts( "libc backtrace:" );
for ( ; i < n; i++ )
printf( "ip = %p\n", array[ i ] );
}
typedef void ( *fn )();
void zoo()
{
libcBackTrace();
myBackTrace();
}
void bar( fn f )
{
f();
}
void foo()
{
bar( &zoo );
}
int main()
{
foo();
return 0;
}