[インデックス 15355] ファイルの概要
このコミットは、Goランタイムにおけるヒープ破損のバグを修正するものです。具体的には、新しいM(OSスレッド)が作成される際に、そのMに関連付けられたG(ゴルーチン)がガベージコレクタの対象リストに正しく追加されていなかった問題を解決します。
コミット
commit 94fab3cad33b7d5eb7422d319fd88b2db9896a75
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Feb 21 21:59:46 2013 +0400
runtime: fix heap corruption
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7397049
---
src/pkg/runtime/proc.c | 6 ++++++\n 1 file changed, 6 insertions(+)
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index 5c36ddf745..9909182b6b 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -972,6 +972,12 @@ runtime·newextram(void)\n mp->locked = LockInternal;\n mp->lockedg = gp;\n gp->lockedm = mp;\n+ // put on allg for garbage collector\n+ if(runtime·lastg == nil)\n+ runtime·allg = gp;\n+ else\n+ runtime·lastg->alllink = gp;\n+ runtime·lastg = gp;\n schedunlock();\n
// Add m to the extra list.\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/94fab3cad33b7d5eb7422d319fd88b2db9896a75
元コミット内容
runtime: fix heap corruption
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7397049
変更の背景
Goランタイムは、M(OSスレッド)、P(プロセッサ)、G(ゴルーチン)という3つの主要な抽象化を用いて並行処理を管理しています。MはOSスレッドを表し、PはGoコードを実行するための論理プロセッサを表し、Gは軽量な実行単位であるゴルーチンを表します。
runtime·newextram
関数は、新しいM(OSスレッド)を作成し、それをGoランタイムのスケジューラに統合する役割を担っています。この関数が呼び出されるのは、既存のMがすべてビジーであるか、または特定のシステムコールをブロックしている場合など、追加のOSスレッドが必要になったときです。
このコミットが修正する問題は、runtime·newextram
によって新しく作成されたMに紐付けられるゴルーチン(gp
)が、ガベージコレクタ(GC)がスキャンすべきゴルーチンのグローバルリスト(allg
)に正しく追加されていなかったことに起因します。GCは、プログラムが使用しているメモリを特定し、不要になったメモリを解放する役割を担っています。GCが正しく動作するためには、到達可能なすべてのオブジェクトを追跡できる必要があります。ゴルーチンのスタックは、他のオブジェクトへの参照を保持している可能性があるため、GCはすべての活動中のゴルーチンをスキャンする必要があります。
もし新しく作成されたゴルーチンがallg
リストに追加されないと、GCはそのゴルーチンを「見つける」ことができません。その結果、そのゴルーチンが保持しているメモリや、そのゴルーチンから参照されている他のオブジェクトが、GCによって到達不可能と誤って判断され、解放されてしまう可能性があります。これは「ヒープ破損」として現れ、プログラムのクラッシュ、予期せぬ動作、またはデータの破損につながる非常に深刻なバグです。
前提知識の解説
- Goランタイム: Goプログラムの実行を管理する低レベルのシステム。スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。C言語とGo言語で実装されています。
- M, P, Gモデル: Goの並行処理モデルの核心。
- G (Goroutine): Goにおける軽量な実行単位。数KBのスタックを持ち、数百万個作成することも可能。
- P (Processor): Goコードを実行するための論理プロセッサ。GをM上で実行するためのコンテキストを提供します。Pの数は通常、CPUコア数に制限されます。
- M (Machine/OS Thread): 実際のOSスレッド。PとGをOS上で実行します。Mは複数のPを切り替えて使用したり、システムコールでブロックされたりすることがあります。
- ガベージコレクタ (GC): プログラムが動的に確保したメモリのうち、もはや使用されていない(到達不可能になった)ものを自動的に解放するシステム。GoのGCは並行かつ低遅延で動作するように設計されています。
allg
リスト: Goランタイム内部で管理される、現在活動中のすべてのゴルーチン(G)を追跡するためのグローバルなリンクリスト。GCは、このallg
リストを辿ることで、すべてのゴルーチンのスタックをスキャンし、到達可能なメモリを特定します。- ヒープ破損 (Heap Corruption): プログラムがヒープメモリ(動的に確保されるメモリ領域)を不適切に操作した結果、メモリの内容が破壊される状態。これは通常、解放済みのメモリへのアクセス(use-after-free)、バッファオーバーフロー、またはGCの不適切な動作によって引き起こされます。ヒープ破損は、セキュリティ脆弱性やプログラムの不安定性の主要な原因となります。
runtime·lastg
:allg
リストの末尾を指すポインタ。新しいゴルーチンをリストに追加する際に使用されます。alllink
:g
構造体内のフィールドで、allg
リンクリストの次の要素へのポインタとして機能します。
技術的詳細
このコミットは、src/pkg/runtime/proc.c
ファイル内のruntime·newextram
関数に6行のコードを追加しています。この関数は、新しいM(OSスレッド)を初期化し、それに紐付けられた初期ゴルーチン(gp
)を設定する責任があります。
追加されたコードの目的は、この新しく作成されたゴルーチンgp
を、ガベージコレクタが使用するグローバルなゴルーチンリストallg
に確実に登録することです。
具体的な追加ロジックは以下の通りです。
// put on allg for garbage collector
: コメントが示すように、このセクションはガベージコレクタのためにgp
をallg
リストに追加するものです。if(runtime·lastg == nil)
:runtime·lastg
はallg
リストの最後のゴルーチンを指します。もしruntime·lastg
がnil
であれば、これはallg
リストが現在空であることを意味します。runtime·allg = gp;
:allg
リストが空の場合、新しく作成されたゴルーチンgp
がリストの最初の要素(かつ唯一の要素)となります。したがって、runtime·allg
(リストの先頭)はgp
を指すように設定されます。else runtime·lastg->alllink = gp;
:allg
リストが空でない場合、新しく作成されたゴルーチンgp
はリストの末尾に追加されます。これは、現在のリストの最後のゴルーチン(runtime·lastg
が指す)のalllink
フィールドをgp
に設定することで実現されます。これにより、gp
がリストの新しい最後の要素としてリンクされます。runtime·lastg = gp;
: 最後に、runtime·lastg
ポインタは、新しく追加されたゴルーチンgp
を指すように更新されます。これにより、次回新しいゴルーチンが追加される際に、正しくリストの末尾にリンクできるようになります。
この変更により、runtime·newextram
によって作成されるすべてのゴルーチンが、GCによって確実にスキャンされるようになり、GCがこれらのゴルーチンに関連するメモリを誤って解放する可能性が排除され、結果としてヒープ破損が防止されます。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
ファイルのruntime·newextram
関数内に以下の6行が追加されました。
@@ -972,6 +972,12 @@ runtime·newextram(void)\n mp->locked = LockInternal;\n mp->lockedg = gp;\n gp->lockedm = mp;\n+ // put on allg for garbage collector\n+ if(runtime·lastg == nil)\n+ runtime·allg = gp;\n+ else\n+ runtime·lastg->alllink = gp;\n+ runtime·lastg = gp;\n schedunlock();\n
// Add m to the extra list.\n
コアとなるコードの解説
追加されたコードは、Goランタイムのガベージコレクタがすべての活動中のゴルーチンを追跡するために使用するallg
リンクリストの整合性を保証します。
runtime·allg
: グローバルなg
(ゴルーチン)のリンクリストの先頭ポインタ。runtime·lastg
: グローバルなg
のリンクリストの末尾ポインタ。gp
:runtime·newextram
関数によって新しく作成され、現在のM(OSスレッド)に関連付けられたゴルーチン。gp->alllink
:g
構造体内のフィールドで、allg
リンクリストにおける次のゴルーチンへのポインタ。
このコードブロックは、新しいゴルーチンgp
をallg
リストの末尾にアトミックに追加する標準的なリンクリスト操作です。
- リストが空の場合:
runtime·lastg == nil
が真の場合、これはallg
リストにまだゴルーチンが一つも存在しないことを意味します。この場合、gp
がリストの最初のゴルーチンとなるため、runtime·allg
(リストの先頭)とruntime·lastg
(リストの末尾)の両方がgp
を指すように設定されます。 - リストが空でない場合:
runtime·lastg == nil
が偽の場合、リストには既に一つ以上のゴルーチンが存在します。この場合、runtime·lastg->alllink = gp;
によって、現在のリストの最後のゴルーチンがgp
を指すようにリンクが更新され、gp
がリストの新しい末尾となります。その後、runtime·lastg = gp;
によってruntime·lastg
ポインタ自体がgp
を指すように更新されます。
この操作により、runtime·newextram
が呼び出されるたびに、新しく作成されたゴルーチンがガベージコレクタの監視下に置かれることが保証され、ヒープ破損の原因となる可能性のあるメモリ管理の不整合が解消されます。
関連リンク
- Goのスケジューラに関するドキュメントや解説: GoのM, P, Gモデルについて深く理解するために役立ちます。
- Goのガベージコレクタに関するドキュメント:
allg
リストがGCでどのように使用されるか、GCの動作原理について詳細を知ることができます。
参考にした情報源リンク
- https://golang.org/cl/7397049 (Goの変更リスト)
- Go言語の公式ドキュメント (特にランタイムとガベージコレクタのセクション)
- Goのソースコード (特に
src/runtime/proc.go
やsrc/runtime/mgc.go
など、スケジューラやGCに関連するファイル) - GoのM, P, Gモデルに関する技術ブログや解説記事 (一般的なGoの並行処理モデルの理解のため)
- ヒープ破損に関する一般的なコンピュータサイエンスの資料 (ヒープ破損がどのように発生し、なぜ危険なのかを理解するため)
[インデックス 15355] ファイルの概要
このコミットは、Goランタイムにおけるヒープ破損のバグを修正するものです。具体的には、新しいM(OSスレッド)が作成される際に、そのMに関連付けられたG(ゴルーチン)がガベージコレクタの対象リストに正しく追加されていなかった問題を解決します。
コミット
commit 94fab3cad33b7d5eb7422d319fd88b2db9896a75
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Feb 21 21:59:46 2013 +0400
runtime: fix heap corruption
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7397049
---
src/pkg/runtime/proc.c | 6 ++++++\n 1 file changed, 6 insertions(+)
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index 5c36ddf745..9909182b6b 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -972,6 +972,12 @@ runtime·newextram(void)\n mp->locked = LockInternal;\n mp->lockedg = gp;\n gp->lockedm = mp;\n+ // put on allg for garbage collector\n+ if(runtime·lastg == nil)\n+ runtime·allg = gp;\n+ else\n+ runtime·lastg->alllink = gp;\n+ runtime·lastg = gp;\n schedunlock();\n
// Add m to the extra list.\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/94fab3cad3cad33b7d5eb7422d319fd88b2db9896a75
元コミット内容
runtime: fix heap corruption
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7397049
変更の背景
Goランタイムは、M(OSスレッド)、P(プロセッサ)、G(ゴルーチン)という3つの主要な抽象化を用いて並行処理を管理しています。MはOSスレッドを表し、PはGoコードを実行するための論理プロセッサを表し、Gは軽量な実行単位であるゴルーチンを表します。
runtime·newextram
関数は、新しいM(OSスレッド)を作成し、それをGoランタイムのスケジューラに統合する役割を担っています。この関数が呼び出されるのは、既存のMがすべてビジーであるか、または特定のシステムコールをブロックしている場合など、追加のOSスレッドが必要になったときです。
このコミットが修正する問題は、runtime·newextram
によって新しく作成されたMに紐付けられるゴルーチン(gp
)が、ガベージコレクタ(GC)がスキャンすべきゴルーチンのグローバルリスト(allg
)に正しく追加されていなかったことに起因します。GCは、プログラムが使用しているメモリを特定し、不要になったメモリを解放する役割を担っています。GCが正しく動作するためには、到達可能なすべてのオブジェクトを追跡できる必要があります。ゴルーチンのスタックは、他のオブジェクトへの参照を保持している可能性があるため、GCはすべての活動中のゴルーチンをスキャンする必要があります。
もし新しく作成されたゴルーチンがallg
リストに追加されないと、GCはそのゴルーチンを「見つける」ことができません。その結果、そのゴルーチンが保持しているメモリや、そのゴルーチンから参照されている他のオブジェクトが、GCによって到達不可能と誤って判断され、解放されてしまう可能性があります。これは「ヒープ破損」として現れ、プログラムのクラッシュ、予期せぬ動作、またはデータの破損につながる非常に深刻なバグです。
前提知識の解説
- Goランタイム: Goプログラムの実行を管理する低レベルのシステム。スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。C言語とGo言語で実装されています。
- M, P, Gモデル: Goの並行処理モデルの核心。
- G (Goroutine): Goにおける軽量な実行単位。数KBのスタックを持ち、数百万個作成することも可能。
- P (Processor): Goコードを実行するための論理プロセッサ。GをM上で実行するためのコンテキストを提供します。Pの数は通常、CPUコア数に制限されます。
- M (Machine/OS Thread): 実際のOSスレッド。PとGをOS上で実行します。Mは複数のPを切り替えて使用したり、システムコールでブロックされたりすることがあります。
- ガベージコレクタ (GC): プログラムが動的に確保したメモリのうち、もはや使用されていない(到達不可能になった)ものを自動的に解放するシステム。GoのGCは並行かつ低遅延で動作するように設計されています。
allg
リスト: Goランタイム内部で管理される、現在活動中のすべてのゴルーチン(G)を追跡するためのグローバルなリンクリスト。GCは、このallg
リストを辿ることで、すべてのゴルーチンのスタックをスキャンし、到達可能なメモリを特定します。- ヒープ破損 (Heap Corruption): プログラムがヒープメモリ(動的に確保されるメモリ領域)を不適切に操作した結果、メモリの内容が破壊される状態。これは通常、解放済みのメモリへのアクセス(use-after-free)、バッファオーバーフロー、またはGCの不適切な動作によって引き起こされます。ヒープ破損は、セキュリティ脆弱性やプログラムの不安定性の主要な原因となります。
runtime·lastg
:allg
リストの末尾を指すポインタ。新しいゴルーチンをリストに追加する際に使用されます。alllink
:g
構造体内のフィールドで、allg
リンクリストの次の要素へのポインタとして機能します。
技術的詳細
このコミットは、src/pkg/runtime/proc.c
ファイル内のruntime·newextram
関数に6行のコードを追加しています。この関数は、新しいM(OSスレッド)を初期化し、それに紐付けられた初期ゴルーチン(gp
)を設定する責任があります。
追加されたコードの目的は、この新しく作成されたゴルーチンgp
を、ガベージコレクタが使用するグローバルなゴルーチンリストallg
に確実に登録することです。
具体的な追加ロジックは以下の通りです。
// put on allg for garbage collector
: コメントが示すように、このセクションはガベージコレクタのためにgp
をallg
リストに追加するものです。if(runtime·lastg == nil)
:runtime·lastg
はallg
リストの最後のゴルーチンを指します。もしruntime·lastg
がnil
であれば、これはallg
リストが現在空であることを意味します。runtime·allg = gp;
:allg
リストが空の場合、新しく作成されたゴルーチンgp
がリストの最初の要素(かつ唯一の要素)となります。したがって、runtime·allg
(リストの先頭)はgp
を指すように設定されます。else runtime·lastg->alllink = gp;
:allg
リストが空でない場合、新しく作成されたゴルーチンgp
はリストの末尾に追加されます。これは、現在のリストの最後のゴルーチン(runtime·lastg
が指す)のalllink
フィールドをgp
に設定することで実現されます。これにより、gp
がリストの新しい最後の要素としてリンクされます。runtime·lastg = gp;
: 最後に、runtime·lastg
ポインタは、新しく追加されたゴルーチンgp
を指すように更新されます。これにより、次回新しいゴルーチンが追加される際に、正しくリストの末尾にリンクできるようになります。
この変更により、runtime·newextram
によって作成されるすべてのゴルーチンが、GCによって確実にスキャンされるようになり、GCがこれらのゴルーチンに関連するメモリを誤って解放する可能性が排除され、結果としてヒープ破損が防止されます。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
ファイルのruntime·newextram
関数内に以下の6行が追加されました。
@@ -972,6 +972,12 @@ runtime·newextram(void)\n mp->locked = LockInternal;\n mp->lockedg = gp;\n gp->lockedm = mp;\n+ // put on allg for garbage collector\n+ if(runtime·lastg == nil)\n+ runtime·allg = gp;\n+ else\n+ runtime·lastg->alllink = gp;\n+ runtime·lastg = gp;\n schedunlock();\n
// Add m to the extra list.\n
コアとなるコードの解説
追加されたコードは、Goランタイムのガベージコレクタがすべての活動中のゴルーチンを追跡するために使用するallg
リンクリストの整合性を保証します。
runtime·allg
: グローバルなg
(ゴルーチン)のリンクリストの先頭ポインタ。runtime·lastg
: グローバルなg
のリンクリストの末尾ポインタ。gp
:runtime·newextram
関数によって新しく作成され、現在のM(OSスレッド)に関連付けられたゴルーチン。gp->alllink
:g
構造体内のフィールドで、allg
リンクリストにおける次のゴルーチンへのポインタ。
このコードブロックは、新しいゴルーチンgp
をallg
リストの末尾にアトミックに追加する標準的なリンクリスト操作です。
- リストが空の場合:
runtime·lastg == nil
が真の場合、これはallg
リストにまだゴルーチンが一つも存在しないことを意味します。この場合、gp
がリストの最初のゴルーチンとなるため、runtime·allg
(リストの先頭)とruntime·lastg
(リストの末尾)の両方がgp
を指すように設定されます。 - リストが空でない場合:
runtime·lastg == nil
が偽の場合、リストには既に一つ以上のゴルーチンが存在します。この場合、runtime·lastg->alllink = gp;
によって、現在のリストの最後のゴルーチンがgp
を指すようにリンクが更新され、gp
がリストの新しい末尾となります。その後、runtime·lastg = gp;
によってruntime·lastg
ポインタ自体がgp
を指すように更新されます。
この操作により、runtime·newextram
が呼び出されるたびに、新しく作成されたゴルーチンがガベージコレクタの監視下に置かれることが保証され、ヒープ破損の原因となる可能性のあるメモリ管理の不整合が解消されます。
関連リンク
- Goのスケジューラに関するドキュメントや解説: GoのM, P, Gモデルについて深く理解するために役立ちます。
- Goのガベージコレクタに関するドキュメント:
allg
リストがGCでどのように使用されるか、GCの動作原理について詳細を知ることができます。
参考にした情報源リンク
- https://golang.org/cl/7397049 (Goの変更リスト)
- Go言語の公式ドキュメント (特にランタイムとガベージコレクタのセクション)
- Goのソースコード (特に
src/runtime/proc.go
やsrc/runtime/mgc.go
など、スケジューラやGCに関連するファイル) - GoのM, P, Gモデルに関する技術ブログや解説記事 (一般的なGoの並行処理モデルの理解のため)
- ヒープ破損に関する一般的なコンピュータサイエンスの資料 (ヒープ破損がどのように発生し、なぜ危険なのかを理解するため)