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

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

原文地址:https://www.cnblogs.com/sky-heaven/p/10384816.html

时间: 2024-10-13 01:14:11

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

Cocos2d-x3.0游戏实例之《别救我》第六篇——从代码中获取UI控件

这篇的内容很简单,获取UI控件,然后使用它. 还记得我们在UI编辑器中给三个按钮分别命名了吧? 现在要用上了. 笨木头花心贡献,啥?花心?不呢,是用心~ 转载请注明,原文地址: http://www.benmutou.com/blog/archives/918 文章来源:笨木头与游戏开发 根据名字查找控件 首先给TollgateScene再include一些头文件,不然等会编译又报错了: #include "editor-support/cocostudio/CCSGUIReader.h&quo

java代码中获取进程process id(转)

另一方面,线程ID=进程ID+内部线程对象ID并不成立,    参考: blog.csdn.net/heyetina/article/details/6633901     如何在java代码中获取进程process id, 实现方法如下所示:    参考: rednaxelafx.iteye.com/blog/716918 http://www.cnblogs.com/mumuxinfei/p/3678854.html

js代码中碰到的函数

第一个--->字符串的截取substring()方法 substring(a,b)--->[a,b)区间截取字符.下标从0开始.从a下标开始,截取到b下标的前一个字符.返回一个新的字符串 1 <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script> 2 <script type="text/javascript"> 3

获取存储过程返回值及代码中获取返回值

获取存储过程返回值及代码中获取返回值 1.OUPUT参数返回值例: 向Order表插入一条记录,返回其标识 CREATE PROCEDURE [dbo].[nb_order_insert](@o_buyerid int ,@o_id bigint OUTPUT)ASBEGINSET NOCOUNT ON;BEGININSERT INTO [Order](o_buyerid )VALUES (@o_buyerid )SET @o_id = @@IDENTITYENDEND 存储过程中获得方法: D

Java代码中获取Json的key值

测试json字符串: {"access_token":"hkbQl5o_l67dZ7_vJRATKBwTLk9Yj5QyMuOJThAr8Baj0xWf4wxW1p4ym4iTZ3-ptWRRoVtbCNJnyjGLfed_4bB0HiqGP8v-aXOJUUk1tFs","expires_in":7200,"refresh_token":"_or_31yEp8XhxGWvrAzdWIkXfFlKjfb3iTRpfa

在ASP.NET项目中的web.config文件里配置数据库连接并在程序代码中获取连接字符串

  1.在<connectionStrings> 标签里添加连接 <connectionStrings> <add name="ConnectionName" connectionString="Server=.\SQLEXPRESS;Database=DatabaseName;UserID=sa;Password=abc123" providerName="System.Data.SqlClient" />

uefi bios代码中如果找到函数的实现?

写在最前,摘自 K&R The only legal operations on a structure are copying it or assigning to it as a unit, taking its address with & (读作 ampersand),  and accessing its members, 翻译成中文是这样的,对于一个结构体而言,唯一合法的操作是把它当成一个整体去给它赋值,或者拷贝到另一个地方. 取它的地址,访问它的成员. UEFI 定义了很多个

ASP.NET WebForm中JavaScript修改了页面上Label的值,如何在后台代码中获取

在用ASP.NET WebForm开发一个项目时,遇到如下的一个情况 页面上有一个Textbox控件,还有2个Label 控件. 当Textbox控件中的值更改时,两个Label控件上的值做相应的更改, 这一点是通过页面中嵌入的JavaScript来实现的. 但是,Label控件上的值更改后,在后端.cs代码中,通过Label.Text 并不能取到更改后的值. order.aspx页面代码如下: <%@ Page Language="C#" AutoEventWireup=&qu

jQuery从html代码中获取对应标签的写法

#################################常用的标签选择器######################################### 1.示例代码 <div id='t1'></div> 获取id为t1标签 $('#t1') 2.示例代码 <div class='t2'></div> 获取div标签 $('.t2') 3.示例代码 <p>dagagagragag</p> <p>dagagag