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

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

このコミットは、Go言語のリンカ (cmd/ld) におけるDWARFデバッグ情報の生成に関する修正です。具体的には、Goのマップ型 (map) の内部構造である map.bucket から、デバッグ情報として不要な汎用的な data フィールドを除外し、デバッガがマップのキーと値を正確に解釈できるようにするための変更です。

コミット

commit 059c10b552d8e8331a5621fa73d1fcb914cc913e
Author: Keith Randall <khr@golang.org>
Date:   Wed Apr 2 19:46:47 2014 -0700

    cmd/ld: get rid of map.bucket's data field from dwarf info.
    
    The data field is the generic array that acts as a standin
    for the keys and values arrays for the generic runtime code.
    We want to substitute the keys and values arrays for the data
    array, not just add keys and values in addition to it.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/81160044

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

https://github.com/golang/go/commit/059c10b552d8e8331a5621fa73d1fcb914cc913e

元コミット内容

cmd/ld: get rid of map.bucket's data field from dwarf info.

The data field is the generic array that acts as a standin
for the keys and values arrays for the generic runtime code.
We want to substitute the keys and values arrays for the data
array, not just add keys and values in addition to it.

LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/81160044

変更の背景

Goのマップ型 (map) は、内部的にはハッシュテーブルとして実装されており、そのデータは「バケット」と呼ばれる構造体に格納されます。Goランタイムの内部では、これらのバケットはジェネリックな data フィールドを持っており、これはキーと値のペアを格納するための汎用的な配列として機能します。この data フィールドは、Goのマップが任意のキーと値の型を扱えるようにするための、ランタイム内部の抽象化メカニズムです。

しかし、デバッグ情報(特にDWARF形式)を生成する際、このジェネリックな data フィールドをそのままDWARF情報として公開すると、デバッガがマップの実際のキーと値の構造を正確に解釈できなくなる問題がありました。デバッガは、マップのバケットが持つべき具体的な keys 配列と values 配列を認識し、それらを通じてマップの内容を検査できるようにする必要があります。もし data フィールドがDWARF情報に残ったままだと、デバッガは data フィールドと keys/values 配列の両方を認識してしまい、混乱を招く可能性がありました。

このコミットの目的は、Goリンカ (cmd/ld) が生成するDWARFデバッグ情報において、map.bucket 型から汎用的な data フィールドを除外し、代わりにデバッガが期待する具体的な keysvalues の配列情報を提供することです。これにより、デバッガがGoのマップの内部構造をより正確に理解し、デバッグ体験を向上させることが可能になります。

前提知識の解説

  • DWARF (Debugging With Attributed Record Formats):

    • DWARFは、ソースレベルのデバッグを可能にするために、コンパイラやリンカによって生成される標準的なデバッグ情報フォーマットです。実行可能ファイルに埋め込まれるか、別のファイルとして提供されます。
    • デバッガはDWARF情報を使用して、プログラムの実行中に変数名、型情報、関数名、ソースコードの行番号などを解決し、開発者がソースコードレベルでデバッグできるようにします。
    • DWARFは、プログラムの型システム(構造体、配列、ポインタなど)を記述するための豊富なメカニズムを提供します。各型や変数、関数などは「Debugging Information Entry (DIE)」として表現され、それぞれが属性(名前、型、場所など)を持ちます。
  • Goのマップ (map) の内部構造:

    • Goのマップは、キーと値のペアを格納するための組み込み型です。内部的にはハッシュテーブルとして実装されています。
    • マップのデータは「バケット」(runtime.bmap 構造体、または単に bmap と呼ばれることが多い)と呼ばれる固定サイズの配列に格納されます。各バケットは通常、最大8つのキーと値のペアを保持できます。
    • Goランタイムの内部コードでは、バケットのキーと値の配列は、型アサーションやポインタ演算を通じてアクセスされるジェネリックな data フィールドとして扱われることがあります。これは、マップが任意のキーと値の型を扱えるようにするための汎用的なメカニズムです。
    • 具体的には、bmap 構造体は tophash 配列、その後にキーがまとめて格納され、さらにその後に値がまとめて格納されるメモリレイアウトを持ちます。また、オーバーフローバケットへのポインタも持ち、ハッシュ衝突を処理します。
    • デバッグの観点からは、この汎用的な data フィールドではなく、具体的なキーの型と値の型を持つ keys 配列と values 配列として情報が提供されることが望ましいです。
  • Goリンカ (cmd/ld):

    • cmd/ld はGo言語のリンカであり、コンパイルされたオブジェクトファイル(.o ファイル)を結合して実行可能ファイルや共有ライブラリを生成します。
    • リンカの重要な役割の一つは、デバッグ情報を実行可能ファイルに埋め込むことです。これには、プログラムのシンボル情報、型情報、ソースコードとのマッピングなどが含まれます。
    • cmd/ld は、Goの型システムに関する情報を受け取り、それをDWARF形式に変換して出力します。この変換プロセスにおいて、Goの内部的な型表現とDWARFの型表現の間のマッピングが適切に行われる必要があります。このコミットの時点では、src/cmd/ld/dwarf.c がDWARF情報の生成の一部を担っていました。

技術的詳細

Goのマップは、その汎用性のため、ランタイム内部で型アサーションやポインタ演算を多用します。特に map.bucket 構造体は、キーと値の実際の型に依存しない形で data フィールドを持っていました。この data フィールドは、ランタイムがキーと値を操作するための抽象的なポインタとして機能します。

しかし、デバッガがプログラムのメモリを検査する際、DWARF情報に基づいて型を解釈します。もし map.bucket のDWARF情報に data フィールドが含まれていると、デバッガはそのフィールドを認識し、誤った型情報や冗長な情報を提供してしまう可能性がありました。デバッガが本当に必要としているのは、マップの具体的なキーと値の型に基づいた keys 配列と values 配列の情報です。

このコミットは、リンカが map.bucket のDWARF情報を生成する際に、data フィールドを意図的に除外するロジックを追加します。これにより、リンカは map.bucket のDWARF表現を、ランタイムの内部実装の詳細から切り離し、デバッガがより直感的に理解できる keysvalues の配列として提示できるようにします。これは、デバッグ体験の向上に直接貢献します。

具体的には、dwarf.c 内の synthesizemaptypes 関数が変更されました。この関数は、Goのマップ型に対するDWARF情報を合成する役割を担っています。以前は、汎用的な bucket 構造体の全ての子要素(フィールド)をそのままコピーしていましたが、この変更により、data という名前のフィールドをコピー対象から除外するようになりました。そして、その代わりに、具体的なキーと値の型を持つ keysvalues フィールドをDWARF情報として追加します。

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

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

  1. copychildrenexcept 関数の追加と copychildren の変更:

    • copychildrenexcept という新しい静的関数が導入されました。この関数は、src DIEの子要素を dst DIEにコピーしますが、except で指定されたDIE(子要素)はコピーから除外します。
    • 既存の copychildren 関数は、この copychildrenexceptexceptnil を渡す形で呼び出すように変更され、互換性を保ちつつ新しい除外ロジックを再利用できるようにしました。
  2. synthesizemaptypes 関数の変更:

    • この関数内で、マップのバケット (dwhb) のDWARF情報を生成する際に、copychildren の代わりに copychildrenexcept が使用されるようになりました。
    • 具体的には、copychildrenexcept(dwhb, bucket, find(bucket, "data")); という行が追加されました。これは、汎用的な bucket 構造体から data という名前のフィールドを見つけ出し、そのフィールドを dwhb へのコピーから除外することを意味します。
    • この変更により、map.bucket のDWARF情報から data フィールドが取り除かれ、その後に keysvalues フィールドが適切に追加されるようになります。

コアとなるコードの解説

// src/cmd/ld/dwarf.c

// Copies src's children into dst. Copies attributes by value.
// DWAttr.data is copied as pointer only.  If except is one of
// the top-level children, it will not be copied.
static void
copychildrenexcept(DWDie *dst, DWDie *src, DWDie *except)
{
 	DWDie *c;
 	DWAttr *a;
 
 	for (src = src->child; src != nil; src = src->link) {
		if(src == except) // ここで除外対象のDIEをスキップ
			continue;
 		c = newdie(dst, src->abbrev, getattr(src, DW_AT_name)->data);
 		for (a = src->attr; a != nil; a = a->link)
 			newattr(c, a->atr, a->cls, a->value, a->data);
		copychildrenexcept(c, src, nil); // 再帰呼び出しでは除外は行わない
 	}
 	reverselist(&dst->child);
}

static void
copychildren(DWDie *dst, DWDie *src)
{
	copychildrenexcept(dst, src, nil); // 既存のcopychildrenはexceptなしで呼び出す
}

// ... (中略) ...

// synthesizemaptypes関数内
// ...
		// Copy over all fields except the field "data" from the generic bucket.
		// "data" will be replaced with keys/values below.
		copychildrenexcept(dwhb, bucket, find(bucket, "data")); // ここでdataフィールドを除外
		
		fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "keys");
		newrefattr(fld, DW_AT_type, dwhk);
		newmemberoffsetattr(fld, BucketSize + PtrSize);

		// ... (valuesフィールドの追加など) ...
  • copychildrenexcept: この関数は、DWARFのDIE(Debugging Information Entry)の階層構造をコピーする際に、特定の子DIEを除外する機能を提供します。except 引数に除外したい子DIEのポインタを渡すことで、そのDIEとその属性、さらにその子孫もコピーされなくなります。これにより、map.bucketdata フィールドのような、デバッグ情報として不適切な内部フィールドをDWARFTreeから削除することが可能になります。

  • synthesizemaptypes: この関数は、Goのマップ型 (map) のDWARF情報を合成する中心的な役割を担っています。Goのマップはコンパイル時に具体的な型が決まるため、リンカはこれらの具体的な型情報に基づいて、デバッガが理解できる map.bucket のDWARF表現を生成する必要があります。

    • 変更前は、汎用的な bucket 構造体のすべてのフィールドをそのままコピーしていました。
    • 変更後は、copychildrenexcept(dwhb, bucket, find(bucket, "data")); という呼び出しにより、bucket DIEから data という名前のフィールドを検索し、そのフィールドを dwhb (マップのバケットを表すDIE) へのコピーから除外します。
    • この除外の後、keysvalues という名前の新しいフィールドが dwhb に追加されます。これらのフィールドは、マップの実際のキーと値の型 (dwhkdwhv) を参照し、適切なオフセット情報 (BucketSize + PtrSize など) を持ちます。これにより、デバッガは map.bucket のメモリレイアウトを正確に解釈し、キーと値のペアにアクセスできるようになります。

この一連の変更により、Goのリンカは、ランタイムの内部実装に深く依存する data フィールドをデバッグ情報から隠蔽し、デバッガがGoのマップをより自然かつ正確に検査できるような、高レベルで意味のあるDWARF情報を提供するようになりました。

関連リンク

参考にした情報源リンク