おしながき

ELFファイルフォーマット

  • .eh_frameセクションの構造と読み方

DWARFファイルフォーマット

NCURSESライブラリ

  • NCURSES Programing HOWTO ワタクシ的ほんやく
    1. Tools and Widget Libraries
    2. Just For Fun !!!
    3. References
  • その他、自分メモ
  • NCURSES雑多な自分メモ01


最近の更新 (Recent Changes)

2019-09-24
2013-10-10
2013-10-03
2013-10-01
2013-09-29
目次に戻る:DWARFファイルフォーマット

.debug_frameセクションの構造

最初に!

えと、.debug_frameな話は、まだ規格書(DWARF V3)を読んだだけで、実機での確認してません! よーは、えーかげんってことです!
.debug_lineもまだ怪しい多数ですが、それ以上に信じないでね。(2013/4/22現在)
間違い指摘は歓迎っす!(どのみち、このあとプログラム書いてバグではまるだけなので。。。)


.debug_frameセクションって何に使うの?

.debug_frameセクションには、Call Frameの情報が格納されている。
Call Frameって? えと、C言語(というか、多分ほかの言語のほとんども)では関数呼び出した時、スタックに戻り先のアドレスやら自動変数やらをつっこむわけですが、このときのスタックの構造って、通常どう突っ込むか、決まってます。
んで、呼び出すたびに毎回同じつっこみ方の構造をスタックに作るので、このつっこみ方の構造=Call Frameなのです。

よーは、このセクションを解読すると、多分以下のことが分かります。

  • スタックもしくはレジスタのどこに、今実行中の関数が戻る先のアドレスが入っているのか?

これ分かることで、今実行中の関数、はどいつが、どっから呼ばれたが分かるのです!これはデバッガでは重要ですね。

ということで、約1年版振りに本業が落ち着いたので、解読再開!


.debug_frame内のデータ格納方法の考え方

えと、これ難しいっす! (ワタクシの英語力が足りなさすぎるというツッコミもありですがね。。。)

ヘッダとはうんちゃら構造の説明の前に、まずデータの持ち方の考え方から。これが面倒

アドレス CFA Reg0 Reg1 ... RegX
addr 0 CFA 0 Reg0値0 Reg1値1 ... RegX 値1
addr 1 CFA 1 Reg0値1 Reg1値2 ... RegX 値2
addr 2 CFA 2 Reg0値2 Reg1値3 ... RegX 値3
addr 3 CFA 3 Reg0値3 Reg1値4 ... RegX 値 4

まず、仮想的にこーいう表があるとします。(これを Call Frame Information: CFI と呼びますらしいです)。
んで、1行はアセンブラの命令1つ、の単位。列のそれぞれの意味は

  • アドレス: 関数の頭からの命令の相対アドレスです。関数の冒頭アドレスはヘッダにあり。
  • CFA: Call Frame Addressの略です。これは、正確には名の通り、CallFrameの開始アドレスを示すための「計算ルール」。

例えば、仮想のReg7+0のアドレスがCFAなら、"Reg7+0"が書き込まれる。この計算ルールの表現方法は後述です。(2013/05/05 誤り訂正)

  • Reg 0/1/。。。/X: これが面倒。まず、DWARFの.debug_frameでは、実態のCPUとは別に、仮想的に(勝手に)レジスタを作ることができます。作り方は後述だけど。

んで、この勝手に作ったレジスタは、CPUのホンモノレジスタとおんなじモノと割り当てる、とか、の設定ができます。
で、最後に表の行、すなわち、命令実行していくなかでそれぞれのレジスタがどう変化していくか、値はどこにあるのか、を命令毎に一行づつ、示して行くようになっています。

この表の例は、まぁDWARF V3のappendix D.6の例を見た方が分かりやすいので、こんなん知りたいってヒトはそっち見てくださいです。


(少し脱線) DWARF V3のappendix D.6の解釈

あ、ちなみにこの例のアセンブラ(motorola 88000)の命令文体系、ちとヘンです。以下オトシアナ(規格を和訳)

  • 典型的に、命令の形式は” <データ格納先reg.>, <データ元reg>, <定数>”
  • load/storeの命令のアドレスは、データ元regと定数を足した値によって計算されます。

ってあります。これ、このこのまま読むと

store R1, R7, 8

の例なら、R1にR7+8の値を突っ込む、intel形式なら、 ”mov r1, dword ptr r7+8”になる、と誰もが読むはずですが。。。。ブッブー!
正しくは、上の例は、R7+8のアドレスに、R1を突っ込む、すなわちintel君語なら”mov dword ptr R7+8, R1”になります!
でも、loadは、”load R1, R7, 8”の場合、intel語では”mov r1, dword ptr R7+8”になります。
うーん、僕がintel語に毒されているのか。。。m88000がヘンなのか?
識者のみなさま、RISCってこんなもんですかね?使ったことないんで分からんのですが。。。。

※どっちにしても、いまどき例をm88000で書いていることがうーんだったります。


CFI内でのCFA、Regの表現ルール

CFI内でのCFA、Reg(仮想レジスタ)の表現方法は、.debug_line同様、またまたバイト/ビットの世界でのエンコードで表現されちゃいます。
これらの、命令と具体的な意味は後述ですが、ここではその説明の際の「ルール」なるものを書きます。
これ、頭っから読んでくれている方は、この段階では「ふーん」だけにしときましょう。考えればこんがらがること、必定です!
以下8種類あります。

  • 1: undefined
    • レジスタに対してこれが指定された場合は、前のCallFrame、すなわち、呼び出し元の親関数におけるこのレジスタの値は、引き継がれないことものである、との宣言になります。
    • ようは、このレジスタの値はこの関数でブッ壊しても、元に戻さないレジスタですよ、ってことです。
  • 2: same value
    • 前のCallFrame、すなわち呼び出し元 親関数から引き継がれた事前の値は、この関数の実行によって破壊されない、ことの宣言っす。
    • よーは、仮にこの関数中でレジスタ値ブッ壊す場合は、stackに疎開させて、ちゃんと返しますよってことです。
  • 3: offset(N)
    • 指定されたレジスタの前の値は、その時点のCFA+Nのアドレスに保存しますた、ってことです。
    • Nは、符号付きのオフセット、すなわちCFAからの相対アドレスですね。
    • 通常、これはstackにレジスタの値突っ込みました、って時に使います。
  • 4: val_offset(N)
    • 指定されたレジスタの前の値は、CFA+Nのアドレスの場所にあります。ってことを示します。
    • Nは、符号付きのオフセット、すなわちCFAからの相対アドレスです。
  • 5: register(R)
    • 指定されたレジスタの前の値は、レジスタ番号Rで指定されるレジスタの中にコピられました、ってことを示します。
  • 6: expression(E)
    • 指定されたレジスタの前の値は、DWARF数式Eで計算されるアドレスの中にあります、ってことを示します。
    • DWARF数式表現E は別途(まだ未読 2013/5/5時点)
  • 7: val_expression(E)
    • 指定されたレジスタの前の値は、DWARF数式表現Eで計算された値になります、ってことを示します。
  • 8: architectural
    • コンパイラ拡張による情報 (のため、それ以上の記述なし)


.debug_frameセクションのデータ表現

前置きはこん位として、実際の.debug_frameのデータ構造です。これは、以下の2つのデータ構造が、ひたすら並んでる、だけです。(と思われます。 2013/5/5現在)

  • CIE (Common Information Entry) : 上の表での「列」を定義するデータ構造
  • FDE (Frame Description entry) : 上の表での「行」を作り、もしくは前の行のあるデータ(reg x やCFA)の変更を行う

(2013/5/8 以下大嘘(誤解)が判明。取消です)
ここで意識しておきたいのは、以下の点。(C言語の場合) すなわち、CIE1つとそれにヒモ付くFDE複数個、のセットで1つの関数を表すってこと。
1つの関数=1つのCIE、という形になる。(これはとーぜんで、関数1つを呼び出す際のABIのパターンは当然1つだから。また、関数(の引数などの条件)によってアセンブラレベルの呼び出し方、すなわちCFAの持ち方が変わるのは当然だから)

  • 関数内の命令ステップ数分、FDEがある。 (ただし、CFAに全く関係がないところはないのかもしれない。これが要調査) ←これだけは、たぶん正解(だと思う)

いずれにしても、CIE、FDEのデータの持ち方、はx86-64でちゃんと調べる必要があり、つもりです。

そして、データの格納構造としては、以下の様な感じでひたすら、連続してつめこまれています。(ただし、アドレスのバウンダリは意識され、余った分には0がパディング?)

CIE1, FDE1-1, FDE1-2, ... FDE1-x, CIE2, FDE2-1, FDE2-2, ..., (CIE1つとFDE x個の塊が続く...)
XXX: CIEとFDEの.debug_frame内でのデータ格納方法、順番は要確認です。(2013/5/7 誤り訂正)

なお、CIE/FDEのヘッダは、どちらも最初の4バイトはその後のその構造のデータサイズ、次の8バイト(64bitの場合、32bitなら4byte)の値までは構造が同じになっていて、2つめの項目の値が-1かどうかで、CIEかFDEかを見分けます。


ということで、この後はCIE/FDEを見て行きますです。

目次に戻る:DWARFファイルフォーマット