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

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

コミット

コミットハッシュ: 79b106ec62542d589fcbc9a079e1bada7e7db46c 作者: Rob Pike r@golang.org 日付: Tue Jul 15 01:38:05 2014 +0000

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

https://github.com/golang/go/commit/79b106ec62542d589fcbc9a079e1bada7e7db46c

元コミット内容

cmd/ld: generate correct upper bound value for array types.

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/115820043

変更の背景

このコミットは、Go言語のリンカ (cmd/ld) が配列型に対するDWARFデバッグ情報を生成する際に発生していたバグを修正するものです。具体的には、DWARFの DW_AT_upper_bound 属性に誤った値が設定されていたため、デバッガがGoの配列のサイズを正しく解釈できない問題がありました。

DWARFの仕様において、DW_AT_upper_bound は配列の最大インデックス(つまり、要素数から1を引いた値)を示すべきです。しかし、リンカは誤って配列の要素数そのものを DW_AT_upper_bound として出力していました。この不整合により、デバッガが配列の境界を誤認し、デバッグセッション中に配列の内容を正確に表示できないなどの問題を引き起こす可能性がありました。

この修正の目的は、リンカが生成するDWARF情報がDWARF標準に準拠するようにし、Goプログラムのデバッグ体験を向上させることにあります。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。

  • Goのリンカ (cmd/ld): Go言語のビルドプロセスにおいて、cmd/ld は非常に重要な役割を担っています。Goのソースコードはまずコンパイラによってオブジェクトファイルに変換され、その後、これらのオブジェクトファイルと必要なライブラリがリンカによって結合され、最終的な実行可能バイナリが生成されます。リンカは、シンボルの解決、アドレスの割り当て、そしてデバッグ情報の埋め込みなど、多岐にわたるタスクを実行します。

  • DWARF (Debugging With Attributed Record Formats): DWARFは、コンパイルされたプログラムのデバッグ情報を格納するための標準的なフォーマットです。デバッガ(GDBやDelveなど)は、このDWARF情報を使用して、実行中のプログラムのメモリ上のアドレスをソースコードの行番号、変数名、型情報などにマッピングします。これにより、開発者はソースコードレベルでプログラムをステップ実行したり、変数の値を検査したりすることが可能になります。DWARF情報は、Debugging Information Entries (DIEs) と呼ばれるツリー構造で構成され、各DIEは関数、変数、型などのプログラム要素を記述し、属性 (Attributes) を通じて詳細な情報を提供します。

  • DW_AT_upper_bound 属性: DWARFにおいて、DW_AT_upper_bound は、配列やサブレンジの最大インデックスを示す属性です。例えば、要素数 N の配列がある場合、インデックスは 0 から N-1 までとなります。したがって、DW_AT_upper_bound には N-1 の値が設定されるべきです。これに対し、DW_AT_count 属性は配列の要素数そのもの (N) を示します。デバッガはこれらの属性を組み合わせて配列のサイズと構造を理解します。

  • Goの配列型: Go言語において、配列の長さは型の一部です。例えば、[5]int[10]int は異なる型として扱われます。これは、Goの型システムが配列の長さを厳密にチェックし、コンパイル時に固定されたサイズを持つことを保証するためです。Goランタイムは、これらの配列型の内部表現を管理し、必要に応じてその長さをデコードするメカニズムを持っています。

  • decodetype_arraylen 関数: decodetype_arraylen は、Goランタイムの内部で使用される関数で、Goの型情報の内部表現から配列の長さを抽出する役割を担っています。この関数は、Goの型システムが配列の長さを正確に認識し、リフレクションやガベージコレクションなどのランタイム操作で利用できるようにするために不可欠です。

技術的詳細

このコミットは、Goリンカ (cmd/ld) 内の2つのC言語ソースファイル (src/cmd/ld/decodesym.csrc/cmd/ld/dwarf.c) に変更を加えています。

問題点: Goリンカは、Goの配列型に対応するDWARFデバッグ情報を生成する際に、DW_AT_upper_bound 属性に誤った値を設定していました。具体的には、配列の要素数(例: [5]int5)をそのまま DW_AT_upper_bound に設定していました。しかし、DWARFの仕様では DW_AT_upper_bound は最大インデックス(例: [5]int4)を意味するため、デバッガがこの情報を読み取ると、配列のサイズを1つ大きく誤解釈してしまう可能性がありました。

修正点:

  1. src/cmd/ld/decodesym.c の変更: decodetype_arraylen 関数は、Goの型情報シンボル (LSym *s) から配列の長さをデコードします。この関数内の decode_inuxi の呼び出しにおいて、配列の長さを読み取るためのオフセットが s->p + commonsize() + PtrSize から s->p + commonsize() + 2*PtrSize に変更されました。 この変更は、Goの型情報の内部メモリレイアウトが変更されたことに対応している可能性が高いです。Goの型情報は、リンカがシンボルを処理する際に内部的に使用する構造体であり、その構造体のフィールドのオフセットが変更された場合、このようにポインタのオフセットを調整する必要があります。これにより、decodetype_arraylen が常に正しいメモリ位置から配列の長さを正確に読み取れるようになります。

  2. src/cmd/ld/dwarf.c の変更: defgotype 関数は、Goの型情報をDWARF形式のDIE (Debugging Information Entry) に変換する主要な関数です。配列型 (DW_ABRV_ARRAYTYPE) の処理において、DW_AT_upper_bound 属性を設定する行が修正されました。 変更前: newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, decodetype_arraylen(gotype), 0); 変更後: newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, decodetype_arraylen(gotype)-1, 0); // -1: want upper bound, not count. この修正により、decodetype_arraylen(gotype) が返す配列の要素数から 1 を減算した値が DW_AT_upper_bound に設定されるようになりました。これにより、DWARFの仕様に準拠した正しい最大インデックス値がデバッグ情報に書き込まれることになります。コメント // -1: want upper bound, not count. は、この変更の意図(要素数ではなく最大インデックスを設定する)を明確に示しています。

影響: この修正により、Goのリンカが生成するDWARFデバッグ情報がより正確になり、デバッガがGoの配列型を正しく解釈できるようになります。結果として、デバッグセッション中に配列の要素数や境界が正確に表示され、開発者はより効率的にGoプログラムをデバッグできるようになります。

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

diff --git a/src/cmd/ld/decodesym.c b/src/cmd/ld/decodesym.c
index da48d3786e..1773387f54 100644
--- a/src/cmd/ld/decodesym.c
+++ b/src/cmd/ld/decodesym.c
@@ -104,7 +104,7 @@ decodetype_arrayelem(LSym *s)
 vlong
 decodetype_arraylen(LSym *s)
 {
-	return decode_inuxi(s->p + commonsize()+PtrSize, PtrSize);
+	return decode_inuxi(s->p + commonsize()+2*PtrSize, PtrSize);
 }
 
 // Type.PtrType.elem
@@ -120,6 +120,7 @@ decodetype_mapkey(LSym *s)
 {
 	return decode_reloc_sym(s, commonsize());	// 0x1c / 0x30
 }
+
 LSym*
 decodetype_mapvalue(LSym *s)
 {
diff --git a/src/cmd/ld/dwarf.c b/src/cmd/ld/dwarf.c
index 3c8b33f6b4..f3e8781641 100644
--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -992,7 +992,7 @@ defgotype(LSym *gotype)
 		s = decodetype_arrayelem(gotype);
 		newrefattr(die, DW_AT_type, defgotype(s));
 		fld = newdie(die, DW_ABRV_ARRAYRANGE, "range");
-		newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, decodetype_arraylen(gotype), 0);
+		newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, decodetype_arraylen(gotype)-1, 0); // -1: want upper bound, not count.
 		newrefattr(fld, DW_AT_type, find_or_diag(&dwtypes, "uintptr"));
 		break;
 

コアとなるコードの解説

  • src/cmd/ld/decodesym.c の変更: decodetype_arraylen 関数は、Goの型情報シンボル (LSym *s) から配列の長さをデコードする役割を担っています。この関数は、シンボル s のメモリ上のデータ (s->p) から、commonsize()PtrSize (ポインタのサイズ) を加算したオフセット位置にある値を読み取っていました。 変更前: return decode_inuxi(s->p + commonsize()+PtrSize, PtrSize); 変更後: return decode_inuxi(s->p + commonsize()+2*PtrSize, PtrSize); この変更は、Goの型情報の内部構造、特に配列の長さを格納するフィールドのオフセットが変更されたことを示唆しています。PtrSize2*PtrSize になったことで、配列の長さが格納されている正しいメモリ位置から値を読み取れるようになり、decodetype_arraylen が正確な配列長を返すことが保証されます。

  • src/cmd/ld/dwarf.c の変更: defgotype 関数は、Goの型情報をDWARF形式のDIEに変換する処理を行います。配列型を処理するブロック内で、DW_AT_upper_bound 属性を設定する部分が修正されました。 変更前は、decodetype_arraylen(gotype) が返す配列の要素数(例: [5]int なら 5)をそのまま DW_AT_upper_bound に設定していました。 変更後: newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, decodetype_arraylen(gotype)-1, 0); この変更により、decodetype_arraylen(gotype) が返す要素数から 1 を減算した値が DW_AT_upper_bound に設定されるようになりました。これにより、DWARFの仕様で定義されている「最大インデックス」という正しい意味での値がデバッグ情報に書き込まれます。追加されたコメント // -1: want upper bound, not count. は、この修正の意図を明確に示しており、要素数ではなく最大インデックスを設定することが目的であることを強調しています。

関連リンク

参考にした情報源リンク