[インデックス 19723] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) において、Go固有の型情報をDWARFデバッグ情報に追加することを目的としています。具体的には、Goの複合型(スライス、マップ、チャネルなど)をより正確に表現するためのカスタムDWARF属性を導入し、それらの属性をDWARF情報に適切に埋め込むためのリンカのコードを修正しています。これにより、デバッガがGoプログラムの型情報をより詳細に理解できるようになります。
コミット
commit e1821692caf23cade9de7e150b13188dd0c97479
Author: Russ Cox <rsc@golang.org>
Date: Fri Jul 11 23:10:00 2014 -0400
cmd/ld: add go-specific dwarf type information
LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/116720043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e1821692caf23cade9de7e150b13188dd0c97479
元コミット内容
cmd/ld: add go-specific dwarf type information
変更の背景
Go言語は、スライス、マップ、チャネル、インターフェースなど、C言語やC++にはない独自の組み込み型を持っています。これらのGo固有の型は、従来のDWARF標準属性だけではその構造やセマンティクスを完全に表現することが困難でした。デバッガがGoプログラムの実行中にこれらの型の内部構造(例えば、マップのキーと値の型、スライスの要素型など)を正確に表示するためには、より詳細なデバッグ情報が必要となります。
このコミットの背景には、Goの型システムをDWARFデバッグ情報に適切にマッピングし、デバッガのGoプログラムに対するサポートを向上させるという目的があります。既存のDWARF属性では表現しきれないGo固有の情報を、カスタム属性として追加することで、デバッガがGoの複合型をより正確に解釈し、開発者によりリッチなデバッグ体験を提供できるようになります。
前提知識の解説
DWARF (Debugging With Attributed Record Formats)
DWARFは、コンパイラやリンカによって生成される実行可能ファイルに埋め込まれるデバッグ情報の標準フォーマットです。デバッガはDWARF情報を使用して、コンパイルされたバイナリコードを元のソースコードにマッピングし、変数、型、関数、行番号などの情報を提供します。これにより、開発者はソースコードレベルでプログラムの実行をステップ実行したり、変数の値を検査したりすることができます。
DWARFは、Debugging Information Entry (DIE) と呼ばれるツリー構造で情報を表現します。各DIEは、プログラムエンティティ(例えば、関数、変数、型)を表し、属性 (Attribute) と呼ばれるキーと値のペアでその特性を記述します。属性は DW_AT_
で始まる定数で識別され、その値の形式は DW_FORM_
で始まる定数で指定されます。
Go言語の型システム
Go言語は、C言語のようなプリミティブ型に加えて、以下のような特徴的な複合型を持っています。
- スライス (Slice): 可変長配列のようなもので、内部的にはポインタ、長さ、容量の3つの要素で構成されます。
- マップ (Map): キーと値のペアを格納するハッシュテーブルです。キーの型と値の型を持ちます。
- チャネル (Channel): ゴルーチン間で値を送受信するための通信メカニズムです。チャネルが扱う要素の型を持ちます。
- インターフェース (Interface): メソッドの集合を定義する型です。
これらの型は、C言語の構造体や配列とは異なるセマンティクスを持つため、標準のDWARF属性だけではその特性を完全に表現することが難しい場合があります。
cmd/ld
(Goリンカ)
cmd/ld
はGo言語のリンカであり、コンパイルされたオブジェクトファイルやライブラリを結合して実行可能ファイルを生成します。このプロセスの中で、デバッグ情報(DWARF)も実行可能ファイルに埋め込まれます。リンカは、Goのコンパイラから渡された型情報やシンボル情報に基づいて、DWARFのDIEと属性を生成する役割を担っています。
uint8
と uint16
uint8
は8ビットの符号なし整数、uint16
は16ビットの符号なし整数です。このコミットでは、DWARF属性の識別子 (attr
) の型が uint8
から uint16
に変更されています。これは、標準のDWARF属性に加えて、Go固有のカスタム属性を定義するために、より広い範囲の識別子が必要になったことを意味します。uint8
では0-255までの値しか扱えませんが、uint16
では0-65535までの値を扱えるようになります。
uleb128
uleb128
(Unsigned Little Endian Base 128) は、DWARFで広く使用される可変長エンコーディング方式です。これは、小さな数値を効率的にエンコードし、大きな数値にも対応できるため、デバッグ情報のサイズを削減するのに役立ちます。
技術的詳細
このコミットの主要な技術的変更点は、Go固有の型情報をDWARFに埋め込むためのカスタム属性の導入と、それに伴うリンカのDWARF生成ロジックの修正です。
-
カスタムDWARF属性の定義:
DW_AT_go_kind = 0x2900
DW_AT_go_key = 0x2901
DW_AT_go_elem = 0x2902
これらの新しい属性は、Goの型システムをより詳細に表現するために導入されました。0x2900
から始まる値は、標準のDWARF属性と衝突しないように、ユーザー定義の属性範囲として割り当てられています。DW_AT_go_kind
: Goの型の種類(例:KindMap
,KindSlice
,KindFunc
など)を格納するために使用されます。DW_AT_go_key
: マップのキーの型への参照を格納するために使用されます。DW_AT_go_elem
: スライス、チャネル、マップの値の型(要素型)への参照を格納するために使用されます。
-
属性IDの型拡張:
DWAttrForm
構造体のattr
フィールドがuint8
からuint16
に変更されました。DWAttr
構造体のatr
フィールドもuint8
からuint16
に変更されました。newattr
,getattr
,newrefattr
といった属性操作関数の引数もuint8
からuint16
に変更されました。 この変更は、新しいカスタム属性の識別子(0x2900
など)がuint8
の範囲(0-255)を超えるため、必須でした。これにより、より多くのカスタム属性を定義できるようになります。
-
DWARFアブレビエーションテーブルの更新:
DWAbbrev
テーブルは、DWARFのDIEが持つ属性のリストと形式を定義します。このテーブルに、ARRAYTYPE
,TYPEDEF
,SUBROUTINE_TYPE
,MAPTYPE
,POINTER_TYPE
,STRUCTURE_TYPE
などのGo固有の型に対応するエントリに、新しく定義されたDW_AT_go_kind
,DW_AT_go_key
,DW_AT_go_elem
属性が追加されました。これにより、これらのGoの型がDWARFで表現される際に、Go固有のメタデータが付与されるようになります。 -
属性書き込みロジックの改善:
writeabbrev
関数が、アブレビエーションテーブルの属性をuleb128
形式で書き出すように修正されました。以前はstrlen
を使用していましたが、これはuint8
の配列を文字列として扱っていたため、uint16
に拡張された属性IDには対応できませんでした。新しい実装では、DWAttrForm
の配列をループし、各属性IDと形式を個別にuleb128put
で書き出すことで、より汎用的な処理を実現しています。putattrs
関数も、属性をDIEに書き込む際のロジックが改善されました。以前は属性を一時的な配列に格納していましたが、新しい実装では、アブレビエーションで定義された属性の順序に従って、DIEに実際に存在する属性を検索し、書き出すようになりました。これにより、属性の順序が保証され、より正確なDWARF情報が生成されます。
-
Go型からDWARFへのマッピングの更新:
defgotype
関数は、Goの型情報をDWARFのDIEに変換する主要な関数です。この関数内で、Goのスライス、マップ、チャネルなどの型がDWARFに変換される際に、以前使用されていたDW_AT_internal_elem_type
,DW_AT_internal_key_type
,DW_AT_internal_val_type
といった内部的な属性が、新しく定義されたDW_AT_go_elem
やDW_AT_go_key
に置き換えられました。また、すべてのGoの型に対してDW_AT_go_kind
属性が追加されるようになりました。
これらの変更により、Goのリンカは、Go固有の型構造をより正確かつ標準的な方法でDWARFデバッグ情報に表現できるようになり、デバッガがGoプログラムの内部状態をより深く理解するための基盤が強化されました。
コアとなるコードの変更箇所
src/cmd/ld/dwarf.c
ファイルが変更されています。
-
DWAttrForm
構造体の変更:typedef struct DWAttrForm DWAttrForm; struct DWAttrForm { - uint8 attr; + uint16 attr; uint8 form; };
-
Go固有のDWARF属性の定義:
// Go-specific type attributes. enum { DW_AT_go_kind = 0x2900, DW_AT_go_key = 0x2901, DW_AT_go_elem = 0x2902, DW_AT_internal_location = 253, // params and locals; not emitted };
-
DWAbbrev
テーブルへのGo固有属性の追加: 様々なDW_TAG
(例:ARRAYTYPE
,TYPEDEF
,MAPTYPE
など) の定義にDW_AT_go_kind
,DW_AT_go_key
,DW_AT_go_elem
が追加されています。 例:/* ARRAYTYPE */ { DW_TAG_array_type, DW_CHILDREN_no, DW_AT_name, DW_FORM_string, DW_AT_type, DW_FORM_ref_addr, DW_AT_byte_size, DW_FORM_udata, + DW_AT_go_kind, DW_FORM_data1, 0, 0 },
-
writeabbrev
関数の修正: 属性の書き出しロジックがuint16
に対応するように変更されました。static void writeabbrev(void) { - int i, n; + int i, j; + DWAttrForm *f; abbrevo = cpos(); for (i = 1; i < DW_NABRV; i++) { uleb128put(i); uleb128put(abbrevs[i].tag); cput(abbrevs[i].children); - // 0 is not a valid attr or form, and DWAbbrev.attr is - // 0-terminated, so we can treat it as a string - n = strlen((char*)abbrevs[i].attr) / 2; - strnput((char*)abbrevs[i].attr, - (n+1) * sizeof(DWAttrForm)); + for(j=0; j<nelem(abbrevs[i].attr); j++) { + f = &abbrevs[i].attr[j]; + uleb128put(f->attr); + uleb128put(f->form); + if(f->attr == 0) + break; + } } cput(0); abbrevsize = cpos() - abbrevo; }
-
DWAttr
構造体の変更:typedef struct DWAttr DWAttr; struct DWAttr { DWAttr *link; - uint8 atr; // DW_AT_ + uint16 atr; // DW_AT_ uint8 cls; // DW_CLS_ vlong value; char *data; };
-
属性操作関数の引数型変更:
newattr
,getattr
,newrefattr
のattr
引数がuint8
からuint16
に変更されました。 -
putattrs
関数の修正: 属性の書き込みロジックが変更されました。static void putattrs(int abbrev, DWAttr* attr) { - DWAttr *attrs[DW_AT_recursive + 1]; DWAttrForm* af; - - memset(attrs, 0, sizeof attrs); - for( ; attr; attr = attr->link) - if (attr->atr < nelem(attrs)) - attrs[attr->atr] = attr; - - for(af = abbrevs[abbrev].attr; af->attr; af++) - if (attrs[af->attr]) - putattr(abbrev, af->form, - attrs[af->attr]->cls, - attrs[af->attr]->value, - attrs[af->attr]->data); - else - putattr(abbrev, af->form, 0, 0, nil); + DWAttr *ap; + + for(af = abbrevs[abbrev].attr; af->attr; af++) { + for(ap=attr; ap; ap=ap->link) { + if(ap->atr == af->attr) { + putattr(abbrev, af->form, + ap->cls, + ap->value, + ap->data); + goto done; + } + } + putattr(abbrev, af->form, 0, 0, nil); + done:; + } }
-
古い内部属性の削除:
-// Fake attributes for slices, maps and channel -enum { - DW_AT_internal_elem_type = 250, // channels and slices - DW_AT_internal_key_type = 251, // maps - DW_AT_internal_val_type = 252, // maps - DW_AT_internal_location = 253, // params and locals -};
-
defgotype
関数でのGo固有属性の使用:KindChan
,KindMap
,KindArray
などのGoの型を処理する際に、DW_AT_internal_elem_type
などがDW_AT_go_elem
やDW_AT_go_key
に置き換えられ、DW_AT_go_kind
が追加されました。 例:case KindChan: die = newdie(&dwtypes, DW_ABRV_CHANTYPE, name); newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0); s = decodetype_chanelem(gotype); - newrefattr(die, DW_AT_internal_elem_type, defgotype(s)); + newrefattr(die, DW_AT_go_elem, defgotype(s)); break; // ... // すべてのGo型に対してDW_AT_go_kindを追加 newattr(die, DW_AT_go_kind, DW_CLS_CONSTANT, kind, 0);
-
synthesizeslicetypes
,synthesizemaptypes
,synthesizechantypes
関数でのGo固有属性の使用: これらの関数でも、古い内部属性が新しいGo固有属性に置き換えられました。
コアとなるコードの解説
このコミットの核心は、Go言語のリンカが生成するDWARFデバッグ情報に、Go固有の型セマンティクスを正確に反映させるための変更です。
-
DWAttrForm
とDWAttr
のuint16
への拡張: これは、Go固有のカスタム属性を定義するための基盤となります。DWARFの標準属性はuint8
の範囲に収まりますが、Goの複雑な型システムを表現するためには、標準属性だけでは不十分です。0x2900
から始まる新しい属性IDはuint8
の範囲を超えるため、これらの構造体のattr
およびatr
フィールドをuint16
に拡張する必要がありました。これにより、Goは独自の属性を定義し、デバッグ情報に埋め込むことが可能になります。 -
DW_AT_go_kind
,DW_AT_go_key
,DW_AT_go_elem
の導入: これらのカスタム属性は、Goの複合型(スライス、マップ、チャネル)の内部構造をデバッガに伝えるために不可欠です。DW_AT_go_kind
: Goの型がどのような種類であるか(例:int
,string
,[]int
,map[string]int
など)を数値で表現します。これにより、デバッガはGoの型をより正確に識別し、適切な表示を行うことができます。DW_AT_go_key
: マップ型の場合、そのキーの型への参照を格納します。デバッガはこれを利用して、マップのキーの型を特定し、デバッグ時にキーの値を正しく解釈できます。DW_AT_go_elem
: スライス、チャネル、マップの値の型(要素型)への参照を格納します。これにより、デバッガはこれらの複合型の要素の型を特定し、内部の要素を正しく表示できます。
-
writeabbrev
とputattrs
のロジック改善: これらの関数は、DWARFのDIEと属性をバイナリ形式で書き出す役割を担っています。以前の実装は、属性IDがuint8
の範囲に収まることを前提としていたり、属性の書き出し順序が厳密でなかったりする可能性がありました。今回の変更では、uint16
の属性IDに対応するためにuleb128put
を使用し、アブレビエーションで定義された属性の順序に厳密に従って属性を書き出すようにロジックが修正されました。これにより、生成されるDWARF情報の正確性と互換性が向上します。 -
defgotype
におけるGo型とDWARF属性のマッピング:defgotype
関数は、Goのランタイム型情報 (_type
構造体など) をDWARFのDIEに変換する中心的な部分です。このコミットでは、Goのスライス、マップ、チャネルといった型がDWARFに変換される際に、新しく定義されたDW_AT_go_elem
やDW_AT_go_key
属性が使用されるようになりました。これにより、Goのリンカは、Goの型システムが持つ豊かなセマンティクスをDWARFデバッグ情報に直接反映させることが可能になり、デバッガがGoの複合型をより深く理解し、デバッグ体験を向上させるための重要なステップとなります。
これらの変更は、Goのデバッグ体験を向上させるための基盤を構築し、デバッガがGoのプログラムの内部構造をより正確に可視化できるようにすることに貢献しています。
関連リンク
- Go CL 116720043: https://golang.org/cl/116720043
参考にした情報源リンク
- DWARF Debugging Information Format: https://dwarfstd.org/
- Go言語の型: https://go.dev/tour/basics/11
- Go言語のリンカ (
cmd/ld
): Goのソースコードリポジトリ内のsrc/cmd/ld
ディレクトリ - ULEB128: https://en.wikipedia.org/wiki/LEB128
- Goの型システムに関する議論 (一般的な情報源): Goの公式ブログやGoのIssueトラッカーで、デバッグ情報や型システムに関する議論が参照されることがあります。
- 例: Go Issue Tracker: https://github.com/golang/go/issues
- Go Blog: https://go.dev/blog/ (このコミットに直接関連する特定のブログ記事やIssueは特定できませんでしたが、一般的な情報源として記載します。)