オープンソースを楽しむエンジニア達のこだわり 〜 デバッグ情報を得る
前回、ftrace で引数を表示するためにスタックフレームの位置を得る方法を 紹介しましたが、実際に引数の値を得るためにはプロトタイプ情報(引数の数 や型情報)が必要になることが解りました。
通常通りコンパイルした実行バイナリファイルには変数の型情報は含まれてい ませんが gcc のコンパイルオプションに -g を付けるとオブジェクトファイ ルに型情報を含め、様々なデバック情報を含めることが出来ます。今回はこのデバッグ情 報の詳細と利用する方法について紹介してみたいと思います。
DWARF とは
DWARF とは今回利用するデバッグ情報を保持するためのフォーマットであり、 通常デバッグ情報は ELF ファイルの .debug_info セクションに この DWARF 形式で納められています。
gcc の -g オプションを付けてコンパイルしたオブジェクトファイルに対し readelf -S を実行すると .debug_info セクションが含まれていることが確認 できます。
DWARF の構造
DWARF 形式のデータは Die(Debugging Information Entry) というデータ構造 の集合から構成され、それぞれの Die は tag という識別子で種類を見分ける ことが出来ます。代表的な tag は以下のような物があります。
| tag | 概要 | 含まれる情報 | DW_TAG_base_type | 基本型に関する情報 | 型の名前、型のサイズ、符号あり/なし |
| DW_TAG_pointer_type | 参照型に関する情報 | 型のサイズ、型情報(DW_TAG_base_type) への参照 |
| DW_TAG_variable | 変数に関する情報 | 変数名、宣言されているファイルや行数、 型情報(DW_TAG_base_type への参照) |
| DW_TAG_subprogram | 関数に関する情報 | 関数名、関数のアドレス、宣言されているファイルや行数 |
| DW_TAG_formal_parameter | 引数に関する情報 | 引数名、型情報(DW_TAG_base_type への参照) |
DWARF 情報全体の構造は上記のような tag で分別できる Die をノードから成る 木構造で表現できます。
.
|-- DW_TAG_compile_unit
| |-- DW_TAG_base_type
| |-- DW_TAG_pointer_type
| | ...
|
|-- DW_TAG_compile_unit
| |-- DW_TAG_base_type
| | ...
| |-- DW_TAG_pointer_type
| |-- DW_TAG_subprogram
| | |-- DW_TAG_formal_parameter
| | |-- DW_TAG_formal_parameter
| | `-- DW_TAG_formal_parameter
| |-- DW_TAG_variable
| | ...
|
`-- DW_TAG_compile_unit
|-- DW_TAG_base_type
| ...
たとえば以下のような、関数を定義した場合
void func(int a, short b, char c)
{
}
int main()
{
int i=0;
func(1, 2, 3);
}
以下のような構造の DWARF情報が構成されます。
.
`-- DW_TAG_compile_unit
|-- [1]DW_TAG_base_type(型の名前: int、サイズ: 4byte)
|-- [2]DW_TAG_base_type(型の名前: short、サイズ: 2byte)
|-- [3]DW_TAG_base_type(型の名前: char、サイズ: 1byte)
|-- DW_TAG_subprogram(関数名: func)
| |-- DW_TAG_formal_parameter(引数名: a, 型は [1])
| |-- DW_TAG_formal_parameter(引数名: b, 型は [2])
| `-- DW_TAG_formal_parameter(引数名: c, 型は [3])
|-- DW_TAG_variable(変数名: i, 型は[1])
| ...
DWARF 情報を取得する
DWARF 情報の構造について説明したところで、実際に取得する方法を簡単に紹 介します。今回、DWARF 情報を取得するために libdwarf という ライブラリを使ってみます。
困ったことにこのライブラリに関するドキュメントが見あたらなくて使用方法 がなかなか解らなかったのですが、ソースコードに付属している dwarfdump.c というコードがとても参考になりました。 以下にざっくりと、libdwarf を使用してデバッグ情報を得るための手順を示 します。
- elf 構造の初期化と読み込み
- dwarf データの初期化と読み込み dwarf_elf_init() に elf_begin() で初期化したelf オブジェクトと Dwarf_Debug 構造体を渡し dwarf データを扱う準備をします。
- コンパイルユニットを列挙する dwarf_next_cu_header() を呼び出すことでルートの Die 下にある コンパイ ルユニットを列挙することが出来ます。
- Die の tag を取得する 各 Die の tag は以下のように dwarf_tag()を呼ぶことで得ることが出来ます。
- 子ノード(Die) を取得する 関数などの Die は更に子の Die を持ちます、この様な子 Die を取得するに はdwarf_child() を呼び出すことで得ることが出来ます。
- Die の 詳細を取得する 以下に 型情報の Die から型名とサイズを得るコードを示します。
まず実行ファイルを open() したファイルディスクリプタを elf_begin() に 渡し、elf データとしての初期化を行います。
# elf_begin() を呼ぶ前に elf_version() を呼んでおく必要があります。
Dwarf_Half tag;
Dwarf_Error err;
dwarf_tag(die, &tag, &err);
if(tag == DW_TAG_subprogram){
/* この die は関数 */
}
Dwarf_Error err;
Dwarf_Attribute attr;
char *name;
Dwarf_Unsigned size;
dwarf_attr(die, DW_AT_name, &attr, &err);
dwarf_formstring(attr, &name, &err);
dwarf_attr(die, DW_AT_byte_size, &attr, &err);
dwarf_formsdata(attr, &size, &err);
printf("型名: %s, 型サイズ: %s\n", name, size);
実行結果例
型名: int, 型サイズ: 4 型名: short, 型サイズ: 2 型名: char, 型サイズ: 1
この様に得る情報の種類によって呼び出す dwarf API が変わるので注意が必 要です。
libdwarf の非常に大雑把な使い方を書きましたが具体的なコードは ftrace の prototype.c に有りますので興味のある方はご覧ください。
ftrace 0.93
ftrace の新しいバージョンです、以下からダウンロードしておためしください
変更点は、- PowerPC, AMD 64bit CPUで動作するようになりました。 上記のアーキテクチャの場合最適化を無効にするため対象のプログラムのコン パイル時に -O0 オプションを付けなくてはならないことが解りました。
次回は、ftrace の各CPU アークテクチャ毎の対応について紹介しようと思い ます。
