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

有时候需要知道一个函数是被哪个函数调用的。比如,一个函数被成千上百个文件的函数调用,加入其中一个调用不对导致除了问题的话,要找出是那个地方调用的话,一个笨方法是找到每个调用的地方,加上打印信息,但这显然是不现实的。此外,有些调用的地方可能是以库的形式存在的,这样的话,就没有办法通过加打印信息找出来了。


一种较好的方法是,重新写一个同样接口的函数,里面打印出调用者函数的名字(甚至是  backtrace)让系统运行的时候,在调用原来函数的地方,自动调用我们重新写的那个函数。我们可以使用环境变量 LD_PRELOAD 来达到这个目的。做法是:先把我们自己写的函数编成一个共享库,然后在系统运行的时候,让 LD_PRELOAD指向这共享库。

man ld-linux 可以查到 这个环境变量的详细信息。简言之,它指向的共享库会被最优先装载进来

下面我们以函数 memcpy()为例说明。

我们重写的函数在文件 backtrace.c里面,如下:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

/* ... */
static void * handle;
static void * (*mymemcpy)(void *, const void *, size_t);

__attribute__ ((constructor)) void Initialize(void)
{
  char * error;
  handle = dlopen("/lib/i386-linux-gnu/libc-2.15.so", RTLD_LAZY);
  if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
   }
   dlerror();

  *(void **)(&mymemcpy) = dlsym(handle, "memcpy");
   if ((error = dlerror()) != NULL)  {
           fprintf(stderr, "%s\n", error);
           exit(EXIT_FAILURE);
   }
}

__attribute__ ((destructor)) void Finalize(void)
{
	if(handle)
	{
		dlclose(handle);
	}
}

void * memcpy(void * dest, const void *src, size_t size)
{

	if(mymemcpy)
	{
		(*mymemcpy)(dest, src, size);
	}
        /* .... */
#if 1//DEBUG == 1
 //       {
                Dl_info dli;
                /* this only works in a shared object context */
                dladdr(__builtin_return_address(0), &dli);
                fprintf(stderr, "debug trace [%d]: %s "
                                "called by %p [ %s(%p) %s(%p) ].\n",
                                getpid(), __func__,
                                 __builtin_return_address(0),
                                strrchr(dli.dli_fname, '/') ?
                                        strrchr(dli.dli_fname, '/')+1 : dli.dli_fname,
                                dli.dli_fbase, dli.dli_sname, dli.dli_saddr);
                dladdr(__builtin_return_address(1), &dli);
                fprintf(stderr, "debug trace [%d]: %*s "
                                "called by %p [ %s(%p) %s(%p) ].\n",
                                getpid(), strlen(__func__), "...",
                                __builtin_return_address(1),
                                strrchr(dli.dli_fname, '/') ?
                                        strrchr(dli.dli_fname, '/')+1 : dli.dli_fname,
                                dli.dli_fbase, dli.dli_sname, dli.dli_saddr);
 //       }
#endif
        /* .... */
}

这个代码是根据下面的代码改写的:

http://www.clifford.at/cfun/elfstuff/


测试代吗如下(test5.c)

int main(void)
{
	char arr[5];
	memcpy(arr, "haha", 4);
	printf("arr = %s\n", arr);
	return 0;
}

用如下命令编译: 

 gcc -fpic -shared -g backtrace.c  -o libstrace.so -ldl
 gcc -g test5.c  -o test5

执行如下:

LD_PRELOAD=./libstrace.so ./test5
arr = haha

所加的打印信息没有,看来重新写的那个函数没有被调用到。

是不是 memcpy函数根本就没有调用到呢?(比如,被编译器优化掉了)

下面看一下汇编语言,里面有没有对这个函数的调用:

 objdump  -d -S test5 | grep -A10 memcpy 
	memcpy(arr, "haha", 4);
 8048449:	8d 44 24 17          	lea    0x17(%esp),%eax
 804844d:	c7 00 68 61 68 61    	movl   $0x61686168,(%eax)
	printf("arr = %s\n", arr);
 8048453:	8d 44 24 17          	lea    0x17(%esp),%eax
 8048457:	89 44 24 04          	mov    %eax,0x4(%esp)
 804845b:	c7 04 24 50 85 04 08 	movl   $0x8048550,(%esp)
 8048462:	e8 d9 fe ff ff       	call   8048340 <printf@plt>
	return 0;
 8048467:	b8 00 00 00 00       	mov    $0x0,%eax
确实没有!

原因是  GCC出于效率上考虑,使用了内建的内存拷贝函数。

可以加上选项不用内建的函数:

gcc -g -fno-builtin-memcpy test5.c  -o test5
然后重新执行:

$ LD_PRELOAD=./libstrace.so  ./test5
debug trace [8004]: memcpy called by 0x8048495 [ test5(0x8048000) (null)((nil)) ].
debug trace [8004]:    ... called by 0xb757f4d3 [ libc.so.6(0xb7566000) __libc_start_main(0xb757f3e0) ].
arr = haha
现在总算调到了。

但是,调用着函数名字还是没有打印出来。。

重新编译一下, 加上一个选项:

 gcc -g -export-dynamic -fno-builtin-memcpy test5.c  -o test5

上面新加的选项还可以是-rdynamic

然后重新之执行:

$ LD_PRELOAD=./libstrace.so  ./test5debug trace [8103]: memcpy called by 0x8048625 [ test5(0x8048000) main(0x80485f4) ].
debug trace [8103]:    ... called by 0xb754b4d3 [ libc.so.6(0xb7532000) __libc_start_main(0xb754b3e0) ].
arr = haha

现在基本上大功告成了.


更进一步,还可以打印出整个调用链的 backstrace

man backtrace 给出了一个例子。这里就不重复了。

你可能感兴趣的:(在代码中获取调用者函数的名字)