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

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

このコミットは、Go言語のランタイムにおけるGDB (GNU Debugger) でのマップ(ハッシュマップ)の表示に関するバグを修正するものです。具体的には、GDBがGoのマップ型を正しく解釈し、その内容を適切に表示できるようにするための変更が含まれています。

コミット

commit fb3ed166e39fd13689b35ca7aa2496e113cbeaf5
Author: Keith Randall <khr@golang.org>
Date:   Fri Mar 29 11:04:07 2013 -0700

    runtime: fix gdb printing of maps
    Fixes #5098
    
    R=minux.ma, bradfitz, khr, rsc
    CC=golang-dev
    https://golang.org/cl/7746045
---
 src/cmd/ld/dwarf.c             | 72 +++++++++++++++++++++++++++++++++++++++---
 src/pkg/runtime/hashmap.c      |  2 +-\
 src/pkg/runtime/runtime-gdb.py | 49 ++++++++++++++++------------
 3 files changed, 98 insertions(+), 25 deletions(-)

diff --git a/src/cmd/ld/dwarf.c b/src/cmd/ld/dwarf.c
index 4bf788e64e..436e1e67ef 100644
--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -1130,14 +1130,24 @@ mkinternaltypename(char *base, char *arg1, char *arg2)
 	return n;
 }
 
+// synthesizemaptypes is way too closely married to runtime/hashmap.c
+enum {
+	MaxKeySize = 128,
+	MaxValSize = 128,
+	BucketSize = 8,
+};
 
 static void
 synthesizemaptypes(DWDie *die)
 {
 
-\tDWDie *hash, *dwh, *keytype, *valtype;
+\tDWDie *hash, *bucket, *dwh, *dwhk, *dwhv, *dwhb, *keytype, *valtype, *fld;
+\tint indirect_key, indirect_val;
+\tint keysize, valsize;
+\tDWAttr *a;\n 
-\thash\t\t= defgotype(lookup_or_diag(\"type.runtime.hmap\"));
+\thash\t\t= walktypedef(defgotype(lookup_or_diag(\"type.runtime.hmap\")));
+\tbucket\t\t= walktypedef(defgotype(lookup_or_diag(\"type.runtime.bucket\")));
 \n \tif (hash == nil)
 \t\treturn;
@@ -1146,8 +1156,59 @@ synthesizemaptypes(DWDie *die)
 \t\tif (die->abbrev != DW_ABRV_MAPTYPE)
 \t\t\tcontinue;
 
-\t\tkeytype = (DWDie*) getattr(die, DW_AT_internal_key_type)->data;
-\t\tvaltype = (DWDie*) getattr(die, DW_AT_internal_val_type)->data;
+\t\tkeytype = walktypedef((DWDie*) getattr(die, DW_AT_internal_key_type)->data);
+\t\tvaltype = walktypedef((DWDie*) getattr(die, DW_AT_internal_val_type)->data);\n+\n+\t\t// compute size info like hashmap.c does.\n+\t\ta = getattr(keytype, DW_AT_byte_size);\n+\t\tkeysize = a ? a->value : PtrSize;  // We don\'t store size with Pointers\n+\t\ta = getattr(valtype, DW_AT_byte_size);\n+\t\tvalsize = a ? a->value : PtrSize;\n+\t\tindirect_key = 0;\n+\t\tindirect_val = 0;\n+\t\tif(keysize > MaxKeySize) {\n+\t\t\tkeysize = PtrSize;\n+\t\t\tindirect_key = 1;\n+\t\t}\n+\t\tif(valsize > MaxValSize) {\n+\t\t\tvalsize = PtrSize;\n+\t\t\tindirect_val = 1;\n+\t\t}\n+\n+\t\t// Construct type to represent an array of BucketSize keys\n+\t\tdwhk = newdie(&dwtypes, DW_ABRV_ARRAYTYPE,\n+\t\t\t      mkinternaltypename(\"[]key\",\n+\t\t\t\t\t\t getattr(keytype, DW_AT_name)->data, nil));\n+\t\tnewattr(dwhk, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize * keysize, 0);\n+\t\tnewrefattr(dwhk, DW_AT_type, indirect_key ? defptrto(keytype) : keytype);\n+\t\tfld = newdie(dwhk, DW_ABRV_ARRAYRANGE, \"size\");\n+\t\tnewattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, BucketSize, 0);\n+\t\tnewrefattr(fld, DW_AT_type, find_or_diag(&dwtypes, \"uintptr\"));\n+\t\t\n+\t\t// Construct type to represent an array of BucketSize values\n+\t\tdwhv = newdie(&dwtypes, DW_ABRV_ARRAYTYPE, \n+\t\t\t      mkinternaltypename(\"[]val\",\n+\t\t\t\t\t\t getattr(valtype, DW_AT_name)->data, nil));\n+\t\tnewattr(dwhv, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize * valsize, 0);\n+\t\tnewrefattr(dwhv, DW_AT_type, indirect_val ? defptrto(valtype) : valtype);\n+\t\tfld = newdie(dwhv, DW_ABRV_ARRAYRANGE, \"size\");\n+\t\tnewattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, BucketSize, 0);\n+\t\tnewrefattr(fld, DW_AT_type, find_or_diag(&dwtypes, \"uintptr\"));\n+\n+\t\t// Construct bucket<K,V>\n+\t\tdwhb = newdie(&dwtypes, DW_ABRV_STRUCTTYPE,\n+\t\t\t      mkinternaltypename(\"bucket\",\n+\t\t\t\t\t\t getattr(keytype, DW_AT_name)->data,\n+\t\t\t\t\t\t getattr(valtype, DW_AT_name)->data));\n+\t\tcopychildren(dwhb, bucket);\n+\t\tfld = newdie(dwhb, DW_ABRV_STRUCTFIELD, \"keys\");\n+\t\tnewrefattr(fld, DW_AT_type, dwhk);\n+\t\tnewmemberoffsetattr(fld, BucketSize + PtrSize);\n+\t\tfld = newdie(dwhb, DW_ABRV_STRUCTFIELD, \"values\");\n+\t\tnewrefattr(fld, DW_AT_type, dwhv);\n+\t\tnewmemberoffsetattr(fld, BucketSize + PtrSize + BucketSize * keysize);\n+\t\tnewattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize + PtrSize + BucketSize * keysize + BucketSize * valsize, 0);\n+\t\tsubstitutetype(dwhb, \"overflow\", defptrto(dwhb));\n \n \t\t// Construct hash<K,V>\n \t\tdwh = newdie(&dwtypes, DW_ABRV_STRUCTTYPE,\n@@ -1155,9 +1216,12 @@ synthesizemaptypes(DWDie *die)\n \t\t\t\tgetattr(keytype, DW_AT_name)->data,\n \t\t\t\tgetattr(valtype, DW_AT_name)->data));\n \t\tcopychildren(dwh, hash);\n+\t\tsubstitutetype(dwh, \"buckets\", defptrto(dwhb));\n+\t\tsubstitutetype(dwh, \"oldbuckets\", defptrto(dwhb));\n \t\tnewattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT,\n \t\t\tgetattr(hash, DW_AT_byte_size)->value, nil);\n \n+\t\t// make map type a pointer to hash<K,V>\n \t\tnewrefattr(die, DW_AT_type, defptrto(dwh));\n \t}\n }\ndiff --git a/src/pkg/runtime/hashmap.c b/src/pkg/runtime/hashmap.c\nindex 6cd5c480d5..a2ad1a0812 100644\n--- a/src/pkg/runtime/hashmap.c\n+++ b/src/pkg/runtime/hashmap.c\n@@ -222,7 +222,7 @@ hash_init(MapType *t, Hmap *h, uint32 hint)\n \t\tkeysize = sizeof(byte*);\n \t}\n \tvaluesize = t->elem->size;\n-\tif(valuesize >= MAXVALUESIZE) {\n+\tif(valuesize > MAXVALUESIZE) {\n \t\tflags |= IndirectValue;\n \t\tvaluesize = sizeof(byte*);\n \t}\ndiff --git a/src/pkg/runtime/runtime-gdb.py b/src/pkg/runtime/runtime-gdb.py\nindex eff9a40037..cb70ca028e 100644\n--- a/src/pkg/runtime/runtime-gdb.py\n+++ b/src/pkg/runtime/runtime-gdb.py\n@@ -84,26 +84,35 @@ class MapTypePrinter:\n \t\treturn str(self.val.type)\n \n \tdef children(self):\n-\t\tstab = self.val[\'st\']\n-\t\ti = 0\n-\t\tfor v in self.traverse_hash(stab):\n-\t\t\tyield (\"[%d]\" % i, v[\'key\'])\n-\t\t\tyield (\"[%d]\" % (i + 1), v[\'val\'])\n-\t\t\ti += 2\n-\n-\tdef traverse_hash(self, stab):\n-\t\tptr = stab[\'entry\'].address\n-\t\tlast = stab[\'last\']\n-\t\twhile ptr <= last:\n-\t\t\tv = ptr.dereference()\n-\t\t\tptr = ptr + 1\n-\t\t\tif v[\'hash\'] == 0: continue\n-\t\t\tif v[\'hash\'] & 63 == 63:   # subtable\n-\t\t\t\tfor v in self.traverse_hash(v[\'key\'].cast(self.val[\'st\'].type)):\n-\t\t\t\t\tyield v\n-\t\t\telse:\n-\t\t\t\tyield v\n-\n+\t\tB = self.val[\'b\']\n+\t\tbuckets = self.val[\'buckets\']\n+\t\toldbuckets = self.val[\'oldbuckets\']\n+\t\tflags = self.val[\'flags\']\n+\t\tinttype = self.val[\'hash0\'].type\n+\t\tcnt = 0\n+\t\tfor bucket in xrange(2 ** B):\n+\t\t\tbp = buckets + bucket\n+\t\t\tif oldbuckets:\n+\t\t\t\toldbucket = bucket & (2 ** (B - 1) - 1)\n+\t\t\t\toldbp = oldbuckets + oldbucket\n+\t\t\t\tif (oldb[\'overflow\'].cast(inttype) & 1) == 0: # old bucket not evacuated yet\n+\t\t\t\t\tif bucket >= 2 ** (B - 1): continue   # already did old bucket\n+\t\t\t\t\tbp = oldbp\n+\t\t\twhile bp:\n+\t\t\t\tb = bp.dereference()\n+\t\t\t\tfor i in xrange(8):\n+\t\t\t\t\tif b[\'tophash\'][i] != 0:\n+\t\t\t\t\t\tk = b[\'keys\'][i]\n+\t\t\t\t\t\tv = b[\'values\'][i]\n+\t\t\t\t\t\tif flags & 1:\n+\t\t\t\t\t\t\tk = k.dereference()\n+\t\t\t\t\t\tif flags & 2:\n+\t\t\t\t\t\t\tv = v.dereference()\n+\t\t\t\t\t\tyield \'%d\' % cnt, k\n+\t\t\t\t\t\tyield \'%d\' % (cnt + 1), v\n+\t\t\t\t\t\tcnt += 2\n+\t\t\t\tbp = b[\'overflow\']\n \n class ChanTypePrinter:\n \t\"\"\"Pretty print chan[T] types.\n```

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

[https://github.com/golang/go/commit/fb3ed166e39fd13689b35ca7aa2496e113cbeaf5](https://github.com/golang/go/commit/fb3ed166e39fd13689b35ca7aa2496e113cbeaf5)

## 元コミット内容

このコミットの元の内容は、GoランタイムにおけるGDBでのマップの表示に関するバグを修正することです。具体的には、GDBがGoのマップの内部構造を正しく解釈できず、デバッグ時にマップの内容を適切に表示できない問題に対処しています。コミットメッセージには `Fixes #5098` とありますが、Goの公式GitHubリポジトリではこの番号のIssueは直接見つかりませんでした。これは、内部的なトラッカーの番号であるか、あるいは古いIssueトラッカーの参照である可能性があります。

## 変更の背景

Go言語のデバッグにおいて、GDBは非常に重要なツールです。しかし、Goのマップ(ハッシュマップ)はC言語の構造体とは異なる独自の内部構造を持っています。GDBがGoのマップ変数を検査しようとすると、その内部構造を理解できないため、意味のある情報を表示できませんでした。

この問題は、Goのマップがキーと値を直接格納するのではなく、バケットと呼ばれる内部構造に格納し、さらにオーバーフローバケットやリサイズ中の古いバケットといった複雑なメカニズムを持っていることに起因します。GDBがこれらの内部構造を適切に辿れないと、マップのキーと値のペアを正確に表示することができません。

このコミットは、GDBがGoのマップの内部構造を正しく「理解」し、デバッガユーザーに対してマップの内容を分かりやすく表示できるようにするために行われました。これにより、Goプログラムのデバッグ体験が大幅に向上します。

## 前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

*   **Go言語のマップ(`map`型)**: Goのマップは、キーと値のペアを格納するハッシュテーブルの実装です。内部的には、`runtime.hmap`という構造体と、`runtime.bmap`(バケット)という構造体で構成されています。マップは動的にサイズが変更され、リサイズ時には古いバケットと新しいバケットが共存する期間があります。キーと値のサイズによっては、直接バケットに格納されるのではなく、ポインタとして格納される(間接参照される)こともあります。
*   **GDB (GNU Debugger)**: GDBは、C、C++、Goなど様々なプログラミング言語のプログラムをデバッグするための強力なツールです。プログラムの実行を一時停止し、変数の値を検査したり、メモリの内容を調べたり、スタックトレースを表示したりする機能を提供します。
*   **GDB Pretty-printers**: GDBは、特定のデータ構造をより人間が読みやすい形式で表示するための「pretty-printer」と呼ばれるPythonスクリプトをサポートしています。これにより、複雑なデータ構造(例えば、Goのマップやスライス)を、GDBのデフォルトの表示よりも分かりやすく整形して表示できます。Go言語のランタイムには、Goの組み込み型をGDBで適切に表示するための`runtime-gdb.py`というpretty-printerが含まれています。
*   **DWARF (Debugging With Attributed Record Formats)**: DWARFは、ソースレベルデバッガがプログラムの実行ファイルからデバッグ情報を取得するための標準的なフォーマットです。変数名、型情報、ソースコードの行番号と実行可能コードのアドレスのマッピングなどが含まれます。Goコンパイラとリンカは、デバッグ情報をDWARF形式で生成します。GDBは、このDWARF情報を利用してプログラムのシンボルや型を理解します。
*   **`src/cmd/ld/dwarf.c`**: Goのリンカ(`cmd/ld`)の一部で、DWARFデバッグ情報の生成を担当します。Goの型情報(特にマップのような複雑な型)をGDBが理解できるDWARF形式に変換するロジックが含まれています。
*   **`src/pkg/runtime/hashmap.c`**: GoランタイムにおけるマップのC言語実装です。マップの初期化、キーと値の格納、バケットの管理、リサイズなどの低レベルなロジックが含まれています。
*   **`src/pkg/runtime/runtime-gdb.py`**: Goランタイムに付属するGDB pretty-printerスクリプトです。Goのマップ、スライス、チャネルなどの型をGDBでデバッグする際に、その内容を整形して表示するためのPythonコードが含まれています。

## 技術的詳細

このコミットは、主に以下の3つのファイルにわたる変更を通じて、GDBでのGoマップの表示を改善しています。

1.  **`src/cmd/ld/dwarf.c` の変更**:
    *   このファイルは、Goの型情報をDWARF形式に変換する役割を担っています。
    *   `synthesizemaptypes` 関数が大幅に拡張されています。この関数は、Goのマップ型(`type.runtime.hmap`)とバケット型(`type.runtime.bucket`)のDWARF表現を生成します。
    *   Goのマップのキーと値が、そのサイズに応じて直接格納されるか(インライン)、ポインタとして間接参照されるか(アウトオブライン)を判断するロジックが追加されました。これは、`runtime/hashmap.c` で行われる実際のメモリレイアウトの決定ロジックと同期しています。`MaxKeySize` (128バイト) と `MaxValSize` (128バイト) という定数が導入され、これを超えると値が間接参照されるようになります。
    *   GDBがマップのバケット構造を正しく解釈できるように、バケット内のキーと値の配列(`keys`と`values`)に対応するDWARF型が動的に生成されます。これらは、`BucketSize` (8) とキー/値のサイズに基づいて配列として表現されます。
    *   `bucket<K,V>` というジェネリックなバケット型と、`hash<K,V>` というジェネリックなマップ型がDWARFで合成され、それぞれのフィールド(`keys`, `values`, `overflow`, `buckets`, `oldbuckets`など)が適切な型とオフセットで定義されます。これにより、GDBはこれらの内部構造を辿ってマップの内容を読み取れるようになります。

2.  **`src/pkg/runtime/hashmap.c` の変更**:
    *   `hash_init` 関数内の `valuesize` のチェックが `valuesize >= MAXVALUESIZE` から `valuesize > MAXVALUESIZE` に変更されています。これは、値が間接参照される条件を微調整するもので、Dwarf情報の生成ロジックと整合性を保つための小さな修正です。

3.  **`src/pkg/runtime/runtime-gdb.py` の変更**:
    *   このPythonスクリプトは、GDBのpretty-printerとして機能し、Goのマップ型を整形して表示します。
    *   `MapTypePrinter` クラスの `children` メソッドと `traverse_hash` メソッドが完全に書き換えられています。
    *   古い実装は、Goのマップの内部構造の変更に対応できておらず、正しくマップの内容を辿ることができませんでした。
    *   新しい実装では、Goのマップの現在の内部構造(バケット、オーバーフローバケット、リサイズ中の古いバケットなど)を正確に反映するようにロジックが更新されています。
    *   `self.val['b']` (バケットのビット数)、`self.val['buckets']` (現在のバケット配列)、`self.val['oldbuckets']` (リサイズ中の古いバケット配列)、`self.val['flags']` (キーや値が間接参照されているかを示すフラグ) などのマップの内部フィールドを直接参照し、それらをGDBのPython APIを通じて辿ることで、マップ内のすべてのキーと値のペアを列挙できるようになっています。
    *   特に、リサイズ中のマップにおいて、古いバケットと新しいバケットの両方から要素を正しく取得するロジックが追加されています。
    *   キーや値が間接参照されている場合(`flags & 1` または `flags & 2`)、それらをデリファレンス(ポインタの指す先の値を取得)する処理も含まれています。

これらの変更により、Goのマップの複雑な内部構造がGDBに正しく提示され、`print` コマンドなどでマップの内容をデバッグ時に確認できるようになります。

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

### `src/cmd/ld/dwarf.c`

```c
 // synthesizemaptypes is way too closely married to runtime/hashmap.c
 enum {
 	MaxKeySize = 128,
 	MaxValSize = 128,
 	BucketSize = 8,
 };

 static void
 synthesizemaptypes(DWDie *die)
 {
-	DWDie *hash, *dwh, *keytype, *valtype;
-	hash		= defgotype(lookup_or_diag("type.runtime.hmap"));
+	DWDie *hash, *bucket, *dwh, *dwhk, *dwhv, *dwhb, *keytype, *valtype, *fld;
+	int indirect_key, indirect_val;
+	int keysize, valsize;
+	DWAttr *a;
+
+	hash		= walktypedef(defgotype(lookup_or_diag("type.runtime.hmap")));
+	bucket		= walktypedef(defgotype(lookup_or_diag("type.runtime.bucket")));

 	if (hash == nil)
 		return;
@@ -1146,8 +1156,59 @@ synthesizemaptypes(DWDie *die)
 		if (die->abbrev != DW_ABRV_MAPTYPE)
 			continue;

-		keytype = (DWDie*) getattr(die, DW_AT_internal_key_type)->data;
-		valtype = (DWDie*) getattr(die, DW_AT_internal_val_type)->data;
+		keytype = walktypedef((DWDie*) getattr(die, DW_AT_internal_key_type)->data);
+		valtype = walktypedef((DWDie*) getattr(die, DW_AT_internal_val_type)->data);
+
+		// compute size info like hashmap.c does.
+		a = getattr(keytype, DW_AT_byte_size);
+		keysize = a ? a->value : PtrSize;  // We don't store size with Pointers
+		a = getattr(valtype, DW_AT_byte_size);
+		valsize = a ? a->value : PtrSize;
+		indirect_key = 0;
+		indirect_val = 0;
+		if(keysize > MaxKeySize) {
+			keysize = PtrSize;
+			indirect_key = 1;
+		}
+		if(valsize > MaxValSize) {
+			valsize = PtrSize;
+			indirect_val = 1;
+		}
+
+		// Construct type to represent an array of BucketSize keys
+		dwhk = newdie(&dwtypes, DW_ABRV_ARRAYTYPE,
+			      mkinternaltypename("[]key",
+						 getattr(keytype, DW_AT_name)->data, nil));
+		newattr(dwhk, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize * keysize, 0);
+		newrefattr(dwhk, DW_AT_type, indirect_key ? defptrto(keytype) : keytype);
+		fld = newdie(dwhk, DW_ABRV_ARRAYRANGE, "size");
+		newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, BucketSize, 0);
+		newrefattr(fld, DW_AT_type, find_or_diag(&dwtypes, "uintptr"));
+		
+		// Construct type to represent an array of BucketSize values
+		dwhv = newdie(&dwtypes, DW_ABRV_ARRAYTYPE, 
+			      mkinternaltypename("[]val",
+						 getattr(valtype, DW_AT_name)->data, nil));
+		newattr(dwhv, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize * valsize, 0);
+		newrefattr(dwhv, DW_AT_type, indirect_val ? defptrto(valtype) : valtype);
+		fld = newdie(dwhv, DW_ABRV_ARRAYRANGE, "size");
+		newattr(fld, DW_AT_upper_bound, DW_CLS_CONSTANT, BucketSize, 0);
+		newrefattr(fld, DW_AT_type, find_or_diag(&dwtypes, "uintptr"));
+
+		// Construct bucket<K,V>
+		dwhb = newdie(&dwtypes, DW_ABRV_STRUCTTYPE,
+			      mkinternaltypename("bucket",
+						 getattr(keytype, DW_AT_name)->data,
+						 getattr(valtype, DW_AT_name)->data));
+		copychildren(dwhb, bucket);
+		fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "keys");
+		newrefattr(fld, DW_AT_type, dwhk);
+		newmemberoffsetattr(fld, BucketSize + PtrSize);
+		fld = newdie(dwhb, DW_ABRV_STRUCTFIELD, "values");
+		newrefattr(fld, DW_AT_type, dwhv);
+		newmemberoffsetattr(fld, BucketSize + PtrSize + BucketSize * keysize);
+		newattr(dwhb, DW_AT_byte_size, DW_CLS_CONSTANT, BucketSize + PtrSize + BucketSize * keysize + BucketSize * valsize, 0);
+		substitutetype(dwhb, "overflow", defptrto(dwhb));

 		// Construct hash<K,V>
 		dwh = newdie(&dwtypes, DW_ABRV_STRUCTTYPE,
@@ -1155,9 +1216,12 @@ synthesizemaptypes(DWDie *die)
 				getattr(keytype, DW_AT_name)->data,
 				getattr(valtype, DW_AT_name)->data));
 		copychildren(dwh, hash);
+		substitutetype(dwh, "buckets", defptrto(dwhb));
+		substitutetype(dwh, "oldbuckets", defptrto(dwhb));
 		newattr(dwh, DW_AT_byte_size, DW_CLS_CONSTANT,
 			getattr(hash, DW_AT_byte_size)->value, nil);

+		// make map type a pointer to hash<K,V>
 		newrefattr(die, DW_AT_type, defptrto(dwh));
 	}
 }

src/pkg/runtime/hashmap.c

@@ -222,7 +222,7 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
 		keysize = sizeof(byte*);
 	}\n 	valuesize = t->elem->size;
-\tif(valuesize >= MAXVALUESIZE) {\n+\tif(valuesize > MAXVALUESIZE) {\n \t\tflags |= IndirectValue;\n \t\tvaluesize = sizeof(byte*);\n \t}\

src/pkg/runtime/runtime-gdb.py

@@ -84,26 +84,35 @@ class MapTypePrinter:
 		return str(self.val.type)
 
 	def children(self):
-\t\tstab = self.val['st']
-\t\ti = 0
-\t\tfor v in self.traverse_hash(stab):
-\t\t\tyield ("[%d]" % i, v['key'])
-\t\t\tyield ("[%d]" % (i + 1), v['val'])
-\t\t\ti += 2
-\n-\tdef traverse_hash(self, stab):
-\t\tptr = stab['entry'].address
-\t\tlast = stab['last']
-\t\twhile ptr <= last:
-\t\t\tv = ptr.dereference()
-\t\t\tptr = ptr + 1
-\t\t\tif v['hash'] == 0: continue
-\t\t\tif v['hash'] & 63 == 63:   # subtable
-\t\t\t\tfor v in self.traverse_hash(v['key'].cast(self.val['st'].type)):
-\t\t\t\t\tyield v
-\t\t\telse:
-\t\t\t\tyield v
-\n+\t\tB = self.val['b']
+\t\tbuckets = self.val['buckets']
+\t\toldbuckets = self.val['oldbuckets']
+\t\tflags = self.val['flags']
+\t\tinttype = self.val['hash0'].type
+\t\tcnt = 0
+\t\tfor bucket in xrange(2 ** B):
+\t\t\tbp = buckets + bucket
+\t\t\tif oldbuckets:
+\t\t\t\toldbucket = bucket & (2 ** (B - 1) - 1)
+\t\t\t\toldbp = oldbuckets + oldbucket
+\t\t\t\toldb = oldbp.dereference()
+\t\t\t\tif (oldb['overflow'].cast(inttype) & 1) == 0: # old bucket not evacuated yet
+\t\t\t\t\tif bucket >= 2 ** (B - 1): continue   # already did old bucket
+\t\t\t\t\tbp = oldbp
+\t\t\twhile bp:
+\t\t\t\tb = bp.dereference()
+\t\t\t\tfor i in xrange(8):
+\t\t\t\t\tif b['tophash'][i] != 0:
+\t\t\t\t\t\tk = b['keys'][i]
+\t\t\t\t\t\tv = b['values'][i]
+\t\t\t\t\t\tif flags & 1:
+\t\t\t\t\t\t\tk = k.dereference()
+\t\t\t\t\t\tif flags & 2:
+\t\t\t\t\t\t\tv = v.dereference()
+\t\t\t\t\t\tyield '%d' % cnt, k
+\t\t\t\t\t\tyield '%d' % (cnt + 1), v
+\t\t\t\t\t\tcnt += 2
+\t\t\t\tbp = b['overflow']

コアとなるコードの解説

src/cmd/ld/dwarf.c

このファイルでは、Goのマップの内部構造をGDBが理解できるように、DWARFデバッグ情報を生成するロジックが追加・修正されています。

  • MaxKeySize, MaxValSize, BucketSize 定数: これらはGoランタイムのマップ実装(hashmap.c)と同期しており、キーと値がバケットに直接格納されるか、それともポインタとして間接参照されるかを決定する閾値と、バケットあたりのエントリ数を示します。
  • synthesizemaptypes 関数の拡張:
    • type.runtime.hmap (マップヘッダ) と type.runtime.bucket (バケット) のDWARF表現を取得します。
    • マップのキーと値の実際のサイズを計算し、MaxKeySizeMaxValSize を超える場合は、indirect_keyindirect_val フラグを立てて、間接参照されることを示します。
    • キーと値の配列のDWARF型生成: BucketSize とキー/値のサイズに基づいて、バケット内のキーと値の配列(keysvalues)に対応するDWARF配列型 (dwhk, dwhv) を動的に生成します。これにより、GDBはバケット内のキーと値のブロックを配列として認識できます。
    • バケット構造のDWARF型生成: bucket<K,V> というジェネリックな構造体型 (dwhb) をDWARFで合成します。この構造体には、keysvaluesoverflow といったフィールドが、それぞれ適切な型とメモリオフセットで定義されます。特に overflow フィールドは、次のオーバーフローバケットへのポインタとして定義されます。
    • マップヘッダ構造のDWARF型更新: hash<K,V> というジェネリックなマップヘッダ型 (dwh) を更新し、その bucketsoldbuckets フィールドが、新しく生成されたバケット型 (dwhb) へのポインタとして定義されるようにします。
    • 最終的に、Goのマップ型自体が、この合成された hash<K,V> 型へのポインタとしてGDBに提示されるように設定されます。

これらの変更により、GDBはGoのマップの内部構造を、その動的な性質やメモリレイアウトを含めて正確に理解し、デバッグ時にマップの内容を適切に表示するための基盤が提供されます。

src/pkg/runtime/hashmap.c

このファイルでは、マップの初期化ロジックに小さな修正が加えられています。

  • valuesize >= MAXVALUESIZE から valuesize > MAXVALUESIZE への変更は、値が間接参照される条件を微調整するものです。これは、Dwarf情報の生成ロジックと整合性を保つためのもので、Goのマップが内部的に値をどのように格納するかという低レベルな動作に影響します。

src/pkg/runtime/runtime-gdb.py

このPythonスクリプトは、GDBのpretty-printerとして、Goのマップの表示ロジックを完全に刷新しています。

  • MapTypePrinter.children メソッド: このメソッドは、GDBがマップの内容を列挙する際に呼び出されます。
    • マップの内部フィールドである b (バケットのビット数)、buckets (現在のバケット配列)、oldbuckets (リサイズ中の古いバケット配列)、flags (キーや値が間接参照されているかを示すフラグ) を取得します。
    • xrange(2 ** B) を使用して、すべての可能なバケットインデックスを反復処理します。
    • リサイズ中のマップの処理: oldbuckets が存在する場合、リサイズ中のマップの要素を正しく処理するために、古いバケットから要素を取得するロジックが含まれています。これにより、リサイズ中にマップの内容が欠落することなく表示されます。
    • バケットの走査: 各バケット (bp) を辿り、その中のエントリ (b['tophash'][i] != 0 で有効なエントリをチェック) を列挙します。
    • キーと値の取得とデリファレンス: バケットからキー (k) と値 (v) を取得します。flags をチェックし、キーや値が間接参照されている場合は、k.dereference()v.dereference() を使って実際の値を取得します。
    • yield を使って、GDBに対してキーと値のペアを順次返します。これにより、GDBはマップの内容を (key, value) の形式で表示できます。

この新しいpretty-printerは、Goのマップの複雑な内部構造(バケット、オーバーフロー、リサイズ)を正確に理解し、GDBユーザーに対してマップの内容を直感的かつ完全に表示することを可能にします。

関連リンク

参考にした情報源リンク