2009年09月24日
DWARF と有限状態機械 (3)
これまでの流れ
- DWARF と有限状態機械
- DWARF と有限状態機械 (2)
概要、プログラムヘッダときて、今回のバイトコードで行番号プログラムの仕様は最後です。
行番号プログラムの目的は、1つのコンパイル単位中の行番号情報表を構築することです。つまり、プログラムを実行すると、表が出力として得られます。
この表は、命令アドレス、ファイル名、行、列、各種位置フラグ(ブレークポイント関係など)など、有限状態機械の状態と1列が対応するようなものです。つまり、行番号プログラムの実行過程における有限状態機械の状態遷移の記録=行番号情報表となります。
この表において、命令アドレスは増加する一方ですが、行番号はコンパイラの最適化などによる命令の並び替えによって減少する場合もあります。
- DWARF と有限状態機械
- DWARF と有限状態機械 (2)
概要、プログラムヘッダときて、今回のバイトコードで行番号プログラムの仕様は最後です。
行番号プログラムの目的は、1つのコンパイル単位中の行番号情報表を構築することです。つまり、プログラムを実行すると、表が出力として得られます。
この表は、命令アドレス、ファイル名、行、列、各種位置フラグ(ブレークポイント関係など)など、有限状態機械の状態と1列が対応するようなものです。つまり、行番号プログラムの実行過程における有限状態機械の状態遷移の記録=行番号情報表となります。
この表において、命令アドレスは増加する一方ですが、行番号はコンパイラの最適化などによる命令の並び替えによって減少する場合もあります。
行番号プログラムのバイトコードには、12種類のstandard opcodesと、3種類のextended opcodes、そしてspecial opcodesがあります。
special opcodesは1バイト固定のコードで、以下の6つの動作が共通で、違いはlineレジスタとaddressレジスタの演算の際の値だけです。
そしてこの時、ヘッダの説明の際には端寄ったline_baseとline_rangeフィールドが使用されます。line_baseはlineレジスタに加算される値の最小値を、line_rangeは加算される値の範囲を定義します。
special opcodeのオペコード値は、以下のような式になります。
もしオペコードが255以上になる場合は、standard opcodeを代わりに使用する必要があります。
special opcodeを解釈する際、addressレジスタの増加量は以下のようになります。
lineレジスタの増加量は以下のようになります。
例えばline_baseが-1で、line_rangeが4の場合、0から59までの範囲の命令アドレスの増加を、-1から2までの範囲の行番号の増減を、special opcodesの1バイトで表現できます。(それを越える範囲を表現する際には、後述するstandard opcodeを使用する必要があります。)
なかなか複雑ですが、special opcodesの動作効率はターゲットマシンの性質に強く依存するので、バイトコードの動作は固定ではなく、ヘッダによってカスタマイズが可能な仕様になっています。これにより、より効率的にデバッグ情報をエンコードすることができます。(そもそも少しでも容量を削減するためにこのようなバイトコードによって情報を保持しているのですから、効率は最優先となります。)
standard opcodeは12個存在します。
extended opcodeは3つです。
これで行番号プログラムの仕様は終了です。
special opcodesは1バイト固定のコードで、以下の6つの動作が共通で、違いはlineレジスタとaddressレジスタの演算の際の値だけです。
- lineレジスタに符号付き整数を加算する。
- addressレジスタの値に、符号無し整数 × ヘッダのminimum_instruction_lengthフィールドを加算する。
- 表に現在の状態機械の状態を記録する。(1行追加)
- basic_blockレジスタをfalseにセットする。
- prologue_endレジスタをfalseにセットする。
- epilogue_beginレジスタをfalseにセットする。
そしてこの時、ヘッダの説明の際には端寄ったline_baseとline_rangeフィールドが使用されます。line_baseはlineレジスタに加算される値の最小値を、line_rangeは加算される値の範囲を定義します。
special opcodeのオペコード値は、以下のような式になります。
オペコード = (増やしたい行数 - line_base) + (line_range × アドレス増減値) + opcode_base
もしオペコードが255以上になる場合は、standard opcodeを代わりに使用する必要があります。
special opcodeを解釈する際、addressレジスタの増加量は以下のようになります。
アドレス増加量 = ((オペコード - opcode_base) / line_range) × minimum_instruction_length
lineレジスタの増加量は以下のようになります。
ライン増加量 = line_base + ((オペコード - opcode_base) % line_range)
例えばline_baseが-1で、line_rangeが4の場合、0から59までの範囲の命令アドレスの増加を、-1から2までの範囲の行番号の増減を、special opcodesの1バイトで表現できます。(それを越える範囲を表現する際には、後述するstandard opcodeを使用する必要があります。)
なかなか複雑ですが、special opcodesの動作効率はターゲットマシンの性質に強く依存するので、バイトコードの動作は固定ではなく、ヘッダによってカスタマイズが可能な仕様になっています。これにより、より効率的にデバッグ情報をエンコードすることができます。(そもそも少しでも容量を削減するためにこのようなバイトコードによって情報を保持しているのですから、効率は最優先となります。)
standard opcodeは12個存在します。
- DW_LNS_copy
現在の状態機械のレジスタ値を基に、表に1行を追加した後、basic_block、prologue_end、epilogue_beginレジスタをfalseにセットします。
- DW_LNS_advance_pc
符号無しLEB128オペランドを1つ取り、minimum_instruction_length倍した後addressレジスタに加算します。
- DW_LNS_advance_line
符号無しLEB128オペランドを1つ取り、lineレジスタにセットします。
- DW_LNS_set_file
符号無しLEB128オペランドを1つ取り、fileレジスタにセットします。
- DW_LNS_set_column
符号無しLEB128オペランドを1つ取り、columnレジスタにセットします。
- DW_LNS_negate_stmt
is_stmtレジスタの値を論理否定し、再びセットします。
- DW_LNS_set_basic_block
basic_blockレジスタにtrueをセットします。
- DW_LNS_const_add_pc
special opcode 255に対応するアドレスの加算値をminimum_instruction_lengthフィールド倍した結果をaddressレジスタに加算します。
special opcode 255の値までのaddress加算は1バイトで、その値の3倍までの範囲のaddress加算がこのオペコード+special opcodeの2バイトで可能です。それ以上のケースでのみ、3バイト以上のDW_LNS_advance_pcが必要となります。
- DW_LNS_fixed_advance_pc
uhalfオペランドを1つ取り、addressレジスタに加算します。
これは唯一LEB128エンコードではないオペランドを取るstandard opcodeです。(minimum_instruction_lengthフィールドによる乗算も行われません。)
このオペコードは、LEB128エンコード自体を未サポートだったり、special opcodeがオーバーフローした場合はDW_LNS_advance_pcを使うなどの複雑な判定に未対応なアセンブラでも、(圧縮を犠牲にすれば)使用することができます。
- DW_LNS_set_prologue_end
prologue_endレジスタにtrueをセットします。
ブレークポイントが関数エントリにセットされた場合、通常は関数の本当のエントリポイントではなく、関数フレームの作成や局所宣言の処理などが完了した直後の、最初の行に対応するprologue endポイントで実行を停止して欲しいはずです。一般にデバッガは、どこがそのようなポイントなのかを知らないので、このコマンドで設定する必要があります。
最適化されたコードの場合は、そのようなprologue endが複数になる場合があります。例えば、特定の条件を満たす場合は即座に関数を抜けるような関数が最適化された場合、関数フレームを作成する前に条件チェックが行われるようなコードが生成される場合などがあるからです。
また、prologue endは行番号情報単独で決定できるわけではなく、(インライン化されたものも含む)サブルーチン情報を合わせて決定されます。
- DW_LNS_set_epilogue_begin
ブレークポイントが関数の終わりにセットされた場合、通常は関数の本当に最後の命令が実行された直後ではなく、最後の行が実行された直後で、まだローカル変数などが生きているepilogue beginポイントで実行を停止して欲しいはずです。一般にデバッガは、どこがそのようなポイントなのかを知らないので、このコマンドで設定する必要があります。
epilogue beginもまた行番号情報単独では決定できず、サブルーチン情報と合わせて決定されます。
トリビアルな関数の場合、prologue_endとepilogue beginは同じアドレスになる場合があります。
- DW_LNS_set_isa
符号無しLEB128オペランドを1つ取り、isaレジスタにセットします。
extended opcodeは3つです。
- DW_LNE_end_sequence
end_sequenceレジスタにtrueをセットし、現在の状態機械の状態に基づき、表に1行を追加します。その後、全てのレジスタを初期値に設定します。全ての行番号プログラムはこのオペコードで終了しなければなりません。そのとき最後の命令+1(バイト)のアドレスの行が追加されます。
- DW_LNE_set_address
ターゲットマシンのアドレス幅と同じサイズのオペランドを再配置可能なアドレスとして取り、addressレジスタにセットします。
唯一このオペコードのみが、アドレスの差分ではなく、再配置可能アドレスそのものをレジスタにストアします。
- DW_LNE_define_file
2回目に説明したファイルエントリをそのまま引数に取り、新しいファイルエントリを動的に定義します。
これで行番号プログラムの仕様は終了です。