在Linux中跟踪本地函数调用的工具
我正在寻找像ltrace或strace这样的工具,可以跟踪可执行文件中的本地定义的函数。 ltrace只跟踪dynamic库调用,并且只跟踪系统调用。 例如,给定以下C程序:
#include <stdio.h> int triple ( int x ) { return 3 * x; } int main (void) { printf("%d\n", triple(10)); return 0; }
使用ltrace
运行程序会显示printf
的调用,因为这是一个标准的库函数(这是我系统上的一个dynamic库), strace
将显示来自启动代码的所有系统调用,用于实现printf的系统调用,以及关机代码,但我想要的东西,会告诉我,函数triple
被调用。 假设本地函数没有被优化编译器内联,并且二进制文件没有被删除(符号被删除),是否有一个工具可以做到这一点?
编辑
一些澄清:
- 如果该工具还为非本地function提供跟踪信息,那也没问题。
- 我不想重新编译支持特定工具的程序,可执行文件中的符号信息就足够了。
- 如果我可以使用该工具附加到现有的进程,如我可以用ltrace / strace,我会非常好。
假设你只想通知特定的function,你可以这样做:
编译debugging信息(因为你已经有符号信息,你可能也有足够的debugging)
特定
#include <iostream> int fac(int n) { if(n == 0) return 1; return n * fac(n-1); } int main() { for(int i=0;i<4;i++) std::cout << fac(i) << std::endl; }
使用gdb来跟踪:
[js@HOST2 cpp]$ g++ -g3 test.cpp [js@HOST2 cpp]$ gdb ./a.out (gdb) b fac Breakpoint 1 at 0x804866a: file test.cpp, line 4. (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >bt 1 >c >end (gdb) run Starting program: /home/js/cpp/a.out #0 fac (n=0) at test.cpp:4 1 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 1 #0 fac (n=2) at test.cpp:4 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 2 #0 fac (n=3) at test.cpp:4 #0 fac (n=2) at test.cpp:4 #0 fac (n=1) at test.cpp:4 #0 fac (n=0) at test.cpp:4 6 Program exited normally. (gdb)
以下是我收集所有function的地址:
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
请注意,不是只打印当前帧( bt 1
),你可以做任何你喜欢的事情,打印一些全局的值,执行一些shell命令或者邮件,如果它碰到了fatal_bomb_exploded
函数:)不幸的是,gcc输出一些“Current语言改变“之间的消息。 但是,这很容易被清除。 没什么大不了。
系统Tap可用于现代Linux机器(Fedora 10,RHEL 5等)。
首先下载para-callgraph.stp脚本。
然后运行:
$ 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
另请参阅: 观察systemtap和oprofile更新
使用Uprobes (从Linux 3.5开始)
假设你想用-l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
-
cd /usr/src/linux-`uname -r`/tools/perf
-
for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
-
sudo ./perf record -agR $(for j in $(sudo ./perf probe -l | cut -d' ' -f3); do echo "-e $j"; done) ~/Desktop/datalog-2.2/datalog -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
-
sudo ./perf report -G
假设您可以使用gcc选项-finstrument-functions
重新编译(不需要更改源代码)您要跟踪的代码,那么可以使用etrace来获取函数调用图。
以下是输出结果:
\-- 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 equivalent)能够过滤要跟踪的库。 当我发现strace没有这样的能力时,我感到很惊讶。
$ sudo yum install frysk $ ftrace -sym:'*' -- ./a.out
更多: ftrace.1
如果这些函数没有内联,你甚至可能使用objdump -d <program>
运气。
举一个例子,让我们在GCC 4.3.2的main
开始时拿出一个赃物:
$ 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
在所有的汇编程序中花费一点努力,但是你可以看到来自给定函数的所有可能的调用。 使用gprof
或其他一些公用程序并不容易,但它有几个明显的优点:
- 您通常不需要重新编译应用程序来使用它
- 它显示所有可能的函数调用,而类似
gprof
只显示执行的函数调用。
有一个用gdb自动跟踪函数调用的shell脚本。 但它不能附加到运行过程。
blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
工具的副本 – callgraph.tar.gz
http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz
它转储程序中的所有函数,并在每个函数上生成一个带有断点的gdb命令文件。 在每个断点处,执行“回溯2”和“继续”。
这个脚本在大型项目(〜数千个函数)上相当慢,所以我在函数列表中添加一个filter(通过egrep)。 这很简单,几乎每天都用这个剧本。
如果将该函数外部化为外部库,则还应该能够看到它被调用(使用ltrace)。
这个工作的原因是因为ltrace把自己放在你的应用程序和库之间,并且当所有的代码都被一个文件内化时,它不能拦截这个调用。
即:ltrace xterm
从X库中发布了一些东西,X几乎不是系统。
除此之外,唯一可行的方法是通过prof标志或debugging符号进行编译时截取。
我只是跑过这个应用程序,看起来很有趣:
http://www.gnu.org/software/cflow/
但我不认为这是你想要的。
查看跟踪,Linux C / C ++应用程序的跟踪框架: https : //github.com/baruch/traces#readme
它需要使用其工具重新编译代码,但会提供所有函数,参数和返回值的列表。 有一个交互式的,可以轻松导航大数据样本。
希望Valgrind的callgrind或cachegrind工具能够为您提供所需的信息。
Gprof可能是你想要的
注意:这不是基于linux内核的ftrace,而是我最近devise的一个工具,用于完成本地function跟踪和控制stream程。 Linux ELF x86_64 / x86_32被公开支持。