有时候需要知道一个函数是被哪个函数调用的。比如,一个函数被成千上百个文件的函数调用,加入其中一个调用不对导致除了问题的话,要找出是那个地方调用的话,一个笨方法是找到每个调用的地方,加上打印信息,但这显然是不现实的。此外,有些调用的地方可能是以库的形式存在的,这样的话,就没有办法通过加打印信息找出来了。
一种较好的方法是,重新写一个同样接口的函数,里面打印出调用者函数的名字(甚至是 backtrace)让系统运行的时候,在调用原来函数的地方,自动调用我们重新写的那个函数。我们可以使用环境变量 LD_PRELOAD 来达到这个目的。做法是:先把我们自己写的函数编成一个共享库,然后在系统运行的时候,让 LD_PRELOAD指向这共享库。
man ld-linux 可以查到 这个环境变量的详细信息。简言之,它指向的共享库会被最优先装载进来
下面我们以函数 memcpy()为例说明。
我们重写的函数在文件 backtrace.c里面,如下:
#define _GNU_SOURCE
#include
#include
#include
/* ... */
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
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 给出了一个例子。这里就不重复了。