9    Atom ツールの使用および開発

プログラム分析ツールは,コンピュータ設計者およびソフトウェア・エンジニアにとってきわめて重要です。 プログラム分析ツールを使用して,コンピュータ設計者は新しいアーキテクチャ設計のテストと測定を行い,ソフトウェア・エンジニアは,プログラム内のコードのクリティカルな部分を特定したり,分岐予測や命令スケジューリング・アルゴリズムがどの程度機能しているかを検査します。 プログラム分析ツールは,基本ブロックのカウントから,データのキャッシュ・シミュレーションに至るまでの問題に必要です。 これらのタスクを実行するツールは異なるように思われますが,コードの計測によって,それぞれを簡単で効率的に実現できます。

Atom では,さまざまなツールを作成できる柔軟なコード計測インタフェースを提供します。 Atom は,計測およびオブジェクト・コード操作のための機構を提供し,ツール設計者がプログラムの計測するポイントを特定できるようにして,それぞれの問題に固有の部分とすべての問題に共通の部分とを分離します。 Atom は,完全なプログラムを構成するオブジェクト・モジュール上で動作するので,どのコンパイラおよび言語からも独立しています。

この章では,次の 2 つの項目について説明します。

9.1    Atom ツールの実行

以降の各項では,次の項目について説明します。

9.1.1    インストール済みの Atom ツールの使用

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 ツール,または製品としてカスタマに配布される Atom ツールには,通常,固有のソースの代わりに .o オブジェクト・モジュールがインストールされています。 Tru64 UNIX の hiprof(1)pixie(1)third(1) の各コマンド,および Visual Thread 製品には,このようにして配布,インストール,および実行される Atom ツールが含まれています。 規約により,計測ファイル,分析ファイル,および記述ファイルは,/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) コマンドは,省略時の設定の toolname.desc ファイルではなく toolname.env.desc ファイルを検索します。 このコマンドは,指定した環境の記述ファイルが見つからない場合にエラー・メッセージを表示します。

9.1.2    開発中のテスト用ツール

atom(1) コマンドの 2 つ目のコマンド形式は,開発中の新しい Atom ツールのコンパイルと実行を容易にする目的で提供されています。 次のように,コマンド行で計測および分析ファイルの名前を直接指定するだけです。

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

9.1.3    Atom オプション

-tool および -env オプションを除いて,atom コマンドの 2 つの形式はいずれも, atom(1) に記述されている残りのオプションをすべて受け付けます。 特に注意を必要とするオプションは次のとおりです。

-A1

セーブおよびリストアする必要のあるレジスタ数を減少させることにより,Atom が分析ルーチンの呼び出しを最適化できるようにします。 いくつかのツールでは,このオプションを指定すると,計測機構付きアプリケーションの性能が 2 倍向上します (その代わり,アプリケーションのサイズが多少増加します)。 省略時の動作では,Atom はこれらの最適化を適用しません。

-all

共用実行可能ファイル内の,静的にロードしたシェアード・ライブラリをすべて計測します。

-debug

計測ルーチンを 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

計測ルーチンをオプションの ladebug デバッガでデバッグできるようにします。 Atom は,計測ルーチンの起動時に ladebug に制御を移します。 計測ルーチンがスレッド化されている場合,または内部に C++ のコードが含まれている場合には,ladebug を使用します。 詳細は,『Ladebug Debugger Manual』を参照してください。

-excobj

指定したシェアード・ライブラリを計測から除きます。 -excobj オプションを複数回使用すると,複数のシェアード・ライブラリを指定できます。

-fork

fork のサポートが必要であることを指定します。 このオプションを使用すると,マルチスレッド・アプリケーションでデッドロックを回避します。

-ga (-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] }

-gap

デバッグ情報を持った計測機構付きプログラムを作成します。 このオプションを使用すると,分析ルーチンおよびアプリケーション・ルーチンのデバッグが可能になります。 アプリケーション内の変数およびプロシージャの名前すべてに,接頭語の "_APP_" が付加されます。 -gpa を使用する場合は,省略時の -A0 オプション (-A1 ではない) をお勧めします。

-gp

デバッグ情報を持った計測機構付きプログラムを作成します。 このオプションを使用すると,アプリケーション・ルーチンをシンボリック・デバッガでデバッグできるようになります。

-gpa

デバッグ情報を持った計測機構付きプログラムを作成します。 このオプションを使用すると,分析ルーチンおよびアプリケーション・ルーチンのデバッグが可能になります。 分析オブジェクト内の変数およびプロシージャの名前すべてに,接頭語の "_ANA_" が付加されます。 -gpa を使用する場合は,省略時の -A0 オプション (-A1 ではない) をお勧めします。

-heapbase

分析ヒープのベースを変更します。 省略時の分析ヒープの位置が,アプリケーション・プログラムが使用するアドレス範囲と競合する場合は -heapbase オプションを使用します。 新しいベースとして,新しい 16 進数のアドレス位置か,省略時の 31 ビットのアドレス位置か,アプリケーションの bss セグメントの末尾の次のページのいずれかを選択できます。

-ii

以前に計測したシェアード・ライブラリの再使用 (または増分の計測) をできるようにします。

-incobj

指定したシェアード・ライブラリを計測します。 -incobj オプションを複数回使用して,複数のシェアード・ライブラリを指定することもできます。

-keep

Atom が作成する一時ファイルを,現行の作業ディレクトリに置き,計測が完了しても削除しないことを指定します。

-L

ライブラリのディレクトリでシェアード・オブジェクト・ライブラリを検索する順序を変更し,Atom が省略時のライブラリ・ディレクトリをまったく検索しないようにします。 このオプションは,省略時のディレクトリを検索せず,-Ldir が指定したディレクトリのみを検索するようにする場合に使用します。

-Ldir

ライブラリのディレクトリでシェアード・オブジェクト・ライブラリを検索する順序を変更し,Atom が dir ディレクトリを検索した後で省略時のライブラリ・ディレクトリを検索するようにします。 複数の -Ldir オプションを指定すると,複数のディレクトリ名を指定できます。

-map

計測機構付き実行可能ファイルのセクションの開始アドレスのリストを作成します。

-o

実行可能な出力ファイルの名前を指定します。

-pthread

スレッド・セーフ・サポートが必須であることを指定します。 このオプションは,スレッド・アプリケーションの計測を行う際に使用します。

-shlibdir

Atom が計測機構付きシェアード・ライブラリを書き込む既存のディレクトリを指定します。

-suffix

Atom が計測機構付きバージョンを書き込む際に,各オブジェクト名に付加するファイル名の接尾語を指定します。

-toolargs

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

-v

Atom が計測機構付きプログラムを作成する際に行う各ステップを表示します。

-version

Atom のバージョン番号を表示します。

-w0

通常は出力されないような警告も含め,すべての警告メッセージを表示します。

-w1

無視してかまわない警告メッセージは出力しません。 これは省略時の動作です。

-w2

分析ルーチンを処理する際に出力される警告メッセージは出力しません。

-w3

シェアード・ライブラリエラーの処理に関する警告メッセージは出力しません。

-Wla および -Wca

指定したオプションを,それぞれ分析ファイルのリンクとコンパイルのフェーズに渡します。

-Wli および -Wci

指定したオプションを,それぞれ計測ファイルのリンクとコンパイルのフェーズに渡します。

9.2    Atom ツールの開発

この節では,Atom ツールの開発方法について説明します。

9.2.1    Atom によるアプリケーションの表示

Atom は,アプリケーションをコンポーネントの階層として表示します。

  1. プログラム

    実行可能プログラムとすべてのシェアード・ライブラリを含みます。

  2. オブジェクトの集合

    オブジェクトは,メインの実行可能プログラムまたは任意のシェアード・ライブラリのいずれかです。 オブジェクトは,独自の属性 (名前など) のセットを持ち,プロシージャの集合から構成されます。

  3. プロシージャの集合

    各プロシージャはエントリ・ポイントと基本ブロックの集合から構成されます。

  4. 基本ブロックの集合

    各基本ブロックは命令の集合から構成されます。

  5. 命令の集合

Atom ツールは,アプリケーション・プログラム内のプロシージャ,エントリ・ポイント,基本ブロック,または命令の境界に,計測ポイントを挿入します。 たとえば,基本ブロック・カウント・ツールは,各基本ブロックの先頭を計測し,データ・キャッシュ・シミュレータは,ロードおよびストアの各命令を計測し,分岐予測アナライザは,各条件付き分岐命令を計測します。

Atom では,どの計測ポイントでも,ツールによって,分析ルーチンへの呼び出しを挿入できます。 ツールでは,呼び出しが,オブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,または命令の,前に行われるか後に行われるかを指定できます。

9.2.2    Atom の計測ルーチン

ツールの計測ルーチンには,アプリケーションのオブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,および命令をトラバースして,計測ポイントを探索したり,分析プロシージャの呼び出しを追加したり,アプリケーションの計測機構付きバージョンを作成するコードが含まれています。

atom_instrumentation_routines(5) で説明されているように,計測ルーチンは,ツールの必要に応じて,次のインタフェースのうちのいずれか 1 つを利用できます。

Instrument (int iargc, char **iargv, Obj *obj)

Atom は,アプリケーション・プログラムの各オブジェクトに対して Instrument ルーチンを呼び出します。 その結果,Instrument ルーチンは,オブジェクト・ナビゲーション・ルーチン (GetFirstObj など) を使用する必要がなくなります。 Atom は,Instrument ルーチンに次のオブジェクトを渡す前に,変更された各オブジェクトを自動的にコーディングするので,Instrument ルーチンは,BuildObjWriteObj,または ReleaseObj ルーチンを呼び出す必要がありません。 Instrument インタフェースを使用する場合には,InstrumentInit ルーチンを定義して,Atom が最初のオブジェクトに対して Instrument を呼び出す前に,必要なタスク (分析ルーチン・プロトタイプの定義,プログラム・レベルの計測呼び出しの追加,グローバルな初期化など) を実行することができます。 また,InstrumentFini ルーチンを定義して,Atom が最後のオブジェクトに対して Instrument を呼び出した後で,必要なタスク (グローバルなクリーンアップなど) を実行することもできます。

InstrumentAll (int iargc, char **iargv)

Atom は,アプリケーション・プログラム全体に対して一度 InstrumentAll ルーチンを呼び出します。 これは,ツールの計測コード自体がアプリケーションのオブジェクトをトラバースする方法を決定できるようにします。 このメソッドでは,InstrumentInit または InstrumentFini ルーチンは存在しません。 InstrumentAll ルーチンは,Atom のオブジェクト・ナビゲーション・ルーチンを呼び出し,BuildObjWriteObj,または ReleaseObj ルーチンを使用して,アプリケーションのオブジェクトを管理する必要があります。

計測ルーチン・インタフェースに関係なく,Atom は,-toolargs オプションで指定された引数をルーチンに渡します。 Instrument インタフェースの場合,Atom は,現在のオブジェクトを指すポインタも渡します。

9.2.3    Atom の計測インタフェース

Atom は,アプリケーションを計測するための総合的なインタフェースを提供します。 このインタフェースでは,次のタイプの処理をサポートしています。

9.2.3.1    プログラム内のナビゲーション

atom_application_navigation(5) で説明しているように,Atom のアプリケーション・ナビゲーション・ルーチンは,Atom ツールの計測ルーチンが,次のように,分析プロシージャの呼び出しを追加するアプリケーション内の位置を見つけられるようにします。

9.2.3.2    オブジェクトの作成

atom_object_management(5) に説明があるように,Atom のオブジェクト管理ルーチンによって,Atom ツールの InstrumentAll ルーチンは,オブジェクトの作成,コーディング,および解放を行うことができます。

BuildObj ルーチンは,Atom がオブジェクトを処理するために必要とする内部データ構造体を作成します。 InstrumentAll ルーチンは,オブジェクト内のプロシージャをトラバースして,分析ルーチン呼び出しをオブジェクトに追加する前に,BuildObj ルーチンを呼び出さなければなりません。 WriteObj ルーチンは,指定されたオブジェクトの計測機構付きバージョンをコーディングして,以前に BuildObj ルーチンが作成した内部データ構造体の割り当てを解除します。 ReleaseObj ルーチンは,指定のオブジェクトの内部データ構造体の割り当てを解除しますが,オブジェクトの計測機構付きバージョンは書き出しません。

IsObjBuilt ルーチンは,指定したオブジェクトが BuildObj ルーチンによって作成されているが,まだ WriteObj ルーチンによってコーディングされていないか,あるいは ReleaseObj ルーチンによって解放されていない場合,非ゼロの値を返します。

9.2.3.3    アプリケーションの構成要素に関する情報の取得

atom_application_query(5) に説明があるように,Atom のアプリケーション照会ルーチンによって,計測ルーチンは,プログラムおよびそのオブジェクト,プロシージャ,エントリ・ポイント,基本ブロック,命令に関する静的情報を取得できます。

表 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 命令が指定されたタイプ (ロード命令,ストア命令,条件付き分岐,または無条件分岐) であるかどうかを示します。

9.2.3.4    名前および呼び出しターゲットの解決

Atom のシンボル解決ルーチンにより,atom_application_symbols(5) に記載したように,Atom ツールの計測ルーチンはアプリケーション・オブジェクト,プロシージャ,エントリ・ポイント,命令を探索します。 これらは,指定されるか,または呼び出しサイトのターゲットになっています。

9.2.3.5    分析ルーチン呼び出しの追加

atom_application_instrumentation(5) で説明しているように,Atom のアプリケーション・計測ルーチンは,次のように,アプリケーションのさまざまな位置に任意のプロシージャ呼び出しを追加します。

9.2.3.6    エントリ・ポイント呼び出しへの介入

Atom の呼び出し介入ルーチンは, atom_application_instrumentation(5) で述べているように,メインまたは代替エントリ・ポイントへのアプリケーションの呼び出しに,次のように置換分析ルーチンを呼び出すことで介入します。

たとえば,次のような ReplaceProtoReplaceEntry の呼び出しのペアは, 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));

9.2.4    Atom の記述ファイル

atom_description_file(5) で説明しているように,Atom ツールの記述ファイルは,ツールの計測および分析ファイルを識別して記述します。 また,コンパイル,リンク,および起動時に,ccld,および atom コマンドによって使用されるオプションを指定することもできます。 各 Atom ツールは,最低 1 つの記述ファイルを提供しなければなりません。

Atom の記述ファイルには,2 つのタイプがあります。

上記の記述ファイル名で tool および environment 部分に指定する名前は,ユーザがツールの起動時に atom コマンドの -tool および -env オプションに指定する値に対応します。

Atom の記述ファイルは,一連のタグと値を含むテキスト・ファイルです。 ファイルの構文についての詳細は, atom_description_file(5) を参照してください。

9.2.5    分析プロシージャの作成

計測機構付きアプリケーションは,分析プロシージャを呼び出して,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-1prof ツールなど),イベントは子ではなく親で発生するため,回数を保持するデータ構造体は,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);
}

9.2.7    分析ルーチンからの計測機構付き PC の決定

Xlate(5) で説明するように,Atom のアドレス変換ルーチンは,選択された命令に対して計測機構付き PC (プログラム・カウンタ) を決定できるようにします。 これらの関数を使用して,計測機構付きアプリケーション内の命令の PC を,計測機構のないアプリケーション内の PC に変換するテーブルを作成できます。

命令のアドレスをアドレス変換バッファに渡すために,計測ルーチンは,まず CreateXlate ルーチンを呼び出します。 アドレス変換バッファが作成された後に,計測ルーチンは,AddXlateAddress ルーチンを呼び出すことで命令のアドレスを追加します。 エントリ・ポイントのアドレスは,AddXlateEntry ルーチンを呼び出すことでアドレス変換バッファに追加することもできます。 アドレス変換バッファに保持できるのは,単一オブジェクトからのアドレスだけです。

Atom ツールの計測ルーチンは,AddCallProto 呼び出しでの分析ルーチンのプロトタイプ定義に示されるように,XLATE * タイプのパラメータとして,アドレス変換バッファを分析ルーチンに渡します。

計測機構付き PC を決定するもう 1 つの方法は,分析ルーチンのプロトタイプで REGV の仮パラメータ・タイプを指定して,REG_IPC 値を渡す方法です。

Atom ツールの分析ルーチンは,次の分析インタフェースを使用して,渡されたアドレス変換バッファにアクセスします。

次の例に,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);
}

9.2.8    サンプル・ツール

この項では,プロシージャ・トレース,命令プロファイル,データ・キャッシュ・シミュレーションという 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  }

  1. Atom 計測ルーチンおよびデータ構造体の定義を取り込みます。 [例に戻る]

  2. InstrumentAll プロシージャを定義します。 この計測ルーチンは,各分析プロシージャへのインタフェースを定義して,計測するアプリケーションの正しい位置にそれらのプロシージャの呼び出しを挿入します。 [例に戻る]

  3. AddCallProto ルーチンを呼び出して,ProcTrace 分析プロシージャを定義します。 ProcTrace は,タイプ char * の単一の引数をとります。 [例に戻る]

  4. アプリケーション内の各オブジェクトを循環するルーチン GetFirstObj および GetNextObj を呼び出します。 プログラムが非共用としてリンクされた場合は,単一のオブジェクトしか存在しません。 プログラムが呼び出し共用としてリンクされた場合には,複数のオブジェクト (メインの実行可能プログラムに 1 つと,動的にリンクされたシェアード・ライブラリごとに 1 つ) を含んでいます。 メイン・プログラムは常に最初のオブジェクトです。 [例に戻る]

  5. 最初のオブジェクトを構築します。 オブジェクトは,まず構築しなければ使用できません。 非常にまれですが,オブジェクトを構築できないことがあります。 InstrumentAll ルーチンは,非ゼロ値を返すことによってこの状態を Atom に報告します。 [例に戻る]

  6. ルーチン GetFirstObjProc および GetNextProc を呼び出して,アプリケーション・プログラム内の各プロシージャ内を 1 ステップずつ実行します [例に戻る]

  7. 各プロシージャに対して,プロシージャ名を探索する ProcName プロシージャを呼び出します。 アプリケーションで利用可能なシンボル・テーブル情報の量によって,static として定義されているものなど,いくつかのプロシージャ名が利用できないことがあります。 -g1 オプションを指定してアプリケーションをコンパイルすると,このレベルのシンボル情報が提供されます。 このような場合,Atom は NULL を返します。 [例に戻る]

  8. NULL プロシージャ名文字列を UNKNOWN に変換します。 [例に戻る]

  9. AddCallProc ルーチンを呼び出して,p によってポイントされるプロシージャの呼び出しを追加します。 ProcBefore 引数は,分析プロシージャがプロシージャ内の他のすべての命令の前に追加されることを示します。 この計測ポイントで呼び出される分析プロシージャ名は,ProcTrace です。 最後の引数は分析プロシージャに渡されるものです。 この場合は,11 行目で取得されたプロシージャ名です。 [例に戻る]

  10. 計測機構付きオブジェクト・ファイルをディスクに書き込みます。 [例に戻る]

計測ファイルは 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 }
 
 

  1. 分析プロシージャへのインタフェースを定義します。 [例に戻る]

  2. CloseFile 分析プロシージャへの呼び出しをプログラムの最後に追加します。 [例に戻る]

  3. オブジェクト内の各プロシージャをループします。 [例に戻る]

  4. プロシージャ内の各基本ブロックをループします。 [例に戻る]

  5. この基本ブロック内の命令が実行される前に,ProcedureCount 分析プロシージャの呼び出しを追加します。 ProcedureCount の引数タイプは,11 行目のプロトタイプで定義されます。 最初の引数は int タイプのプロシージャ・インデックスであり,2 番目の引数も int で,基本ブロック内の命令の数です。 ProcedureCount 分析プロシージャは,基本ブロック内の命令の数をプロシージャごとのデータ構造体に追加します。 同様に,ProcedureCalls 分析プロシージャは,各呼び出しが呼び出されたプロシージャの実行を開始する前に,プロシージャの呼び出しカウントに加算します。 [例に戻る]

  6. ProcedurePrint 分析プロシージャの呼び出しをプログラムの最後に追加します。 ProcedurePrint 分析プロシージャは,このプロシージャの命令の使用と呼び出しカウントを要約した 1 行をプリントします。 [例に戻る]

  7. プロシージャ・インデックスを増分します。 [例に戻る]

  8. OpenFile 分析プロシージャの呼び出しをプログラムの先頭に追加して,アプリケーション内のプロシージャ数を表す int をそのプロシージャに渡します。 OpenFile プロシージャは,命令を集計するプロシージャごとのデータ構造体を割り当てて,出力ファイルをオープンします。 [例に戻る]

  9. プロシージャ名を決定します。 [例に戻る]

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 }
 
 

  1. カウントのデータ構造体を割り当てます。 calloc 関数はカウント・データにゼロ詰めします。 [例に戻る]

  2. 呼び出されることのないプロシージャをフィルタにかけます。 [例に戻る]

  3. 出力ファイルをクローズします。 分析プロシージャ内でオープンされたファイルは,ツールで明示的にクローズする必要があります。 [例に戻る]

計測ファイルと分析ファイルを指定すると,ツールは完成します。 このツールの応用例を示すために,次のように,"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  }

  1. 現在の基本ブロック内の各命令を調べます。 [例に戻る]

  2. 命令がロードまたはストアの場合,Reference 分析プロシージャの呼び出しを追加して,データ参照の実効アドレスを渡します。 [例に戻る]

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