转自:http://www.aiuxian.com/article/p-361301.html
有时候需要知道一个函数是被哪个函数调用的。比如,一个函数被成千上百个文件的函数调用,加入其中一个调用不对导致除了问题的话,要找出是那个地方调用的话,一个笨方法是找到每个调用的地方,加上打印信息,但这显然是不现实的。此外,有些调用的地方可能是以库的形式存在的,这样的话,就没有办法通过加打印信息找出来了。
一种较好的方法是,重新写一个同样接口的函数,里面打印出调用者函数的名字(甚至是 backtrace)让系统运行的时候,在调用原来函数的地方,自动调用我们重新写的那个函数。我们可以使用环境变量 LD_PRELOAD 来达到这个目的。做法是:先把我们自己写的函数编成一个共享库,然后在系统运行的时候,让 LD_PRELOAD指向这共享库。
man ld-linux 可以查到 这个环境变量的详细信息。简言之,它指向的共享库会被最优先装载进来
下面我们以函数 memcpy()为例说明。
我们重写的函数在文件 backtrace.c里面,如下:
08 |
static void * (*mymemcpy)( void *, const void *, size_t ); |
10 |
__attribute__ ((constructor)) void Initialize( void ) |
13 |
handle = dlopen( "/lib/i386-linux-gnu/libc-2.15.so" , RTLD_LAZY); |
15 |
fprintf (stderr, "%s\n" , dlerror()); |
20 |
*( void **)(&mymemcpy) = dlsym(handle, "memcpy" ); |
21 |
if ((error = dlerror()) != NULL) { |
22 |
fprintf (stderr, "%s\n" , error); |
27 |
__attribute__ ((destructor)) void Finalize( void ) |
35 |
void * memcpy ( void * dest, const void *src, size_t size) |
40 |
(*mymemcpy)(dest, src, size); |
47 |
dladdr(__builtin_return_address(0), &dli); |
48 |
fprintf (stderr, "debug trace [%d]: %s " |
49 |
"called by %p [ %s(%p) %s(%p) ].\n" , |
51 |
__builtin_return_address(0), |
52 |
strrchr (dli.dli_fname, '/' ) ? |
53 |
strrchr (dli.dli_fname, '/' )+1 : dli.dli_fname, |
54 |
dli.dli_fbase, dli.dli_sname, dli.dli_saddr); |
55 |
dladdr(__builtin_return_address(1), &dli); |
56 |
fprintf (stderr, "debug trace [%d]: %*s " |
57 |
"called by %p [ %s(%p) %s(%p) ].\n" , |
58 |
getpid(), strlen (__func__), "..." , |
59 |
__builtin_return_address(1), |
60 |
strrchr (dli.dli_fname, '/' ) ? |
61 |
strrchr (dli.dli_fname, '/' )+1 : dli.dli_fname, |
62 |
dli.dli_fbase, dli.dli_sname, dli.dli_saddr); |
这个代码是根据下面的代码改写的:
链接地址
测试代吗如下(test5.c)
4 |
memcpy (arr, "haha" , 4); |
5 |
printf ( "arr = %s\n" , arr); |
用如下命令编译:
1 |
gcc -fpic -shared -g backtrace.c -o libstrace.so -ldl |
1 |
gcc -g test5.c -o test5 |
执行如下:
1 |
LD_PRELOAD=./libstrace.so ./test5 |
所加的打印信息没有,看来重新写的那个函数没有被调用到。
是不是 memcpy函数根本就没有调用到呢?(比如,被编译器优化掉了)
下面看一下汇编语言,里面有没有对这个函数的调用:
01 |
objdump -d -S test5 | grep -A10 memcpy |
02 |
memcpy(arr, "haha", 4); |
03 |
8048449: 8d 44 24 17 lea 0x17(%esp),%eax |
04 |
804844d: c7 00 68 61 68 61 movl $0x61686168,(%eax) |
05 |
printf("arr = %s\n", arr); |
06 |
8048453: 8d 44 24 17 lea 0x17(%esp),%eax |
07 |
8048457: 89 44 24 04 mov %eax,0x4(%esp) |
08 |
804845b: c7 04 24 50 85 04 08 movl $0x8048550,(%esp) |
09 |
8048462: e8 d9 fe ff ff call 8048340 <printf@plt> |
11 |
8048467: b8 00 00 00 00 mov $0x0,%eax |
确实没有!
原因是 GCC出于效率上考虑,使用了内建的内存拷贝函数。
可以加上选项不用内建的函数:
1 |
gcc -g -fno-builtin-memcpy test5.c -o test5 |
然后重新执行:
1 |
$ LD_PRELOAD=./libstrace.so ./test5 |
2 |
debug trace [8004]: memcpy called by 0x8048495 [ test5(0x8048000) (null)((nil)) ]. |
3 |
debug trace [8004]: ... called by 0xb757f4d3 [ libc.so.6(0xb7566000) __libc_start_main(0xb757f3e0) ]. |
现在总算调到了。
但是,调用着函数名字还是没有打印出来。。
重新编译一下, 加上一个选项:
1 |
gcc -g -export-dynamic -fno-builtin-memcpy test5.c -o test5 |
上面新加的选项还可以是-rdynamic
然后重新之执行:
1 |
$ LD_PRELOAD=./libstrace.so ./test5debug trace [8103]: memcpy called by 0x8048625 [ test5(0x8048000) main(0x80485f4) ]. |
2 |
debug trace [8103]: ... called by 0xb754b4d3 [ libc.so.6(0xb7532000) __libc_start_main(0xb754b3e0) ]. |
现在基本上大功告成了.
更进一步,还可以打印出整个调用链的 backstrace
man backtrace 给出了一个例子。这里就不重复了。