[インデックス 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
フィールドを除外し、代わりにデバッガが期待する具体的な keys
と values
の配列情報を提供することです。これにより、デバッガが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表現を、ランタイムの内部実装の詳細から切り離し、デバッガがより直感的に理解できる keys
と values
の配列として提示できるようにします。これは、デバッグ体験の向上に直接貢献します。
具体的には、dwarf.c
内の synthesizemaptypes
関数が変更されました。この関数は、Goのマップ型に対するDWARF情報を合成する役割を担っています。以前は、汎用的な bucket
構造体の全ての子要素(フィールド)をそのままコピーしていましたが、この変更により、data
という名前のフィールドをコピー対象から除外するようになりました。そして、その代わりに、具体的なキーと値の型を持つ keys
と values
フィールドをDWARF情報として追加します。
コアとなるコードの変更箇所
変更は src/cmd/ld/dwarf.c
ファイルに集中しています。
-
copychildrenexcept
関数の追加とcopychildren
の変更:copychildrenexcept
という新しい静的関数が導入されました。この関数は、src
DIEの子要素をdst
DIEにコピーしますが、except
で指定されたDIE(子要素)はコピーから除外します。- 既存の
copychildren
関数は、このcopychildrenexcept
をexcept
にnil
を渡す形で呼び出すように変更され、互換性を保ちつつ新しい除外ロジックを再利用できるようにしました。
-
synthesizemaptypes
関数の変更:- この関数内で、マップのバケット (
dwhb
) のDWARF情報を生成する際に、copychildren
の代わりにcopychildrenexcept
が使用されるようになりました。 - 具体的には、
copychildrenexcept(dwhb, bucket, find(bucket, "data"));
という行が追加されました。これは、汎用的なbucket
構造体からdata
という名前のフィールドを見つけ出し、そのフィールドをdwhb
へのコピーから除外することを意味します。 - この変更により、
map.bucket
のDWARF情報からdata
フィールドが取り除かれ、その後にkeys
とvalues
フィールドが適切に追加されるようになります。
- この関数内で、マップのバケット (
コアとなるコードの解説
// 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.bucket
のdata
フィールドのような、デバッグ情報として不適切な内部フィールドをDWARFTreeから削除することが可能になります。 -
synthesizemaptypes
: この関数は、Goのマップ型 (map
) のDWARF情報を合成する中心的な役割を担っています。Goのマップはコンパイル時に具体的な型が決まるため、リンカはこれらの具体的な型情報に基づいて、デバッガが理解できるmap.bucket
のDWARF表現を生成する必要があります。- 変更前は、汎用的な
bucket
構造体のすべてのフィールドをそのままコピーしていました。 - 変更後は、
copychildrenexcept(dwhb, bucket, find(bucket, "data"));
という呼び出しにより、bucket
DIEからdata
という名前のフィールドを検索し、そのフィールドをdwhb
(マップのバケットを表すDIE) へのコピーから除外します。 - この除外の後、
keys
とvalues
という名前の新しいフィールドがdwhb
に追加されます。これらのフィールドは、マップの実際のキーと値の型 (dwhk
とdwhv
) を参照し、適切なオフセット情報 (BucketSize + PtrSize
など) を持ちます。これにより、デバッガはmap.bucket
のメモリレイアウトを正確に解釈し、キーと値のペアにアクセスできるようになります。
- 変更前は、汎用的な
この一連の変更により、Goのリンカは、ランタイムの内部実装に深く依存する data
フィールドをデバッグ情報から隠蔽し、デバッガがGoのマップをより自然かつ正確に検査できるような、高レベルで意味のあるDWARF情報を提供するようになりました。
関連リンク
- Go CL 81160044: https://golang.org/cl/81160044
参考にした情報源リンク
- Go map internal structure:
- DWARF debugging information:
- Go linker (
cmd/ld
) and DWARF generation:- https://github.com/golang/go/tree/master/src/cmd/link/internal/ld
- https://go.dev/src/cmd/link/internal/ld/dwarf.go (Note: While the commit references
dwarf.c
, modern Go linker DWARF generation is primarily indwarf.go
.)