web-dev-qa-db-ja.com

Linuxでローカル関数呼び出しをトレースするツール

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が呼び出されたことを示すものが必要です。ローカル関数が最適化コンパイラによってインライン化されておらず、バイナリが削除されていない(シンボルが削除されていない)と仮定すると、これを実行できるツールはありますか?

編集

いくつかの説明:

  • ツールが非ローカル関数のトレース情報も提供する場合は問題ありません。
  • 特定のツールをサポートしてプログラムを再コンパイルする必要はありません。実行可能ファイルのシンボル情報で十分です。
  • このツールを使用して、ltrace/straceでできるように既存のプロセスにアタッチできれば、本当にうれしいです。
58
Robert Gamble

特定の機能についてのみ通知を受けたい場合、次のようにできます。

デバッグ情報を使用してコンパイルします(すでにシンボル情報を持っているので、おそらく十分なデバッグもあります)

与えられた

#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

参照: 監視、システムタップ、およびプロファイル更新

20
callgiraffe

probes の使用(Linux 3.5以降)

パラメーター_~/Desktop/datalog-2.2/datalog_を指定して呼び出したときに、_-l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl_のすべての関数をトレースしたいと仮定します。

  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_

list of functions in datalog binarycall tree when selecting dl_pushlstring, showing how main called loadfile called dl_load called program called rule which called literal which in turn called other functions that ended up calling dl_pushlstring, scan (parent: program, that is, the third scan from the top) which called dl_pushstring and so on

11
Janus Troelsen

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にそのような機能がないことを発見したとき、私は驚きました。

9
philant
$ Sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out

詳細: ftrace.1

4
callgiraffe

その関数を外部ライブラリに外部化すると、呼び出されるのを見ることができるはずです(ltraceを使用)。

これが機能する理由は、ltraceがアプリとライブラリの間に置かれ、すべてのコードが1つのファイルに内部化されると、呼び出しをインターセプトできないためです。

すなわち:ltrace xterm

xライブラリからものを吐き出し、Xはほとんどシステムではありません。

これ以外では、それを行う唯一の実際の方法は、profフラグまたはデバッグシンボルを介したコンパイル時インターセプトです。

私はこのアプリを実行しましたが、面白そうです:

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

しかし、私はそれがあなたが望むものだとは思わない。

2
Kent Fredric

Gdbを使用して関数呼び出しのトレースを自動化するシェルスクリプトがあります。しかし、実行中のプロセスにアタッチすることはできません。

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を使用)。非常に簡単で、ほぼ毎日このスクリプトを使用しています。

2
osgx

関数がインライン化されていない場合は、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のようなものは実行された関数呼び出しのみを表示します。
2
Tom

Linux C/C++アプリケーションのトレースフレームワークであるトレースを参照してください。 https://github.com/baruch/traces#readme

インストルメントでコードを再コンパイルする必要がありますが、すべての関数、そのパラメーター、および戻り値のリストが提供されます。大きなデータサンプルの簡単なナビゲーションを可能にするインタラクティブな機能があります。

1
Greythorn

Gprof はあなたが望むものかもしれません

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初期化ボイラープレートが含まれています
  • f0f1およびf2は互いに期待どおりに呼び出されます
  • 関数ポインターで呼び出した場合でも、pointedも表示されます。コマンドライン引数を渡した場合は、呼び出されていない可能性があります。
  • not_calledは、実行時に呼び出されなかったため、追加のコマンドライン引数を渡さなかったため、表示されません。

valgrindの素晴らしい点は、特別なコンパイルオプションを必要としないことです。

そのため、ソースコードがなくても実行可能ファイルのみを使用できます。

valgrindは、軽量の「仮想マシン」を介してコードを実行することにより、それを管理しています。

Ubuntu 18.04でテスト済み。

注:これは、Linuxカーネルベースのftraceではなく、ローカル関数のトレースと制御フローを実現するために最近設計したツールです。 Linux ELF x86_64/x86_32は公的にサポートされています。

https://github.com/leviathansecurity/ftrace

0
elfmaster

Valgrindcallgrindまたはcachegrindツール が、希望する情報を提供してくれることを願っています。

0
activout.se