おしながき

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ファイルフォーマット

TAG詳細(その10) 構造体、union、クラス編

ということで、今度はタイトルの通り、構造体(srtuct)、union、classです。
こいつらは、いろいろとあるので、ちと長くなりそーです。

いちおう、基礎(つか、原文の翻訳)

  • C言語、C++、Pascalやそのたいろんな言語では、関係するいくつかの型を集めた型をプログラマが定義できるよーになってます。
    • これを、C言語、C++なら”構造体(struct)”といい、所属するそれぞれの型を”メンバ”っていいます。
    • Pascalなら”レコード”といい、所属する型を”フィールド”っていいます。
  • これら、所属する型は、独自のメモリ上の場所をに存在しています。
    • 但し、C言語、C++の”union(共用体)”なるものだけは、同じメモリを共有しますです。
  • Pascalや他の言語では”識別共有?(discriminated unionな勝手訳)”とか”可変レコード(variant record)”なるもんがあるです。
    • コイツは、選択し得るサブ構造体(variants)の選択が、それらのサブ構造体の一部(discriminant)のレコードの値に基づくです。(スーパー直訳で、意味分かってないです。。。)
  • C++やJAVAは、構造体に似た”クラス”なるモンがあるです。
    • クラスは、そのクラスや構造体内に限定されるサブルーチンである”メンバ関数”を持つです。
  • C++の構造体はCの構造体の上位互換であって、ちょっとした違いを持っているクラス、とほとんど同等の位置付けです。
    • さらに議論しちゃうと、 C++のクラスの文法はCの構造体とほとんど同じく理解可能です。

※以上、原文前書きの「そのまんま」な訳ですが、ま、変な点はないですね。(ただーし、Pascalはワカラナイので、何とも言えない。。。)


構造体、union、クラスのDIE

まず、こいつら3つ自体は、以下表のTAGで表現されます。

No. TAG名 対象
1 DW_TAG_structure_type 構造体
2 DW_TAG_union_type union(共用体)
3 DW_TAG_class_type クラス


そして、これらのメンバは、上記表のTAGを持つDIEの「子DIE」として表現され、この子DIEの登場順番は、ソース上の並びと同じ順です。

んで、次に上の3つのTAGが持ち得るAttributeですね。

No. Attribute名 せつめいでっせ
1 DW_AT_name (ソース上で構造体、union、クラスに名前が与えられている場合)
構造体、union、クラスの名前(NULL終り文字列)
2 DW_AT_byte_size 構造体、union、クラスのバイト単位のサイズ。
なお、もしこれらのサイズがコンパイル時に固定的に決めることができる場合、バイト数を「整数」で持ってます。
逆に、コンパイル時に決めれない時には、DWARF expressionや他のDIEへの参照など、動的にサイズを決めるためのものを持ってます
3 DW_AT_declaration 以下参照でーす
4 DW_AT_specification これは、以下を参照です。
5 DW_AT_start_scope この構造体、union、クラスが有効になるのが、DW_TAG_base_typeのスコープとなるTAG(一般に、親TAG)のDW_AT_low_pcからでなく、途中から始まる場合、有効となる最初のコードアドレスです。
これは「TAG詳細(その06) 変数/関数の引数/定数編」を参照です。


(No.3) DW_AT_declaration & (No.4) DW_AT_specification

これですが、ひとことでいうと「2つの構造体やクラスに跨って分割したメンバ定義をやらかしているケース」の場合の対応です。と、いってもなんやねん、ということになっちゃうので、まず以下サンプルソースです。

// test.cpp
#include<stdio.h>

struct  B       {
        struct  A;
};

struct  B::A { 
        int temp;
};

int main(int argc, char *argv[])
{
        struct  B::A    ba;

        ba.temp = 5;
        printf("%d\n", ba.temp );
        return 0x00;
}
ちょーえーかげんな例ですが、見ての通り、

  • 構造体Bでは、そのメンバとして構造体Aを定義している。(これは普通)
  • その次で、ん?構造体B::A を定義!
  • 構造体Aの定義は、ない!

というケースです。
これ、ワタクシは知らなかった。。。のですが、「C++のソース(.cpp)なら、これコンパイル通ります!!」
(ちなみに、g++はOKですが、gccに食わせたり、test.cなファイル名にすると即死です。また、このソースに”struct A”な構造体A単体の宣言を加えてもNGです。念のため)

よーするに、C++言語では、上記の例の様に「B::A」という名前での宣言が、可能ってことです。

(※これよくよく考えてみると、ま、構造体でさすがに常習的にこんな書き方はせんと思いますが、もしこれがclass定義なら、継承やらでしょっちゅうありそーな感じですね。なので、C++では案外ふつーなのかもと思いました)


ただ、このケース、ふつーの書き方と比較した場合、ひとつだけ問題がありまして「構造体Aの宣言がなされていません!」
で、今回対象とする「DW_AT_declaration」「DW_AT_specification」は、こんなケースの対策用Attributeってことになるのです。ということで、これらの使われ方です。(上記サンプルの例にそって書きます)

  • DW_AT_declaration
    • これは、ちゃんと単体で定義されていない「構造体A」用のAttributeです。
    • 構造体Aは、少なくとも、構造体Bの中だけでは、構造体Aとして単体でメンバ扱いされちゃってます。
      • ので、まずDWARF的には「構造体A」は存在することになります。(構造体Bの子DIEとして)
      • この構造体Bの子DIEである、「構造体A」のDIEは、Aという名前を持っているので、「DW_AT_name」は持っています。
    • ただし、構造体A単体での宣言、さらにはそのメンバの宣言もなされていないので、構造体A単体でのサイズはありません。
      • ので、この子DIEである構造体AのDIEには「DW_AT_byte_size」は持ちません。
      • その代わりに、「DW_AT_declaration」を持っていて、「ボク宣言されてないよぉ〜」って泣くわけです。(こいつはflagで、trueになるです)
    • 当然ながら、構造体Aの定義は、ソースのどこにもない(あったら、コンパイラに御説教される)ので、構造体Bの子DIE(メンバ)として以外の、構造体AのDIEはありません。
  • DW_AT_specification
    • これは、「構造体B::A」用のAttributeです。
    • 構造体B::Aとしては、上記のtemp.cppなCompilation Unit(オブジェクトファイル)内で、立派に宣言され、存在しています。
      • よって、構造体B::Aとして「DW_TAG_structure_type」なTAGを持って、所帯を構えています!
      • int型のtemp君をメンバにこさえています。ので、temp君を子DIEとして持っています!
      • もちろん、実体もサイズもありますから「DW_AT_byte_size」は持っています。
      • た、だ、し、名前(DW_AT_name)だけは、実は持っていません。これは。。。
        • まず、デバッガが構造体B::Aのデータ(メンバやB::A自体のポインタなど)にアクセスする場合は、構造体B::Aのインスタンス、つまりここでは変数baが分かれば、十分です。
          • B::Aという構造体名でアクセスことは、ほぼない!
          • 言い替えれば、変数baのメンバがtempであることが分かれば十分。こいつの構造体名が「B::A」であることは知る必要なし!
        • 変数baは、関数mainのTAGであるDW_TAG_subprogramの子DIEとして表現され、そのDW_AT_typeは「構造体B::A」のDIEになってます。
        • これらを総合すると、つまり「構造体B::AのTAGに、DW_AT_nameで「B::A」の名前を持っておくのは、ムダ!」ということになります。
        • よって、DWARFデータの容量削減の一貫として、構造体B::AのDW_AT_nameはわざと削除しています!
          • どーしても、必要なら、構造体Bの定義とDW_AT_specification先が構造体Aであることから、デバッガ側で勝手に考えろ、ってことです。
    • つまり、「構造体B::A」があたかも存在し、子DIEとなるtemp君もいるようにDWARF情報が生成されるけど、この「構造体B::A」はデバッガからすれば名前はいらないので、持たせていない、ということになる

ということで、まぁ複雑ですね。

が、さらに面倒な例があってですね。。。

  • 構造体、union、クラスのメンバの定義が、その構造体、union、クラス宣言の中にない場合
    • つまり、以下のよーなケースです。
      • C++のクラスのメンバ関数: 良くclassの中にはメソッド定義だけして、classなカッコの外に”クラス名::メソッド名”を用いてメソッド関数のロジック定義しますが、これがビンゴのケースです。
      • C言語の構造体内で”static”宣言されたメンバ: こいつはstatic宣言されてしまっているため、どっからでもこのメンバへのアクセスが可能=実体はこの構造体の中にいない、ことになるからです。
        • (ちと特殊、てかこのパターン、一般的には「ソースの書き方がヘン」(考えるよりもソース修正の方が先!))
    • この時、(構造体、union、クラス内で定義されていない)メンバは、「その構造体、union、クラス内で宣言はされているよ」ってことを示すためだけの「構造体、union、クラスの子DIE」として存在します。
      • つまり、class宣言のカッコ内のメソッド定義だけのやつ、ですね。
      • このDIEには、「DW_AT_declaration」を持っていて、「ボク別の場所に本体いるからね〜」って宣言しています。
      • また、このDIEの部分には実体がいないので、「DW_AT_low_pc」など、メモリアドレスを示すなどのAttributeはもっていません。
    • さらに、そのメンバの実際の定義となるDIEも、別のところに存在します。
      • 別の場所で、”クラス名::メソッド名”な名称で書くメソッド関数の中身、です。
      • このDIEは「DW_AT_specification」を持っていて、中身はclassのカッコ内の、対応する「メソッド定義」のDIEへの参照になっています。


なお、最後に、上のソースの例で、仮に「構造体B::A」な宣言ではなく、「構造体B」と「構造体A」がそれぞれ宣言されており、「構造体Bのメンバは構造体A」「構造体Aのメンバはint型変数」の一般的なケースであれば、このDW_AT_specification/DW_AT_declarationは登場せず、単に構造体Bの子DIE(メンバ)が、構造体Aの実体のDIEになり、この実体DIEのDW_AT_typeが「構造体A」の宣言になる、というふつーのDWARFになります。

んで、ここまで構造体、union、クラス結構めんどーでしたが、まだまだありそうです。。。


データメンバ(メンバ関数除く)

次に、構造体/union/クラスの「データメンバ」です。 なお、このセクションの記述は「メンバ関数」は同じくクラスのメンバだけど、一旦除外です。
構造体/union/クラスの「データメンバ」は、構造体/union/クラスのDIEの子DIEとして表現され、「DW_TAG_member」なタグを持っていることになってます
ということで、毎度おなじみこのTAGが持っているAttributeな一覧です。

No. Attribute名 いみ
1 DW_AT_name ソース上でのメンバ名。(NULLエンド文字列)
なお、C++での名前なしunionの場合、このAttributeは存在しないか、あっても0バイトの文字列になるです
2 DW_AT_type データメンバの「型」
3 DW_AT_accessibility データメンバのアクセス記述子。
なお、このAttributeが存在しなかった場合、以下である、ということになっています
「クラス」の場合 → ”private”が指定されたものとみなす
「構造体/union/インタフェース」の場合 → ”public”が指定されたものとみなす
4 DW_AT_mutable データメンバがミュータブル(内容の変更が可能かどうか)のフラグ。trueなら変更可。
よーは、スレッドセーフかどうかってことでしょうか?
5 DW_AT_data_member_location (データメンバの宣言がされている)構造体、クラスの先頭アドレスを起点とした、データメンバの格納アドレス。
ちょっとめんどいので、以下参照
6-1 DW_AT_byte_size (データメンバがビットフィールドの場合)
ビットフィールドのメンバ、および前後のパディングなどを含む、バイト単位のサイズ
なお、DW_AT_typeの値などからサイズが自明な場合は省略されてしまうこともあります。。。
6-2 DW_AT_bit_offset (データメンバがビットフィールドの場合)
もっとも左の(一番大きなビット)の左からビットフィールドが開始するビットまでのビット数
※以下例参照
6-3 DW_AT_bit_size (データメンバがビットフィールドの場合)
対象のデータメンバが使用するビット数。
つまり、上のNo.6-2で示されたビットからこの値を足したビットまでの範囲をそのデータメンバは使うってこと。
※以下例参照


(No.5) DW_AT_data_member_locationの補足

このAttributeの役割は、「(データメンバの宣言がされている)構造体、クラスの先頭アドレスを起点とした、データメンバの格納アドレス。」ですが、表現方法にちょっと以下の癖ありです。

  • この値が「定数」なら
    • データメンバを含む構造体/クラスの先頭アドレス対象のデータメンバの格納アドレスまでのバイト単位の「オフセット」
    • この定数(自体の)サイズは、DWARFの容量削減テクニックによって(最適なサイズに勝手に)縮められることがある。(と、原文読めます。ま、ありがたいのでいいですが)
  • それ以外(「定数」以外)なら
    • 「Location description」になるうえに、以下の注意事項ありです。
      • 「Location description」の評価前に、そのデータメンバを含む構造体/クラスの先頭アドレスを「DWARF expression のスタック」の先頭につっこんでおくこと
        • この作業は、DWARF expressionでの「DW_OP_push_object_address」の操作で、構造体/クラスの先頭アドレスをつっこむのと同じです。
        • (矛盾しますが。。。)このスタックプッシュ、必ず必要ってわけではないです。仮に、Location descriptionの内容がレジスタや特定アドレスを指すだけのケースであれば、不要です。
          (そうではなく、がちゃがちゃ計算するケースではプッシュ要)
      • 評価の結果が、データメンバの格納場所になります

なお、unionは全てのデータメンバが同じ位置から始まりますので、このAttributeはないはずです。 また、仮に構造体Aの中に構造体Bを宣言していて(※但し、AもBもちゃんと個別に宣言されている)、構造体Bの中にあるデータメンバの場合、「構造体の先頭アドレス」は当然構造体Bの先頭アドレスになります。


(No.6-x)ビットフィールドの表現について

ビットフィールドによるデータメンバの場合、上の表の通り「DW_AT_byte_size」「DW_AT_bit_offset」「DW_AT_bit_size」がセットで加わります。
それぞれの意味は上表の通りですが、ちと注意があるのと、原文記載の例を書いておきます

  • ビットフィールドで表現されたデータメンバの格納場所(Location description表記)は、「対象のビットフィールドを含む、無名オブジェクトのアドレス」として計算されます。
    • このアドレスは、(無論)そのビットフィールドを含む、構造体、union、クラスの先頭アドレスからの相対アドレス、つまりオフセットです。
    • この無名オブジェクトのByteサイズは、DW_AT_byte_sizeで示されます。
    • ビットフィールドオブジェクトの先頭ビット(つまり、DW_AT_bit_offsetの値)は、この無名オブジェクトの最高位ビット(一番左)から対象ビットフィールドオブジェクトの最上位ビットまでのオフセットです。(ま、これはそーやね)


んでは、一発例です。
まず、対象のソース(ビットフィールドのC言語定義) ※原文からの拝借です

  struct S {
    int  j: 5;
    int k: 6;
    int m: 5;
    int n: 8;
この構造体Sが、仮に32bitマシンに存在した場合

  • これら4つのデータメンバのDW_AT_data_member_locationは、全て同じアドレスを指します。但し
    • Little Endianなら、この32bit=4byte中、一番小さななアドレス値
    • Big Endianなら、一番大きなアドレス値
  • Little Endianなら、DW_AT_bit_offsetとDW_AT_bit_sizeは以下の様な値になります。
    データオブジェクト名 ビット範囲 左からのオフセット
    DW_AT_bit_offset
    ビット幅
    DW_AT_bit_size
    備考
    (padding) (31-24) (0) (8) パディングなので、実際にこれは表現されないです
    n 23-16 8 8
    m 15-11 16 5
    k 10-5 21 6
    j 4-0 27 5
  • Big Endianなら、DW_AT_bit_offsetとDW_AT_bit_sizeは以下の様な値になります。
    データオブジェクト名 ビット範囲 左からのオフセット
    DW_AT_bit_offset
    ビット幅
    DW_AT_bit_size
    備考
    j 31-25 0 5
    k 26-21 5 6
    m 20-16 11 5
    n 15-8 16 8
    (padding) (7-0) (24) (8) パディングなので、実際にこれは表現されないです


可変構造体

  • 構造体の一部が可変する場合、可変部分はその構造体のDIEの「子DIE」として表現され、「DW_TAG_variant_part」なTAGのDIEとなります。
  • もしこの可変部分の「判別子(=可変部分がどうなるかを示すフラグみたいなもの?)」を持っている場合
    • この「判別子」は、可変部分のDIE(DW_TAG_variant_part)の子DIEとして表現されるみたいです
      • この判別子のDIEは、構造体のデータメンバのDIEの形式となっているみたいです
  • 可変部分のDIE(DW_TAG_variant_part)は、以下の特徴を持っています。
    • 「DW_AT_discr」を持っていることがあるみたいです
      • 「判別子」となるメンバのDIEへの参照
    • 可変部分の変数は、それぞれ「DW_TAG_variant」なTAGのDIEとなり、いずれも可変部分のDIEの「子DIE」となるです。
      • 与えられた変数によって選ばれた値(つまり判別子によって選択された値?)は、3通りの方法で表すことができるみたいです。
        • 方法1) 「DW_AT_discr_value」なattributeに「単純なケースへのラベル」を示す値を持たせる方法
          • この値は、xLEB128形式になっており、判別子の型が符号付きならsLEB128、符号なしならuLEB128
        • 方法2) 「DW_AT_discr_list」なAttributeに「判別子の値のリスト」を持たせる方法
          • このAtrributeの値は、blockであり、この中にケースへのラベルとラベルの範囲を混ぜた形でもっています。
          • このリストのアイテムは、リストのアイテムが単純なラベルか、それともラベルの範囲かを決めるための「判別子の値記述子」が前に付加された形となている。
            • 「判別子の値記述子」は以下表の通り
              記述子名 value
              DW_DSC_label 0x00
              DW_DSC_range 0x01
            • 「単純なラベル」である場合、上の方法1と同じ方法でxLEB128の値が格納されている。
            • 「ラベル」の場合、2つのLEB128な数値で表現され、1つめをhigh、2つめをlowと呼ぶ。この2つの値は方法1に書いた方法で符号の有無がきまります。
        • 方法3) 「DW_AT_discr_value」「DW_AT_discr_list」のいずれも持たない場合、もしくは「DW_AT_discr_list」がサイズ0の場合
          • デフォルトの変数が判別子となる
  • 特定の変数(判別子)によって選択される構造体の可変部分は、可変部分のDIEの「子DIE」として表現され、ソース上に登場する宣言順序と同じ順番となっている。


★この「可変部分」英訳はちょー直訳!です。ここは、英語が難解、と言う以上に、「そもそも構造体の可変部分」ってなに?ということが分かっていません。

少なくとも、CやC++で、構造体内の何かの変数の値次第で、その後の構造体の構造/メンバが変化する仕組みを、言語としてはサポートしていないように思うのですが。。。
※だから、そういったことが必要なケースでは、ポインタキャスト頑張るので通常。
ということは、何か他の言語だと思いますが、何の言語だろこれ?


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