我正在寻找可以跟踪可执行文件中本地定义的函数的ltrace或strace之类的工具。 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
的东西。假设优化编译器没有内联本地函数,并且二进制文件没有被剥离(符号被删除),是否有工具可以做到这一点?
修改
一些澄清:
答案 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
答案 2 :(得分:11)
假设您想使用参数~/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
答案 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 /
工具的复制 - 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初始化样板f0
,f1
和f2
按照预期彼此调用pointed
也会显示,即使我们用函数指针调用它。如果我们通过命令行参数,它可能没有被调用。not_called
未显示,因为它未在运行中调用,因为我们没有传递额外的命令行参数。关于valgrind
的一个很酷的事情是它不需要任何特殊的编译选项。
因此,即使您没有源代码,也可以使用它,只能使用可执行文件。
valgrind
通过轻量级“虚拟机”运行代码来实现这一目标。
在Ubuntu 18.04上测试。
答案 11 :(得分:0)
希望callgrind or cachegrind tools的Valgrind能够为您提供所需的信息。
答案 12 :(得分:0)
注意:这不是基于Linux内核的ftrace,而是我最近设计用于完成本地函数跟踪和控制流的工具。 Linux ELF x86_64 / x86_32是公开支持的。