在Linux中跟踪本地函数调用的工具
我正在寻找像or这样的工具,它可以在可执行文件中跟踪本地定义的函数。ltrace只跟踪动态库调用,strace只跟踪系统调用。例如,给定以下C程序:在Linux中跟踪本地函数调用的工具,linux,debugging,trace,Linux,Debugging,Trace,我正在寻找像or这样的工具,它可以在可执行文件中跟踪本地定义的函数。ltrace只跟踪动态库调用,strace只跟踪系统调用。例如,给定以下C程序: #include <stdio.h> int triple ( int x ) { return 3 * x; } int main (void) { printf("%d\n", triple(10)); return 0; } #包括 整数三元组(整数x) { 返回3*x; } 内部主(空) { printf(“%
#include <stdio.h>
int triple ( int x )
{
return 3 * x;
}
int main (void)
{
printf("%d\n", triple(10));
return 0;
}
#包括
整数三元组(整数x)
{
返回3*x;
}
内部主(空)
{
printf(“%d\n”,三(10));
返回0;
}
使用ltrace
运行程序将显示对printf
的调用,因为这是一个标准库函数(在我的系统上是一个动态库),strace
将显示启动代码中的所有系统调用、用于实现printf的系统调用和关闭代码,但是我需要一些东西来显示调用了函数triple
。假设本地函数没有被优化编译器内联,并且二进制文件没有被剥离(删除符号),有没有工具可以做到这一点
编辑
几点澄清:
- 如果该工具还为非本地函数提供跟踪信息,则可以
- 我不想在支持特定工具的情况下重新编译程序,可执行文件中的符号信息应该足够了
- 如果我能像使用ltrace/strace一样使用该工具附加到现有流程,我会非常高兴
tmp=$(mktemp)
readelf -s ./a.out | gawk '
{
if($4 == "FUNC" && $2 != 0) {
print "# code for " $NF;
print "b *0x" $2;
print "commands";
print "silent";
print "bt 1";
print "c";
print "end";
print "";
}
}' > $tmp;
gdb --command=$tmp ./a.out;
rm -f $tmp
请注意,不只是打印当前帧(
bt1
),您可以做任何您喜欢的事情,打印一些全局的值,执行一些shell命令,或者在点击fatal\u bomb\u explodesd
函数:)时发送一些内容。遗憾的是,gcc在这两者之间会输出一些“当前语言已更改”的消息。但这很容易被抹掉。没什么大不了的。如果将该函数外部化到外部库中,您还应该能够看到它被调用(使用ltrace)
这样做之所以有效,是因为ltrace将自身置于应用程序和库之间,当所有代码都内部化为一个文件时,它无法拦截调用
ie:ltrace xterm
从X库中吐出东西,X很难成为系统
除此之外,唯一真正的方法是通过prof标志或调试符号进行编译时截取
我刚刚浏览了这个应用程序,它看起来很有趣:
但我认为这不是你想要的
$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
更多信息:假设您可以使用gcc选项
-finstrument functions
重新编译(无需更改源代码)要跟踪的代码,您可以使用来获取函数调用图
以下是输出结果:
\-- main
| \-- Crumble_make_apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
在Solaris上,truss(strace等效)能够过滤要跟踪的库。当我发现斯特拉斯没有这样的能力时,我很惊讶 系统点击可以在现代Linux设备(Fedora 10、RHEL 5等)上使用 首先下载脚本 然后运行:
$ sudo stap para-callgraph.stp 'process("/bin/ls").function("*")' -c /bin/ls
0 ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276 ls(12631): ->human_options spec=0x0 opts=0x61a28c block_size=0x61a290
365 ls(12631): <-human_options return=0x0
496 ls(12631): ->clone_quoting_options o=0x0
657 ls(12631): ->xmemdup p=0x61a600 s=0x28
815 ls(12631): ->xmalloc n=0x28
908 ls(12631): <-xmalloc return=0x1efe540
950 ls(12631): <-xmemdup return=0x1efe540
990 ls(12631): <-clone_quoting_options return=0x1efe540
1030 ls(12631): ->get_quoting_style o=0x1efe540
$sudo stap para-callgraph.stp'进程(“/bin/ls”)。函数(“*”)-c/bin/ls
0 ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276 ls(12631):->人类选项规范=0x0选项=0x61a28c块大小=0x61a290
365 ls(12631):克隆“引用”选项o=0x0
657 ls(12631):->xmemdup p=0x61a600 s=0x28
815 ls(12631):->xmalloc n=0x28
908ls(12631):如果函数没有内联,那么您甚至可以使用objdump-d
一个例子,让我们在GCC 4.3.2的<代码>主< /代码>例程开始时进行掠夺:
$ objdump `which gcc` -d | grep '\(call\|main\)'
08053270 <main>:
8053270: 8d 4c 24 04 lea 0x4(%esp),%ecx
--
8053299: 89 1c 24 mov %ebx,(%esp)
805329c: e8 8f 60 ff ff call 8049330 <strlen@plt>
80532a1: 8d 04 03 lea (%ebx,%eax,1),%eax
--
80532cf: 89 04 24 mov %eax,(%esp)
80532d2: e8 b9 c9 00 00 call 805fc90 <xmalloc_set_program_name>
80532d7: 8b 5d 9c mov 0xffffff9c(%ebp),%ebx
--
80532e4: 89 04 24 mov %eax,(%esp)
80532e7: e8 b4 a7 00 00 call 805daa0 <expandargv>
80532ec: 8b 55 9c mov 0xffffff9c(%ebp),%edx
--
8053302: 89 0c 24 mov %ecx,(%esp)
8053305: e8 d6 2a 00 00 call 8055de0 <prune_options>
805330a: e8 71 ac 00 00 call 805df80 <unlock_std_streams>
805330f: e8 4c 2f 00 00 call 8056260 <gcc_init_libintl>
8053314: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp)
--
805331c: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8053323: e8 78 5e ff ff call 80491a0 <signal@plt>
8053328: 83 e8 01 sub $0x1,%eax
$objdump`which gcc`-d|grep'\(调用\|main\)'
08053270 :
8053270:8d 4c 24 04 lea 0x4(%esp),%ecx
--
8053299:89 1c 24 mov%ebx,(%esp)
805329c:e8 8f 60 ff ff呼叫8049330
80532a1:8d 04 03 lea(%ebx,%eax,1),%eax
--
80532cf:89 04 24 mov%eax,(%esp)
80532d2:e8 b9 c9 00 00呼叫805fc90
80532d7:8b 5d 9c mov 0xffffff9c(%ebp),%ebx
--
80532e4:89 04 24 mov%eax,(%esp)
80532e7:e8 b4 a7 00 00呼叫805daa0
80532ec:8b 55 9c mov 0xffffff9c(%ebp),%edx
--
8053302:89 0c 24 mov%ecx,(%esp)
8053305:e8 d6 2a 00 00呼叫8055de0
805330a:e8 71 ac 00 00呼叫805df80
805330f:e8 4c 2f 00 00呼叫8056260
8053314:c7 44 24 04 01 00动产$0x1,0x4(%esp)
--
805331c:C704240200MOVL$0x2,(%esp)
8053323:e8 78 5e ff ff呼叫80491a0
8053328:83 e8 01子$0x1,%eax
遍历所有汇编程序需要一些努力,但是您可以看到来自给定函数的所有可能调用。它不像gprof
或上面提到的其他一些实用程序那样容易使用,但它有几个明显的优点:
- 您通常不需要重新编译应用程序来使用它
- 它显示所有可能的函数调用,而类似于
gprof
的东西只显示已执行的函数调用
有一个shell脚本,用于使用gdb自动跟踪函数调用。但它不能附加到正在运行的进程
blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
该页的副本-
工具的副本-callgraph.tar.gz
它从程序中转储所有函数,并生成一个gdb命令文件,每个函数上都有断点。在每个断点处,执行“回溯2”和“继续”
这个脚本在大型项目上相当慢(~数千个函数)
$ sudo stap para-callgraph.stp 'process("/bin/ls").function("*")' -c /bin/ls
0 ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276 ls(12631): ->human_options spec=0x0 opts=0x61a28c block_size=0x61a290
365 ls(12631): <-human_options return=0x0
496 ls(12631): ->clone_quoting_options o=0x0
657 ls(12631): ->xmemdup p=0x61a600 s=0x28
815 ls(12631): ->xmalloc n=0x28
908 ls(12631): <-xmalloc return=0x1efe540
950 ls(12631): <-xmemdup return=0x1efe540
990 ls(12631): <-clone_quoting_options return=0x1efe540
1030 ls(12631): ->get_quoting_style o=0x1efe540
$ objdump `which gcc` -d | grep '\(call\|main\)'
08053270 <main>:
8053270: 8d 4c 24 04 lea 0x4(%esp),%ecx
--
8053299: 89 1c 24 mov %ebx,(%esp)
805329c: e8 8f 60 ff ff call 8049330 <strlen@plt>
80532a1: 8d 04 03 lea (%ebx,%eax,1),%eax
--
80532cf: 89 04 24 mov %eax,(%esp)
80532d2: e8 b9 c9 00 00 call 805fc90 <xmalloc_set_program_name>
80532d7: 8b 5d 9c mov 0xffffff9c(%ebp),%ebx
--
80532e4: 89 04 24 mov %eax,(%esp)
80532e7: e8 b4 a7 00 00 call 805daa0 <expandargv>
80532ec: 8b 55 9c mov 0xffffff9c(%ebp),%edx
--
8053302: 89 0c 24 mov %ecx,(%esp)
8053305: e8 d6 2a 00 00 call 8055de0 <prune_options>
805330a: e8 71 ac 00 00 call 805df80 <unlock_std_streams>
805330f: e8 4c 2f 00 00 call 8056260 <gcc_init_libintl>
8053314: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp)
--
805331c: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8053323: e8 78 5e ff ff call 80491a0 <signal@plt>
8053328: 83 e8 01 sub $0x1,%eax
int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }
int main(int argc, char **argv) {
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
}
sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234