当前位置:  开发笔记 > 编程语言 > 正文

在Linux中跟踪本地函数调用的工具

如何解决《在Linux中跟踪本地函数调用的工具》经验,为你挑选了4个好方法。

我正在寻找像ltrace或strace这样的工具,它可以跟踪可执行文件中的本地定义函数.ltrace仅跟踪动态库调用,而strace仅跟踪系统调用.例如,给定以下C程序:

#include 

int triple ( int x )
{
  return 3 * x;
}

int main (void)
{
  printf("%d\n", triple(10));
  return 0;
}

运行程序ltrace将显示调用,printf因为这是一个标准库函数(我的系统上是一个动态库),strace并将显示启动代码,用于实现printf的系统调用和关闭代码的所有系统调用,但我想要一些能告诉我函数triple被调用的东西.假设优化编译器没有内联本地函数,并且二进制文件没有被剥离(符号被删除),是否有工具可以做到这一点?

编辑

几点澄清:

如果该工具还提供非本地功能的跟踪信息,那也没关系.

我不想重新编译支持特定工具的程序,可执行文件中的符号信息应该足够了.

如果我可以使用该工具附加到现有的进程,就像我可以使用ltrace/strace一样,我会非常高兴.

Johannes Sch.. 52

假设您只想获得特定功能的通知,您可以这样做:

用调试信息编译(因为你已经有了符号信息,你可能也有足够的调试)

特定

#include 

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)

以下是我收集所有函数地址的方法:

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输出一些"当前语言已更改"两者之间的消息.但这很容易被人看出来.没什么大不了.



1> Johannes Sch..:

假设您只想获得特定功能的通知,您可以这样做:

用调试信息编译(因为你已经有了符号信息,你可能也有足够的调试)

特定

#include 

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)

以下是我收集所有函数地址的方法:

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输出一些"当前语言已更改"两者之间的消息.但这很容易被人看出来.没什么大不了.


@litb,我需要这个功能,所以我编写了一个Python脚本来完成你的建议,为OpenGrok和GraphViz点生成输出.如果有人有兴趣,你可以访问https://github.com/EmmetCaulfield/ftrace.它做我需要的,但我怀疑它是否非常稳定.因人而异.

2> 小智..:

System 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

另请参阅:Observe,systemtap和oprofile更新


只是想注意,这可能取决于内核编译选项; 例如,我得到了相同的命令:"`语义错误:在解析探测点进程("/ bin/ls")时没有内核CONFIG_UTRACE的情况下进程探测不可用.function("*").call`"

3> Janus Troels..:

使用Uprobes(从Linux 3.5开始)

假设您想在~/Desktop/datalog-2.2/datalog使用参数调用它时跟踪所有函数-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

数据记录二进制中的函数列表 选择dl_pushlstring时调用树,显示主调用loadfile调用dl_load如何称为程序调用规则调用literal,后者又调用其他函数调用dl_pushlstring,扫描(parent:program,即从顶部开始的第三次扫描)调用dl_pushstring等



4> philant..:

假设您可以使用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没有这样的能力时,我感到很惊讶.

推荐阅读
贴进你的心聆听你的世界
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有