これは、実行前に静的分析によって、または実行時に動的分析によって行うことができます。
実行時にこれを行う方法は2つしかなく、どちらもインストルメンテーションコードになります。
ソースコードをインストルメントする
ソースコードを変更(「機器」)して、必要なものを追跡します。たとえば、すべての呼び出しサイトを変更して、呼び出し元の関数名を効果的に含む追加の引数を渡し、すべての関数エントリを変更して、その関数へのエントリを記録します(たとえば、事実上、呼び出された関数名)と呼び出し元の関数名。インストルメンテーションプロセスでは、インストルメントされたポイントからソースファイルと行に戻って相互参照を作成し、後でレポートできるようにする必要があります。
これは、プログラム変換システム(PTS)を使用して完全に自動化された方法で実行できます。このシステムは、ソースコードを解析してASTを作成し、ASTを変更してから、ソースを再生成できます。多くの場合、PTSを使用すると、フォームのソースレベルのパターンとして変更を書き込むことができます。「これが表示された場合は、それを置き換えてください」。(いいえ、正規表現はこれを行うことができません)。
これを行う方法の拡張例は、私のテクニカルペーパー「任意の言語のブランチカバレッジ」にあります。ペーパーショーでは、PTSの内容について説明し、次に一般的な計装変換を示します。
Cのような言語でこれを「正しく」行うのは難しい場合があります。これは、基盤として完全なCパーサーが必要であるためです。これは、それ自体が主要な技術的偉業です。このような獣を完成した形で入手するのが最善です(PTSの中にはこれを持っているものもありますが、ConcinnelleまたはDMSを検討してください)。(別の回答は、GCCにこのインストルメンテーション機能の多くが組み込まれていることを示唆しています)。
合理的に行われると、追加のインストルメンテーションの実行コストはかなり控えめです(10-30%)。この概念は、テストカバレッジツールとタイミングプロファイラーを実装するために使用されています。これらは、動的な呼び出しのセットを追跡することによって動作します。これはまさにあなたが望むもののようです。
オブジェクトコードをインストルメントする
コードを実行する実行エンジンを変更して、必要な詳細を追跡します。Cプログラムでこれを実行したい場合、実行エンジンは基本的に基礎となる命令セットです。
プロセッサチップ自体を合理的に変更することはできないため、次のいずれかを行う必要があります。
これらを実際の命令セットに実装することは、命令セット自体(IntelのX86命令セットは巨大です)、オブジェクトファイル形式などによって複雑になります。このパスがソースインストルメンテーションパスよりも簡単であると期待しないでください。別の問題が発生することを期待してください。
標準的な問題の1つは、何を計測するかを知ることです。オブジェクトコードスペースは、対象のアプリケーションの一部ではないコード(ライブラリ、システムルーチンなど)でいっぱいであるため、テストカバレッジデータを収集するのは面白くありません。このためには、アプリケーションコードとそうでないものを区別するためのシンボルテーブル(ペア)が必要です。また、結果を報告するには、名前から元のソースファイル内のポイントまでトレースできる必要があります。
オブジェクトコードのインストルメンテーションに関するもう1つの問題は、インライン化された関数fooが実行された(「カバーされた」)かどうかを判断するのが難しいことです。ソースインストルメンテーションスキームにはこの問題はありません。これは、fooがコンパイル/インライン化される前にインストルメント化されるため、インストルメンテーションもインライン化されるためです。
スレッド
スレッドの処理は直交する問題です。ファクトX-calls-Yを記録する各場所で、OSから取得したスレッドID(または同等の)Tを添付するだけで、X-calls-Y-in-Tが生成されます。なぜあなたがこれをしたいのかはあなたの質問からは明らかではありません。
静的解析
ランタイム実装のすべての面倒をスキップして、コールグラフを生成できる静的アナライザーを構築/取得することを決定できます。C用のこれらの取得はClangまたはDMSを使用して可能かもしれません。自分で構築するのは難しいでしょう。ここで、完全なCパーサー、完全なデータフローとポイントツー分析、およびコールグラフの構築が必要です。詳細はこの段落に収まりません。