[インデックス 16106] ファイルの概要
このコミットは、Goランタイムにおけるメモリ管理の改善に関するものです。具体的には、解放されたメモリブロックがゼロクリアされる必要があることを示すためのマーカーパターンを変更しています。以前は単純な 1
を使用していましたが、これを 0xfeedfeedfeedfeedll
および 0xdeaddeaddeaddeadll
という、より特徴的なパターンに変更することで、デバッグやメモリの状態の識別を容易にしています。
コミット
commit c80f5b9bf7c67e1338d04bc0cf21fe285c3a80df
Author: Carl Shapiro <cshapiro@google.com>
Date: Thu Apr 4 14:18:52 2013 -0700
runtime: use a distinct pattern to mark free blocks in need of zeroing
R=golang-dev, dvyukov, khr, cshapiro
CC=golang-dev
https://golang.org/cl/8392043
---
src/pkg/runtime/malloc.goc | 4 ++--
src/pkg/runtime/mgc0.c | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index a30129ffc1..f1d25a793f 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -160,7 +160,7 @@ runtime·free(void *v)
if(sizeclass == 0) {
// Large object.
size = s->npages<<PageShift;
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed"
+ *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling unmarkspan and MHeap_Free:
// they might coalesce v into other spans and change the bitmap further.
runtime·markfreed(v, size);
@@ -170,7 +170,7 @@ runtime·free(void *v)
// Small object.
size = runtime·class_to_size[sizeclass];
if(size > sizeof(uintptr))
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling MCache_Free:
// it might coalesce v and other blocks into a bigger span
// and change the bitmap further.
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 2d129eb8ed..caf1b10e34 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1607,7 +1607,7 @@ sweepspan(ParFor *desc, uint32 idx)
if(cl == 0) {
// Free large span.
runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
+ *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
runtime·MHeap_Free(runtime·mheap, s, 1);
c->local_alloc -= size;
c->local_nfree++;
@@ -1622,7 +1622,7 @@ sweepspan(ParFor *desc, uint32 idx)
break;
}
if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
end->next = (MLink*)p;
end = (MLink*)p;
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c80f5b9bf7c67e1338d04bc0cf21fe285c3a80df
元コミット内容
runtime: use a distinct pattern to mark free blocks in need of zeroing
R=golang-dev, dvyukov, khr, cshapiro
CC=golang-dev
https://golang.org/cl/8392043
変更の背景
Goランタイムは、セキュリティと予測可能な動作を保証するために、解放されたメモリブロックを再利用する前にゼロクリアする(内容を0で埋める)必要があります。これは、以前のデータが新しいデータに漏洩するのを防ぐため、また、プログラムが未初期化のメモリを読み込むことによる未定義の動作を防ぐために重要です。
このコミット以前は、解放されたメモリブロックがゼロクリアを必要とすることを示すために、そのブロックの先頭に 1
という値が書き込まれていました。しかし、この 1
という値は非常に一般的な値であり、メモリが実際にゼロクリアされていない場合でも、たまたま 1
が書き込まれているように見えることがありました。これにより、デバッグ時にメモリの状態を正確に判断することが困難になるという問題がありました。
特に、ガベージコレクション(GC)のサイクル中にメモリが解放され、その後すぐに再利用されるようなシナリオでは、この 1
というマーカーが誤解を招く可能性がありました。デバッガーでメモリダンプを調査する際、1
が存在してもそれが意図されたマーカーなのか、それとも単なる古いデータの一部なのかを区別するのが難しかったのです。
この問題を解決し、デバッグの効率を向上させるために、より特徴的で、通常のデータとしては現れにくいパターンをマーカーとして使用することが決定されました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステムです。メモリ管理(アロケーション、解放、ガベージコレクション)、ゴルーチンのスケジューリング、チャネル通信など、Go言語の並行処理モデルを支える重要な機能を提供します。
- ガベージコレクション (Garbage Collection, GC): プログラムが動的に確保したメモリ領域のうち、もはや使用されていない(参照されていない)ものを自動的に解放し、再利用可能にするプロセスです。GoのGCは並行かつ低遅延で動作するように設計されています。
- メモリゼロクリア (Memory Zeroing): メモリブロックを解放した後、または新しいメモリブロックを割り当てる前に、その内容をすべてゼロ(0)で埋める処理です。これにより、以前のデータが新しいデータに漏洩するのを防ぎ、セキュリティを向上させます。Goでは、新しいメモリ割り当ては常にゼロクリアされた状態で提供されます。
uintptr
: Go言語におけるポインタ型の一つで、ポインタの値を符号なし整数として表現します。メモリアドレスを直接操作する際に使用されます。PageShift
: メモリページサイズに関連する定数です。1 << PageShift
は、システムにおけるメモリページのバイトサイズを表します。メモリ管理は通常、ページ単位で行われます。sizeclass
: Goランタイムのメモリ管理において、オブジェクトのサイズを分類するために使用される概念です。異なるサイズのオブジェクトは異なるsizeclass
に属し、それぞれに最適化されたアロケーション戦略が適用されます。sizeclass == 0
は通常、大きなオブジェクト(large object)を示します。MHeap_Free
: Goランタイムのヒープ(mheap
)からメモリを解放するための関数です。大きなメモリブロックの解放に関与します。MCache_Free
: GoランタイムのMキャッシュ(mcache
)からメモリを解放するための関数です。小さなオブジェクトの解放に関与します。Mキャッシュは、各P(プロセッサ)にローカルなキャッシュであり、ロックなしで高速なアロケーションと解放を可能にします。sweepspan
: ガベージコレクションの「スイープ」フェーズの一部として実行される関数です。マークフェーズで到達可能とされなかったメモリブロック(スパン)を解放し、再利用可能にする役割を担います。
技術的詳細
このコミットの核心は、解放されたメモリブロックがゼロクリアされる必要があることを示すための「マジックナンバー」の導入です。
-
0xfeedfeedfeedfeedll
: このパターンは、src/pkg/runtime/malloc.goc
で使用されています。malloc.goc
は、Goランタイムのメモリ割り当てと解放の基本的なロジックを扱います。runtime·free
関数内で、解放されるメモリブロックの先頭(または特定のオフセット)にこの値が書き込まれます。0xfeedfeed
は、プログラミングにおいて「フィード(餌)」や「ダミーデータ」を示すためによく使われる16進数のパターンです。このパターンを繰り返すことで、非常に特徴的で、通常のプログラムデータとしては現れにくい値となっています。ll
サフィックスは、C言語の文法でlong long
型のリテラルであることを示しており、64ビットの値を表現しています。これは、uintptr
が通常64ビットアーキテクチャで64ビット幅を持つことに対応しています。
-
0xdeaddeaddeaddeadll
: このパターンは、src/pkg/runtime/mgc0.c
で使用されています。mgc0.c
は、Goのガベージコレクションのコアロジックの一部を実装しています。sweepspan
関数内で、GCによって解放されるスパン(メモリブロックの集合)の先頭にこの値が書き込まれます。0xdeaddead
は、プログラミングにおいて「デッド(死んだ)」メモリや「無効な」メモリを示すためによく使われる16進数のパターンです。これもまた、非常に特徴的で、通常のデータとは区別しやすい値です。
これらのマジックナンバーを使用する主な理由は以下の通りです。
- デバッグの容易性: メモリダンプを調査する際に、これらの特徴的なパターンを見つけることで、そのメモリブロックが「解放済みであり、かつゼロクリア待ちの状態である」ことを一目で識別できます。これにより、メモリリークや不正なメモリアクセスなどの問題をデバッグする際の効率が大幅に向上します。
- 誤認識の防止: 単純な
1
という値は、プログラムの通常のデータとして頻繁に現れる可能性があります。そのため、1
がマーカーとして書き込まれたのか、それとも単に以前のデータの一部として残っていたのかを区別するのが困難でした。マジックナンバーは、このような誤認識のリスクを劇的に低減します。 - メモリの健全性の確認: これらのパターンが、ゼロクリアされるべきメモリ領域に意図せず残っている場合、それはランタイムのバグやメモリ管理の問題を示唆している可能性があります。これにより、ランタイムのメモリ管理の健全性をより厳密に検証できるようになります。
この変更は、Goランタイムの堅牢性とデバッグ能力を向上させるための、低レベルながらも重要な改善です。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index a30129ffc1..f1d25a793f 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -160,7 +160,7 @@ runtime·free(void *v)
if(sizeclass == 0) {
// Large object.
size = s->npages<<PageShift;
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed"
+ *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling unmarkspan and MHeap_Free:
// they might coalesce v into other spans and change the bitmap further.
runtime·markfreed(v, size);
@@ -170,7 +170,7 @@ runtime·free(void *v)
// Small object.
size = runtime·class_to_size[sizeclass];
if(size > sizeof(uintptr))
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling MCache_Free:
// it might coalesce v and other blocks into a bigger span
// and change the bitmap further.
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 2d129eb8ed..caf1b10e34 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1607,7 +1607,7 @@ sweepspan(ParFor *desc, uint32 idx)
if(cl == 0) {
// Free large span.
runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
+ *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
runtime·MHeap_Free(runtime·mheap, s, 1);
c->local_alloc -= size;
c->local_nfree++;
@@ -1622,7 +1622,7 @@ sweepspan(ParFor *desc, uint32 idx)
break;
}
if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
end->next = (MLink*)p;
end = (MLink*)p;
コアとなるコードの解説
このコミットでは、src/pkg/runtime/malloc.goc
と src/pkg/runtime/mgc0.c
の2つのファイルが変更されています。
src/pkg/runtime/malloc.goc
の変更点:
-
Large object (大きなオブジェクト) の解放時:
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed" + *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
runtime·free
関数内で、sizeclass == 0
の場合(大きなオブジェクトの場合)、解放されるスパンの開始アドレス(s->start<<PageShift
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。以前は1
でしたが、これを0xfeedfeedfeedfeedll
に変更しています。これは、メモリブロックの先頭ワードにこのマジックナンバーを書き込むことで、そのブロックが解放済みであり、かつゼロクリア待ちの状態であることを明示的に示します。 -
Small object (小さなオブジェクト) の解放時:
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed" + ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
runtime·free
関数内で、小さなオブジェクトの場合(size > sizeof(uintptr)
の条件付き)、解放されるオブジェクトの2番目のuintptr
ワード(((uintptr*)v)[1]
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xfeedfeedfeedfeedll
に変更されています。これは、小さなオブジェクトの特定のオフセットにマジックナンバーを書き込むことで、同様の目的を果たします。
src/pkg/runtime/mgc0.c
の変更点:
-
Large span (大きなスパン) のスイープ時:
- *(uintptr*)p = 1; // needs zeroing + *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
sweepspan
関数内で、ガベージコレクションによって大きなスパンが解放される際、そのスパンの開始アドレス(p
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xdeaddeaddeaddeadll
に変更されています。これは、GCがメモリを解放する際に、そのメモリがゼロクリアされるべきであることを示すためのものです。 -
Small object (小さなオブジェクト) のスイープ時:
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed" + ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
sweepspan
関数内で、小さなオブジェクトがスイープされる際、そのオブジェクトの2番目のuintptr
ワード(((uintptr*)p)[1]
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xdeaddeaddeaddeadll
に変更されています。
これらの変更は、Goランタイムのメモリ管理において、解放されたメモリがゼロクリアされるべき状態であることを、より明確かつデバッグしやすい形で示すためのものです。異なるマジックナンバー(0xfeedfeed
と 0xdeaddead
)が使用されているのは、それぞれ異なるメモリ解放のパス(通常の free
とGCの sweep
)を区別するため、または単に異なるパターンを導入してデバッグ時の識別をさらに容易にするためと考えられます。
関連リンク
- Go CL (Code Review) ページ: このコミットの元となったコードレビューの議論と詳細が確認できます。 https://golang.org/cl/8392043
参考にした情報源リンク
- Go言語のソースコード(
src/pkg/runtime/malloc.goc
,src/pkg/runtime/mgc0.c
) - Goのガベージコレクションに関するドキュメントやブログ記事(一般的なGo GCの仕組み理解のため)
- メモリデバッグにおけるマジックナンバーの使用に関する一般的な情報
0xfeedfeed
や0xdeaddead
といった16進数パターンがデバッグで使われる慣習に関する情報uintptr
,PageShift
,sizeclass
などのGoランタイム内部の概念に関する情報# [インデックス 16106] ファイルの概要
このコミットは、Goランタイムにおけるメモリ管理の改善に関するものです。具体的には、解放されたメモリブロックがゼロクリアされる必要があることを示すためのマーカーパターンを変更しています。以前は単純な 1
を使用していましたが、これを 0xfeedfeedfeedfeedll
および 0xdeaddeaddeaddeadll
という、より特徴的なパターンに変更することで、デバッグやメモリの状態の識別を容易にしています。
コミット
commit c80f5b9bf7c67e1338d04bc0cf21fe285c3a80df
Author: Carl Shapiro <cshapiro@google.com>
Date: Thu Apr 4 14:18:52 2013 -0700
runtime: use a distinct pattern to mark free blocks in need of zeroing
R=golang-dev, dvyukov, khr, cshapiro
CC=golang-dev
https://golang.org/cl/8392043
---
src/pkg/runtime/malloc.goc | 4 ++--
src/pkg/runtime/mgc0.c | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index a30129ffc1..f1d25a793f 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -160,7 +160,7 @@ runtime·free(void *v)
if(sizeclass == 0) {
// Large object.
size = s->npages<<PageShift;
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed"
+ *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling unmarkspan and MHeap_Free:
// they might coalesce v into other spans and change the bitmap further.
runtime·markfreed(v, size);
@@ -170,7 +170,7 @@ runtime·free(void *v)
// Small object.
size = runtime·class_to_size[sizeclass];
if(size > sizeof(uintptr))
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling MCache_Free:
// it might coalesce v and other blocks into a bigger span
// and change the bitmap further.
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 2d129eb8ed..caf1b10e34 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1607,7 +1607,7 @@ sweepspan(ParFor *desc, uint32 idx)
if(cl == 0) {
// Free large span.
runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
+ *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
runtime·MHeap_Free(runtime·mheap, s, 1);
c->local_alloc -= size;
c->local_nfree++;
@@ -1622,7 +1622,7 @@ sweepspan(ParFor *desc, uint32 idx)
break;
}
if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
end->next = (MLink*)p;
end = (MLink*)p;
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c80f5b9bf7c67e1338d04bc0cf21fe285c3a80df
元コミット内容
runtime: use a distinct pattern to mark free blocks in need of zeroing
R=golang-dev, dvyukov, khr, cshapiro
CC=golang-dev
https://golang.org/cl/8392043
変更の背景
Goランタイムは、セキュリティと予測可能な動作を保証するために、解放されたメモリブロックを再利用する前にゼロクリアする(内容を0で埋める)必要があります。これは、以前のデータが新しいデータに漏洩するのを防ぐため、また、プログラムが未初期化のメモリを読み込むことによる未定義の動作を防ぐために重要です。
このコミット以前は、解放されたメモリブロックがゼロクリアを必要とすることを示すために、そのブロックの先頭に 1
という値が書き込まれていました。しかし、この 1
という値は非常に一般的な値であり、メモリが実際にゼロクリアされていない場合でも、たまたま 1
が書き込まれているように見えることがありました。これにより、デバッグ時にメモリの状態を正確に判断することが困難になるという問題がありました。
特に、ガベージコレクション(GC)のサイクル中にメモリが解放され、その後すぐに再利用されるようなシナリオでは、この 1
というマーカーが誤解を招く可能性がありました。デバッガーでメモリダンプを調査する際、1
が存在してもそれが意図されたマーカーなのか、それとも単なる古いデータの一部なのかを区別するのが難しかったのです。
この問題を解決し、デバッグの効率を向上させるために、より特徴的で、通常のデータとしては現れにくいパターンをマーカーとして使用することが決定されました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステムです。メモリ管理(アロケーション、解放、ガベージコレクション)、ゴルーチンのスケジューリング、チャネル通信など、Go言語の並行処理モデルを支える重要な機能を提供します。
- ガベージコレクション (Garbage Collection, GC): プログラムが動的に確保したメモリ領域のうち、もはや使用されていない(参照されていない)ものを自動的に解放し、再利用可能にするプロセスです。GoのGCは並行かつ低遅延で動作するように設計されています。
- メモリゼロクリア (Memory Zeroing): メモリブロックを解放した後、または新しいメモリブロックを割り当てる前に、その内容をすべてゼロ(0)で埋める処理です。これにより、以前のデータが新しいデータに漏洩するのを防ぎ、セキュリティを向上させます。Goでは、新しいメモリ割り当ては常にゼロクリアされた状態で提供されます。
uintptr
: Go言語におけるポインタ型の一つで、ポインタの値を符号なし整数として表現します。メモリアドレスを直接操作する際に使用されます。PageShift
: メモリページサイズに関連する定数です。1 << PageShift
は、システムにおけるメモリページのバイトサイズを表します。メモリ管理は通常、ページ単位で行われます。sizeclass
: Goランタイムのメモリ管理において、オブジェクトのサイズを分類するために使用される概念です。異なるサイズのオブジェクトは異なるsizeclass
に属し、それぞれに最適化されたアロケーション戦略が適用されます。sizeclass == 0
は通常、大きなオブジェクト(large object)を示します。MHeap_Free
: Goランタイムのヒープ(mheap
)からメモリを解放するための関数です。大きなメモリブロックの解放に関与します。MCache_Free
: GoランタイムのMキャッシュ(mcache
)からメモリを解放するための関数です。小さなオブジェクトの解放に関与します。Mキャッシュは、各P(プロセッサ)にローカルなキャッシュであり、ロックなしで高速なアロケーションと解放を可能にします。sweepspan
: ガベージコレクションの「スイープ」フェーズの一部として実行される関数です。マークフェーズで到達可能とされなかったメモリブロック(スパン)を解放し、再利用可能にする役割を担います。
技術的詳細
このコミットの核心は、解放されたメモリブロックがゼロクリアされる必要があることを示すための「マジックナンバー」の導入です。
-
0xfeedfeedfeedfeedll
: このパターンは、src/pkg/runtime/malloc.goc
で使用されています。malloc.goc
は、Goランタイムのメモリ割り当てと解放の基本的なロジックを扱います。runtime·free
関数内で、解放されるメモリブロックの先頭(または特定のオフセット)にこの値が書き込まれます。0xfeedfeed
は、プログラミングにおいて「フィード(餌)」や「ダミーデータ」を示すためによく使われる16進数のパターンです。このパターンを繰り返すことで、非常に特徴的で、通常のプログラムデータとしては現れにくい値となっています。ll
サフィックスは、C言語の文法でlong long
型のリテラルであることを示しており、64ビットの値を表現しています。これは、uintptr
が通常64ビットアーキテクチャで64ビット幅を持つことに対応しています。
-
0xdeaddeaddeaddeadll
: このパターンは、src/pkg/runtime/mgc0.c
で使用されています。mgc0.c
は、Goのガベージコレクションのコアロジックの一部を実装しています。sweepspan
関数内で、GCによって解放されるスパン(メモリブロックの集合)の先頭にこの値が書き込まれます。0xdeaddead
は、プログラミングにおいて「デッド(死んだ)」メモリや「無効な」メモリを示すためによく使われる16進数のパターンです。これもまた、非常に特徴的で、通常のデータとは区別しやすい値です。
これらのマジックナンバーを使用する主な理由は以下の通りです。
- デバッグの容易性: メモリダンプを調査する際に、これらの特徴的なパターンを見つけることで、そのメモリブロックが「解放済みであり、かつゼロクリア待ちの状態である」ことを一目で識別できます。これにより、メモリリークや不正なメモリアクセスなどの問題をデバッグする際の効率が大幅に向上します。
- 誤認識の防止: 単純な
1
という値は、プログラムの通常のデータとして頻繁に現れる可能性があります。そのため、1
がマーカーとして書き込まれたのか、それとも単に以前のデータの一部として残っていたのかを区別するのが困難でした。マジックナンバーは、このような誤認識のリスクを劇的に低減します。 - メモリの健全性の確認: これらのパターンが、ゼロクリアされるべきメモリ領域に意図せず残っている場合、それはランタイムのバグやメモリ管理の問題を示唆している可能性があります。これにより、ランタイムのメモリ管理の健全性をより厳密に検証できるようになります。
この変更は、Goランタイムの堅牢性とデバッグ能力を向上させるための、低レベルながらも重要な改善です。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index a30129ffc1..f1d25a793f 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -160,7 +160,7 @@ runtime·free(void *v)
if(sizeclass == 0) {
// Large object.
size = s->npages<<PageShift;
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed"
+ *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling unmarkspan and MHeap_Free:
// they might coalesce v into other spans and change the bitmap further.
runtime·markfreed(v, size);
@@ -170,7 +170,7 @@ runtime·free(void *v)
// Small object.
size = runtime·class_to_size[sizeclass];
if(size > sizeof(uintptr))
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
// Must mark v freed before calling MCache_Free:
// it might coalesce v and other blocks into a bigger span
// and change the bitmap further.
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 2d129eb8ed..caf1b10e34 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1607,7 +1607,7 @@ sweepspan(ParFor *desc, uint32 idx)
if(cl == 0) {
// Free large span.
runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
+ *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
runtime·MHeap_Free(runtime·mheap, s, 1);
c->local_alloc -= size;
c->local_nfree++;
@@ -1622,7 +1622,7 @@ sweepspan(ParFor *desc, uint32 idx)
break;
}
if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
+ ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
end->next = (MLink*)p;
end = (MLink*)p;
コアとなるコードの解説
このコミットでは、src/pkg/runtime/malloc.goc
と src/pkg/runtime/mgc0.c
の2つのファイルが変更されています。
src/pkg/runtime/malloc.goc
の変更点:
-
Large object (大きなオブジェクト) の解放時:
- *(uintptr*)(s->start<<PageShift) = 1; // mark as "needs to be zeroed" + *(uintptr*)(s->start<<PageShift) = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
runtime·free
関数内で、sizeclass == 0
の場合(大きなオブジェクトの場合)、解放されるスパンの開始アドレス(s->start<<PageShift
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。以前は1
でしたが、これを0xfeedfeedfeedfeedll
に変更しています。これは、メモリブロックの先頭ワードにこのマジックナンバーを書き込むことで、そのブロックが解放済みであり、かつゼロクリア待ちの状態であることを明示的に示します。 -
Small object (小さなオブジェクト) の解放時:
- ((uintptr*)v)[1] = 1; // mark as "needs to be zeroed" + ((uintptr*)v)[1] = (uintptr)0xfeedfeedfeedfeedll; // mark as "needs to be zeroed"
runtime·free
関数内で、小さなオブジェクトの場合(size > sizeof(uintptr)
の条件付き)、解放されるオブジェクトの2番目のuintptr
ワード(((uintptr*)v)[1]
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xfeedfeedfeedfeedll
に変更されています。これは、小さなオブジェクトの特定のオフセットにマジックナンバーを書き込むことで、同様の目的を果たします。
src/pkg/runtime/mgc0.c
の変更点:
-
Large span (大きなスパン) のスイープ時:
- *(uintptr*)p = 1; // needs zeroing + *(uintptr*)p = (uintptr)0xdeaddeaddeaddeadll; // needs zeroing
sweepspan
関数内で、ガベージコレクションによって大きなスパンが解放される際、そのスパンの開始アドレス(p
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xdeaddeaddeaddeadll
に変更されています。これは、GCがメモリを解放する際に、そのメモリがゼロクリアされるべきであることを示すためのものです。 -
Small object (小さなオブジェクト) のスイープ時:
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed" + ((uintptr*)p)[1] = (uintptr)0xdeaddeaddeaddeadll; // mark as "needs to be zeroed"
sweepspan
関数内で、小さなオブジェクトがスイープされる際、そのオブジェクトの2番目のuintptr
ワード(((uintptr*)p)[1]
)に、ゼロクリアが必要であることを示すマーカーを書き込んでいます。ここでも1
から0xdeaddeaddeaddeadll
に変更されています。
これらの変更は、Goランタイムのメモリ管理において、解放されたメモリがゼロクリアされるべき状態であることを、より明確かつデバッグしやすい形で示すためのものです。異なるマジックナンバー(0xfeedfeed
と 0xdeaddead
)が使用されているのは、それぞれ異なるメモリ解放のパス(通常の free
とGCの sweep
)を区別するため、または単に異なるパターンを導入してデバッグ時の識別をさらに容易にするためと考えられます。
関連リンク
- Go CL (Code Review) ページ: このコミットの元となったコードレビューの議論と詳細が確認できます。 https://golang.org/cl/8392043
参考にした情報源リンク
- Go言語のソースコード(
src/pkg/runtime/malloc.goc
,src/pkg/runtime/mgc0.c
) - Goのガベージコレクションに関するドキュメントやブログ記事(一般的なGo GCの仕組み理解のため)
- メモリデバッグにおけるマジックナンバーの使用に関する一般的な情報
0xfeedfeed
や0xdeaddead
といった16進数パターンがデバッグで使われる慣習に関する情報uintptr
,PageShift
,sizeclass
などのGoランタイム内部の概念に関する情報