zoukankan      html  css  js  c++  java
  • 在代码中获取调用者函数的名字【转】

    转自: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 ", dlerror());
    16         exit(EXIT_FAILURE);
    17    }
    18    dlerror();
    19  
    20   *(void **)(&mymemcpy) = dlsym(handle, "memcpy");
    21    if ((error = dlerror()) != NULL)  {
    22            fprintf(stderr, "%s ", 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) ]. ",
    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) ]. ",
    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 ", 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 ", 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 给出了一个例子。这里就不重复了。

  • 相关阅读:
    SpringCloudStream实例
    Gateway环境搭建,通过YML文件配置
    Hystrix图形化监控
    Hystrix服务降级
    SpringBootのRedis
    springboot之缓存
    springboot整合JPA
    留言板
    Python 京东口罩监控+抢购
    2019年 自我总结
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/10384816.html
Copyright © 2011-2022 走看看