17

他のソフトウェアの関数呼び出しを追跡するソフトウェアを作りたいです。
「実行時」のどこからどの関数が呼び出されているかのようになります。

例:

int main ()
{
    a ();
    b ();
    c ();
    return 0;
}

a () 
{
   d ();
   e ();
}

b ()
{
   e ();
   f ();
}

これを現在CforCで記述したい場合、実行時(最初の呼び出しから開始)に呼び出しを追跡するにはどうすればよいですか?スレッドありとスレッドなしですか?

ヒント?

4

5 に答える 5

8

これは、実行前に静的分析によって、または実行時に動的分析によって行うことができます。

実行時にこれを行う方法は2つしかなく、どちらもインストルメンテーションコードになります。

ソースコードをインストルメントする

ソースコードを変更(「機器」)して、必要なものを追跡します。たとえば、すべての呼び出しサイトを変更して、呼び出し元の関数名を効果的に含む追加の引数を渡し、すべての関数エントリを変更して、その関数へのエントリを記録します(たとえば、事実上、呼び出された関数名)と呼び出し元の関数名。インストルメンテーションプロセスでは、インストルメントされたポイントからソースファイルと行に戻って相互参照を作成し、後でレポートできるようにする必要があります。

これは、プログラム変換システム(PTS)を使用して完全に自動化された方法で実行できます。このシステムは、ソースコードを解析してASTを作成し、ASTを変更してから、ソースを再生成できます。多くの場合、PTSを使用すると、フォームのソースレベルのパターンとして変更を書き込むことができます。「これが表示された場合は、それを置き換えてください」。(いいえ、正規表現はこれを行うことができません)。

これを行う方法の拡張例は、私のテクニカルペーパー「任意の言語のブランチカバレッジ」にあります。ペーパーショーでは、PTSの内容について説明し、次に一般的な計装変換を示します。

Cのような言語でこれを「正しく」行うのは難しい場合があります。これは、基盤として完全なCパーサーが必要であるためです。これは、それ自体が主要な技術的偉業です。このような獣を完成した形で入手するのが最善です(PTSの中にはこれを持っているものもありますが、ConcinnelleまたはDMSを検討してください)。(別の回答は、GCCにこのインストルメンテーション機能の多くが組み込まれていることを示唆しています)。

合理的に行われると、追加のインストルメンテーションの実行コストはかなり控えめです(10-30%)。この概念は、テストカバレッジツールとタイミングプロファイラーを実装するために使用されています。これらは、動的な呼び出しのセットを追跡することによって動作します。これはまさにあなたが望むもののようです。

オブジェクトコードをインストルメントする

コードを実行する実行エンジンを変更して、必要な詳細を追跡します。Cプログラムでこれを実行したい場合、実行エンジンは基本的に基礎となる命令セットです。

プロセッサチップ自体を合理的に変更することはできないため、次のいずれかを行う必要があります。

  • オブジェクトコードを変更します(プログラム変換に相当するオブジェクトコードを使用します。概念については前の段落を参照してください。実装の詳細については、PINやValgrindなどのツールを参照してください)。

  • または、呼び出し命令に遭遇したときに必要な情報を収集する、ある種の巧妙な命令セットインタープリターを作成します(これは、その解釈の性質のため、かなり遅い可能性があります)。

これらを実際の命令セットに実装することは、命令セット自体(IntelのX86命令セットは巨大です)、オブジェクトファイル形式などによって複雑になります。このパスがソースインストルメンテーションパスよりも簡単であると期待しないでください。別の問題が発生することを期待してください。

標準的な問題の1つは、何を計測するかを知ることです。オブジェクトコードスペースは、対象のアプリケーションの一部ではないコード(ライブラリ、システムルーチンなど)でいっぱいであるため、テストカバレッジデータを収集するのは面白くありません。このためには、アプリケーションコードとそうでないものを区別するためのシンボルテーブル(ペア)が必要です。また、結果を報告するには、名前から元のソースファイル内のポイントまでトレースできる必要があります。

オブジェクトコードのインストルメンテーションに関するもう1つの問題は、インライン化された関数fooが実行された(「カバーされた」)かどうかを判断するのが難しいことです。ソースインストルメンテーションスキームにはこの問題はありません。これは、fooがコンパイル/インライン化される前にインストルメント化されるため、インストルメンテーションもインライン化されるためです。

スレッド

スレッドの処理は直交する問題です。ファクトX-calls-Yを記録する各場所で、OSから取得したスレッドID(または同等の)Tを添付するだけで、X-calls-Y-in-Tが生成されます。なぜあなたがこれをしたいのかはあなたの質問からは明らかではありません。

静的解析

ランタイム実装のすべての面倒をスキップして、コールグラフを生成できる静的アナライザーを構築/取得することを決定できます。C用のこれらの取得はClangまたはDMSを使用して可能かもしれません。自分で構築するのは難しいでしょう。ここで、完全なCパーサー、完全なデータフローとポイントツー分析、およびコールグラフの構築が必要です。詳細はこの段落に収まりません。

于 2014-06-20T09:27:19.937 に答える
7

これはプラットフォームに大きく依存します。必要なのは、デバッガーが行うことを多かれ少なかれ行うことです。

私がこれに取り組む方法(新しいアーキテクチャでgdbをビルドするために必要なツールをデバッグする必要があったときに、実際にこれをすべて一度実装しました):

デバッグしたいプログラムのシンボルテーブルを読んでください。コメントでLinuxと言ったので、開始するには、ELFファイルを読み取るか、ELF仕様を読み取って、自分で何かを実装するライブラリが必要です。

ptracesyscallを使用して、関数にブレークポイントを作成しますmain。運が良ければ、システムにはptraceブレークポイントを作成し、カーネルに簿記を保持する機能があります。そうでない場合は、CPUアーキテクチャのブレークポイント命令を理解し、自分でブレークポイントを実装する必要があります。

次に、共有ライブラリがいつロードされるかがわかるように、ダイナミックローダーのデバッグフックを把握する必要があります。ライブラリのシンボルテーブルも必要になります。

これですべてのシンボルができました(確かに、これはプログラムがしばらく実行された後です。ダイナミックライブラリをロードする必要があったためですが、プログラムはで開始するふりをしますmain)取得したすべての関数にブレークポイントを作成します。シンボルテーブル。

プログラムを実行します。ブレークポイントに到達するたびに、シンボルテーブルで命令ポインタを逆に検索します。関数の名前をファイルまたは保存したい場所に保存します。関数呼び出しを追跡できるようになりました。

または、デバッガーを使用します。gdbは、おそらくこのようなことを行うようにスクリプト化できます。

于 2013-03-19T09:13:11.827 に答える
4

これらはオープンソースツールではないことをお詫び申し上げますが、過去に研究に使用したことがあり、必要な機能を有効にしているため、それらを試してみるとヒントが得られる可能性があります。

Linuxでは、Pinがどのように機能するかを見てみてください。

Windowsでは、Detoursを見てください。

于 2013-03-19T08:29:55.147 に答える
3

組み込みのインストルメンテーション機能を使用しGCCて、ソースコードのインストルメンテーションを実行します。

チュートリアルへのリンクは次のとおりです:http://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/

基本的に、各関数を呼び出す前にインストルメンテーション関数を呼び出すようにプログラムに指示し、パラメーターとして2つのアドレスを指定します。そのインストルメンテーション関数を自分で提供すると、fromアドレスとtoアドレスで呼び出されます。from-addressは、to-addressの関数がどこから呼び出されているかを示します。

次に、アドレスを関数名に変換する必要があります(これは、ソースコードファイルの行番号を指定して行うことができます)。GNU binutilsツールaddr2lineを使用すると、バイナリファイルがデバッグ情報()でコンパイルされている場合、to-およびfrom-addressを行番号に変換できますgcc -g ...

このアプローチを使用してインストルメンテーションを開始するのは非常に高速です。

編集:

ソースがなく、バイナリのみが手元にある場合は、バイナリを逆アセンブルまたは解析してアセンブリにすることができます。このようにして、プログラムを論理ブロックに分割し、そこからジャンプすることができます。関数名はソースコードなしでは簡単に復元できないため、以前にプログラムを分割したロジックブロック内のジャンプのみを追跡できるリスクがあります。これは、いくつかのデバッガーがAFAIKを実行することです。OllyDbg (Windowsのみ)とIDA proは、視覚的なフローチャートを表示したり、色付きのブロック図などを表示したりできると思いますあなたは本当にソースなしで一生懸命働かなければなりませんが、手元にソースコードがあれば、あなたはあなたが望むことを正確に行うためのあらゆる手段を持っています。間に1つのステップを追加するだけで、になります。write code -> compile -> executewrite code -> insert instrumentation -> compile -> execute

于 2014-06-20T09:55:50.340 に答える
1

面倒な方法は、関数の最初と最後の行としてprintfを挿入することです。またはデバッガーを使用します。問題は、すべての変数を0 / nullに設定し、コードのバグを「解決」するデバッガーである可能性があります。

私の解決策は ctraceを使用していました

于 2014-06-21T19:19:59.337 に答える