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
が呼び出されたことを示すものが必要です。ローカル関数が最適化コンパイラによってインライン化されておらず、バイナリが削除されていない(シンボルが削除されていない)と仮定すると、これを実行できるツールはありますか?
編集
いくつかの説明:
特定の機能についてのみ通知を受けたい場合、次のようにできます。
デバッグ情報を使用してコンパイルします(すでにシンボル情報を持っているので、おそらく十分なデバッグもあります)
与えられた
#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
)、好きなことをすることができます。グローバルの値を出力したり、シェルコマンドを実行したり、fatal_bomb_exploded
function :)悲しいことに、gccはその間にいくつかの「現在の言語が変更されました」というメッセージを出力します。しかし、それは簡単に誤解されています。大きな問題ではない。
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
パラメーター_~/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
_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では、トラス(同等のトレース)には、トレースするライブラリをフィルタリングする機能があります。 straceにそのような機能がないことを発見したとき、私は驚きました。
$ Sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
詳細: ftrace.1
その関数を外部ライブラリに外部化すると、呼び出されるのを見ることができるはずです(ltraceを使用)。
これが機能する理由は、ltraceがアプリとライブラリの間に置かれ、すべてのコードが1つのファイルに内部化されると、呼び出しをインターセプトできないためです。
すなわち:ltrace xterm
xライブラリからものを吐き出し、Xはほとんどシステムではありません。
これ以外では、それを行う唯一の実際の方法は、profフラグまたはデバッグシンボルを介したコンパイル時インターセプトです。
私はこのアプリを実行しましたが、面白そうです:
http://www.gnu.org/software/cflow/
しかし、私はそれがあなたが望むものだとは思わない。
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を使用)。非常に簡単で、ほぼ毎日このスクリプトを使用しています。
関数がインライン化されていない場合は、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
のようなものは実行された関数呼び出しのみを表示します。Linux C/C++アプリケーションのトレースフレームワークであるトレースを参照してください。 https://github.com/baruch/traces#readme
インストルメントでコードを再コンパイルする必要がありますが、すべての関数、そのパラメーター、および戻り値のリストが提供されます。大きなデータサンプルの簡単なナビゲーションを可能にするインタラクティブな機能があります。
Gprof はあなたが望むものかもしれません
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でテスト済み。
注:これは、Linuxカーネルベースのftraceではなく、ローカル関数のトレースと制御フローを実現するために最近設計したツールです。 Linux ELF x86_64/x86_32は公的にサポートされています。
Valgrind の callgrindまたはcachegrindツール が、希望する情報を提供してくれることを願っています。