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

[インデックス 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と属性を生成する役割を担っています。

uint8uint16

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生成ロジックの修正です。

  1. カスタム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: スライス、チャネル、マップの値の型(要素型)への参照を格納するために使用されます。
  2. 属性IDの型拡張:

    • DWAttrForm 構造体の attr フィールドが uint8 から uint16 に変更されました。
    • DWAttr 構造体の atr フィールドも uint8 から uint16 に変更されました。
    • newattr, getattr, newrefattr といった属性操作関数の引数も uint8 から uint16 に変更されました。 この変更は、新しいカスタム属性の識別子(0x2900 など)が uint8 の範囲(0-255)を超えるため、必須でした。これにより、より多くのカスタム属性を定義できるようになります。
  3. 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固有のメタデータが付与されるようになります。

  4. 属性書き込みロジックの改善:

    • writeabbrev 関数が、アブレビエーションテーブルの属性を uleb128 形式で書き出すように修正されました。以前は strlen を使用していましたが、これは uint8 の配列を文字列として扱っていたため、uint16 に拡張された属性IDには対応できませんでした。新しい実装では、DWAttrForm の配列をループし、各属性IDと形式を個別に uleb128put で書き出すことで、より汎用的な処理を実現しています。
    • putattrs 関数も、属性をDIEに書き込む際のロジックが改善されました。以前は属性を一時的な配列に格納していましたが、新しい実装では、アブレビエーションで定義された属性の順序に従って、DIEに実際に存在する属性を検索し、書き出すようになりました。これにより、属性の順序が保証され、より正確なDWARF情報が生成されます。
  5. 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_elemDW_AT_go_key に置き換えられました。また、すべてのGoの型に対して DW_AT_go_kind 属性が追加されるようになりました。

これらの変更により、Goのリンカは、Go固有の型構造をより正確かつ標準的な方法でDWARFデバッグ情報に表現できるようになり、デバッガがGoプログラムの内部状態をより深く理解するための基盤が強化されました。

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

src/cmd/ld/dwarf.c ファイルが変更されています。

  1. DWAttrForm 構造体の変更:

    typedef struct DWAttrForm DWAttrForm;
    struct DWAttrForm {
    -	uint8 attr;
    +	uint16 attr;
    	uint8 form;
    };
    
  2. 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
    };
    
  3. 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
    	},
    
  4. 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;
    }
    
  5. 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;
    };
    
  6. 属性操作関数の引数型変更: newattr, getattr, newrefattrattr 引数が uint8 から uint16 に変更されました。

  7. 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:;
    +	}
    }
    
  8. 古い内部属性の削除:

    -// 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
    -};
    
  9. defgotype 関数でのGo固有属性の使用: KindChan, KindMap, KindArray などのGoの型を処理する際に、DW_AT_internal_elem_type などが DW_AT_go_elemDW_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);
    
  10. synthesizeslicetypes, synthesizemaptypes, synthesizechantypes 関数でのGo固有属性の使用: これらの関数でも、古い内部属性が新しいGo固有属性に置き換えられました。

コアとなるコードの解説

このコミットの核心は、Go言語のリンカが生成するDWARFデバッグ情報に、Go固有の型セマンティクスを正確に反映させるための変更です。

  1. DWAttrFormDWAttruint16 への拡張: これは、Go固有のカスタム属性を定義するための基盤となります。DWARFの標準属性は uint8 の範囲に収まりますが、Goの複雑な型システムを表現するためには、標準属性だけでは不十分です。0x2900 から始まる新しい属性IDは uint8 の範囲を超えるため、これらの構造体の attr および atr フィールドを uint16 に拡張する必要がありました。これにより、Goは独自の属性を定義し、デバッグ情報に埋め込むことが可能になります。

  2. 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: スライス、チャネル、マップの値の型(要素型)への参照を格納します。これにより、デバッガはこれらの複合型の要素の型を特定し、内部の要素を正しく表示できます。
  3. writeabbrevputattrs のロジック改善: これらの関数は、DWARFのDIEと属性をバイナリ形式で書き出す役割を担っています。以前の実装は、属性IDが uint8 の範囲に収まることを前提としていたり、属性の書き出し順序が厳密でなかったりする可能性がありました。今回の変更では、uint16 の属性IDに対応するために uleb128put を使用し、アブレビエーションで定義された属性の順序に厳密に従って属性を書き出すようにロジックが修正されました。これにより、生成されるDWARF情報の正確性と互換性が向上します。

  4. defgotype におけるGo型とDWARF属性のマッピング: defgotype 関数は、Goのランタイム型情報 (_type 構造体など) をDWARFのDIEに変換する中心的な部分です。このコミットでは、Goのスライス、マップ、チャネルといった型がDWARFに変換される際に、新しく定義された DW_AT_go_elemDW_AT_go_key 属性が使用されるようになりました。これにより、Goのリンカは、Goの型システムが持つ豊かなセマンティクスをDWARFデバッグ情報に直接反映させることが可能になり、デバッガがGoの複合型をより深く理解し、デバッグ体験を向上させるための重要なステップとなります。

これらの変更は、Goのデバッグ体験を向上させるための基盤を構築し、デバッガがGoのプログラムの内部構造をより正確に可視化できるようにすることに貢献しています。

関連リンク

参考にした情報源リンク

  • 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トラッカーで、デバッグ情報や型システムに関する議論が参照されることがあります。