[インデックス 18356] ファイルの概要
このコミットは、Go言語のデバッグ情報処理ライブラリである debug/dwarf
および debug/elf
パッケージに、DWARF 4形式の型情報(Type Unit)の読み込みサポートを追加するものです。具体的には、DWARF 4で導入された .debug_types
セクションの解析機能と、構造体フィールドのバイトオフセット表現に関するDWARF 3の変更に対応しています。
コミット
commit bd997b24f7a7cdb57bb9bf02fbfacef1f59f0f89
Author: Ian Lance Taylor <iant@golang.org>
Date: Mon Jan 27 10:18:22 2014 -0800
debug/dwarf, debug/elf: add support for reading DWARF 4 type info
In DWARF 4 the debug info for large types is put into
.debug_type sections, so that the linker can discard duplicate
info. This change adds support for reading type units.
Another small change included here is that DWARF 3 supports
storing the byte offset of a struct field as a formData rather
than a formDwarfBlock.
R=golang-codereviews, r
CC=golang-codereviews
https://golang.org/cl/56300043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bd997b24f7a7cdb57bb9bf02fbfacef1f59f0f89
元コミット内容
debug/dwarf
および debug/elf
パッケージに、DWARF 4の型情報読み込みサポートを追加。
DWARF 4では、大きな型のデバッグ情報が .debug_types
セクションに配置され、リンカが重複情報を破棄できるようになっています。この変更は、型単位(type units)の読み込みサポートを追加します。
また、DWARF 3では、構造体フィールドのバイトオフセットを formData
として格納できるようになり、formDwarfBlock
ではなくなりました。この小さな変更も含まれています。
変更の背景
このコミットの主な背景は、デバッグ情報フォーマットであるDWARFの進化に対応することです。特に、DWARF 4で導入された新しいメカニズムである「型単位(Type Unit)」のサポートが重要です。
従来のDWARFバージョン(DWARF 3まで)では、すべてのデバッグ情報、特に型定義は .debug_info
セクションにまとめて格納されていました。しかし、大規模なプログラムでは、同じ型が複数のコンパイル単位(ソースファイル)で定義されたり、インクルードされたりすることが頻繁にあります。これにより、.debug_info
セクションに重複する型情報が大量に生成され、デバッグ情報のサイズが肥大化するという問題がありました。これは、コンパイル時間、リンク時間、そして最終的なバイナリサイズに悪影響を及ぼします。
DWARF 4は、この問題を解決するために .debug_types
セクションを導入しました。
.debug_types
セクションは、独立した型定義の単位である「型単位(Type Unit)」を格納するために使用されます。各型単位は、単一のプライマリ型とその8バイトのシグネチャを定義します。これにより、リンカは重複する型情報を容易に識別し、破棄できるようになります。結果として、デバッグ情報のサイズを削減し、リンカの効率を向上させることができます。
Go言語のツールチェインが生成するデバッグ情報がDWARF 4に準拠するためには、debug/dwarf
パッケージがこの新しい形式を正しく解析できる必要があります。このコミットは、そのための基盤を提供します。
また、DWARF 3における構造体フィールドのバイトオフセットの表現方法の変更(formDwarfBlock
から formData
へ)も、互換性を保つために対応が必要でした。これは、DWARFの属性値のエンコーディングに関する細かな変更であり、デバッガが正確なオフセット情報を取得するために重要です。
前提知識の解説
DWARF (Debugging With Attributed Record Formats)
DWARFは、プログラムのデバッグ情報を表現するための標準的なフォーマットです。コンパイラによって生成され、実行可能ファイルに埋め込まれます。デバッガはDWARF情報を使用して、ソースコードレベルでのデバッグ(変数名の解決、行番号へのマッピング、スタックトレースの表示など)を可能にします。
DWARFは、主に以下のセクションで構成されます。
.debug_info
: プログラムの構造(関数、変数、型など)に関する主要なデバッグ情報が格納されます。.debug_abbrev
:.debug_info
セクションのエントリの構造を定義する省略形式(abbreviations)が格納されます。これにより、.debug_info
のサイズを削減できます。.debug_line
: ソースコードの行番号と実行可能コードのアドレスのマッピングが格納されます。.debug_str
: 文字列定数(変数名、型名など)が格納されます。.debug_aranges
: アドレス範囲とコンパイル単位のマッピングが格納されます。.debug_frame
: スタックフレームのレイアウトに関する情報が格納されます。
DWARF 4
DWARF 4は、DWARFの主要な改訂版の一つで、いくつかの新機能と改善が導入されました。このコミットに関連する最も重要な変更点は、型単位(Type Unit)とそれらを格納する.debug_types
セクションの導入です。
- 型単位(Type Unit): DWARF 4で導入された新しい種類のコンパイル単位です。これは、単一の型定義とその型を識別するための8バイトのシグネチャを含みます。型単位は、
.debug_types
セクションに格納されます。 .debug_types
セクション: 型単位を格納するための新しいセクションです。これにより、リンカは重複する型定義を識別し、破棄することが可能になり、デバッグ情報のサイズを削減できます。他のデバッグ情報セクション(例:.debug_info
)は、formRefSig8
という新しい形式の参照を使用して、.debug_types
セクション内の型単位を参照します。
DWARF Attributes and Forms
DWARFでは、デバッグ情報エントリ(DIE: Debugging Information Entry)が、プログラム要素(変数、関数、型など)の属性を記述します。各属性は、その値の形式(Form)を持ちます。
formDwarfBlock
: 可変長のバイト列を格納するための形式です。DWARF式(DWARF Expression)など、バイトコードのシーケンスを表現するのに使われます。formData
: 固定長または可変長の数値データを格納するための形式です。このコミットでは、構造体フィールドのバイトオフセットがformData
として格納されるようになったことが言及されています。これは、以前はformDwarfBlock
で表現されていた可能性があり、より直接的な数値表現への変更を示唆しています。formRefSig8
: DWARF 4で導入された新しい形式で、8バイトのシグネチャを使用して.debug_types
セクション内の型単位を参照します。
ELF (Executable and Linkable Format)
ELFは、Unix系システムで広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。デバッグ情報は通常、ELFファイル内の特定のセクションに埋め込まれます。debug/elf
パッケージは、GoプログラムがELFファイルを解析し、その中のデバッグ情報セクションにアクセスするための機能を提供します。
技術的詳細
このコミットは、主に src/pkg/debug/dwarf
と src/pkg/debug/elf
パッケージに影響を与えます。
debug/dwarf
パッケージの変更点
-
const.go
:- DWARF 4で導入された新しい
format
定数(formSecOffset
,formExprloc
,formFlagPresent
)が追加されています。 - DWARF 3およびDWARF 4で導入された新しい
Tag
定数(TagCondition
,TagSharedType
,TagTypeUnit
,TagRvalueReferenceType
,TagTemplateAlias
)が追加されています。特にTagTypeUnit
は、型単位を表すために重要です。 tagNames
マップもこれらの新しいタグに対応するように更新されています。
- DWARF 4で導入された新しい
-
entry.go
:Reader
型にclone()
とoffset()
メソッドが追加されています。これらは、新しいtypeReader
インターフェースで使用され、型情報の読み込み時にリーダーの状態を複製したり、現在のオフセットを取得したりするために利用されます。
-
open.go
:Data
構造体にtypeSigs map[uint64]*typeUnit
が追加されています。これは、型シグネチャ(8バイト)をキーとして、対応するtypeUnit
をキャッシュするために使用されます。AddTypes(name string, types []byte) error
メソッドが追加されています。このメソッドは、.debug_types
セクションの生データをData
オブジェクトに追加し、解析を開始します。複数の.debug_types
セクションが存在する場合に対応できるようになっています。
-
type.go
:typeReader
インターフェースが新しく定義されています。これは、.debug_info
セクションと.debug_types
セクションのどちらからでも型情報を読み取れるようにするための抽象化です。Reader
と新しく導入されるtypeUnitReader
がこのインターフェースを実装します。Type(off Offset)
メソッドがreadType
という新しい内部ヘルパー関数を呼び出すように変更されています。readType
は、typeReader
インターフェースと型キャッシュを受け取ることで、.debug_info
と.debug_types
の両方から型を読み込むロジックを共通化しています。typeOf
クロージャ内で、AttrType
属性の値がOffset
だけでなくuint64
(型シグネチャ)である場合も処理できるようになっています。uint64
の場合は、d.sigToType(toff)
を呼び出して、対応する型単位から型情報を取得します。これは、formRefSig8
形式の参照に対応するものです。- 構造体フィールドのオフセットを処理する部分で、
AttrDataMemberLoc
の値が[]byte
(formDwarfBlock
)だけでなくint64
(formData
)である場合も処理できるようになっています。これは、DWARF 3で導入された変更に対応します。
-
typeunit.go
(新規ファイル):typeUnit
構造体が定義されています。これは、.debug_types
セクション内の個々の型単位を表します。parseTypes(name string, types []byte) error
メソッドが実装されています。このメソッドは、.debug_types
セクションの生データを解析し、各型単位のヘッダ情報(長さ、バージョン、アブレビエーションオフセット、シグネチャ、型オフセットなど)を読み取ります。そして、解析されたtypeUnit
オブジェクトをData.typeSigs
マップにシグネチャをキーとして格納します。sigToType(sig uint64) (Type, error)
メソッドが実装されています。このメソッドは、与えられた型シグネチャに対応する型単位を見つけ、その型単位から実際の型情報を読み込みます。型情報はキャッシュされます。typeUnitReader
構造体と、それがtypeReader
インターフェースを実装するメソッド(Seek
,Next
,clone
,offset
)が定義されています。これにより、debug/dwarf
の既存の型解析ロジックが、.debug_types
セクション内の型単位に対しても透過的に動作するようになります。
-
unit.go
:parseUnits
関数で、サポートするDWARFバージョンに4
が追加されています。
debug/elf
パッケージの変更点
file.go
:DWARF()
メソッドが変更されています。従来の.debug_info
などのセクションを解析した後、ELFファイル内の.debug_types
セクションを検索するロジックが追加されています。.debug_types
セクションが見つかった場合、そのデータをdwarf.Data.AddTypes()
メソッドに渡して、DWARF 4の型単位の解析を開始します。- リロケーション(
SHT_RELA
またはSHT_REL
タイプで、.debug_types
セクションを参照するもの)も適用されるようになっています。これは、.debug_types
セクション内のオフセットが、リンカによって調整される可能性があるためです。
コアとなるコードの変更箇所
src/pkg/debug/dwarf/const.go
--- a/src/pkg/debug/dwarf/const.go
+++ b/src/pkg/debug/dwarf/const.go
@@ -207,6 +207,7 @@ const (
formRef8 format = 0x14
formRefUdata format = 0x15
formIndirect format = 0x16
+ // The following are new in DWARF 4.
formSecOffset format = 0x17
formExprloc format = 0x18
formFlagPresent format = 0x19
@@ -264,15 +265,22 @@ const (
TagVariantPart Tag = 0x33
TagVariable Tag = 0x34
TagVolatileType Tag = 0x35
- TagDwarfProcedure Tag = 0x36
- TagRestrictType Tag = 0x37
- TagInterfaceType Tag = 0x38
- TagNamespace Tag = 0x39
- TagImportedModule Tag = 0x3A
- TagUnspecifiedType Tag = 0x3B
- TagPartialUnit Tag = 0x3C
- TagImportedUnit Tag = 0x3D
- TagMutableType Tag = 0x3E
+ // The following are new in DWARF 3.
+ TagDwarfProcedure Tag = 0x36
+ TagRestrictType Tag = 0x37
+ TagInterfaceType Tag = 0x38
+ TagNamespace Tag = 0x39
+ TagImportedModule Tag = 0x3A
+ TagUnspecifiedType Tag = 0x3B
+ TagPartialUnit Tag = 0x3C
+ TagImportedUnit Tag = 0x3D
+ TagMutableType Tag = 0x3E // Later removed from DWARF.
+ TagCondition Tag = 0x3F
+ TagSharedType Tag = 0x40
+ // The following are new in DWARF 4.
+ TagTypeUnit Tag = 0x41
+ TagRvalueReferenceType Tag = 0x42
+ TagTemplateAlias Tag = 0x43
)
var tagNames = [...]string{
@@ -332,6 +340,11 @@ var tagNames = [...]string{
TagPartialUnit: "PartialUnit",
TagImportedUnit: "ImportedUnit",
TagMutableType: "MutableType",
+ TagCondition: "Condition",
+ TagSharedType: "SharedType",
+ TagTypeUnit: "TypeUnit",
+ TagRvalueReferenceType: "RvalueReferenceType",
+ TagTemplateAlias: "TemplateAlias",
}
func (t Tag) String() string {
src/pkg/debug/dwarf/open.go
--- a/src/pkg/debug/dwarf/open.go
+++ b/src/pkg/debug/dwarf/open.go
@@ -26,6 +26,7 @@ type Data struct {
abbrevCache map[uint32]abbrevTable
order binary.ByteOrder
typeCache map[Offset]Type
+ typeSigs map[uint64]*typeUnit
unit []unit
}
@@ -49,6 +50,7 @@ func New(abbrev, aranges, frame, info, line, pubnames, ranges, str []byte) (*Dat
str: str,
abbrevCache: make(map[uint32]abbrevTable),
typeCache: make(map[Offset]Type),
+ typeSigs: make(map[uint64]*typeUnit),
}
// Sniff .debug_info to figure out byte order.
@@ -75,3 +77,11 @@ func New(abbrev, aranges, frame, info, line, pubnames, ranges, str []byte) (*Dat
d.unit = u
return d, nil
}
+
+// AddTypes will add one .debug_types section to the DWARF data. A
+// typical object with DWARF version 4 debug info will have multiple
+// .debug_types sections. The name is used for error reporting only,
+// and serves to distinguish one .debug_types section from another.
+func (d *Data) AddTypes(name string, types []byte) error {
+ return d.parseTypes(name, types)
+}
src/pkg/debug/dwarf/type.go
--- a/src/pkg/debug/dwarf/type.go
+++ b/src/pkg/debug/dwarf/type.go
@@ -251,23 +251,37 @@ func (t *TypedefType) String() string { return t.Name }\n \n func (t *TypedefType) Size() int64 { return t.Type.Size() }\n \n+// typeReader is used to read from either the info section or the\n+// types section.\n+type typeReader interface {\n+\tSeek(Offset)\n+\tNext() (*Entry, error)\n+\tclone() typeReader\n+\toffset() Offset\n+}\n+\n+// Type reads the type at off in the DWARF ``info'' section.\n func (d *Data) Type(off Offset) (Type, error) {\n-\tif t, ok := d.typeCache[off]; ok {\n+\treturn d.readType("info", d.Reader(), off, d.typeCache)\n+}\n+\n+// readType reads a type from r at off of name using and updating a\n+// type cache.\n+func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Offset]Type) (Type, error) {\n+\tif t, ok := typeCache[off]; ok {\n \t\treturn t, nil\n \t}\n-\n-\tr := d.Reader()\n \tr.Seek(off)\n \te, err := r.Next()\n \tif err != nil {\n \t\treturn nil, err\n \t}\n \tif e == nil || e.Offset != off {\n-\t\treturn nil, DecodeError{"info", off, "no type at offset"}\n+\t\treturn nil, DecodeError{name, off, "no type at offset"}\n \t}\n \n \t// Parse type from Entry.\n-\t// Must always set d.typeCache[off] before calling\n+\t// Must always set typeCache[off] before calling\n \t// d.Type recursively, to handle circular types correctly.\n \tvar typ Type\n \n@@ -290,7 +304,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t\t\treturn nil\n \t\t\t}\n \t\t\tif kid == nil {\n-\t\t\t\terr = DecodeError{"info", r.b.off, "unexpected end of DWARF entries"}\n+\t\t\t\terr = DecodeError{name, r.offset(), "unexpected end of DWARF entries"}\n \t\t\t\treturn nil\n \t\t\t}\n \t\t\tif kid.Tag == 0 {\n@@ -313,15 +327,21 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t// Get Type referred to by Entry's AttrType field.\n \t// Set err if error happens. Not having a type is an error.\n \ttypeOf := func(e *Entry) Type {\n-\t\ttoff, ok := e.Val(AttrType).(Offset)\n-\t\tif !ok {\n+\t\t\ttval := e.Val(AttrType)\n+\t\t\tvar t Type\n+\t\t\tswitch toff := tval.(type) {\n+\t\t\tcase Offset:\n+\t\t\t\tif t, err = d.readType(name, r.clone(), toff, typeCache); err != nil {\n+\t\t\t\t\treturn nil\n+\t\t\t\t}\n+\t\t\tcase uint64:\n+\t\t\t\tif t, err = d.sigToType(toff); err != nil {\n+\t\t\t\t\treturn nil\n+\t\t\t\t}\n+\t\t\tdefault:\
\t\t\t// It appears that no Type means "void".\n \t\t\treturn new(VoidType)\n \t\t}\n-\t\tvar t Type\n-\t\tif t, err = d.Type(toff); err != nil {\n-\t\t\treturn nil\n-\t\t}\n \t\treturn t\n \t}\n \n@@ -337,7 +357,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\tdimensions are in left to right order.\n \t\tt := new(ArrayType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tif t.Type = typeOf(e); err != nil {\n \t\t\tgoto Error\n \t\t}\n@@ -363,7 +383,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t\t\t}\n \t\t\t\tndim++\n \t\t\tcase TagEnumerationType:\n-\t\t\t\terr = DecodeError{"info", kid.Offset, "cannot handle enumeration type as array bound"}\n+\t\t\t\terr = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}\n \t\t\t\tgoto Error\n \t\t\t}\n \t\t}\n@@ -383,12 +403,12 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\tname, _ := e.Val(AttrName).(string)\n \t\tenc, ok := e.Val(AttrEncoding).(int64)\n \t\tif !ok {\n-\t\t\terr = DecodeError{"info", e.Offset, "missing encoding attribute for " + name}\n+\t\t\terr = DecodeError{name, e.Offset, "missing encoding attribute for " + name}\n \t\t\tgoto Error\n \t\t}\n \t\tswitch enc {\n \t\tdefault:\n-\t\t\terr = DecodeError{"info", e.Offset, "unrecognized encoding attribute value"}\n+\t\t\terr = DecodeError{name, e.Offset, "unrecognized encoding attribute value"}\n \t\t\tgoto Error\n \n \t\tcase encAddress:\n@@ -408,7 +428,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\tcase encUnsignedChar:\n \t\t\ttyp = new(UcharType)\n \t\t}\n-\t\td.typeCache[off] = typ\n+\t\ttypeCache[off] = typ\n \t\tt := typ.(interface {\n \t\t\tBasic() *BasicType\n \t\t}).Basic()\n@@ -433,7 +453,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t// There is much more to handle C++, all ignored for now.\n \t\tt := new(StructType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tswitch e.Tag {\n \t\tcase TagClassType:\n \t\t\tt.Kind = "class"\n@@ -453,12 +473,13 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t\t\tif f.Type = typeOf(kid); err != nil {\n \t\t\t\t\tgoto Error\n \t\t\t\t}\n-\t\t\t\tif loc, ok := kid.Val(AttrDataMemberLoc).([]byte); ok {\n+\t\t\t\tswitch loc := kid.Val(AttrDataMemberLoc).(type) {\n+\t\t\t\tcase []byte:\n \t\t\t\t\t// TODO: Should have original compilation\n \t\t\t\t\t// unit here, not unknownFormat.\n \t\t\t\t\tb := makeBuf(d, unknownFormat{}, "location", 0, loc)\n \t\t\t\t\tif b.uint8() != opPlusUconst {\n-\t\t\t\t\t\terr = DecodeError{"info", kid.Offset, "unexpected opcode"}\n+\t\t\t\t\t\terr = DecodeError{name, kid.Offset, "unexpected opcode"}\n \t\t\t\t\t\tgoto Error\n \t\t\t\t\t}\n \t\t\t\t\tf.ByteOffset = int64(b.uint())\n@@ -466,6 +487,8 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t\t\t\t\terr = b.err\n \t\t\t\t\t\tgoto Error\n \t\t\t\t\t}\n+\t\t\t\tcase int64:\n+\t\t\t\t\tf.ByteOffset = loc\n \t\t\t\t}\n \n \t\t\t\thaveBitOffset := false\n@@ -502,7 +525,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\tAttrType: subtype\n \t\tt := new(QualType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tif t.Type = typeOf(e); err != nil {\n \t\t\tgoto Error\n \t\t}\n@@ -526,7 +549,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\t\tAttrConstValue: value of constant\n \t\tt := new(EnumType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tt.EnumName, _ = e.Val(AttrName).(string)\n \t\tt.Val = make([]*EnumValue, 0, 8)\n \t\tfor kid := next(); kid != nil; kid = next() {\n@@ -552,7 +575,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\tAttrAddrClass: address class [ignored]\n \t\tt := new(PtrType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tif e.Val(AttrType) == nil {\n \t\t\tt.Type = &VoidType{}\n \t\t\tbreak\n@@ -571,7 +594,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\tTagUnspecifiedParameter: final ...\n \t\tt := new(FuncType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tif t.ReturnType = typeOf(e); err != nil {\n \t\t\tgoto Error\n \t\t}\n@@ -598,7 +621,7 @@ func (d *Data) Type(off Offset) (Type, error) {\n \t\t//\tAttrType: type definition [required]\n \t\tt := new(TypedefType)\n \t\ttyp = t\n-\t\td.typeCache[off] = t\n+\t\ttypeCache[off] = t\n \t\tt.Name, _ = e.Val(AttrName).(string)\n \t\tt.Type = typeOf(e)\n \t}\n@@ -620,7 +643,7 @@ Error:\n \t// If the parse fails, take the type out of the cache\n \t// so that the next call with this offset doesn't hit\n \t// the cache and return success.\n-\tdelete(d.typeCache, off)\n+\tdelete(typeCache, off)\n \treturn nil, err\n }\n \ndiff --git a/src/pkg/debug/dwarf/type_test.go b/src/pkg/debug/dwarf/type_test.go\nindex b5b255f6f4..2cb85e74bb 100644\n--- a/src/pkg/debug/dwarf/type_test.go\n+++ b/src/pkg/debug/dwarf/type_test.go\n@@ -73,6 +73,8 @@ func TestTypedefsMachO(t *testing.T) {\n \ttestTypedefs(t, machoData(t, "testdata/typedef.macho"), "macho")\n }\n \n+func TestTypedefsELFDwarf4(t *testing.T) { testTypedefs(t, elfData(t, "testdata/typedef.elf4"), "elf") }\n+\n func testTypedefs(t *testing.T, d *Data, kind string) {\n \tr := d.Reader()\n \tseen := make(map[string]bool)\n```
### `src/pkg/debug/dwarf/typeunit.go` (新規ファイル)
```go
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dwarf
import (
"fmt"
"strconv"
)
// Parse the type units stored in a DWARF4 .debug_types section. Each
// type unit defines a single primary type and an 8-byte signature.
// Other sections may then use formRefSig8 to refer to the type.
// The typeUnit format is a single type with a signature. It holds
// the same data as a compilation unit.
type typeUnit struct {
unit
toff Offset // Offset to signature type within data.
name string // Name of .debug_type section.
cache Type // Cache the type, nil to start.
}
// Parse a .debug_types section.
func (d *Data) parseTypes(name string, types []byte) error {
b := makeBuf(d, unknownFormat{}, name, 0, types)
for len(b.data) > 0 {
base := b.off
dwarf64 := false
n := b.uint32()
if n == 0xffffffff {
n64 := b.uint64()
if n64 != uint64(uint32(n64)) {
b.error("type unit length overflow")
return b.err
}
n = uint32(n64)
dwarf64 = true
}
hdroff := b.off
vers := b.uint16()
if vers != 4 {
b.error("unsupported DWARF version " + strconv.Itoa(int(vers)))
return b.err
}
var ao uint32
if !dwarf64 {
ao = b.uint32()
} else {
ao64 := b.uint64()
if ao64 != uint64(uint32(ao64)) {
b.error("type unit abbrev offset overflow")
return b.err
}
ao = uint32(ao64)
}
atable, err := d.parseAbbrev(ao)
if err != nil {
return err
}
asize := b.uint8()
sig := b.uint64()
var toff uint32
if !dwarf64 {
toff = b.uint32()
} else {
to64 := b.uint64()
if to64 != uint64(uint32(to64)) {
b.error("type unit type offset overflow")
return b.err
}
toff = uint32(to64)
}
boff := b.off
d.typeSigs[sig] = &typeUnit{
unit: unit{
base: base,
off: boff,
data: b.bytes(int(Offset(n) - (b.off - hdroff))),
atable: atable,
asize: int(asize),
vers: int(vers),
is64: dwarf64,
},
toff: Offset(toff),
name: name,
}
if b.err != nil {
return b.err
}
}
return nil
}
// Return the type for a type signature.
func (d *Data) sigToType(sig uint64) (Type, error) {
tu := d.typeSigs[sig]
if tu == nil {
return nil, fmt.Errorf("no type unit with signature %v", sig)
}
if tu.cache != nil {
return tu.cache, nil
}
b := makeBuf(d, tu, tu.name, tu.off, tu.data)
r := &typeUnitReader{d: d, tu: tu, b: b}
t, err := d.readType(tu.name, r, Offset(tu.toff), make(map[Offset]Type))
if err != nil {
return nil, err
}
tu.cache = t
return t, nil
}
// typeUnitReader is a typeReader for a tagTypeUnit.
type typeUnitReader struct {
d *Data
tu *typeUnit
b buf
err error
}
// Seek to a new position in the type unit.
func (tur *typeUnitReader) Seek(off Offset) {
tur.err = nil
doff := off - tur.tu.off
if doff < 0 || doff >= Offset(len(tur.tu.data)) {
tur.err = fmt.Errorf("%s: offset %d out of range; max %d", tur.tu.name, doff, len(tur.tu.data))
return
}
tur.b = makeBuf(tur.d, tur.tu, tur.tu.name, off, tur.tu.data[doff:])
}
// Next reads the next Entry from the type unit.
func (tur *typeUnitReader) Next() (*Entry, error) {
if tur.err != nil {
return nil, tur.err
}
if len(tur.tu.data) == 0 {
return nil, nil
}
e := tur.b.entry(tur.tu.atable, tur.tu.base)
if tur.b.err != nil {
tur.err = tur.b.err
return nil, tur.err
}
return e, nil
}
// clone returns a new reader for the type unit.
func (tur *typeUnitReader) clone() typeReader {
return &typeUnitReader{
d: tur.d,
tu: tur.tu,
b: makeBuf(tur.d, tur.tu, tur.tu.name, tur.tu.off, tur.tu.data),
}
}
// offset returns the current offset.
func (tur *typeUnitReader) offset() Offset {
return tur.b.off
}
src/pkg/debug/elf/file.go
--- a/src/pkg/debug/elf/file.go
+++ b/src/pkg/debug/elf/file.go
@@ -601,7 +601,44 @@ func (f *File) DWARF() (*dwarf.Data, error) {\n \t}\n \n \tabbrev, info, str := dat[0], dat[1], dat[2]\n-\treturn dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str)\n+\td, err := dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\t// Look for DWARF4 .debug_types sections.\n+\tfor i, s := range f.Sections {\n+\t\tif s.Name == ".debug_types" {\n+\t\t\tb, err := s.Data()\n+\t\t\tif err != nil && uint64(len(b)) < s.Size {\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\n+\t\t\tfor _, r := range f.Sections {\n+\t\t\t\tif r.Type != SHT_RELA && r.Type != SHT_REL {\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\t\t\t\tif int(r.Info) != i {\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\t\t\t\trd, err := r.Data()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\treturn nil, err\n+\t\t\t\t}\n+\t\t\t\terr = f.applyRelocations(b, rd)\n+\t\t\t\tif err != nil {\n+\t\t\t\t\treturn nil, err\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\terr = d.AddTypes(fmt.Sprintf("types-%d", i), b)\n+\t\t\tif err != nil {\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn d, nil\n }\n \n // Symbols returns the symbol table for f.\n```
## コアとなるコードの解説
このコミットの核心は、DWARF 4の型単位(Type Unit)をGoの `debug/dwarf` パッケージで解析できるようにすることです。
1. **`typeReader` インターフェースの導入**:
* `type.go` で定義された `typeReader` インターフェースは、デバッグ情報セクション(`.debug_info` または `.debug_types`)からDWARFエントリを読み込むための抽象化を提供します。これにより、型解析ロジックが、どのセクションからデータを読み込んでいるかに依存しなくなります。
* 既存の `Reader` 型(`.debug_info` を読み込む)と、新しく追加される `typeUnitReader`(`.debug_types` を読み込む)がこのインターフェースを実装します。
2. **`Data` 構造体への `typeSigs` の追加**:
* `open.go` の `Data` 構造体に `typeSigs map[uint64]*typeUnit` が追加されました。これは、DWARF 4の型単位が持つ8バイトのシグネチャをキーとして、対応する `typeUnit` オブジェクトを格納するためのマップです。これにより、シグネチャを使って型単位を効率的に検索できるようになります。
3. **`AddTypes` メソッドと `parseTypes` 関数の実装**:
* `open.go` の `AddTypes` メソッドは、`debug/elf` パッケージから `.debug_types` セクションの生データを受け取り、`parseTypes` 関数を呼び出します。
* `typeunit.go` に実装された `parseTypes` 関数は、`.debug_types` セクションのデータを解析し、各型単位のヘッダ情報(長さ、バージョン、アブレビエーションオフセット、シグネチャ、型オフセットなど)を読み取ります。読み取った情報から `typeUnit` オブジェクトを作成し、そのシグネチャをキーとして `Data.typeSigs` マップに格納します。
4. **`sigToType` メソッドの実装**:
* `typeunit.go` の `sigToType` メソッドは、与えられた型シグネチャに対応する `typeUnit` を `Data.typeSigs` から検索します。
* 見つかった `typeUnit` から `typeUnitReader` を作成し、そのリーダーを使って型単位内の型情報を読み込みます。この際、`readType` ヘルパー関数が再利用されます。これにより、型単位内の型定義も、通常の `.debug_info` セクション内の型定義と同じロジックで解析できます。
5. **`type.go` における `AttrType` の処理拡張**:
* `type.go` の `typeOf` クロージャ(属性 `AttrType` の値を型に変換する)が変更され、`AttrType` の値が `Offset`(従来の参照)だけでなく `uint64`(DWARF 4の型シグネチャ)である場合も処理できるようになりました。`uint64` の場合は、`d.sigToType()` を呼び出して、型シグネチャに対応する型情報を取得します。
6. **構造体フィールドオフセットの `int64` サポート**:
* `type.go` の `StructType` の解析部分で、`AttrDataMemberLoc` 属性の値が `[]byte`(DWARF式)だけでなく `int64`(直接的なバイトオフセット)である場合も処理できるようになりました。これは、DWARF 3で導入された、構造体フィールドのオフセットをより直接的に表現する形式に対応するためです。
7. **`debug/elf` における `.debug_types` セクションの検出と追加**:
* `debug/elf/file.go` の `DWARF()` メソッドが拡張され、ELFファイル内の `.debug_types` セクションを自動的に探し、見つかった場合は `dwarf.Data.AddTypes()` を呼び出して、その内容を `debug/dwarf` パッケージに渡すようになりました。これにより、GoのデバッグツールがDWARF 4形式のデバッグ情報を含むELFバイナリを透過的に処理できるようになります。
これらの変更により、Goのデバッグツールは、DWARF 4で生成されたデバッグ情報、特に型単位を正しく解釈し、デバッグ体験を向上させることができます。
## 関連リンク
* [DWARF Debugging Information Format Standard](https://dwarfstd.org/)
* [DWARF Version 4 Standard (PDF)](https://dwarfstd.org/doc/DWARF4.pdf)
* [Go issue: cmd/ld: support DWARF4 .debug_types sections](https://github.com/golang/go/issues/6786) (このコミットの背景にあるGoのIssue)
## 参考にした情報源リンク
* [DWARF Debugging Information Format Standard](https://dwarfstd.org/)
* [DWARF Version 4 Standard (PDF)](https://dwarfstd.org/doc/DWARF4.pdf)
* [Go issue: cmd/ld: support DWARF4 .debug_types sections](https://github.com/golang/go/issues/6786)
* Go言語の `debug/dwarf` および `debug/elf` パッケージのソースコード
* 一般的なELFファイルフォーマットとDWARFデバッグ情報の解説記事