Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15873] ファイルの概要

このコミットは、Goコンパイラのリンカ(cmd/ld)におけるDWARFデバッグ情報の生成方法に関する修正です。具体的には、unsafe.Pointer型に対するDWARFデバッグ情報の生成において、DW_AT_type属性の生成を停止することで、一部のGDB(特にApple版GDB 6.x)との互換性問題を解決しています。

コミット

commit a891b916bd6d284fba0349804da46ad2135e370c
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri Mar 22 03:58:35 2013 +0800

    cmd/ld: don't generate DW_AT_type attr for unsafe.Pointer to match gcc behavior
    gcc generates only attr DW_AT_byte_size for DW_TAG_pointer_type of "void *",
    but we used to also generate DW_AT_type pointing to imaginary unspecified
    type "void", which confuses some gdb.
    This change makes old Apple gdb 6.x (specifically, Apple version gdb-1515)
    accepts our binary without issue like this:
    (gdb) b 'main.main'
    Die: DW_TAG_unspecified_type (abbrev = 10, offset = 47079)
        has children: FALSE
        attributes:
            DW_AT_name (DW_FORM_string) string: "void"
    Dwarf Error: Cannot find type of die [in module /Users/minux/go/go2.hg/bin/go]
    
    Special thanks to Russ Cox for pointing out the problem in comment #6 of
    CL 7891044.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7744051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a891b916bd6d284fba0349804da46ad2135e370c

元コミット内容

cmd/ld: don't generate DW_AT_type attr for unsafe.Pointer to match gcc behavior

このコミットの目的は、unsafe.Pointer型に対してDWARFデバッグ情報を生成する際に、DW_AT_type属性を生成しないようにすることです。これは、GCCの挙動に合わせるためであり、一部のGDB(特に古いApple版GDB 6.x)がGoのバイナリを正しくデバッグできない問題を解決します。

変更の背景

Goのリンカ(cmd/ld)は、生成されるバイナリにDWARFデバッグ情報を埋め込みます。DWARFは、デバッガがプログラムの実行中に変数、関数、型などの情報を理解するために使用する標準的なデバッグ情報形式です。

以前のGoのリンカは、unsafe.Pointer型(C言語のvoid *に相当)のDWARF表現において、DW_TAG_pointer_typeに加えて、実体のない「void」という型を指すDW_AT_type属性も生成していました。しかし、GCCはvoid *に対してDW_AT_byte_size属性のみを生成し、DW_AT_type属性は生成しません。

このGoのリンカの挙動が、特に古いApple版GDB 6.x(具体的にはgdb-1515)を混乱させ、Goのバイナリをデバッグしようとすると「Dwarf Error: Cannot find type of die」というエラーが発生していました。GDBは、存在しない「void」型への参照を解決できず、デバッグセッションが中断される問題がありました。

この問題は、Russ Cox氏が別の変更リスト(CL 7891044)のコメント#6で指摘したことがきっかけで、この修正が提案されました。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARFは、ソースレベルのデバッガがプログラムの実行中にデバッグ情報を取得するための標準的な形式です。コンパイラやリンカによって生成された実行可能ファイルに埋め込まれ、変数名、型情報、関数名、ソースコードの行番号と実行可能コードのアドレスのマッピングなど、デバッグに必要なあらゆる情報を提供します。

  • DIE (Debugging Information Entry): DWARF情報の基本的な単位で、プログラム内のエンティティ(変数、関数、型など)を表します。各DIEは、そのエンティティに関する属性(Attribute)のセットを持ちます。
  • DW_TAG_pointer_type: DWARFタグの一つで、ポインタ型を表します。
  • DW_AT_type: DWARF属性の一つで、ポインタが指す型や、配列の要素型など、関連する型への参照を示します。
  • DW_AT_byte_size: DWARF属性の一つで、型のサイズ(バイト単位)を示します。
  • DW_TAG_unspecified_type: DWARFタグの一つで、型が指定されていないことを示します。このコミットでは、Goが「void」という名前でこのタグを生成していたことが問題でした。

unsafe.Pointer (Go言語)

Go言語のunsafe.Pointer型は、C言語のvoid *に相当する特殊なポインタ型です。これは、任意の型のポインタを保持でき、また任意の型のポインタに変換することができます。unsafeパッケージは、Goの型安全性をバイパスする機能を提供するため、通常は推奨されませんが、低レベルのシステムプログラミングやC言語との相互運用などで必要となる場合があります。

unsafe.Pointerは、Goのガベージコレクタによって追跡されないため、誤用するとメモリ安全性の問題を引き起こす可能性があります。しかし、デバッグ情報の観点からは、その「型なし」の性質がC言語のvoid *と類似しているため、DWARFでの表現方法が問題となりました。

GDB (GNU Debugger)

GDBは、GNUプロジェクトによって開発されたポータブルなデバッガです。C、C++、Go、Fortranなど、多くのプログラミング言語をサポートしています。GDBは、実行中のプログラムを検査し、ブレークポイントを設定し、変数を変更し、スタックトレースを表示するなどの機能を提供します。

このコミットで問題となったのは、特にAppleが提供する古いバージョンのGDB(gdb-1515など)が、Goのリンカが生成する特定のDWARF情報(unsafe.Pointerに対するDW_AT_type属性)を正しく解釈できなかったことです。

技術的詳細

この修正の核心は、unsafe.PointerのDWARF表現をGCCのvoid *のそれと一致させることです。GCCはvoid *に対してDW_TAG_pointer_typeを生成しますが、そのポインタが指す型を示すDW_AT_type属性は生成しません。代わりに、ポインタ自体のサイズを示すDW_AT_byte_size属性のみを生成します。

Goのリンカは以前、unsafe.Pointerに対してDW_TAG_pointer_typeを生成し、さらに「void」という名前のDW_TAG_unspecified_typeを指すDW_AT_type属性を生成していました。この「void」型は、Goの型システムには存在しない、デバッグ情報のためだけに作られた「架空の」型でした。

古いGDBは、この「架空のvoid型」への参照を解決しようとしますが、その定義を見つけられないため、「Dwarf Error: Cannot find type of die」というエラーを発生させました。

このコミットでは、unsafe.Pointerに対してDW_AT_type属性を生成しない新しいDWARFアブレビエーション(DW_ABRV_BARE_PTRTYPE)を導入し、これを使用するように変更しました。これにより、unsafe.PointerのDWARF表現は、GCCのvoid *の表現と一致し、GDBが期待する形式になります。

コアとなるコードの変更箇所

変更はsrc/cmd/ld/dwarf.cファイルに集中しています。

  1. 新しいアブレビエーションの追加: enumブロックにDW_ABRV_BARE_PTRTYPEが追加されました。これは、void*のようなポインタ型で、DW_AT_type属性を持たないことを示します。

    --- a/src/cmd/ld/dwarf.c
    +++ b/src/cmd/ld/dwarf.c
    @@ -154,6 +154,7 @@ enum
     	DW_ABRV_IFACETYPE,
     	DW_ABRV_MAPTYPE,
     	DW_ABRV_PTRTYPE,
    +	DW_ABRV_BARE_PTRTYPE, // only for void*, no DW_AT_type attr to please gdb 6.
     	DW_ABRV_SLICETYPE,
     	DW_ABRV_STRINGTYPE,
     	DW_ABRV_STRUCTTYPE,
    
  2. DW_ABRV_BARE_PTRTYPEの定義: static struct DWAbbrev配列に、DW_ABRV_BARE_PTRTYPEの具体的な定義が追加されました。これはDW_TAG_pointer_typeであり、子を持たず(DW_CHILDREN_no)、DW_AT_name属性のみを持つことを示します。DW_AT_type属性は含まれていません。

    --- a/src/cmd/ld/dwarf.c
    +++ b/src/cmd/ld/dwarf.c
    @@ -307,6 +308,12 @@ static struct DWAbbrev {\n \t\tDW_AT_type,\tDW_FORM_ref_addr,\n \t\t0, 0\n \t},\n+\t/* BARE_PTRTYPE */\n+\t{\n+\t\tDW_TAG_pointer_type, DW_CHILDREN_no,\n+\t\tDW_AT_name,\tDW_FORM_string,\n+\t\t0, 0\n+\t},\
     
     \t/* SLICETYPE */
     \t{\
    
  3. putattrs関数の修正: putattrs関数内で、属性値がnilの場合にputattrを呼び出す際の引数が0からnilに変更されました。これは、GoのnilがCのNULLに相当し、より正確な表現にするためと考えられます。

    --- a/src/cmd/ld/dwarf.c
    +++ b/src/cmd/ld/dwarf.c
    @@ -717,7 +724,7 @@ putattrs(int abbrev, DWAttr* attr)\
     \t\t\t\tattrs[af->attr]->value,\
     \t\t\t\tattrs[af->attr]->data);\
     \t\telse\
    -\t\t\tputattr(abbrev, af->form, 0, 0, 0);\
    +\t\t\tputattr(abbrev, af->form, 0, 0, nil);\
     }\
     \
     static void putdie(DWDie* die);\
    
  4. defgotype関数の修正: KindUnsafePointerの場合に、DW_ABRV_PTRTYPEの代わりに新しく定義されたDW_ABRV_BARE_PTRTYPEを使用するように変更されました。これにより、unsafe.PointerのDWARF情報からDW_AT_type属性が削除されます。

    --- a/src/cmd/ld/dwarf.c
    +++ b/src/cmd/ld/dwarf.c
    @@ -1009,8 +1016,7 @@ defgotype(Sym *gotype)\
     \t\tbreak;\
     \
     \tcase KindUnsafePointer:\
    -\t\tdie = newdie(&dwtypes, DW_ABRV_PTRTYPE, name);\
    -\t\tnewrefattr(die, DW_AT_type, find(&dwtypes, \"void\"));\
    +\t\tdie = newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, name);\
     \t\tbreak;\
     \
     \tdefault:\
    
  5. dwarfemitdebugsections関数の修正: 初期化時にunsafe.PointerのDWARF情報を生成する箇所でも、同様にDW_ABRV_PTRTYPEの代わりにDW_ABRV_BARE_PTRTYPEを使用するように変更されました。これにより、unsafe.Pointerの初期定義からDW_AT_type属性が削除されます。

    --- a/src/cmd/ld/dwarf.c
    +++ b/src/cmd/ld/dwarf.c
    @@ -2126,8 +2132,7 @@ dwarfemitdebugsections(void)\
     \t// Some types that must exist to define other ones.\
     \tnewdie(&dwtypes, DW_ABRV_NULLTYPE, \"<unspecified>\");\
     \tnewdie(&dwtypes, DW_ABRV_NULLTYPE, \"void\");\
    -\tnewrefattr(newdie(&dwtypes, DW_ABRV_PTRTYPE, \"unsafe.Pointer\"),\
    -\t\tDW_AT_type, find(&dwtypes, \"void\"));\
    +\tnewdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, \"unsafe.Pointer\");\
     \tdie = newdie(&dwtypes, DW_ABRV_BASETYPE, \"uintptr\");  // needed for array size\
     \tnewattr(die, DW_AT_encoding,  DW_CLS_CONSTANT, DW_ATE_unsigned, 0);\
     \tnewattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, PtrSize, 0);\
    

コアとなるコードの解説

このコミットの主要な変更は、src/cmd/ld/dwarf.cファイルにおけるunsafe.PointerのDWARF表現の変更です。

以前は、unsafe.PointerのDWARF情報を作成する際に、DW_ABRV_PTRTYPEというアブレビエーション(DWARF情報のテンプレートのようなもの)を使用し、さらにDW_AT_type属性で「void」という架空の型への参照を追加していました。

// 変更前 (簡略化)
case KindUnsafePointer:
    die = newdie(&dwtypes, DW_ABRV_PTRTYPE, name);
    newrefattr(die, DW_AT_type, find(&dwtypes, "void")); // 問題の原因
    break;

このコミットでは、この挙動を変更するために、以下の手順を踏んでいます。

  1. DW_ABRV_BARE_PTRTYPEの導入: DW_ABRV_PTRTYPEと似ていますが、DW_AT_type属性を含まない新しいアブレビエーションDW_ABRV_BARE_PTRTYPEが定義されました。これは、void *のように、ポインタが指す具体的な型が不明または重要でない場合に最適です。

    // 新しいアブレビエーションの定義 (簡略化)
    /* BARE_PTRTYPE */
    {
        DW_TAG_pointer_type, DW_CHILDREN_no,
        DW_AT_name, DW_FORM_string,
        0, 0 // DW_AT_type がない
    },
    
  2. unsafe.PointerでのDW_ABRV_BARE_PTRTYPEの使用: defgotype関数内でKindUnsafePointerを処理する際に、DW_ABRV_PTRTYPEの代わりにDW_ABRV_BARE_PTRTYPEを使用するように変更されました。これにより、unsafe.PointerのDWARF情報からDW_AT_type属性が完全に削除されます。

    // 変更後 (簡略化)
    case KindUnsafePointer:
        die = newdie(&dwtypes, DW_ABRV_BARE_PTRTYPE, name); // 新しいアブレビエーションを使用
        break;
    

この変更により、Goのリンカが生成するunsafe.PointerのDWARF表現は、GCCがvoid *に対して生成する表現と一致するようになり、古いGDBがGoのバイナリを正しくデバッグできるようになりました。

関連リンク

参考にした情報源リンク