转自:http://www.aiuxian.com/article/p-361301.html
有时候需要知道一个函数是被哪个函数调用的。比如,一个函数被成千上百个文件的函数调用,加入其中一个调用不对导致除了问题的话,要找出是那个地方调用的话,一个笨方法是找到每个调用的地方,加上打印信息,但这显然是不现实的。此外,有些调用的地方可能是以库的形式存在的,这样的话,就没有办法通过加打印信息找出来了。
一种较好的方法是,重新写一个同样接口的函数,里面打印出调用者函数的名字(甚至是 backtrace)让系统运行的时候,在调用原来函数的地方,自动调用我们重新写的那个函数。我们可以使用环境变量 LD_PRELOAD 来达到这个目的。做法是:先把我们自己写的函数编成一个共享库,然后在系统运行的时候,让 LD_PRELOAD指向这共享库。
man ld-linux 可以查到 这个环境变量的详细信息。简言之,它指向的共享库会被最优先装载进来
下面我们以函数 memcpy()为例说明。
我们重写的函数在文件 backtrace.c里面,如下:
01 |
#define _GNU_SOURCE |
02 |
#include <dlfcn.h> |
03 |
#include <stdio.h> |
04 |
#include <stdlib.h> |
05 |
06 |
/* ... */ |
07 |
static void * handle;
|
08 |
static void * (*mymemcpy)( void *, const void *, size_t );
|
09 |
10 |
__attribute__ ((constructor)) void Initialize( void )
|
11 |
{ |
12 |
char * error;
|
13 |
handle = dlopen( "/lib/i386-linux-gnu/libc-2.15.so" , RTLD_LAZY);
|
14 |
if (!handle) {
|
15 |
fprintf (stderr, "%s\n" , dlerror());
|
16 |
exit (EXIT_FAILURE);
|
17 |
}
|
18 |
dlerror();
|
19 |
20 |
*( void **)(&mymemcpy) = dlsym(handle, "memcpy" );
|
21 |
if ((error = dlerror()) != NULL) {
|
22 |
fprintf (stderr, "%s\n" , error);
|
23 |
exit (EXIT_FAILURE);
|
24 |
}
|
25 |
} |
26 |
27 |
__attribute__ ((destructor)) void Finalize( void )
|
28 |
{ |
29 |
if (handle)
|
30 |
{
|
31 |
dlclose(handle);
|
32 |
}
|
33 |
} |
34 |
35 |
void * memcpy ( void * dest, const void *src, size_t size)
|
36 |
{ |
37 |
38 |
if (mymemcpy)
|
39 |
{
|
40 |
(*mymemcpy)(dest, src, size);
|
41 |
}
|
42 |
/* .... */
|
43 |
#if 1//DEBUG == 1 |
44 |
// {
|
45 |
Dl_info dli;
|
46 |
/* this only works in a shared object context */
|
47 |
dladdr(__builtin_return_address(0), &dli);
|
48 |
fprintf (stderr, "debug trace [%d]: %s "
|
49 |
"called by %p [ %s(%p) %s(%p) ].\n" ,
|
50 |
getpid(), __func__,
|
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);
|
63 |
// }
|
64 |
#endif |
65 |
/* .... */
|
66 |
} |
这个代码是根据下面的代码改写的:
测试代吗如下(test5.c)
1 |
int main( void )
|
2 |
{ |
3 |
char arr[5];
|
4 |
memcpy (arr, "haha" , 4);
|
5 |
printf ( "arr = %s\n" , arr);
|
6 |
return 0;
|
7 |
} |
用如下命令编译:
1 |
gcc -fpic -shared -g backtrace.c -o libstrace.so -ldl |
1 |
gcc -g test5.c -o test5 |
执行如下:
1 |
LD_PRELOAD=./libstrace.so ./test5 |
2 |
arr = haha |
所加的打印信息没有,看来重新写的那个函数没有被调用到。
是不是 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> |
10 |
return 0;
|
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) ]. |
4 |
arr = haha |
现在总算调到了。
但是,调用着函数名字还是没有打印出来。。
重新编译一下, 加上一个选项:
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) ]. |
3 |
arr = haha |
现在基本上大功告成了.
更进一步,还可以打印出整个调用链的 backstrace
man backtrace 给出了一个例子。这里就不重复了。