プログラム分析ツールは,コンピュータ設計者およびソフトウェア・エンジニアにとってきわめて重要です。 プログラム分析ツールを使用して,コンピュータ設計者は新しいアーキテクチャ設計のテストと測定を行い,ソフトウェア・エンジニアは,プログラム内のコードのクリティカルな部分を特定したり,分岐予測や命令スケジューリング・アルゴリズムがどの程度機能しているかを検査します。 プログラム分析ツールは,基本ブロックのカウントから,データのキャッシュ・シミュレーションに至るまでの問題に必要です。 これらのタスクを実行するツールは異なるように思われますが,コードの計測によって,それぞれを簡単で効率的に実現できます。
Atom では,さまざまなツールを作成できる柔軟なコード計測インタフェースを提供します。 Atom は,計測およびオブジェクト・コード操作のための機構を提供し,ツール設計者がプログラムの計測するポイントを特定できるようにして,それぞれの問題に固有の部分とすべての問題に共通の部分とを分離します。 Atom は,完全なプログラムを構成するオブジェクト・モジュール上で動作するので,どのコンパイラおよび言語からも独立しています。
この章では,次の 2 つの項目について説明します。
以降の各項では,次の項目について説明します。
Tru64 UNIX オペレーティング・システムでは,表 9-1
に示す多くの Atom ツールをサンプルとして提供しており,独自にカスタム設計された Atom ツールの開発に役立てることができます。
これらのツールは,実際の運用を目的としたものではなく,Atom のプログラミング・インタフェースを説明するために,ソース形式で配布されます。
9.2 節では,いくつかのツールについて詳細に説明しています。
表 9-1: サンプルのインストール済み Atom ツール
ツール | 説明 |
branch |
条件付き分岐をすべて計測して,正しく予測されている数を確認します。 |
cache |
アプリケーションが 8 KB の直接マップ・キャッシュで実行される場合の,キャッシュ・ミスの割合を確認します。 |
dtb |
アプリケーションが 8 KB のページおよびそれに完全に対応する変換バッファを使用する場合の,dtb (データ変換バッファ) のミスの数を確認します。 |
dyninst |
命令,ロード,ストア,ブロック,およびプロシージャの,基本的な動的カウントを提供します。 |
inline |
インライン化の潜在的候補を特定します。 |
iprof |
各プロシージャが呼び出された回数,および,各プロシージャによって実行された命令の数をプリントします。 |
malloc |
malloc
関数の各呼び出しを記録し,アプリケーションの割り当てられたメモリの要約をプリントします。 |
prof |
pthread
プログラムで各プロシージャによって実行された命令の数をプリントします。 |
ptrace |
各プロシージャの呼び出し時に,その名前をプリントします。 |
replace |
分析ルーチンからアプリケーションの置換したエントリ・ポイントを呼び出します。 |
trace |
実行時に,アドレス・トレースを生成して,すべてのロードとストア操作の実効アドレスのログ,およびすべての基本ブロックの先頭のアドレスのログを取ります。 |
サンプルのツールは,/usr/lib/cmplrs/atom/examples
ディレクトリにあります。
各サンプルには,次の 3 つのファイルが含まれます。
計測ファイル -- Atom の API を使用して,アプリケーション・プログラムを修正し,ツールが提供した追加のルーチンをプログラム実行中の特定の時間に起動するようにする,C ソース・ファイルです。
分析ファイル -- 修正されたプログラムの実行時に起動されたルーチンを含む C ソース・ファイルです。 これらの分析ルーチンは,ツールによって報告される実行時データを収集します。
記述ファイル (toolname.desc
) -- Atom にツールの計測ファイルおよび分析ファイルの名前を通知するテキスト・ファイルです。
また,ツールの実行時に Atom が使用する任意のオプションも通知します。
実際の運用を目的とする Atom ツール,または製品としてカスタマに配布される Atom ツールには,通常,固有のソースの代わりに
.o
オブジェクト・モジュールがインストールされています。
Tru64 UNIX の
hiprof
(1)pixie
(1)third
(1)/usr/lib/cmplrs/atom/tools
ディレクトリに格納されます。
インストール済みの Atom ツールまたはサンプルをアプリケーション・プログラム上で実行するには,次の形式で
atom
(1)
atom
application_program -tool toolname
[-env environment
]
[ options...
]
この形式の
atom
コマンドでは,-tool
オプションは必須であり,-env
オプションを受け入れます。
-tool
オプションは,使用するインストール済みの Atom ツールを識別します。
省略時の設定では,Atom はインストール済みのツールを
/usr/lib/cmplrs/atom/tools
ディレクトリおよび
/usr/lib/cmplrs/atom/examples
ディレクトリ内で検索します。
コロンで区切った追加のディレクトリのリストを
ATOMTOOLPATH
環境変数に追加して,検索パスにディレクトリを追加することができます。
-env
オプションは,ツールの代替バージョンが必要であることを示します。
たとえば,Tru64 UNIX のツールには,-env threads
でスレッド・セーフ・バージョンの実行を要求するものがあります。
atom
(1).desc
ファイルではなく
toolname.env.desc
ファイルを検索します。
このコマンドは,指定した環境の記述ファイルが見つからない場合にエラー・メッセージを表示します。
atom
(1)
atom application_program
instrumentation_file
[analysis_file
]
[options...
]
このコマンド形式は,instrumentation_file パラメータを必要とし,analysis_file パラメータを受け付けます。 ただし,-tool または -env オプションは受け付けません。
instrumentation_file
パラメータには,C ソース・ファイルの名前または Atom ツールの計測プロシージャを含むオブジェクト・モジュールを指定します。
計測プロシージャが複数のファイルに記述されている場合は,ld
コマンドで
-r
オプションを指定すると,各ファイルの
.o
が 1 つのファイルに一緒にリンクされます。
規約により,ほとんどの計測ファイルには接尾語
.inst.c
または
.inst.o
が付けられています。
このパラメータ用にオブジェクト・モジュールを渡す場合は,モジュールを -g1 または -g オプションのいずれかを指定してコンパイルすることを検討してください。 計測プロシージャにエラーが存在する場合,計測プロシージャがこのようにコンパイルされていると,Atom はより完全な診断メッセージを発行できます。
analysis_file
パラメータには,Atom ツールの分析プロシージャを含む C のソース・ファイルまたはオブジェクト・モジュールの名前を指定します。
分析ルーチンが複数のファイルに存在する場合は,ld
コマンドで
-r
オプションを指定すると,各ファイルの
.o
が 1 つのファイルに一緒にリンクされます。
計測ファイルが,計測するアプリケーションに対して分析プロシージャを呼び出さない場合には,分析ファイルを指定する必要はありません。
規約により,ほとんどの分析ファイルには接尾語
.anal.c
または
.anal.o
が付けられます。
分析ルーチンを単一のコンパイル単位としてコンパイルすると,性能が向上する場合があります。
計測ソース・ファイルおよび分析ソース・ファイルは,複数指定することができます。 次の例では,複数のソース・ファイルから計測および分析の複合オブジェクトを作成します。
% cc -c file1.c file2.c % cc -c file7.c file8 % ld -r -o tool.inst.o file1.o file2.o % ld -r -o tool.anal.o file7.o file8.o % atom hello tool.inst.o tool.anal.o -o hello.atom
注意
分析プロシージャは,C++ で記述することもできます。 アプリケーションから呼び出せるようにするには,各プロシージャに
extern "C"
タイプを割り当てる必要があります。 また,atom
コマンドを入力する前に,分析ファイルのコンパイルとリンクも行わなければなりません。 たとえば,次のようになります。% cxx -c tool.a.C % ld -r -o tool.anal.o tool.a.o -lcxx -lexc % atom hello tool.inst.c tool.anal.o -o hello.atom
-tool
および
-env
オプションを除いて,atom
コマンドの 2
つの形式はいずれも,
atom
(1)
セーブおよびリストアする必要のあるレジスタ数を減少させることにより,Atom が分析ルーチンの呼び出しを最適化できるようにします。 いくつかのツールでは,このオプションを指定すると,計測機構付きアプリケーションの性能が 2 倍向上します (その代わり,アプリケーションのサイズが多少増加します)。 省略時の動作では,Atom はこれらの最適化を適用しません。
共用実行可能ファイル内の,静的にロードしたシェアード・ライブラリをすべて計測します。
計測ルーチンを
dbx
デバッガでデバッグできるようにします。
Atom は計測ルーチンの起動時に,シンボリック・デバッガへ制御を移します。
次の例では,ptrace
サンプル・ツールが,dbx
デバッガのもとで実行されます。
計測は 12 行目で停止され,プロシージャ名が出力されます。
% atom hello ptrace.inst.c ptrace.anal.c -o hello.ptrace -debug dbx version 3.11.8 Type 'help' for help. Stopped in InstrumentAll (dbx) stop at 12 [4] stop at "/udir/test/scribe/atom.user/tools/ptrace.inst.c":12 (dbx) c [3] [InstrumentAll:12 ,0x12004dea8] if (name == NULL) name = "UNKNOWN"; (dbx) p name 0x2a391 = "__start"
計測ルーチンをオプションの
ladebug
デバッガでデバッグできるようにします。
Atom は,計測ルーチンの起動時に
ladebug
に制御を移します。
計測ルーチンがスレッド化されている場合,または内部に C++ のコードが含まれている場合には,ladebug
を使用します。
詳細は,『Ladebug Debugger Manual』を参照してください。
指定したシェアード・ライブラリを計測から除きます。 -excobj オプションを複数回使用すると,複数のシェアード・ライブラリを指定できます。
fork のサポートが必要であることを指定します。 このオプションを使用すると,マルチスレッド・アプリケーションでデッドロックを回避します。
(-g)
デバッグ情報を持った計測機構付きプログラムを作成します。 このオプションを使用すると,分析ルーチンをシンボリック・デバッガでデバッグできるようになります。 -ga (または -g) では,できるだけ省略時の -A0 オプション (-A1 ではない) を使用するようにしてください。 たとえば,次のようになります。
% atom hello ptrace.inst.c ptrace.anal.c -o hello.ptrace -ga % dbx hello.ptrace dbx version 3.11.8 Type 'help' for help. (dbx) stop in ProcTrace [2] stop in ProcTrace (dbx) r [2] stopped at [ProcTrace:5 ,0x120005574] fprintf (stderr,"%s\n",name); (dbx) n __start [ProcTrace:6 ,0x120005598] }
デバッグ情報を持った計測機構付きプログラムを作成します。
このオプションを使用すると,分析ルーチンおよびアプリケーション・ルーチンのデバッグが可能になります。
アプリケーション内の変数およびプロシージャの名前すべてに,接頭語の "_APP_
" が付加されます。
-gpa
を使用する場合は,省略時の
-A0
オプション (-A1
ではない) をお勧めします。
デバッグ情報を持った計測機構付きプログラムを作成します。 このオプションを使用すると,アプリケーション・ルーチンをシンボリック・デバッガでデバッグできるようになります。
デバッグ情報を持った計測機構付きプログラムを作成します。
このオプションを使用すると,分析ルーチンおよびアプリケーション・ルーチンのデバッグが可能になります。
分析オブジェクト内の変数およびプロシージャの名前すべてに,接頭語の "_ANA_
" が付加されます。
-gpa
を使用する場合は,省略時の
-A0
オプション (-A1
ではない) をお勧めします。
分析ヒープのベースを変更します。 省略時の分析ヒープの位置が,アプリケーション・プログラムが使用するアドレス範囲と競合する場合は -heapbase オプションを使用します。 新しいベースとして,新しい 16 進数のアドレス位置か,省略時の 31 ビットのアドレス位置か,アプリケーションの bss セグメントの末尾の次のページのいずれかを選択できます。
以前に計測したシェアード・ライブラリの再使用 (または増分の計測) をできるようにします。
指定したシェアード・ライブラリを計測します。 -incobj オプションを複数回使用して,複数のシェアード・ライブラリを指定することもできます。
Atom が作成する一時ファイルを,現行の作業ディレクトリに置き,計測が完了しても削除しないことを指定します。
ライブラリのディレクトリでシェアード・オブジェクト・ライブラリを検索する順序を変更し,Atom が省略時のライブラリ・ディレクトリをまったく検索しないようにします。 このオプションは,省略時のディレクトリを検索せず,-Ldir が指定したディレクトリのみを検索するようにする場合に使用します。
ライブラリのディレクトリでシェアード・オブジェクト・ライブラリを検索する順序を変更し,Atom が dir ディレクトリを検索した後で省略時のライブラリ・ディレクトリを検索するようにします。 複数の -Ldir オプションを指定すると,複数のディレクトリ名を指定できます。
計測機構付き実行可能ファイルのセクションの開始アドレスのリストを作成します。
実行可能な出力ファイルの名前を指定します。
-pthread
スレッド・セーフ・サポートが必須であることを指定します。 このオプションは,スレッド・アプリケーションの計測を行う際に使用します。
Atom が計測機構付きシェアード・ライブラリを書き込む既存のディレクトリを指定します。
Atom が計測機構付きバージョンを書き込む際に,各オブジェクト名に付加するファイル名の接尾語を指定します。
Atom ツールの計測ルーチンに引数を渡します。
Atom は,C プログラムに渡す場合と同様に,引数
argc
および
argv
を使用して,main
プログラムに引数を渡します。
たとえば,次のようになります。
#include <stdio.h> unsigned InstrumentAll(int argc, char **argv) { int i; for (i = 0; i < argc; i++) { printf(stderr,"argv[%d]: %s\n",argv[i]); } }
次の例は,Atom が引数
-toolargs
を渡す方法を示しています。
% atom hello args.inst.c -toolargs="8192 4" argv[0]: hello argv[1]: 8192 argv[2]: 4
Atom が計測機構付きプログラムを作成する際に行う各ステップを表示します。
Atom のバージョン番号を表示します。
通常は出力されないような警告も含め,すべての警告メッセージを表示します。
無視してかまわない警告メッセージは出力しません。 これは省略時の動作です。
分析ルーチンを処理する際に出力される警告メッセージは出力しません。
シェアード・ライブラリエラーの処理に関する警告メッセージは出力しません。
指定したオプションを,それぞれ分析ファイルのリンクとコンパイルのフェーズに渡します。
指定したオプションを,それぞれ計測ファイルのリンクとコンパイルのフェーズに渡します。
この節では,Atom ツールの開発方法について説明します。
9.2.1 Atom によるアプリケーションの表示
Atom は,アプリケーションをコンポーネントの階層として表示します。
プログラム
実行可能プログラムとすべてのシェアード・ライブラリを含みます。
オブジェクトの集合
オブジェクトは,メインの実行可能プログラムまたは任意のシェアード・ライブラリのいずれかです。 オブジェクトは,独自の属性 (名前など) のセットを持ち,プロシージャの集合から構成されます。
プロシージャの集合
各プロシージャはエントリ・ポイントと基本ブロックの集合から構成されます。
基本ブロックの集合
各基本ブロックは命令の集合から構成されます。
命令の集合
Atom ツールは,アプリケーション・プログラム内のプロシージャ,エントリ・ポイント,基本ブロック,または命令の境界に,計測ポイントを挿入します。 たとえば,基本ブロック・カウント・ツールは,各基本ブロックの先頭を計測し,データ・キャッシュ・シミュレータは,ロードおよびストアの各命令を計測し,分岐予測アナライザは,各条件付き分岐命令を計測します。
Atom では,どの計測ポイントでも,ツールによって,分析ルーチンへの呼び出しを挿入できます。
ツールでは,呼び出しが,オブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,または命令の,前に行われるか後に行われるかを指定できます。
9.2.2 Atom の計測ルーチン
ツールの計測ルーチンには,アプリケーションのオブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,および命令をトラバースして,計測ポイントを探索したり,分析プロシージャの呼び出しを追加したり,アプリケーションの計測機構付きバージョンを作成するコードが含まれています。
atom_instrumentation_routines
(5)
Instrument
(int
iargc,
char **iargv,
Obj *obj)
Atom は,アプリケーション・プログラムの各オブジェクトに対して
Instrument
ルーチンを呼び出します。
その結果,Instrument
ルーチンは,オブジェクト・ナビゲーション・ルーチン (GetFirstObj
など) を使用する必要がなくなります。
Atom は,Instrument
ルーチンに次のオブジェクトを渡す前に,変更された各オブジェクトを自動的にコーディングするので,Instrument
ルーチンは,BuildObj
,WriteObj
,または
ReleaseObj
ルーチンを呼び出す必要がありません。
Instrument
インタフェースを使用する場合には,InstrumentInit
ルーチンを定義して,Atom が最初のオブジェクトに対して
Instrument
を呼び出す前に,必要なタスク (分析ルーチン・プロトタイプの定義,プログラム・レベルの計測呼び出しの追加,グローバルな初期化など) を実行することができます。
また,InstrumentFini
ルーチンを定義して,Atom が最後のオブジェクトに対して
Instrument
を呼び出した後で,必要なタスク (グローバルなクリーンアップなど) を実行することもできます。
InstrumentAll
(int
iargc,
char **iargv)
Atom は,アプリケーション・プログラム全体に対して一度
InstrumentAll
ルーチンを呼び出します。
これは,ツールの計測コード自体がアプリケーションのオブジェクトをトラバースする方法を決定できるようにします。
このメソッドでは,InstrumentInit
または
InstrumentFini
ルーチンは存在しません。
InstrumentAll
ルーチンは,Atom のオブジェクト・ナビゲーション・ルーチンを呼び出し,BuildObj
,WriteObj
,または
ReleaseObj
ルーチンを使用して,アプリケーションのオブジェクトを管理する必要があります。
計測ルーチン・インタフェースに関係なく,Atom は,-toolargs
オプションで指定された引数をルーチンに渡します。
Instrument
インタフェースの場合,Atom は,現在のオブジェクトを指すポインタも渡します。
9.2.3 Atom の計測インタフェース
Atom は,アプリケーションを計測するための総合的なインタフェースを提供します。 このインタフェースでは,次のタイプの処理をサポートしています。
プログラムのオブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,および命令の間のナビゲーション (9.2.3.1 項を参照)。
オブジェクトの作成,解放,およびコーディング (9.2.3.2 項を参照)。
アプリケーションのさまざまな構成要素についての情報の取得 (9.2.3.3 項を参照)。
名前および呼び出しターゲットの解決 (9.2.3.4 項を参照)。
プログラム内の目的とする位置への分析ルーチンの呼び出しの追加 (9.2.3.5 項を参照)。
プログラム内のエントリ・ポイント呼出への介入。 9.2.3.6 項を参照してください。
atom_application_navigation
(5)
ルーチン
GetFirstObj
,GetLastObj,
GetNextObj
,および
GetPrevObj
は,プログラムのオブジェクト間をナビゲートします。
非共用プログラムの場合には,オブジェクトは 1 つだけです。
呼び出し共用プログラムの場合は,最初のオブジェクトがメイン・プログラムに対応します。
残りのオブジェクトは,動的にリンクされた各シェアード・ライブラリです。
ルーチン
GetFirstObjProc
および
GetLastObjProc
はそれぞれ,指定されたオブジェクト内の最初のプロシージャまたは最後のプロシージャへのポインタを返します。
ルーチン
GetNextProc
および
GetPrevProc
は,オブジェクトのプロシージャ間をナビゲートします。
ルーチン
GetFirstEntry
および
GetLastEntry
はそれぞれ,指定されたプロシージャについて,最初または最後のエントリ・ポイントへのポインタを返します。
ルーチン
GetNextEntry
および
GetPrevEntry
は,プロシージャのエントリ・ポイント間をナビゲートします。
ルーチン
GetFirstBlock
,GetLastBlock,
GetNextBlock
,および
GetPrevBlock
は,プロシージャの基本ブロック間をナビゲートします。
ルーチン
GetFirstInst
,GetLastInst,
GetNextInst
,および
GetPrevInst
は,基本ブロックの命令の間をナビゲートします。
GetInstBranchTarget
ルーチンは,指定された分岐命令のターゲットである命令を指すポインタを返します。
GetProcObj
ルーチンは,指定されたプロシージャを含むオブジェクトを指すポインタを返します。
ルーチン
GetEntryProc
および
GetBlockProc
は,それぞれ指定されたエントリ・ポイントまたは基本ブロックを含むプロシージャを指すポインタを返します。
GetEntryBlock
ルーチンは,指定したエントリ・ポイントの最初の基本ブロックを指すポインタを返します。
GetInstBlock
ルーチンは,指定した命令を含む基本ブロックを指すポインタを返します。
atom_object_management
(5)InstrumentAll
ルーチンは,オブジェクトの作成,コーディング,および解放を行うことができます。
BuildObj
ルーチンは,Atom がオブジェクトを処理するために必要とする内部データ構造体を作成します。
InstrumentAll
ルーチンは,オブジェクト内のプロシージャをトラバースして,分析ルーチン呼び出しをオブジェクトに追加する前に,BuildObj
ルーチンを呼び出さなければなりません。
WriteObj
ルーチンは,指定されたオブジェクトの計測機構付きバージョンをコーディングして,以前に
BuildObj
ルーチンが作成した内部データ構造体の割り当てを解除します。
ReleaseObj
ルーチンは,指定のオブジェクトの内部データ構造体の割り当てを解除しますが,オブジェクトの計測機構付きバージョンは書き出しません。
IsObjBuilt
ルーチンは,指定したオブジェクトが
BuildObj
ルーチンによって作成されているが,まだ
WriteObj
ルーチンによってコーディングされていないか,あるいは
ReleaseObj
ルーチンによって解放されていない場合,非ゼロの値を返します。
9.2.3.3 アプリケーションの構成要素に関する情報の取得
atom_application_query
(5)
表 9-2
に,プログラムに関する情報を提供するルーチンを示します。
表 9-2: Atom のプログラム照会ルーチン
ルーチン | 説明 |
GetAnalName |
atom
コマンドに渡される分析ファイルの名前を返します。
このルーチンは,1 つの計測ファイルと複数の分析ファイルを使用するツールで役に立ちます。 |
GetErrantShlibName |
Atom による処理が不可能なシェアード・ライブラリの名前を返します。 |
GetErrantShlibErr |
Atom シェアード・ライブラリを処理できない理由を説明するエラー・コードを返します。 |
GetProgInfo |
プログラムのオブジェクト数,ツール・ライタが計測を要求するツールのオブジェクト数,Atom による処理が不可能なシェアード・ライブラリ数のいずれかを返します。 |
表 9-3
に,プログラムのオブジェクトに関する情報を提供するルーチンを示します。
表 9-3: Atom のオブジェクト照会ルーチン
ルーチン | 説明 |
GetObjInfo |
オブジェクトのテキスト,データ,bss セグメントについての情報,オブジェクトに含まれているプロシージャ,エントリ・ポイント,基本ブロック,または命令の数,そのオブジェクト ID,オブジェクトのリンク方法に関する情報,または指定のオブジェクトを計測から除外する必要があるかどうかについての論理値のヒントのいずれかを返します。 |
GetObjInstArray |
オブジェクトに含まれる 32 ビット命令から構成されている配列を返します。 |
GetObjInstCount |
GetObjInstArray
ルーチンによって返される配列に含まれる配列内の命令の数を返します。 |
GetObjName |
指定されたオブジェクトの元のファイル名を返します。 |
GetObjOutName |
計測機構付きオブジェクトの名前を返します。 |
次の計測ルーチンは,プログラムのオブジェクトに関する統計情報をプリントするものであり,Atom のオブジェクト照会ルーチンの使用方法を示しています。
1 #include <stdio.h> 2 #include <cmplrs/atom.inst.h> 3 unsigned InstrumentAll(int argc, char **argv) 4 { 5 Obj *o; Proc *p; 6 const unsigned int *textSection; 7 long textStart; 8 for (o = GetFirstObj(); o != NULL; o = GetNextObj(o)) { 9 BuildObj(o); 10 textSection = GetObjInstArray(o); 11 textStart = GetObjInfo(o,ObjTextStartAddress); 12 printf("Object %d\n", GetObjInfo(o,ObjID)); 13 printf(" Object name: %s\n", GetObjName(o)); 14 printf(" Text segment start: 0x%lx\n", textStart); 15 printf(" Text size: %ld\n", GetObjInfo(o,ObjTextSize)); 16 printf(" Second instruction: 0x%x\n", textSection[1]); 17 ReleaseObj(o); 18 } 19 return(0); 20 }
この計測ルーチンは,プロシージャを実行可能プログラムに追加しないので,分析プロシージャは必要ありません。 次の例に,このツールを使用して,プログラムをコンパイルおよび計測する処理を示します。 計測機構付きプログラムのサンプル実行では,オブジェクト識別子,テキスト・セグメントのコンパイル時の開始アドレス,テキスト・セグメントのサイズ,および 2 番目の命令のバイナリがプリントされます。 逆アセンブラによって,対応する命令を見つけるための便利な方法が提供されます。
% cc hello.c -o hello % atom hello info.inst.c -o hello.info Object 0 Object Name: hello Start Address: 0x120000000 Text Size: 8192 Second instruction: 0x239f001d Object 1 Object Name: /usr/shlib/libc.so Start Address: 0x3ff80080000 Text Size: 901120 Second instruction: 0x239f09cb % dis hello | head -3 0x120000fe0: a77d8010 ldq t12, -32752(gp) 0x120000fe4: 239f001d lda at, 29(zero) 0x120000fe8: 279c0000 ldah at, 0(at) % dis /ust/shlib/libc.so | head -3 0x3ff800bd9b0: a77d8010 ldq t12,-32752(gp) 0x3ff800bd9b4: 239f09cb lda at,2507(zero) 0x3ff800bd9b8: 279c0000 ldah at, 0(at)
表 9-4
に,オブジェクトのプロシージャに関する情報を提供するルーチンを示します。
表 9-4: Atom のプロシージャ照会ルーチン
ルーチン | 説明 |
GetProcInfo |
『Calling Standard for Alpha Systems』 マニュアルおよび『Assembly Language Programmer's Guide』に定義されているように,プロシージャのスタック・フレーム,レジスタの保存,レジスタの使用方法,およびプロローグ特性に関する情報を返します。 これらの値は,Third Degree のような,初期化されていない変数にアクセスするためのスタックを監視するツールにとっては重要です。 プロシージャに含まれているエントリ・ポイントや基本ブロックや命令の数,プロシージャ ID,シンボルの解決のタイプ,最小または最大のソース行番号,最初の命令へのプロシージャのオフセット,代替エントリ・ポイントの有無,プロシージャ間の分岐やジャンプの有無,あるいはそのアドレスが取得されたかどうかの指示などの,プロシージャについての情報を返すこともできます。 |
ProcGP |
プロシージャのグローバル・ポインタ (GP) を返します。 |
ProcFileName |
プロシージャを含むソース・ファイル名を返します。 |
ProcName |
プロシージャ名を返します。 |
ProcPC |
プロシージャ内にある最初の命令のコンパイル時のプログラム・カウンタ (PC) を返します。 |
表 9-5
に,オブジェクトのメインおよび代替エントリ・ポイントに関する情報を提供するルーチンを示します。
表 9-5: Atom エントリ・ポイント照会ルーチン
ルーチン | 説明 |
EntryName |
エントリ・ポイント名を返します。 |
EntryPC |
エントリ・ポイントの最初の命令の,コンパイル時のプログラム・カウンタ (PC) を返します。 |
GetEntryInfo |
エントリ・ポイントに関する情報 (冗長かどうかなど) を返します。 |
GetEntryProc |
エントリ・ポイントを囲む (中に含む) プロシージャを返します。 |
表 9-6
に,プロシージャの基本ブロックに関する情報を提供するルーチンを示します。
表 9-6: Atom の基本ブロック照会ルーチン
ルーチン | 説明 |
BlockPC |
基本ブロック内にある最初の命令のコンパイル時のプログラム・カウンタ (PC) を返します。 |
GetBlockInfo |
基本ブロック内の命令数またはブロック ID を返します。 |
IsBranchTarget |
ブロックが分岐命令のターゲットであるかどうかを示します。 |
表 9-7
に,基本ブロックの命令に関する情報を提供するルーチンを示します。
表 9-7: Atom の命令照会ルーチン
ルーチン | 説明 |
GetInstBinary |
アセンブラ言語命令の 32 ビット・バイナリ表現を返します。 |
GetInstClass |
『Alpha Architecture Reference Manual』に定義されているように,命令クラス (浮動小数点のロードや整数のストアなど) を返します。 |
GetInstInfo |
32 ビット命令全体を解析し,その命令の全部または一部を取得します。 |
GetInstRegEnum |
GetInstInfo
ルーチンによって返されたとき,命令フィールドからレジスタ・タイプ (浮動小数点または整数) を返します。 |
GetInstRegUsage |
可能性のある各ソース・レジスタに対して 1 ビット・セット,可能性のある各デスティネーション・レジスタに対して 1 ビット・セットのビット・マスクを返します。 |
InstLineNo |
命令のソース行番号を返します。 |
InstPC |
命令のコンパイル時のプログラム・カウンタ (PC) を返します。 |
IsInstType |
命令が指定されたタイプ (ロード命令,ストア命令,条件付き分岐,または無条件分岐) であるかどうかを示します。 |
Atom のシンボル解決ルーチンにより,atom_application_symbols(5) に記載したように,Atom ツールの計測ルーチンはアプリケーション・オブジェクト,プロシージャ,エントリ・ポイント,命令を探索します。 これらは,指定されるか,または呼び出しサイトのターゲットになっています。
FindObj
and
FindObjDepthFirst
ルーチンは,全オブジェクトの中で指定されたエントリ・ポイントを検索し (それぞれ幅と深さを優先),指定されたエントリ・ポイントを含むオブジェクトを返します。
FindProc
ルーチンは,メインまたは代替のエントリ・ポイントを含み,オブジェクト内で指定された名前を持つプロシージャを返します。
FindEntry
ルーチンは,オブジェクト内で指定された名前を持つメインまたは代替のエントリ・ポイントを返します。
FindInst
ルーチンは,オブジェクト内で指定された名前を持つ,解決されたシンボルのアドレスにある最初の命令を返します。
GetTargetName
ルーチンは,プロシージャ呼出のターゲットになっているエントリ・ポイントの名前を返します。
GetTargetObj
,GetTargetProc
,GetTargetEntry
ルーチンは,それぞれ,プロシージャ呼び出しのターゲットを含むオブジェクト,プロシージャ,エントリ・ポイントを返します。
GetTargetInst
ルーチンは,指定した命令がジャンプまたは分岐する先の命令を返します。
atom_application_instrumentation
(5)
AddCallProto
ルーチンを使用して,プログラムに追加する各分析プロシージャのプロトタイプを指定する必要があります。
つまり,AddCallProto
呼び出しは,AddCallProgram
,AddCallObj
,AddCallProc
,AddCallEntry
,AddCallBlock
,AddCallInst
の呼び出しに使用される各分析プロシージャの手続型インタフェースを定義しなければなりません。
Atom には,アプリケーションデータと分析データを,定数,レジスタの内容,アドレス解釈構造,計算値 (実効アドレスや分岐条件など) として,追加したプロシージャに渡すための機能を備えています。
プログラムが実行を開始する前または実行を終了した後で,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallProgram
ルーチンを使用します。
通常,このような分析プロシージャは,出力ファイルのオープンやコマンド行オプションの解析など,プログラム全体に適用される処理を行います。
オブジェクトが実行を開始する前または実行を終了した後で,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallObj
ルーチンを使用します。
通常,このような分析プロシージャは,そのプロシージャのデータの初期化など,1 つのオブジェクトに適用される処理を行います。
プロシージャが実行を開始する前または実行を終了した後で,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallProc
ルーチンを使用します。
メインまたは代替エントリ・ポイントが実行を始める前に,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallEntry
ルーチンを使用します。
基本ブロックが実行を開始する前または実行を終了した後で,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallBlock
ルーチンを使用します。
指定の命令が実行される前または実行された後で,分析プロシージャの呼び出しを追加するには,計測ルーチン内で
AddCallInst
ルーチンを使用します。
計測機構付きプログラム内のプロシージャを置換するには,ReplaceProcedure
ルーチンを使用します。
たとえば,Third Degree Atom ツールは,malloc
や
free
などのメモリ割り当て関数を,独自のバージョンに置換して,誤ったメモリ・アクセスおよびメモリ・リークをチェックできるようにします。
Atom
の呼び出し介入ルーチンは,
atom_application_instrumentation
(5)
ReplaceProto
ルーチンを使用して,置換されたエントリ・ポイントの代わりに呼び出される,各置換分析ルーチンのプロトタイプを指定する必要があります。
言い換えると,ReplaceProto
の呼び出しで,ReplaceEntry
ルーチンへの呼び出しの中で参照される各分析ルーチンに対するプロシージャのインタフェースを定義していなければなりません。
Atom の機能では,アプリケーションデータと分析データを,定数,レジスタの内容,アドレス変換構造体,計算された値 (置換エントリの実行時アドレスなど) のいずれかの形式で,置換分析ルーチンに渡すことができます。
ReplaceEntry
ルーチンを使用して,計測機構付きプログラム内のメインまたは代替エントリ・ポイントを,置換分析ルーチンへの呼び出しに置き換えます。
ReplaceEntry
の呼び出しは,置き換えるエントリ・ポイント,置換分析ルーチン,プロシージャのインタフェース引数 (ReplaceProto
でプロトタイプ化) を指定する必要があります。
指定したエントリ・ポイントのみが置き換えられます。
同じアプリケーション内の他のエントリ・ポイントは,別の
ReplaceEntry
呼び出しによって置き換えることができます。
置換ルーチンは分析を実行し,計算済みの
ReplAddrValue
値を使用して置換エントリ・ポイントのエミュレートができます。
このエミュレーションの例については,9.2.6 項
を参照してください。
たとえば,次のような
ReplaceProto
と
ReplaceEntry
の呼び出しのペアは,
memcpy
(3)my_memcpy
を,3 つのアプリケーション引数と 2 つの分析引数 (アプリケーションの
memcpy()
の戻りアドレスと,置き換えたエントリ・ポイントの実行時アドレス) を指定して呼び出します。
ReplaceProto ("my_memcpy(VALUE, VALUE, VALUE, REGV, VALUE)"); ReplaceEntry (FindEntry(obj,"memcpy"), /* entry point to replace */ "my_memcpy", /* replacement analysis routine */ ArgValue, /* application argument */ ArgValue, /* application argument */ ArgValue, /* application argument */ REG_RA, /* analysis argument */ ReplAddrValue)); /* analysis argument */
関連付けられた置換分析ルーチンは,3 つのアプリケーション引数と 2 つの分析引数で宣言されます。
void * my_memcpy (void * s1, const void * s2, size_t n, long call_address, void * (*memcpy_ptr) (void *,const void *,size_t));
atom_description_file
(5)cc
,ld
,および
atom
コマンドによって使用されるオプションを指定することもできます。
各 Atom ツールは,最低 1 つの記述ファイルを提供しなければなりません。
Atom の記述ファイルには,2 つのタイプがあります。
ツールの一般的な使用のための環境を提供する記述ファイル
ツールが提供できる汎用環境は 1 つだけです。 このタイプの記述ファイルの名前は,次の形式になります。
tool.desc
マルチスレッド・アプリケーションやカーネル・モードなど,特定のコンテキストでツールを使用するための環境を提供する記述ファイル
ツールは,それぞれ独自の記述ファイルを持つ,いくつかの専用環境を提供できます。 このタイプの記述ファイルの名前は,次の形式になります。
tool.environment.desc
上記の記述ファイル名で
tool
および
environment
部分に指定する名前は,ユーザがツールの起動時に
atom
コマンドの
-tool
および
-env
オプションに指定する値に対応します。
Atom の記述ファイルは,一連のタグと値を含むテキスト・ファイルです。
ファイルの構文についての詳細は,
atom_description_file
(5)9.2.5 分析プロシージャの作成
計測機構付きアプリケーションは,分析プロシージャを呼び出して,Atom ツールによって定義された特定の関数を実行します。 分析プロシージャは,アプリケーション内部で同じ呼び出し (関数) が計測されている場合でも,システム・コールまたはライブラリ関数を使用できます。 分析ルーチンおよび計測機構付きアプリケーションによって使用されるルーチンは,物理的に区別されます。 分析ルーチンによる呼び出しが可能および不可能なライブラリ・ルーチンは,次のとおりです。
標準 C ライブラリ (libc.a
) ルーチン (システム・コールを含む) は呼び出すことができますが,次のルーチンを除きます。
unwind
(3)
tis
(3)
また,9.2.5.1 項で説明しているように,標準 I/O ルーチンの動作で異なるものがあります。
pthread_atfork
(3)
算術ライブラリ (libm.a
) ルーチンは呼び出すことができます。
マルチスレッドまたは例外処理に関するその他のルーチンは呼び出してはなりません
(たとえば,
pthread
(3)exc_
*,および
libmach
ルーチン)。
特定の環境 (たとえば,X や Motif) を想定するその他のルーチンは,Atom 分析環境では,有効でなかったり,正確ではない可能性があります。
TLS (Thread Locale Storage) は,分析ルーチンではサポートされていません。
9.2.5.1 入出力
ルーチン分析のために提供されている標準 I/O ライブラリは,計測機構付きプログラムが終了したときに,自動的にストリームのフラッシュおよびクローズを行わないため,すべての出力が完了すると,分析コードは,明示的にそれらをフラッシュおよびクローズする必要があります。
また,ルーチン分析のために提供されている
stdout
および
stderr
ストリームは,アプリケーションが
exit()
を呼び出したときにクローズされるため,アプリケーションが終了した後にこれらのストリームを使用する必要がある場合には,分析コードはそれらのストリームの一方または両方の複製を作成しておく必要があります (ProgramAfter
または
ObjAfter
分析ルーチンなど)。
入出力のために他のストリームをオープンする方法については,9.1.1 項
に記載した
prof
ツールを参照してください。
stderr
(または
stderr
の複製) への出力が直ちに表示されるようにするため,分析コードは,setbuf(stream,NULL)
を呼び出してストリームのバッファを解除するか,または
fprintf
呼び出しの各セットの後に
fflush
を呼び出す必要があります。
同様に,C++ ストリームを使用する分析ルーチンは,cerr.flush()
を呼び出すことができます。
9.2.5.2 fork および exec システム・コール
プロセスが
fork
関数を呼び出すが
exec
関数は呼び出さない場合,そのプロセスのクローンが作成され,親の状態の正確なコピーが子に継承されます。
多くの場合,Atom ツールはこの動作を予期しています。
たとえば,命令アドレス・トレース・ツールは,参照が発生した順序で混合された,親と子の両方の参照を調べます。
命令プロファイル・ツール (たとえば表 9-1
で参照されている
trace
ツール) の場合には,ファイルは
ProgramBefore
計測ポイントでオープンされ,結果として,出力ファイル記述子は親プロセスと子プロセスの間で共用されます。
ProgramAfter
計測ポイントで結果がプリントされる場合,出力ファイルには親のデータが含まれ,その後に子のデータが続きます (親プロセスが最初に終了したと仮定した場合)。
イベントをカウントするツールの場合 (表 9-1
の
prof
ツールなど),イベントは子ではなく親で発生するため,回数を保持するデータ構造体は,fork
呼び出しの後,子プロセスでは 0 に戻さなければなりません。
このタイプの Atom ツールは,fork
ライブラリ・プロシージャを計測し,fork
ルーチンのリターン値を引数として分析プロシージャを呼び出すことにより,fork
の正しい処理をサポートできます。
分析プロシージャは引数に 0 (ゼロ) のリターン値を渡されると,子プロセスから呼び出されたと分かります。
すると,カウント変数またはその他のデータ構造体をリセットして,子プロセスだけの統計情報を集計できるようにします。
9.2.6 置換された呼び出し側アプリケーションのエントリ・ポイント
置換されたエントリ・ポイントは,ReplaceEntry
の呼び出し中に渡された
ReplAddrValue
パラメータを使用して置換分析ルーチンから呼び出されることがあります。
ReplAddrValue
引数には,9.2.3.6 項
で述べたような,置換されたエントリ・ポイントのアドレスが含まれています。
このアドレスによって,置換分析ルーチンを用いて,呼び出すだけで置換したエントリ・ポイントをエミュレートできるようになります。
次の例では,memcpy()
関数は分析関数
my_memcpy()
に置き換えられ,これはその代わりに,置き換えられた
memcpy()
関数を呼び出します。
次のソース・リストには,計測コードが含まれています。
#include <string.h> #include <cmplrs/atom.inst.h> unsigned InstrumentAll (int argc, char **argv) { Xlate * px; Obj * o; Entry * e; /* * Prototype the replacement routine. */ ReplaceProto("my_memcpy(VALUE, VALUE, VALUE, REGV, VALUE)"); /* * Resolve the object that contains memcpy(). */ o = FindObj("memcpy"); if (o) { /* * Build the object containing memcpy so the memcpy entry point * can be resolved and replaced. */ if (BuildObj(o)) return(1); /* * Resolve the memcpy entry point. */ e = FindEntry(o, "memcpy"); /* * Prefix the memcpy entry point with atom-generated code to * call the analysis routine my_memcpy instead. */ ReplaceEntry(e, "my_memcpy", ArgValue, ArgValue, ArgValue, REG_RA, ReplAddrValue); /* * Write the instrumented object. */ WriteObj(o); } return (0); }
次のソース・リストには分析コードが含まれています。
#include <stdio.h> #include <stdlib.h> #include <cmplrs/atom.anal.h> /* * Replacement routine for memcpy(); */ void * my_memcpy (void * s1, const void * s2, size_t n, long call_address, void * (*memcpy_ptr) (void *, const void *, size_t)) { void * ptr = 0; /* * Report the call. */ printf ("memcpy called from %lx\n", call_address); /* * Call the original memcpy(). */ ptr = (*memcpy_ptr) (s1, s2, n); return (ptr); }
Xlate
(5)
命令のアドレスをアドレス変換バッファに渡すために,計測ルーチンは,まず
CreateXlate
ルーチンを呼び出します。
アドレス変換バッファが作成された後に,計測ルーチンは,AddXlateAddress
ルーチンを呼び出すことで命令のアドレスを追加します。
エントリ・ポイントのアドレスは,AddXlateEntry
ルーチンを呼び出すことでアドレス変換バッファに追加することもできます。
アドレス変換バッファに保持できるのは,単一オブジェクトからのアドレスだけです。
Atom ツールの計測ルーチンは,AddCallProto
呼び出しでの分析ルーチンのプロトタイプ定義に示されるように,XLATE *
タイプのパラメータとして,アドレス変換バッファを分析ルーチンに渡します。
計測機構付き PC を決定するもう 1 つの方法は,分析ルーチンのプロトタイプで
REGV
の仮パラメータ・タイプを指定して,REG_IPC
値を渡す方法です。
Atom ツールの分析ルーチンは,次の分析インタフェースを使用して,渡されたアドレス変換バッファにアクセスします。
XlateNum
ルーチンは,指定されたアドレス変換バッファ内のアドレスの数を返します。
XlateInstTextStart
ルーチンは,指定されたアドレス変換バッファに対応する計測機構付きオブジェクトのテキスト・セグメントの開始アドレスを返します。
XlateInstTextSize
ルーチンは,テキスト・セグメントのサイズを返します。
XlateLoadShift
ルーチンは,指定されたアドレス変換バッファに対応するオブジェクト内の実行時アドレスとコンパイル時アドレスの差を返します。
XlateAddr
ルーチンは,指定されたアドレス変換バッファ内の指定された位置にある命令の計測機構付き実行時アドレスを返します。
シェアード・ライブラリ内の命令の実行時アドレスは,必ずしもそのコンパイル時アドレスと同じわけではありません。
次の例に,Xlate
ルーチンを使用するツールの計測および分析ファイルによる
Xlate
ルーチンの使用を示します。
このツールは,すべての飛び越し命令のターゲット・アドレスをプリントします。
これを使用するには,次のコマンドを実行します。
% atom progname xlate.inst.c xlate.anal.c -all
次のソース・リスト
(xlate.inst.c)
には,xlate
ツールの計測が含まれています。
#include <stdlib.h> #include <stdio.h> #include <alpha/inst.h> #include <cmplrs/atom.inst.h> static void address_add(unsigned long); static unsigned address_num(void); static unsigned long * address_paddrs(void); static void address_free(void); void InstrumentInit(int iargc, char **iargv) { /* Create analysis prototypes. */ AddCallProto("RegisterNumObjs(int)"); AddCallProto("RegisterXlate(int, XLATE *, long[0])"); AddCallProto("JmpLog(long, REGV)"); /* Pass the number of objects to the analysis routines. */ AddCallProgram(ProgramBefore, "RegisterNumObjs", GetProgInfo(ProgNumberObjects)); } Instrument(int iargc, char **iargv, Obj *obj) { Proc * p; Block * b; Inst * i; Xlate * pxlt; union alpha_instruction bin; ProcRes pres; unsigned long pc; char proto[128]; /* * Create an XLATE structure for this Obj. We use this to translate * instrumented jump target addresses to pure jump target addresses. */ pxlt = CreateXlate(obj, XLATE_NOSIZE); for (p = GetFirstObjProc(obj); p; p = GetNextProc(p)) { for (b = GetFirstBlock(p); b; b = GetNextBlock(b)) { /* * If the first instruction in this basic block has had its * address taken, it's a potential jump target. Add the * instruction to the XLATE and keep track of the pure address * too. */ i = GetFirstInst(b); if (GetInstInfo(i, InstAddrTaken)) { AddXlateAddress(pxlt, i); address_add(InstPC(i)); } for (; i; i = GetNextInst(i)) { bin.word = GetInstInfo(i, InstBinary); if (bin.common.opcode == op_jsr && bin.j_format.function == jsr_jmp) { /* * This is a jump instruction. Instrument it. */ AddCallInst(i, InstBefore, "JmpLog", InstPC(i), GetInstInfo(i, InstRB)); } } } } /* * Re-prototype the RegisterXlate() analysis routine now that we * know the size of the pure address array. */ sprintf(proto, "RegisterXlate(int, XLATE *, long[%d])", address_num()); AddCallProto(proto); /* * Pass the XLATE and the pure address array to this object. */ AddCallObj(obj, ObjBefore, "RegisterXlate", GetObjInfo(obj, ObjID), pxlt, address_paddrs()); /* * Deallocate the pure address array. */ address_free(); } /* ** Maintains a dynamic array of pure addresses. */ static unsigned long * pAddrs; static unsigned maxAddrs = 0; static unsigned nAddrs = 0; /* ** Add an address to the array. */ static void address_add( unsigned long addr) { /* * If there's not enough room, expand the array. */ if (nAddrs >= maxAddrs) { maxAddrs = (nAddrs + 100) * 2; pAddrs = realloc(pAddrs, maxAddrs * sizeof(*pAddrs)); if (!pAddrs) { fprintf(stderr, "Out of memory\n"); exit(1); } } /* * Add the address to the array. */ pAddrs[nAddrs++] = addr; } /* ** Return the number of elments in the address array. */ static unsigned address_num(void) { return(nAddrs); } /* ** Return the array of addresses. */ static unsigned long *address_paddrs(void) { return(pAddrs); } /* ** Deallocate the address array. */ static void address_free(void) { free(pAddrs); pAddrs = 0; maxAddrs = 0; nAddrs = 0; }
次のソース・リスト
(xlate.anal.c)
には,xlate
ツールの分析ルーチンが含まれています。
#include <stdlib.h> #include <stdio.h> #include <cmplrs/atom.anal.h> /* * Each object in the application gets one of the following data * structures. The XLATE contains the instrumented addresses for * all possible jump targets in the object. The array contains * the matching pure addresses. */ typedef struct { XLATE * pXlt; unsigned long * pAddrsPure; } ObjXlt_t; /* * An array with one ObjXlt_t structure for each object in the * application. */ static ObjXlt_t * pAllXlts; static unsigned nObj; static int translate_addr(unsigned long, unsigned long *); static int translate_addr_obj(ObjXlt_t *, unsigned long, unsigned long *); /* ** Called at ProgramBefore. Registers the number of objects in ** this application. */ void RegisterNumObjs( unsigned nobj) { /* * Allocate an array with one element for each object. The * elements are initialized as each object is loaded. */ nObj = nobj; pAllXlts = calloc(nobj, sizeof(pAllXlts)); if (!pAllXlts) { fprintf(stderr, "Out of Memory\n"); exit(1); } } /* ** Called at ObjBefore for each object. Registers an XLATE with ** instrumented addresses for all possible jump targets. Also ** passes an array of pure addresses for all possible jump targets. */ void RegisterXlate( unsigned iobj, XLATE * pxlt, unsigned long * paddrs_pure) { /* * Initialize this object's element in the pAllXlts array. */ pAllXlts[iobj].pXlt = pxlt; pAllXlts[iobj].pAddrsPure = paddrs_pure; } /* ** Called at InstBefore for each jump instruction. Prints the pure ** target address of the jump. */ void JmpLog( unsigned long pc, REGV targ) { unsigned long addr; printf("0x%lx jumps to - ", pc); if (translate_addr(targ, &addr)) printf("0x%lx\n", addr); else printf("unknown\n"); } /* ** Attempt to translate the given instrumented address to its pure ** equivalent. Set '*paddr_pure' to the pure address and return 1 ** on success. Return 0 on failure. ** ** Will always succeed for jump target addresses. */ static int translate_addr( unsigned long addr_inst, unsigned long * paddr_pure) { unsigned long start; unsigned long size; unsigned i; /* * Find out which object contains this instrumented address. */ for (i = 0; i < nObj; i++) { start = XlateInstTextStart(pAllXlts[i].pXlt); size = XlateInstTextSize(pAllXlts[i].pXlt); if (addr_inst >= size && addr_inst < start + size) { /* * Found the object, translate the address using that * object's data. */ return(translate_addr_obj(&pAllXlts[i], addr_inst, paddr_pure)); } } /* * No object contains this address. */ return(0); } /* ** Attempt to translate the given instrumented address to its ** pure equivalent using the given object's translation data. ** Set '*paddr_pure' to the pure address and return 1 on success. ** Return 0 on failure. */ static int translate_addr_obj( ObjXlt_t * pObjXlt, unsigned long addr_inst, unsigned long * paddr_pure) { unsigned num; unsigned i; /* * See if the instrumented address matches any element in the XLATE. */ num = XlateNum(pObjXlt->pXlt); for (i = 0; i < num; i++) { if (XlateAddr(pObjXlt->pXlt, i) == addr_inst) { /* * Matches this XLATE element, return the matching pure * address. */ *paddr_pure = pObjXlt->pAddrsPure[i]; return(1); } } /* * No match found, must not be a possible jump target. */ return(0); }
この項では,プロシージャ・トレース,命令プロファイル,データ・キャッシュ・シミュレーションという 3 つの簡単な例を使用して,基本的なツール作成インタフェースについて説明します。
9.2.8.1 プロシージャ・トレース
ptrace
ツールは,プロシージャが実行される順番にその名前をプリントします。
実行では,アプリケーションの各プロシージャの呼び出しが追加されます。
慣例により,ptrace
ツールの計測は,ptrace.inst.c
ファイルに入れられます。
次の例を参照してください。
1 #include <stdio.h> 2 #include <cmplrs/atom.inst.h> [1] 3 4 unsigned InstrumentAll(int argc, char **argv) [2] 5 { 6 Obj *o; Proc *p; 7 AddCallProto("ProcTrace(char *)"); [3] 8 for (o = GetFirstObj(); o != NULL; o = GetNextObj(o)) { [4] 9 if (BuildObj(o)) return 1; [5] 10 for (p = GetFirstObjProc(o); p != NULL; p = GetNextProc(p)) { [6] 11 const char *name = ProcName(p); [7] 12 if (name == NULL) name = "UNKNOWN"; [8] 13 AddCallProc(p,ProcBefore,"ProcTrace",name); [9] 14 } 15 WriteObj(o); [10] 16 } 17 return(0); 18 }
Atom 計測ルーチンおよびデータ構造体の定義を取り込みます。 [例に戻る]
InstrumentAll
プロシージャを定義します。
この計測ルーチンは,各分析プロシージャへのインタフェースを定義して,計測するアプリケーションの正しい位置にそれらのプロシージャの呼び出しを挿入します。
[例に戻る]
AddCallProto
ルーチンを呼び出して,ProcTrace
分析プロシージャを定義します。
ProcTrace
は,タイプ
char *
の単一の引数をとります。
[例に戻る]
アプリケーション内の各オブジェクトを循環するルーチン
GetFirstObj
および
GetNextObj
を呼び出します。
プログラムが非共用としてリンクされた場合は,単一のオブジェクトしか存在しません。
プログラムが呼び出し共用としてリンクされた場合には,複数のオブジェクト (メインの実行可能プログラムに 1 つと,動的にリンクされたシェアード・ライブラリごとに 1 つ) を含んでいます。
メイン・プログラムは常に最初のオブジェクトです。
[例に戻る]
最初のオブジェクトを構築します。
オブジェクトは,まず構築しなければ使用できません。
非常にまれですが,オブジェクトを構築できないことがあります。
InstrumentAll
ルーチンは,非ゼロ値を返すことによってこの状態を Atom に報告します。
[例に戻る]
ルーチン
GetFirstObjProc
および
GetNextProc
を呼び出して,アプリケーション・プログラム内の各プロシージャ内を 1 ステップずつ実行します
[例に戻る]
各プロシージャに対して,プロシージャ名を探索する
ProcName
プロシージャを呼び出します。
アプリケーションで利用可能なシンボル・テーブル情報の量によって,static
として定義されているものなど,いくつかのプロシージャ名が利用できないことがあります。
-g1
オプションを指定してアプリケーションをコンパイルすると,このレベルのシンボル情報が提供されます。
このような場合,Atom は
NULL
を返します。
[例に戻る]
NULL
プロシージャ名文字列を
UNKNOWN
に変換します。
[例に戻る]
AddCallProc
ルーチンを呼び出して,p
によってポイントされるプロシージャの呼び出しを追加します。
ProcBefore
引数は,分析プロシージャがプロシージャ内の他のすべての命令の前に追加されることを示します。
この計測ポイントで呼び出される分析プロシージャ名は,ProcTrace
です。
最後の引数は分析プロシージャに渡されるものです。
この場合は,11 行目で取得されたプロシージャ名です。
[例に戻る]
計測機構付きオブジェクト・ファイルをディスクに書き込みます。 [例に戻る]
計測ファイルは
ProcTrace
分析プロシージャの呼び出しを追加しました。
このプロシージャは,次の例に示すように,ptrace.anal.c
分析ファイルに定義します。
1 #include <stdio.h> 2 3 void ProcTrace(char *name) 4 { 5 fprintf(stderr, "%s\n",name); 6 }
ProcTrace
分析プロシージャは,引数として渡された文字列を
stderr
にプリントします。
分析プロシージャは,値を返すことができません。
計測ファイルと分析ファイルを指定すると,ツールは完成します。 このツールの応用例を示すために,以下のように,次のアプリケーションをコンパイルおよびリンクします。
#include <stdio.h> main() { printf("Hello world!\n"); }
次の例では,共用されない実行可能プログラムを作成して,ptrace
ツールを適用し,計測機構付き実行可能プログラムを実行します。
この単純なプログラムでは,約 30 個のプロシージャを呼び出します。
% cc -non_shared hello.c -o hello % atom hello ptrace.inst.c ptrace.anal.c -o hello.ptrace % hello.ptrace __start main printf _doprnt __getmbcurmax strchr strlen memcpy . . .
次の例は,呼び出し共用としてリンクされたアプリケーションとともにこのプロセスを繰り返します。
主な違いは,LD_LIBRARY_PATH
環境変数を現在のディレクトリに設定しなければならないことです。
これは,Atom がローカル・ディレクトリに
libc.so
シェアード・ライブラリの計測機構付きバージョンを作成するためです。
% cc hello.c -o hello % atom hello ptrace.inst.c ptrace.anal.c -o hello.ptrace -all % setenv LD_LIBRARY_PATH `pwd` % hello.ptrace __start _call_add_gp_range __exc_add_gp_range malloc cartesian_alloc cartesian_growheap2 __getpagesize __sbrk . . .
アプリケーションの呼び出し共用バージョンは,非共用バージョンが呼び出すプロシージャ数の約 2 倍を呼び出します。
計測されるのは,元のアプリケーション・プログラム内の呼び出しだけです。
ProcTrace
分析プロシージャの呼び出しは元のアプリケーションには存在しなかったため,計測機構付きアプリケーション・プロシージャのトレースには現れません。
同様に,各プロシージャ名をプリントする標準ライブラリ呼び出しも含まれません。
アプリケーションと分析プログラムがともに
printf
関数を呼び出す場合,Atom はこの関数の 2 つのコピーを計測機構付きアプリケーションにリンクします。
アプリケーション・プログラム内のコピーだけが計測されます。
Atom は,複数のエントリ・ポイントを持つプロシージャも正しく計測します。
9.2.8.2 プロファイル・ツール
iprof
サンプル・ツールは,プログラムが実行する命令数をカウントします。
これは,コードのクリティカル・セクションを見つけるのに有用です。
アプリケーションが実行されるたびに,iprof
は,各プロシージャで実行される命令数と各プロシージャの呼び出し回数のプロファイルを含む
iprof.out
というファイルを作成します。
命令数を計算する最も効率的な場所は,各基本ブロックの内部です。
基本ブロックが実行されるたびに,一定数の命令が実行されます。
次の例は,iprof
ツールの計測プロシージャ (iprof.inst.c
) がこれらのタスクをどのように実行するか示したものです。
1 #include <stdio.h> 2 #include <cmplrs/atom.inst.h> 3 static int n = 0; 4 5 static const char * SafeProcName(Proc *); 6 7 void InstrumentInit(int argc, char **argv) 8{ 9 AddCallProto("OpenFile(int)"); [1] 10 AddCallProto("ProcedureCalls(int)"); 11 AddCallProto("ProcedureCount(int,int)"); 12 AddCallProto("ProcedurePrint(int,char*)"); 13 AddCallProto("CloseFile()"); 14 AddCallProgram(ProgramAfter,"CloseFile"); [2] 15 } 16 17 Instrument(int argc, char **argv, Obj *obj) 18 { 19 Proc *p; Block *b; 20 21 for (p = GetFirstObjProc(obj); p != NULL; p = GetNextProc(p)) { [3] 22 AddCallProc(p,ProcBefore,"ProcedureCalls",n); 23 for (b = GetFirstBlock(p); b != NULL; b = GetNextBlock(b)) { [4] 24 AddCallBlock(b,BlockBefore,"ProcedureCount", [5] 25 n,GetBlockInfo(b,BlockNumberInsts)); 26 } 27 AddCallObj(obj, ObjAfter,"ProcedurePrint",n,SafeProcName(p)); [6] 28 n++; [7] 29 } 30 } 31 32 void InstrumentFini(void) 33 { 34 AddCallProgram(ProgramBefore,"OpenFile",n); [8] 35 } 36 37 static const char *SafeProcName(Proc *p) 38 { 39 const char * name; 40 static char buf[128]; 41 42 name = ProcName(p); [9] 43 if (name) 44 return(name); 45 sprintf(buf, "proc_at_0x%lx", ProcPC(p)); 46 return(buf); 47 }
分析プロシージャへのインタフェースを定義します。 [例に戻る]
CloseFile
分析プロシージャへの呼び出しをプログラムの最後に追加します。
[例に戻る]
オブジェクト内の各プロシージャをループします。 [例に戻る]
プロシージャ内の各基本ブロックをループします。 [例に戻る]
この基本ブロック内の命令が実行される前に,ProcedureCount
分析プロシージャの呼び出しを追加します。
ProcedureCount
の引数タイプは,11 行目のプロトタイプで定義されます。
最初の引数は
int
タイプのプロシージャ・インデックスであり,2 番目の引数も
int
で,基本ブロック内の命令の数です。
ProcedureCount
分析プロシージャは,基本ブロック内の命令の数をプロシージャごとのデータ構造体に追加します。
同様に,ProcedureCalls
分析プロシージャは,各呼び出しが呼び出されたプロシージャの実行を開始する前に,プロシージャの呼び出しカウントに加算します。
[例に戻る]
ProcedurePrint
分析プロシージャの呼び出しをプログラムの最後に追加します。
ProcedurePrint
分析プロシージャは,このプロシージャの命令の使用と呼び出しカウントを要約した 1 行をプリントします。
[例に戻る]
プロシージャ・インデックスを増分します。 [例に戻る]
OpenFile
分析プロシージャの呼び出しをプログラムの先頭に追加して,アプリケーション内のプロシージャ数を表す
int
をそのプロシージャに渡します。
OpenFile
プロシージャは,命令を集計するプロシージャごとのデータ構造体を割り当てて,出力ファイルをオープンします。
[例に戻る]
プロシージャ名を決定します。 [例に戻る]
iprof
ツールによって使用される分析プロシージャは,次の例に示すように,iprof.anal.c
ファイルで定義されます。
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 6 long instrTotal = 0; 7 long *instrPerProc; 8 long *callsPerProc; 9 10 FILE *OpenUnique(char *fileName, char *type) 11 { 12 FILE *file; 13 char Name[200]; 14 15 if (getenv("ATOMUNIQUE") != NULL) 16 sprintf(Name,"%s.%d",fileName,getpid()); 17 else 18 strcpy(Name,fileName); 19 20 file = fopen(Name,type); 21 if (file == NULL) 22 { 23 fprintf(stderr,"Atom: can't open %s for %s\n",Name, type); 24 exit(1); 25 } 26 return(file); 27 } 28 29 static FILE *file; 30 void OpenFile(int number) 31 { 32 file = OpenUnique("iprof.out","w"); 33 fprintf(file,"%30s %15s %15s %12s\n","Procedure","Calls", 34 "Instructions","Percentage"); 35 instrPerProc = (long *) calloc(sizeof(long), number); [1] 36 callsPerProc = (long *) calloc(sizeof(long), number); 37 if (instrPerProc == NULL || callsPerProc == NULL) { 38 fprintf(stderr,"Malloc failed\n"); 39 exit(1); 40 } 41 } 42 43 void ProcedureCalls(int number) 44 { 45 callsPerProc[number]++; 46 } 47 48 void ProcedureCount(int number, int instructions) 49 { 50 instrTotal += instructions; 51 instrPerProc[number] += instructions; 52 } 53 54 55 void ProcedurePrint(int number, char *name) 56 { 57 if (instrPerProc[number] > 0) { [2] 58 fprintf(file,"%30s %15ld %15ld %12.3f\n", 59 name, callsPerProc[number], instrPerProc[number], 60 100.0 * instrPerProc[number] / instrTotal); 61 } 62 } 63 64 void CloseFile() [3] 65 { 66 fprintf(file,"\n%30s %15s %15ld\n", "Total", "", instrTotal); 67 fclose(file); 68 }
カウントのデータ構造体を割り当てます。
calloc
関数はカウント・データにゼロ詰めします。
[例に戻る]
呼び出されることのないプロシージャをフィルタにかけます。 [例に戻る]
出力ファイルをクローズします。 分析プロシージャ内でオープンされたファイルは,ツールで明示的にクローズする必要があります。 [例に戻る]
計測ファイルと分析ファイルを指定すると,ツールは完成します。
このツールの応用例を示すために,次のように,"Hello"
というアプリケーションをコンパイルしてリンクします。
#include <stdio.h> main() { printf("Hello world!\n"); }
次の例は,呼び出し共用実行可能プログラムを作成して,iprof
ツールを適用し,計測機構付き実行可能ファイルを実行します。
9.2.8.1 項で説明した
ptrace
ツールとは対照的に,iprof
ツールは
stdout
ではなくファイルにその出力を送ります。
% cc hello.c -o hello % atom hello iprof.inst.c iprof.anal.c -o hello.iprof -all % setenv LD_LIBRARY_PATH `pwd` % hello.iprof Hello world! % more iprof.out Procedure Calls Instructions Percentage __start 1 92 1.487 main 1 15 0.242 . . . printf 1 81 0.926 . . . Total 8750 % unsetenv LD_LIBRARY_PATH
9.2.8.3 データ・キャッシュ・シミュレーション・ツール
命令およびデータ・アドレスのトレースは,長年,キャッシュ動作を収集して分析するための技術として使用されています。 残念なことに,現在のマシン速度ではこれはますます難しくなっています。 たとえば,Alvinn SPEC92 ベンチマークは,合計で 2,603,010,614 個の Alpha 命令について,961,082,150 回のロード,260,196,942 回のストア,73,687,356 個の基本ブロックを実行しています。 各基本ブロックのアドレスと,すべてのロードとストアの実効アドレスを格納しようとすると,10 GB 以上が必要になり,アプリケーションは 100 倍以上遅くなります。
cache
ツールは,オン・ザ・フライ (on-the-fly) シミュレーションを使用して,8 KB の直接マップされたキャッシュで実行されるアプリケーションのキャッシュ・ミス率を決定します。
次の例は,その計測ルーチンを示したものです。
1 #include <stdio.h> 2 #include <cmplrs/atom.inst.h> 3 4 unsigned InstrumentAll(int argc, char **argv) 5 { 6 Obj *o; Proc *p; Block *b; Inst *i; 7 8 AddCallProto("Reference(VALUE)"); 9 AddCallProto("Print()"); 10 for (o = GetFirstObj(); o != NULL; o = GetNextObj(o)) { 11 if (BuildObj(o)) return (1); 12 for (p=GetFirstProc(); p != NULL; p = GetNextProc(p)) { 13 for (b = GetFirstBlock(p); b != NULL; b = GetNextBlock(b)) { 14 for (i = GetFirstInst(b); i != NULL; i = GetNextInst(i)) { [1] 15 if (IsInstType(i,InstTypeLoad) || IsInstType(i,InstTypeStore)) { 16 AddCallInst(i,InstBefore,"Reference",EffAddrValue); [2] 17 } 18 } 19 } 20 } 21 WriteObj(o); 22 } 23 AddCallProgram(ProgramAfter,"Print"); 24 return (0); 25 }
cache
ツールによって使用される分析プロシージャは,次の例に示すように,cache.anal.c
ファイルに定義します。
1 #include <stdio.h> 2 #include <assert.h> 3 #define CACHE_SIZE 8192 4 #define BLOCK_SHIFT 5 5 long tags[CACHE_SIZE >> BLOCK_SHIFT]; 6 long references, misses; 7 8 void Reference(long address) { 9 int index = (address & (CACHE_SIZE-1)) >> BLOCK_SHIFT; 10 long tag = address >> BLOCK_SHIFT; 11 if tags[index] != tag) { 12 misses++; 13 tags[index] = tag; 14 } 15 references++; 16 } 17 void Print() { 18 FILE *file = fopen("cache.out","w"); 19 assert(file != NULL); 20 fprintf(file,"References: %ld\n", references); 21 fprintf(file,"Cache Misses: %ld\n", misses); 22 fprintf(file,"Cache Miss Rate: %f\n", (100.0 * misses) / references); 23 fclose(file); 24 }
計測ファイルと分析ファイルを指定すると,ツールは完成します。 このツールの応用例を示すために,次のように "Hello" というアプリケーションをコンパイルして,リンクします。
#include <stdio.h> main() { printf("Hello world!\n"); }
次の例は,cache
ツールを適用して,アプリケーションの非共用バージョンと呼び出し共用バージョンの両方を計測する例です。
% cc hello.c -o hello % atom hello cache.inst.c cache.anal.c -o hello.cache -all % setenv LD_LIBRARY_PATH `pwd` % hello.cache Hello world! % more cache.out References: 1091 Cache Misses: 225 Cache Miss Rate: 20.623281 % cc -non_shared hello.c -o hello % atom hello cache.inst.c cache.anal.c -o hello.cache -all % hello.cache Hello world! % more cache.out References: 382 Cache Misses: 93 Cache Miss Rate: 24.345550