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

时间:2008-11-22 22:12:14

标签: linux debugging trace

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

#include <stdio.h>

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

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

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

修改

一些澄清:

  • 如果该工具还提供非本地功能的跟踪信息,那也没关系。
  • 我不想重新编译支持特定工具的程序,可执行文件中的符号信息就足够了。
  • 如果我可以使用该工具附加到现有流程,就像我可以使用ltrace / strace一样,我会非常高兴。

13 个答案:

答案 0 :(得分:53)

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

使用调试信息进行编译(因为您已经有符号信息,您可能还有足够的调试)

给定的

#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)

以下是我收集所有功能地址的方法:

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 :(得分:20)

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 and oprofile updates

答案 2 :(得分:11)

使用Uprobes(自Linux 3.5开始)

假设您想使用参数~/Desktop/datalog-2.2/datalog

调用-l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl中的所有功能
  1. cd /usr/src/linux-`uname -r`/tools/perf
  2. for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
  3. 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
  4. sudo ./perf report -G
  5. list of functions in datalog binary call tree when selecting dl_pushlstring, showing how main called loadfile called dl_load called program called rule which called literal which in turn called other functions that ended up calling dl_pushlstring, scan (parent: program, that is, the third scan from the top) which called dl_pushstring and so on

答案 3 :(得分:9)

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

答案 4 :(得分:4)

$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out

更多:ftrace.1

答案 5 :(得分:2)

如果将该函数外部化到外部库中,您还应该能够看到它被调用(使用ltrace)。

这样做的原因是因为ltrace将自己放在你的应用程序和库之间,当所有代码都用一个文件内化时,它就无法拦截调用。

即:ltrace xterm

从X库中掏出东西,而X几乎不是系统。

除此之外,唯一真正的方法是通过prof标志或调试符号进行编译时拦截。

我刚跑过这个看起来很有趣的应用程序:

http://www.gnu.org/software/cflow/

但我不认为那就是你想要的。

答案 6 :(得分:2)

如果没有内联函数,您甚至可以使用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之类的东西只显示已执行的函数调用。

答案 7 :(得分:2)

有一个shell脚本可以使用gdb自动跟踪函数调用。但它无法附加到运行过程。

blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger /

页面副本 - http://web.archive.org/web/20090317091725/http://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”和“继续”。

这个脚本在big porject(〜数千个函数)上相当慢,所以我在函数列表上添加了一个过滤器(通过egrep)。这很容易,我几乎每天都会使用这个脚本。

答案 8 :(得分:1)

Gprof可能就是您想要的

答案 9 :(得分:1)

请参阅trace,Linux C / C ++应用程序的跟踪框架: https://github.com/baruch/traces#readme

它需要使用其仪器重新编译代码,但会提供所有函数,其参数和返回值的列表。有一个交互式,可以轻松导航大数据样本。

答案 10 :(得分:1)

<强> KcacheGrind

https://kcachegrind.github.io/html/Home.html

测试程序:

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

现在你被置于一个非常棒的GUI程序中,其中包含许多有趣的性能数据。

在右下角,选择“调用图”标签。这会显示一个交互式调用图,当您单击这些函数时,它会与其他窗口中的性能指标相关联。

要导出图形,请右键单击它并选择“导出图形”。导出的PNG如下所示:

由此我们可以看出:

  • 根节点是_start,它是实际的ELF入口点,包含glibc初始化样板
  • f0f1f2按照预期彼此调用
  • pointed也会显示,即使我们用函数指针调用它。如果我们通过命令行参数,它可能没有被调用。
  • not_called未显示,因为它未在运行中调用,因为我们没有传递额外的命令行参数。

关于valgrind的一个很酷的事情是它不需要任何特殊的编译选项。

因此,即使您没有源代码,也可以使用它,只能使用可执行文件。

valgrind通过轻量级“虚拟机”运行代码来实现这一目标。

在Ubuntu 18.04上测试。

答案 11 :(得分:0)

希望callgrind or cachegrind toolsValgrind能够为您提供所需的信息。

答案 12 :(得分:0)

注意:这不是基于Linux内核的ftrace,而是我最近设计用于完成本地函数跟踪和控制流的工具。 Linux ELF x86_64 / x86_32是公开支持的。

https://github.com/leviathansecurity/ftrace