在代码中获取调用者函数的名字【转】

时间:2022-10-01 18:11:37

转自: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 给出了一个例子。这里就不重复了。