【问题标题】:Tool to trace local function calls in Linux在 Linux 中跟踪本地函数调用的工具
【发布时间】:2010-09-23 15:13:12
【问题描述】:

我正在寻找像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 一样使用该工具附加到现有进程,我会非常高兴。

【问题讨论】:

  • 您是否研究过使用 gdb 进行跟踪?它曾经说过我它只适用于远程目标。也许您可以使 gdb 与远程目标一起工作并连接到本地主机?不确定,只是一些随机的想法。
  • 我不想中断程序流程,如果 gdb 可以不显眼地跟踪像 ltrace 这样的程序,如果有人告诉我如何,我愿意尝试一下。

标签: linux debugging trace


【解决方案1】:

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

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

给定

#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 在两者之间输出一些“当前语言已更改”消息。但这很容易被发现。没什么大不了的。

【讨论】:

  • 我希望能够跟踪所有本地函数,不想通过显式设置断点来中断程序。
  • 您可以使用 objdump 获取函数及其地址,然后使用 --command 参数将 gdb 指向生成的文件,该文件会自动设置断点。
  • @litb,是的,这就是我现在想要做的,这可能有效,感谢您的洞察力。
  • @litb,这似乎可行,我可以附加到正在运行的进程,不需要额外的调试符号,程序中断合理。我只需要弄清楚如何从脚本启动 gdb 并将输出发送到文件,我需要开始花更多时间在 GDB 上 :)
  • @litb,我需要这个功能,所以我编写了一个 Python 脚本来执行您的建议,为 OpenGrok 和 GraphViz dot 生成输出。如果有人有兴趣,可以通过github.com/EmmetCaulfield/ftrace 获取。它可以满足我的需要,但我怀疑它是否非常稳定。 YMMV。
【解决方案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 and oprofile updates

【讨论】:

  • 只是想注意,这可能取决于内核编译选项;例如我得到了相同的命令:“semantic error: process probes not available without kernel CONFIG_UTRACE while resolving probe point process("/bin/ls").function("*").call
  • 这不适用于我在 Ubuntu 14.04 上使用 semantic error: while resolving probe point: identifier 'process' at a.stp:23:7。 system tap的工作原理是什么?
  • 无需指定完整路径作为process() 的参数,sudo stap para-callgraph.stp 'process.function("*")' -c /bin/ls 也可以。要减少来自没有可用调试符号的库函数的噪音,您可以使用:'process.function("*@*")'
【解决方案3】:

使用Uprobes(Linux 3.5 起)

假设您想在使用参数-l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl 调用它时跟踪~/Desktop/datalog-2.2/datalog 中的所有函数

  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

【讨论】:

    【解决方案4】:

    假设您可以使用 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 等效项)能够过滤要跟踪的库。当我发现 strace 没有这样的能力时,我感到很惊讶。

    【讨论】:

    • 您是否必须针对您的代码编译 + 链接 ptrace.c 才能使其工作?当你有一个庞大的代码库和一个巨大的 make 文件时,这并不总是一个合理的任务:)
    • @philant 我忘记了那个选项。真的很好。
    【解决方案5】:
    $ sudo yum install frysk
    $ ftrace -sym:'*' -- ./a.out
    

    更多:ftrace.1

    【讨论】:

    • 从手册页中我不清楚这是否能满足我的要求,但这个项目似乎处于测试阶段,除了 Fedora 之外的任何平台都没有得到很好的支持。我使用了几个发行版,但都不是 Fedora,看起来尝试让它与它们中的任何一个一起工作都是一个挑战。
    【解决方案6】:

    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 上测试。

    【讨论】:

      【解决方案7】:

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

      之所以可行,是因为 ltrace 将自己置于您的应用程序和库之间,并且当所有代码都被一个文件内部化时,它无法拦截调用。

      即:ltrace xterm

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

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

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

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

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

      【讨论】:

      • 我理解为什么 ltrace 能够做它所做的事情并且跟踪本地函数更加困难,但是如果有一个工具可以附加到进程并自动在所有本地设置断点会很好如果这是必需的,会自动跟踪它们。
      【解决方案8】:

      如果函数没有内联,您甚至可以使用objdump -d &lt;program&gt; 来获得好运。

      举个例子,让我们在 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 这样的东西只会显示已执行的函数调用。

      【讨论】:

        【解决方案9】:

        有一个用于使用 gdb 自动跟踪函数调用的 shell 脚本。但它不能附加到正在运行的进程。

        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 命令文件,每个函数都有断点。在每个断点处,都会执行“backtrace 2”和“continue”。

        这个脚本在大型项目(〜数千个函数)上相当慢,所以我在函数列表上添加了一个过滤器(通过 egrep)。这很简单,我几乎每天都使用这个脚本。

        【讨论】:

        【解决方案10】:

        Gprof 可能是你想要的

        【讨论】:

        • 我不想分析代码,只是跟踪它。我想知道每次调用本地函数时,参数是什么,返回值是什么。我也不想像 gprof 要求的那样,重新编译程序,并为特定工具提供特殊支持。
        【解决方案11】:

        查看 traces,一个用于 Linux C/C++ 应用程序的跟踪框架: https://github.com/baruch/traces#readme

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

        【讨论】:

          【解决方案12】:

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

          【讨论】:

          • 我已经研究了 valgrind 的所有工具,但没有什么能满足我的要求。
          【解决方案13】:

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

          https://github.com/leviathansecurity/ftrace

          【讨论】:

          • 这里能总结一下工作原理吗?为什么不使用 GDB 的 Python API 代替?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-15
          • 2018-10-26
          • 1970-01-01
          • 2014-05-23
          • 2015-07-02
          相关资源
          最近更新 更多