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

在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(“%

我正在寻找像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(“%d\n”,三(10));
返回0;
}
使用
ltrace
运行程序将显示对
printf
的调用,因为这是一个标准库函数(在我的系统上是一个动态库),
strace
将显示启动代码中的所有系统调用、用于实现printf的系统调用和关闭代码,但是我需要一些东西来显示调用了函数
triple
。假设本地函数没有被优化编译器内联,并且二进制文件没有被剥离(删除符号),有没有工具可以做到这一点

编辑

几点澄清:

  • 如果该工具还为非本地函数提供跟踪信息,则可以
  • 我不想在支持特定工具的情况下重新编译程序,可执行文件中的符号信息应该足够了
  • 如果我能像使用ltrace/strace一样使用该工具附加到现有流程,我会非常高兴

希望for会为您提供所需的信息。

可能是您想要的

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

使用调试信息编译(因为您已经有了符号信息,所以在中可能也有足够的调试)

给定

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

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