おしながき

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_locセクションの構造(LocationLists)

.debug_locセクション(Location Lists)って何?

Location Listsは、前のページで触れましたが、Location Descriptionの2つの表現方法のうちのもう1つで、「プログラムの開始から終了までの期間(ライフタイム)で、格納場所がころころ変わったり、プログラムの開始から終了の間の一時期しか存在しないやつ、の格納場所を表現する方法」みたいです。
んで、この情報は、前ページのLocation expressionsとは異なり、専用のELFセクション、.debug_locセクションに格納される仕様になってます。(セクション持ちってことで、凄いヤツなのかしらといったところです)
でも、独立したセクション持ってるってことは。。。また解析が必要っすね。ということで、以下またメモです。


.debug_locセクションのデータ構造

.debug_locセクションは、データの形式的には、以下のデータ構造がひたすら連続してつまってます。

<開始オフセットアドレス> <終了オフセットアドレス> <Location expression>

ただし、その意味の解釈の仕方で、以下の3パターンがあり、それぞれで上記3つのデータ項目の意味が違ったり、値が固定だったり、はたまたデータ項目がなかったり、します。
つーことで、まず以下に3種類のデータ解釈方法です。(それぞれの意味は以下それぞれの節みてね)

No. 解釈パターンの名前(エントリ名) エントリの概要 <開始オフセットアドレス> <終了オフセットアドレス> <Location Expression>
1 Location List Entry (LLE) 生存範囲が限定されている格納場所の情報示すオブジェクト(値とか)の生存範囲とその範囲内での格納場所を示すレコード。 生存範囲の
開始オフセット
生存範囲の
終了オフセット
指定された範囲での
格納場所を示す
Location Expression
2 Base Address
Selection Entry (BASE)
上記No.1 Location List Entryでの開始/終了オフセットのベースアドレスを指定するレコード 0xffffffffffffffff (64bit)
0xffffffff (32bit)
ベースアドレス なし
(このデータは持たない)
3 End of List Entry (ELE) 表現対象のオブジェクトに対する全てのNo.1 Location List Entryが終ったことを示すレコード 0x00 0x00 なし
(このデータは持たない)


んで、これらのパターンの使われ方としては、例えば

  • あるデータAとBがあったとする
  • データAは
    • プログラムカウンタがアドレス1からアドレス3までの範囲にあるとき、場所xに格納されている
    • プログラムカウンタがアドレス5からアドレス7までの範囲にあるとき、場所yに格納されている
  • データBは、
    • (仮定だが、何らかの理由によって)アドレス2000からアドレス2010までの範囲にあるとき、場所zに格納されている

みたいなケースがあったとき、

<LLE(1,3,x)> <LLE(5,7,y)> <ELE(0,0,-)> <BASE(0xffffffff,2000,-)> <LLE(0,10,z)> <ELE(0,0,-)>
※LLEはパターン1 Location List Entryのこと、 BASEはパターン2 Base Address Selection Entryのこと、 ELEはパターン3 End of List Entryのこと。(カッコ)内は、それぞれ(開始オフセット、終了オフセット、Location Expression)を示す

の様に表現されるみたいです。
あと、".debug_locセクション"内には、例えば上の例ではじめの2つのLLEが「データA」のことである、という情報は持っていません。これは.debug_infoで持っていて、".debug_locセクション"は".debug_infoセクション"の該当するデータを指し示すDIEから、指さされる、といった具合です。


Location List Entryのせつめい

(上の例である意味説明できているのでいまさら感ありですが。。。)
このエントリでは、あるデータ(オブジェクト)が、プログラムコードアドレスの場所(範囲)によって、メモリにある、レジスタにある、などの場合、「どのプログラムコードアドレスの範囲を実行中は、対象のデータはどこに格納されているか」を示すレコードです。
当然、あるデータがプログラムコードアドレスの範囲3箇所で、それぞれ別の場所に格納される、という場合は、このレコードは3つできます。そして、通常アドレス範囲が若い方から格納されます。
ということで、まず引数です。

引数名 サイズ 形式 せつめい
生存範囲の開始オフセット 実行マシンのアドレスサイズ
(32bit=4byte, 64bit=8Byte)
アドレス値 対象とするデータの、引数3のLocation Expressionの場所に格納されている期間の、開始アドレスオフセットです。
開始アドレスは、プログラムコードアドレスのオフセットで、これは通常はこのデータのCompailation Unit、よーは定義されているソースファイルのプログラムコード開始アドレスからの差分値です。
(但し、このレコードから、レコードを前に遡っていき、ELEが登場するまでの間にBASE(Base Address Selection Entry)が登場しない場合。BASEがある場合は、BASEで指定されたアドレスからのオフセット値になりますとーぜん)
生存範囲の終了オフセット 実行マシンのアドレスサイズ
(32bit=4byte, 64bit=8Byte)
アドレス値 対象とするデータの、引数3のLocation Expressionの場所に格納されている期間の、終了アドレスオフセットです。
終了アドレスオフセットは、プログラムコードアドレスにおける引数3のLocation Expressionで場所表現できる範囲の最後のアドレスになりますです。
この値はオフセットですが、そのベースとするアドレスの考え方は、生存範囲の開始オフセット、と同じ考え方です。
なお、「開始オフセット」=「終了オフセット」の場合、そのLLEは意味を持たないもの、となります。(範囲がなくなっちゃうので、あるいみとーぜんです)
Location Expression 2Byte(Length) + Length ushortの長さ
+指定された長さのバイナリ
「開始オフセット」〜「終了オフセット」で指定された範囲内における、対象データの格納場所を示すLocation Expressionです。
Location Expression の解析方法は、前ページを見てね。
データですが、まず最初の2Byteはunsigned shortのサイズ(長さ)値です。このサイズ分のデータが、この2Byteの後に続きます。
このデータは、Location Expression、つまりDWARF expression形式で表現された格納場所です。


Base Address Selection Entryのせつめい

このエントリは、前のLocation List Entryの引数である、開始アドレスオフセット、終了アドレスオフセットから実際の開始アドレス、終了アドレスを計算する際の、ベースアドレスを.debug_infoセクションのCompilation Unit(ソースファイル)の開始アドレス、じゃない値とする場合に 使われるエントリです。
要は、強制的に開始/終了アドレスオフセットのベースアドレスを書き換えます。
役割自体は、これだけなんですが、以下数点、注意っす。

  • (そのプログラムの)全てのマシンコードが1つの、連続したセクションに配置されるCompilatin Unit(ソースファイル)の場合、このセクションは不要。 (=通常、登場しない)
  • (このエントリを使うことによって) あるデータの生存期間が重複してしまうことがあり、この時、あるデータの生存期間の範囲として指定された、全ての範囲を合算した際、穴のあく範囲が現れることがある
    • この穴となった範囲では、対象とするデータは存在しない、と考える必要があります。
  • このエントリで指定したベースアドレスの有効範囲は、このエントリ発行した時点、から、最初のELEまで、です。


では、このエントリの引数です。

引数名 サイズ 形式 せつめい
開始アドレスオフセット 4Byte(32bit)
8Byte(64bit)
マシンのアドレスサイズ依存
固定値(参照→) この引数は、BASEであることの「区別」のみに使われます。
区別の仕方は、この値が0xffffffff (32bitアドレス時)、0xffffffffffffffff (64bitアドレス時)ならBASEです。
(なお、0x00ならELE、それ以外ならLLEです)
終了アドレスオフセット 4Byte(32bit)
8Byte(64bit)
マシンアドレスサイズ依存
アドレス値 この引数に、書き換えるベースアドレス値が格納されています

※Location Expression(引数3)は、ありません。(すぐに次のエントリにいきます)


End of List Entryのせつめい

格納場所を示すために必要なLLEを全部書き切ったら、最後にSTOP!の意味としてこの"End of List Entry"が必要です。
引数は、以下です。

引数名 サイズ 形式 せつめい
開始アドレスオフセット 4Byte(32bit)
8Byte(64bit)
マシンのアドレスサイズ依存
固定値(参照→) 0x00固定です。
また、この値が0x00ならELE、と判断するのにも使います
終了アドレスオフセット 4Byte(32bit)
8Byte(64bit)
マシンアドレスサイズ依存
固定値(参照→) 0x00固定です。

※Location Expression(引数3)は、ありません。(すぐに次のエントリにいきます)

この命令のあと、次なる表現対象のデータがあれば、そのデータのLLEかBASEが続きます。この命令のデータバイトが.debug_locセクションの最後のバイトであれば、それでこのセクションのデータは終りです。
※なお、BASE/ELEは、".debug_rangeセクション"の「Base Address Section Entry」「End of List Entry」と等しくなるそうです。とあるが、これはなにいーたいのか、分からん!とりあえず、無視して可


実機での解析例

ということで、実機のデータを解析して、上記の通りか、みてみました。
まず、イケニエになったソースコードです。

#include<stdio.h>

int             a;

void func2( int *b )    {
        *b = 3;

        return;
}

int func1( int a, int *b)       {
        int             c;

        func2( b );
        c = a + *b;

        return c;       
}

int main( int argc, char *argv[])       {
        int             b;
        int             c;

        a = 1;
        c = func1( a, &b );
        printf("a=%d, b=%d, c=%d\n", a, b, c);

        return;
}

ちょーてきとう、ですが、許してね。
で、次に、コンパイル環境。
prompt # uname -a
FreeBSD xxxx.koinec.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #0

prompt # gcc -v
Using built-in specs.
Target: amd64-undermydesk-freebsd
Configured with: FreeBSD/amd64 system compiler
Thread model: posix
gcc version 4.2.1 20070831 patched [FreeBSD]

さて、この環境で、上記ソースをコンパイルして、その中から、.debug_locセクションのバイナリをひっぱがして、解析してみた例が以下です。
見方ですが、"#"の行は、コメントです。

 # Hex Dump by .debug_loc Section

00000000  00 00 00 00 00 00 00 00  : <開始オフセット> = 0x00000000000000000
00000008  01 00 00 00 00 00 00 00  : <終了オフセット> = 0x00000000000000001
00000010  02 00                    : <length> = 0x0002 = 2 Byte
00000012  77 08                    : Location Expression = 0x77 0x08
 # まず、開始オフセットは0ですが、終了オフセットが1なので、
 # このエントリは Location List Entry(LLE) と判断できます。
 # んで、次の2Byte Lengthは2です。
 # ので、続く2byteがこのアドレス範囲のLocation Expression と分かりますね。
 #
 # で、その2Byteは、0x77 0x08です。
 # 最初の0x77は、前の前のページを見てみると DW_OP_breg7 と分かります。
 # で、この命令は引数1として、sLEB128のoffsetを取ります。それが0x08です。
 # 0x08は、sLEB128表現においてもそのまま0x08=8です。
 # ので、このアドレス範囲における、このオブジェクトの格納場所は、
 # "reg7+8"のアドレスの場所に入っているよ、ってことになります。
 # (DWARF expression上ではこの値をDWARFスタックにpushしますが、それでDWARF expressionが
 #  終りなんで、あくまでこの場所に入っている値が、せいかい)
 #
 # で、一つ。開始オフセットって、0x00です。が、これプログラムコードのアドレスです。
 # ので、0x00からってあり得ないですよね。
 # はい。これは上に書いた様に、Base Address Selection Entry(BASE)がこの前にないので、
 # .debug_infoのCompilation Unitのベースアドレスからの相対になります。
 # では、.debug_infoのCompilation Unitを見てみると、
 #    <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
 #     <54>     DW_AT_low_pc      : 0x4005b0
 # とあるので、0x4005b0がベースアドレスです。
 # よって、実際のコードアドレス上では、
 #   開始アドレス = 0x4005b0 + 0x00(開始オフセット) = 0x4005b0
 #   終了アドレス = 0x4005b0 + 0x01(終了オフセット) = 0x4005b1
 # となります。
 #
 # これは下に示す、readelf君の解析結果と一致するです。(よって、せーかい!)


00000014  01 00 00 00 00 00 00 00  : <開始オフセット> = 0x0000000000000001
0000001c  04 00 00 00 00 00 00 00  : <終了オフセット> = 0x0000000000000004
00000024  02 00                    : <lenght> = 0x0002 = 2 Byte
00000026  77 10                    : Location Expression = 0x77 0x10
 # これも、上と同じ様に読むと、
 #   0x4005b1 - 0x4005b4 の時のこのオブジェクトは reg7+16 の場所にいるよ
 # って読めますね。そして、これもreadelf君の見解と一致しています。

00000028  04 00 00 00 00 00 00 00
00000030  14 00 00 00 00 00 00 00
00000038  02 00
0000003a  76 10
 # これも、あっていますよね。

0000003c  00 00 00 00 00 00 00 00  : <開始オフセット> = 0x0000000000000000
00000044  00 00 00 00 00 00 00 00  : <終了オフセット> = 0x0000000000000000
 # んで、次です。このエントリですが、開始オフセットと終了オフセットがどちらも0です。
 # ので、これはEnd of List Entry(ELE)と判別がつきます。
 # つまり、このオブジェクトのLocation Listの情報は終っちゃったってことです。


 # で、いか2つめのLocation List です。説明かつあいですが、ざっと見ただけで大丈夫っぽいです。
0000004c  20 00 00 00 00 00 00 00
00000054  21 00 00 00 00 00 00 00
0000005c  02 00 77 08

00000060  21 00 00 00 00 00 00 00
00000068  24 00 00 00 00 00 00 00
00000070  02 00 77 10

00000074  24 00 00 00 00 00 00 00
0000007c  49 00 00 00 00 00 00 00
00000084  02 00 76 10

00000088  00 00 00 00 00 00 00 00
00000090  00 00 00 00 00 00 00 00

 # そして、3つめです。これも大丈夫っぽいですね。
00000098  50 00 00 00 00 00 00 00
000000a0  51 00 00 00 00 00 00 00
000000a8  02 00 77 08

000000ac  51 00 00 00 00 00 00 00
000000b4  54 00 00 00 00 00 00 00
000000bc  02 00 77 10

000000c0  54 00 00 00 00 00 00 00
000000c8  98 00 00 00 00 00 00 00
000000d0  02 00 76 10

000000d4  00 00 00 00 00 00 00 00
000000dc  00 00 00 00 00 00 00 00


ということで、無事解析できました。

が、とは言えこれほんとーにあってんの、って疑問だったので、readelf君に-wなオプションを注文して、.debug_locセクションの結果を見てみました。それがこれです。

Contents of the .debug_loc section:

    Offset   Begin    End      Expression
    00000000 004005b0 004005b1 (DW_OP_breg7: 8)
    00000000 004005b1 004005b4 (DW_OP_breg7: 16)
    00000000 004005b4 004005c4 (DW_OP_breg6: 16)
    00000000 <End of list>
    0000004c 004005d0 004005d1 (DW_OP_breg7: 8)
    0000004c 004005d1 004005d4 (DW_OP_breg7: 16)
    0000004c 004005d4 004005f9 (DW_OP_breg6: 16)
    0000004c <End of list>
    00000098 00400600 00400601 (DW_OP_breg7: 8)
    00000098 00400601 00400604 (DW_OP_breg7: 16)
    00000098 00400604 00400648 (DW_OP_breg6: 16)
    00000098 <End of list>

ということで、突き合わせて見てみると、「大丈夫!」って結果です。


もうちょっと、実機で解析

ところで、上記の解析例で、どのオブジェクトか知らんけど、原文通りに.debug_locが格納されているよってことは、分かりました。
が、これなんのオブジェクトなんやろ、って思っちゃった訳です。そこで、.debug_infoをちょっとだけreadelf君で調べてみました。以下、.debug_infoの一部抜粋ですが、ます見てみましょう。

The section .debug_info contains:

  Compilation Unit @ offset 0x0:
   Length:        509
   Version:       2
   Abbrev Offset: 0
   Pointer Size:  8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
  <34>     DW_AT_name        : main.c
  <54>     DW_AT_low_pc      : 0x4005b0
  <5c>     DW_AT_high_pc     : 0x400648
 <1><68>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><77>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><88>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><95>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><ab>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><b2>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><c2>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><ce>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><e3>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><ed>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><f6>: Abbrev Number: 3 (DW_TAG_base_type)
 <1><f9>: Abbrev Number: 2 (DW_TAG_base_type)
 <1><101>: Abbrev Number: 4 (DW_TAG_pointer_type)
 <1><107>: Abbrev Number: 5 (DW_TAG_subprogram)
  <109>     DW_AT_name        : func2
  <112>     DW_AT_low_pc      : 0x4005b0
  <11a>     DW_AT_high_pc     : 0x4005c4
* <122>     DW_AT_frame_base  : 0   (location list)
 <2><12a>: Abbrev Number: 6 (DW_TAG_formal_parameter)
 <1><137>: Abbrev Number: 4 (DW_TAG_pointer_type)
 <1><13d>: Abbrev Number: 7 (DW_TAG_subprogram)
  <13f>     DW_AT_name        : func1
  <14c>     DW_AT_low_pc      : 0x4005d0
  <154>     DW_AT_high_pc     : 0x4005f9
* <15c>     DW_AT_frame_base  : 0x4c    (location list)
 <2><164>: Abbrev Number: 6 (DW_TAG_formal_parameter)
 <2><170>: Abbrev Number: 6 (DW_TAG_formal_parameter)
 <2><17c>: Abbrev Number: 8 (DW_TAG_variable)
 <1><189>: Abbrev Number: 7 (DW_TAG_subprogram)
  <18b>     DW_AT_name        : main
  <197>     DW_AT_low_pc      : 0x400600
  <19f>     DW_AT_high_pc     : 0x400648
* <1a7>     DW_AT_frame_base  : 0x98    (location list)
 <2><1af>: Abbrev Number: 6 (DW_TAG_formal_parameter)
 <2><1be>: Abbrev Number: 6 (DW_TAG_formal_parameter)
 <2><1cd>: Abbrev Number: 8 (DW_TAG_variable)
 <2><1d9>: Abbrev Number: 8 (DW_TAG_variable)
 <1><1e6>: Abbrev Number: 4 (DW_TAG_pointer_type)
 <1><1ec>: Abbrev Number: 9 (DW_TAG_variable)


冒頭、"*"の行には、いずれも"(location list)"って書いていますので、これが.debug_locへのリンク部分です。
そして、それぞれの所を見ると、いずれもソースコードの関数のデバッグ情報のところで、さらに属性はDW_AT_frame_baseですね。これはまだ未調査ですが、frame_baseというくらいだから、どうやら関数のFrame Baseの場所を書いているっぽいですね。
ところで、順番前後するので、実はこのサンプル、あえて.debug_frameセクションのものを使いました。(実はたまたま選んだらラッキーだったってことは内緒で)
.debug_frameセクションの構造:INTEL64(amd64)での例の解析1を見て欲しいのですが、どうやら、これ.debug_frameセクションでのCFAと同じですね。。。
CFAは、関数の入口付近の命令で、格納場所が変わって行きます。で、どーもこの.debug_locのオブジェクトと、.debug_frameのCFAは、同じ場所を指しているっぽいです。
なぜ、どちらにも同じ値を持っているのか、分かっていませんが、いずれにせよこれではっきりしたのは、今回の解析結果は「大丈夫そう」ってことでした。

あ、言い忘れた。*の行のDW_AT_frame_baseの後の0x00、0x4c、0x98は、.debug_locの先頭からのバイト位置です。上のバイナリダンプ解析結果のバイナリ位置表示と突き合わせても、一致してますです。

(2013/06/03 なんとか、終了)


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