Linux 中跟踪本地函数调用的工具
- 2024-10-11 08:36:00
- admin 原创
- 90
问题描述:
我正在寻找一种像ltrace或strace这样的工具,它可以跟踪可执行文件中本地定义的函数。ltrace 仅跟踪动态库调用,而 strace 仅跟踪系统调用。例如,给定以下 C 程序:
#include <stdio.h>
int triple ( int x )
{
return 3 * x;
}
int main (void)
{
printf("%d
", triple(10));
return 0;
}
使用 运行程序ltrace
将显示对 的调用,printf
因为这是一个标准库函数(在我的系统上是一个动态库),并将strace
显示启动代码中的所有系统调用、用于实现 printf 的系统调用以及关闭代码,但我想要一些可以显示该函数triple
被调用的东西。假设本地函数尚未被优化编译器内联,并且二进制文件尚未被剥离(删除符号),是否有可以执行此操作的工具?
编辑
需要澄清几点:
如果该工具还提供非本地函数的跟踪信息则没问题。
我不想重新编译支持特定工具的程序,可执行文件中的符号信息就足够了。
如果我能使用该工具附加到现有进程,就像使用 ltrace/strace 一样,那就太好了。
解决方案 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 会在中间输出一些“当前语言已更改”消息。但这很容易通过 grep 删除。没什么大不了的。
解决方案 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 更新
解决方案 3:
使用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
解决方案 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 没有这样的功能时,我感到很惊讶。
解决方案 5:
缓存研磨
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 上测试。
解决方案 6:
由于这个问题在搜索结果中非常突出,我将添加另一种方法,15 年后,这种方法比这里列出的其他方法麻烦更少:uftrace
它需要编译相应的应用程序-pg -g
(或者-finstrument-functions
如果您只对函数名称感兴趣,而不关心参数和返回值)。然后您可以以交互方式运行该命令:
uftrace -a --no-libcall -f none <cmd>
或者,也可以先记录跟踪数据,然后单独输出。
uftrace record -a --no-libcall -f none <cmd>
uftrace replay
后者也可以跨平台工作,例如,您可以在系统 A 上运行记录阶段,将跟踪数据(目录uftrace.data
)传输到系统 B,然后在机器 B 上重放。
使用的选项:
-a
启用所有参数和返回值的输出--no-libcall
隐藏所有标准 libc 函数-f none
隐藏通常打印在前面的持续时间和线程 ID 列
另一个有用的选项是-N
,例如,-N log_*
过滤掉所有以 开头的函数调用log_
。有关更多信息,请参阅 的联机帮助页uftrace-replay
。
如果输出缺少返回值,请禁用链接时优化后重试。由于某种原因,它们在我的测试中被隐藏了。
使用原始选项以交互方式运行 OP 示例(注意:30 是程序到 stdout 的输出):
# uftrace a.out
30
# DURATION TID FUNCTION
0.671 us [802533] | __monstartup();
0.421 us [802533] | __cxa_atexit();
[802533] | main() {
0.060 us [802533] | triple();
11.882 us [802533] | printf();
12.263 us [802533] | } /* main */
C++ fac 示例仅展示要点:
# uftrace --no-libcall -a a.out
1
1
2
6
# DURATION TID FUNCTION
[803250] | _GLOBAL__sub_I_fac() {
107.551 us [803250] | __static_initialization_and_destruction_0(1, 65535);
108.473 us [803250] | } /* _GLOBAL__sub_I_fac */
[803250] | main() {
0.211 us [803250] | fac(0) = 1;
[803250] | fac(1) {
0.080 us [803250] | fac(0) = 1;
0.491 us [803250] | } = 1; /* fac */
[803250] | fac(2) {
[803250] | fac(1) {
0.070 us [803250] | fac(0) = 1;
0.340 us [803250] | } = 1; /* fac */
0.541 us [803250] | } = 2; /* fac */
[803250] | fac(3) {
[803250] | fac(2) {
[803250] | fac(1) {
2.464 us [803250] | fac(0) = 1;
2.725 us [803250] | } = 1; /* fac */
2.916 us [803250] | } = 2; /* fac */
3.086 us [803250] | } = 6; /* fac */
33.463 us [803250] | } = 0; /* main */
解决方案 7:
$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
更多内容:ftrace.1
解决方案 8:
如果将该函数外部化到外部库中,您还应该能够看到它被调用(使用 ltrace)。
这种方法之所以有效,是因为 ltrace 将自身置于您的应用程序和库之间,并且当所有代码都与一个文件内部化时,它无法拦截调用。
例如:ltrace xterm
从 X 库中喷涌出东西,而 X 几乎不是系统。
除此之外,唯一真正可行的方法是通过 prof 标志或调试符号进行编译时拦截。
我刚刚运行了这个应用程序,它看起来很有趣:
http://www.gnu.org/software/cflow/
但我不认为那正是你想要的。
解决方案 9:
如果函数不是内联的,您甚至可能幸运地使用objdump -d <program>
。
举个例子,我们来看看 GCC 4.3.2 例程开头的 loot 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
只会显示已执行的函数调用。
解决方案 10:
有一个 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 命令文件,其中包含每个函数上的断点。在每个断点处,都会执行“backtrace 2”和“continue”。
这个脚本在大型项目(约数千个函数)上相当慢,所以我在函数列表上添加了一个过滤器(通过 egrep)。这很容易,我几乎每天都使用这个脚本。
解决方案 11:
Gprof可能是你想要的
解决方案 12:
查看 traces,一个用于 Linux C/C++ 应用程序的跟踪框架:
https://github.com/baruch/traces#readme
它需要用其工具重新编译您的代码,但会提供所有函数及其参数和返回值的列表。有一个交互式界面,可以轻松浏览大数据样本。
解决方案 13:
希望Valgrind的callgrind 或 cachegrind 工具能够为您提供您所寻求的信息。
解决方案 14:
注意:这不是基于 Linux 内核的 ftrace,而是我最近设计的一个用于完成本地函数跟踪和控制流的工具。Linux ELF x86_64/x86_32 公开支持。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件