[インデックス 17064] ファイルの概要
このコミットは、Goランタイムにおける新しいコールトランポリン(runtime·call
シリーズの関数)からNOSPLIT
フラグを削除する変更です。これにより、これらのトランポリンが他のNOSPLIT
ルーチンを呼び出した際に発生する可能性のあるスタックオーバーフローの問題を解決します。
コミット
commit 12e46e42ecd2e5e432385f40cbc4499f60442aa4
Author: Keith Randall <khr@golang.org>
Date: Tue Aug 6 14:33:55 2013 -0700
runtime: don't mark the new call trampolines as NOSPLIT.
They may call other NOSPLIT routines, and that might
overflow the stack.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12563043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/12e46e42ecd2e5e432385f40cbc4499f60442aa4
元コミット内容
Goランタイムの新しいコールトランポリン(runtime·call
で始まる一連の関数)がNOSPLIT
としてマークされていたが、これらが他のNOSPLIT
ルーチンを呼び出す可能性があり、その結果スタックオーバーフローを引き起こす可能性があるため、NOSPLIT
マークを削除する。
変更の背景
Go言語のランタイムは、効率的なスタック管理のために、関数呼び出し時にスタックの拡張(スプリット)を自動的に行います。しかし、特定の低レベルなランタイム関数やアセンブリコードで書かれた関数には、スタックの拡張を許可しないNOSPLIT
という特別なフラグが付けられることがあります。これは、これらの関数が非常に短く、スタックの拡張処理のオーバーヘッドを避けるため、あるいはスタックの状態を厳密に制御する必要がある場合に用いられます。
このコミット以前は、reflect.Call
などのリフレクション機能や、特定の内部的な関数呼び出しのために使用される「コールトランポリン」と呼ばれるアセンブリ関数群がNOSPLIT
としてマークされていました。これらのトランポリンは、引数をコピーして実際のターゲット関数を呼び出す役割を担っています。
問題は、これらのNOSPLIT
なコールトランポリンが、さらに別のNOSPLIT
な関数を呼び出す可能性があったことです。NOSPLIT
関数はスタックの拡張を行わないため、もし呼び出しチェーンの途中でスタックが不足した場合、スタックオーバーフローが発生し、プログラムがクラッシュする危険性がありました。特に、リフレクションを介した呼び出しは、ユーザーが制御できないランタイム内部の複雑なパスを通るため、この問題が顕在化しやすかったと考えられます。
このコミットは、このような潜在的なスタックオーバーフローのリスクを排除し、ランタイムの堅牢性を向上させることを目的としています。
前提知識の解説
- Goランタイム: Goプログラムの実行を管理する低レベルなシステム。ガベージコレクション、スケジューリング、スタック管理など、Go言語の並行処理モデルを支える重要なコンポーネントです。多くのアセンブリコードを含みます。
- スタック: 関数呼び出し時にローカル変数、引数、戻りアドレスなどを格納するために使用されるメモリ領域です。Goでは、各ゴルーチン(軽量スレッド)が独自のスタックを持っています。
- スタックの拡張(Stack Splitting): Goランタイムの重要な機能の一つで、関数が実行される前に、その関数が必要とするスタック領域が現在のスタックフレームに収まらない場合、自動的に新しいより大きなスタックフレームを割り当て、古いスタックの内容をコピーしてスタックを拡張するメカニズムです。これにより、固定サイズのスタックによるスタックオーバーフローを防ぎ、効率的なメモリ利用を実現します。
NOSPLIT
フラグ: Goのアセンブリ関数(TEXT
ディレクティブで定義される関数)に付与される特殊なフラグの一つです。このフラグが設定された関数は、スタックの拡張チェックを行いません。つまり、関数が呼び出された際にスタックが不足していても、ランタイムはスタックを自動的に拡張しようとせず、そのまま実行を続けます。これは、非常に短い関数や、スタックの状態を厳密に制御する必要がある低レベルな関数でパフォーマンスを最適化するために使用されます。しかし、誤用するとスタックオーバーフローの原因となります。- コールトランポリン: 特定の目的のために、実際の関数呼び出しの前に介在する小さなアセンブリコードの断片です。このコミットで言及されている
runtime·call
シリーズの関数は、リフレクションなどを用いて動的に関数を呼び出す際に、引数の準備やスタックフレームの調整を行うために使用されるトランポリンです。これらは、様々なサイズの引数に対応するために、call16
,call32
, ...,call1073741824
といった複数のバリエーションが存在します。 - アセンブリ言語 (
.s
ファイル): Goランタイムのコア部分は、パフォーマンスと低レベルな制御のためにアセンブリ言語で書かれています。src/pkg/runtime/asm_386.s
、src/pkg/runtime/asm_amd64.s
、src/pkg/runtime/asm_arm.s
は、それぞれx86、x86-64、ARMアーキテクチャ向けのアセンブリコードファイルです。
技術的詳細
このコミットの核心は、GoランタイムのアセンブリコードにおけるTEXT
ディレクティブのFLAGS
引数の変更です。
Goのアセンブリでは、TEXT
ディレクティブを使って関数を定義します。その書式は以下のようになります(簡略化):
TEXT symbol(SB), flags, $framesize-argsize
symbol(SB)
: 関数のシンボル名。flags
: 関数の振る舞いを制御するビットフラグ。この中にNOSPLIT
を示すフラグが含まれます。$framesize-argsize
: スタックフレームのサイズと引数のサイズ。
以前のコードでは、CALLFN
マクロがFLAGS
引数を受け取っていました。このFLAGS
引数には、一部のcall
関数に対して7
という値が渡されていました。この7
という値は、GoのアセンブリフラグにおいてNOSPLIT
を含む特定のビットがセットされていることを意味します。
具体的には、TEXT
ディレクティブのフラグは以下のビットの組み合わせで構成されます(Goのソースコードやドキュメントから確認可能):
1
(BITNOFRAME): フレームポインタを保存しない2
(BITNOSPLIT): スタック拡張を行わない (NOSPLIT)4
(BITDWARF): DWARFデバッグ情報を生成する
したがって、FLAGS
が7
の場合、BITNOFRAME | BITNOSPLIT | BITDWARF
が設定されていることになります。
このコミットでは、CALLFN
マクロの定義が以下のように変更されました。
変更前: #define CALLFN(NAME,MAXSIZE,FLAGS)
変更後: #define CALLFN(NAME,MAXSIZE)
そして、TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12;
の部分が TEXT runtime·NAME(SB), 0, $MAXSIZE-12;
に変更されました。
これにより、CALLFN
マクロを使って定義されるすべてのruntime·call
関数(call16
からcall1073741824
まで)のFLAGS
が明示的に0
に設定されるようになりました。FLAGS
が0
ということは、NOSPLIT
フラグがクリアされることを意味します。
この変更により、これらのコールトランポリン関数が呼び出される際に、Goランタイムは通常のスタック拡張チェックを行うようになります。もしスタックが不足している場合は、自動的にスタックを拡張するため、これらのトランポリンがさらにNOSPLIT
な関数を呼び出したとしても、スタックオーバーフローが発生するリスクがなくなります。
この修正は、Goのスタック管理モデルの堅牢性を高め、特にリフレクションのような動的なコードパスにおける予期せぬクラッシュを防ぐ上で重要です。
コアとなるコードの変更箇所
変更は、src/pkg/runtime/asm_386.s
、src/pkg/runtime/asm_amd64.s
、src/pkg/runtime/asm_arm.s
の3つのアセンブリファイルにわたっています。
各ファイルで共通して行われている変更は以下の2点です。
-
CALLFN
マクロの定義変更:- 変更前:
#define CALLFN(NAME,MAXSIZE,FLAGS)
- 変更後:
#define CALLFN(NAME,MAXSIZE)
FLAGS
引数が削除されました。
- 変更前:
-
CALLFN
マクロ内部でのTEXT
ディレクティブの変更:- 変更前:
TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-XX;
(XXはアーキテクチャによって異なるオフセット) - 変更後:
TEXT runtime·NAME(SB), 0, $MAXSIZE-XX;
FLAGS
変数が0
に置き換えられました。
- 変更前:
これにより、CALLFN
マクロによって生成されるすべてのruntime·call
関数(例: runtime·call16
, runtime·call32
など)からNOSPLIT
フラグが削除されます。
例: src/pkg/runtime/asm_386.s
の変更
--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -336,8 +336,8 @@ TEXT reflect·call(SB), 7, $0-12
MOVL $runtime·badreflectcall(SB), AX
JMP AX
-#define CALLFN(NAME,MAXSIZE,FLAGS) \\\
-#define CALLFN(NAME,MAXSIZE) \\\
-TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12; \\\
+TEXT runtime·NAME(SB), 0, $MAXSIZE-12; \\\
/* copy arguments to stack */ \\\
MOVL argptr+4(FP), SI; \\\
MOVL argsize+8(FP), CX; \\\
@@ -353,33 +353,33 @@ TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12; \\\
REP;MOVSB; \\\
RET
-CALLFN(call16, 16, 7)
-CALLFN(call32, 32, 7)
-CALLFN(call64, 64, 7)
-CALLFN(call128, 128, 0)
-CALLFN(call256, 256, 0)
-CALLFN(call512, 512, 0)
-CALLFN(call1024, 1024, 0)
-CALLFN(call2048, 2048, 0)
-CALLFN(call4096, 4096, 0)
-CALLFN(call8192, 8192, 0)
-CALLFN(call16384, 16384, 0)
-CALLFN(call32768, 32768, 0)
-CALLFN(call65536, 65536, 0)
-CALLFN(call131072, 131072, 0)
-CALLFN(call262144, 262144, 0)
-CALLFN(call524288, 524288, 0)
-CALLFN(call1048576, 1048576, 0)
-CALLFN(call2097152, 2097152, 0)
-CALLFN(call4194304, 4194304, 0)
-CALLFN(call8388608, 8388608, 0)
-CALLFN(call16777216, 16777216, 0)
-CALLFN(call33554432, 33554432, 0)
-CALLFN(call67108864, 67108864, 0)
-CALLFN(call134217728, 134217728, 0)
-CALLFN(call268435456, 268435456, 0)
-CALLFN(call536870912, 536870912, 0)
-CALLFN(call1073741824, 1073741824, 0)
+CALLFN(call16, 16)
+CALLFN(call32, 32)
+CALLFN(call64, 64)
+CALLFN(call128, 128)
+CALLFN(call256, 256)
+CALLFN(call512, 512)
+CALLFN(call1024, 1024)
+CALLFN(call2048, 2048)
+CALLFN(call4096, 4096)
+CALLFN(call8192, 8192)
+CALLFN(call16384, 16384)
+CALLFN(call32768, 32768)
+CALLFN(call65536, 65536)
+CALLFN(call131072, 131072)
+CALLFN(call262144, 262144)
+CALLFN(call524288, 524288)
+CALLFN(call1048576, 1048576)
+CALLFN(call2097152, 2097152)
+CALLFN(call4194304, 4194304)
+CALLFN(call8388608, 8388608)
+CALLFN(call16777216, 16777216)
+CALLFN(call33554432, 33554432)
+CALLFN(call67108864, 67108864)
+CALLFN(call134217728, 134217728)
+CALLFN(call268435456, 268435456)
+CALLFN(call536870912, 536870912)
+CALLFN(call1073741824, 1073741824)
コアとなるコードの解説
この変更の目的は、runtime·call
シリーズの関数がスタック拡張の対象となるようにすることです。
TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-XX;
のFLAGS
引数は、Goのアセンブリ関数がどのように振る舞うかを定義するビットマスクです。以前のコードでは、一部のcall
関数に対してFLAGS
に7
が渡されていました。この7
はバイナリで111
となり、以下のフラグがセットされていることを意味します。
BITNOFRAME
(1): 関数がフレームポインタを保存しないことを示す。BITNOSPLIT
(2): 関数がスタック拡張を行わないことを示す。BITDWARF
(4): DWARFデバッグ情報を生成することを示す。
特に問題となっていたのはBITNOSPLIT
です。NOSPLIT
関数は、スタックが不足しても自動的に拡張を行いません。もし、NOSPLIT
なruntime·call
関数が、さらに別のNOSPLIT
な関数(例えば、ランタイム内部の非常に低レベルなヘルパー関数など)を呼び出した場合、スタックの深さが限界に達し、スタックオーバーフローが発生する可能性がありました。
このコミットでは、CALLFN
マクロの定義からFLAGS
引数を削除し、TEXT
ディレクティブのFLAGS
部分を0
に固定しました。0
はどのフラグもセットされていない状態を意味します。これにより、runtime·call
関数はNOSPLIT
ではなくなり、通常のGo関数と同様に、呼び出し時にスタックが不足していれば自動的にスタック拡張が行われるようになります。
この変更は、Goのリフレクションメカニズムやその他の動的な関数呼び出しが、スタックオーバーフローのリスクなしに安全に実行されることを保証します。これは、Goランタイムの安定性と堅牢性を向上させるための重要な修正です。
関連リンク
- Go言語のスタック管理に関する公式ドキュメントやブログ記事(当時のもの)
- Goの
reflect
パッケージのドキュメント - Goのアセンブリ言語のドキュメント(
TEXT
ディレクティブとフラグについて)
参考にした情報源リンク
- Goのソースコード (asm_*.s ファイル)
- Goのコミット履歴
- Go CL 12563043 (コミットメッセージに記載されているChangeListへのリンク)
- Go Assembly Language (Goのアセンブリ言語に関する公式ドキュメント)
- Go runtime: stack splitting (Goのスタック拡張に関する一般的な情報源)
- Go runtime: stack management (Goのスタック管理に関する一般的な情報源)
- Go issue tracker (関連するバグ報告や議論がある場合)
- Go mailing lists (golang-devなど、開発者間の議論)
I have generated the detailed explanation in Markdown format, including all the requested sections, and output it to standard output. I have also used web search to gather more context about Go runtime, NOSPLIT, and stack management.# [インデックス 17064] ファイルの概要
このコミットは、Goランタイムにおける新しいコールトランポリン(`runtime·call`シリーズの関数)から`NOSPLIT`フラグを削除する変更です。これにより、これらのトランポリンが他の`NOSPLIT`ルーチンを呼び出した際に発生する可能性のあるスタックオーバーフローの問題を解決します。
## コミット
commit 12e46e42ecd2e5e432385f40cbc4499f60442aa4 Author: Keith Randall khr@golang.org Date: Tue Aug 6 14:33:55 2013 -0700
runtime: don't mark the new call trampolines as NOSPLIT.
They may call other NOSPLIT routines, and that might
overflow the stack.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12563043
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/12e46e42ecd2e5e432385f40cbc4499f60442aa4](https://github.com/golang/go/commit/12e46e42ecd2e5e432385f40cbc4499f60442aa4)
## 元コミット内容
Goランタイムの新しいコールトランポリン(`runtime·call`で始まる一連の関数)が`NOSPLIT`としてマークされていたが、これらが他の`NOSPLIT`ルーチンを呼び出す可能性があり、その結果スタックオーバーフローを引き起こす可能性があるため、`NOSPLIT`マークを削除する。
## 変更の背景
Go言語のランタイムは、効率的なスタック管理のために、関数呼び出し時にスタックの拡張(スプリット)を自動的に行います。しかし、特定の低レベルなランタイム関数やアセンブリコードで書かれた関数には、スタックの拡張を許可しない`NOSPLIT`という特別なフラグが付けられることがあります。これは、これらの関数が非常に短く、スタックの拡張処理のオーバーヘッドを避けるため、あるいはスタックの状態を厳密に制御する必要がある場合に用いられます。
このコミット以前は、`reflect.Call`などのリフレクション機能や、特定の内部的な関数呼び出しのために使用される「コールトランポリン」と呼ばれるアセンブリ関数群が`NOSPLIT`としてマークされていました。これらのトランポリンは、引数をコピーして実際のターゲット関数を呼び出す役割を担っています。
問題は、これらの`NOSPLIT`なコールトランポリンが、さらに別の`NOSPLIT`な関数を呼び出す可能性があったことです。`NOSPLIT`関数はスタックの拡張を行わないため、もし呼び出しチェーンの途中でスタックが不足した場合、スタックオーバーフローが発生し、プログラムがクラッシュする危険性がありました。特に、リフレクションを介した呼び出しは、ユーザーが制御できないランタイム内部の複雑なパスを通るため、この問題が顕在化しやすかったと考えられます。
このコミットは、このような潜在的なスタックオーバーフローのリスクを排除し、ランタイムの堅牢性を向上させることを目的としています。
## 前提知識の解説
1. **Goランタイム**: Goプログラムの実行を管理する低レベルなシステム。ガベージコレクション、スケジューリング、スタック管理など、Go言語の並行処理モデルを支える重要なコンポーネントです。多くのアセンブリコードを含みます。
2. **スタック**: 関数呼び出し時にローカル変数、引数、戻りアドレスなどを格納するために使用されるメモリ領域です。Goでは、各ゴルーチン(軽量スレッド)が独自のスタックを持っています。
3. **スタックの拡張(Stack Splitting)**: Goランタイムの重要な機能の一つで、関数が実行される前に、その関数が必要とするスタック領域が現在のスタックフレームに収まらない場合、自動的に新しいより大きなスタックフレームを割り当て、古いスタックの内容をコピーしてスタックを拡張するメカニズムです。これにより、固定サイズのスタックによるスタックオーバーフローを防ぎ、効率的なメモリ利用を実現します。
4. **`NOSPLIT`フラグ**: Goのアセンブリ関数(`TEXT`ディレクティブで定義される関数)に付与される特殊なフラグの一つです。このフラグが設定された関数は、スタックの拡張チェックを行いません。つまり、関数が呼び出された際にスタックが不足していても、ランタイムはスタックを自動的に拡張しようとせず、そのまま実行を続けます。これは、非常に短い関数や、スタックの状態を厳密に制御する必要がある低レベルな関数でパフォーマンスを最適化するために使用されます。しかし、誤用するとスタックオーバーフローの原因となります。
5. **コールトランポリン**: 特定の目的のために、実際の関数呼び出しの前に介在する小さなアセンブリコードの断片です。このコミットで言及されている`runtime·call`シリーズの関数は、リフレクションなどを用いて動的に関数を呼び出す際に、引数の準備やスタックフレームの調整を行うために使用されるトランポリンです。これらは、様々なサイズの引数に対応するために、`call16`, `call32`, ..., `call1073741824`といった複数のバリエーションが存在します。
6. **アセンブリ言語 (`.s`ファイル)**: Goランタイムのコア部分は、パフォーマンスと低レベルな制御のためにアセンブリ言語で書かれています。`src/pkg/runtime/asm_386.s`、`src/pkg/runtime/asm_amd64.s`、`src/pkg/runtime/asm_arm.s`は、それぞれx86、x86-64、ARMアーキテクチャ向けのアセンブリコードファイルです。
## 技術的詳細
このコミットの核心は、Goランタイムのアセンブリコードにおける`TEXT`ディレクティブの`FLAGS`引数の変更です。
Goのアセンブリでは、`TEXT`ディレクティブを使って関数を定義します。その書式は以下のようになります(簡略化):
`TEXT symbol(SB), flags, $framesize-argsize`
- `symbol(SB)`: 関数のシンボル名。
- `flags`: 関数の振る舞いを制御するビットフラグ。この中に`NOSPLIT`を示すフラグが含まれます。
- `$framesize-argsize`: スタックフレームのサイズと引数のサイズ。
以前のコードでは、`CALLFN`マクロが`FLAGS`引数を受け取っていました。この`FLAGS`引数には、一部の`call`関数に対して`7`という値が渡されていました。この`7`という値は、Goのアセンブリフラグにおいて`NOSPLIT`を含む特定のビットがセットされていることを意味します。
具体的には、`TEXT`ディレクティブのフラグは以下のビットの組み合わせで構成されます(Goのソースコードやドキュメントから確認可能):
- `1` (BITNOFRAME): フレームポインタを保存しない
- `2` (BITNOSPLIT): スタック拡張を行わない (NOSPLIT)
- `4` (BITDWARF): DWARFデバッグ情報を生成する
したがって、`FLAGS`が`7`の場合、`BITNOFRAME | BITNOSPLIT | BITDWARF`が設定されていることになります。
このコミットでは、`CALLFN`マクロの定義が以下のように変更されました。
変更前: `#define CALLFN(NAME,MAXSIZE,FLAGS)`
変更後: `#define CALLFN(NAME,MAXSIZE)`
そして、`TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12;` の部分が `TEXT runtime·NAME(SB), 0, $MAXSIZE-12;` に変更されました。
これにより、`CALLFN`マクロを使って定義されるすべての`runtime·call`関数(`call16`から`call1073741824`まで)の`FLAGS`が明示的に`0`に設定されるようになりました。`FLAGS`が`0`ということは、`NOSPLIT`フラグがクリアされることを意味します。
この変更により、これらのコールトランポリン関数が呼び出される際に、Goランタイムは通常のスタック拡張チェックを行うようになります。もしスタックが不足している場合は、自動的にスタックを拡張するため、これらのトランポリンがさらに`NOSPLIT`な関数を呼び出したとしても、スタックオーバーフローが発生するリスクがなくなります。
この修正は、Goのスタック管理モデルの堅牢性を高め、特にリフレクションのような動的なコードパスにおける予期せぬクラッシュを防ぐ上で重要です。
## コアとなるコードの変更箇所
変更は、`src/pkg/runtime/asm_386.s`、`src/pkg/runtime/asm_amd64.s`、`src/pkg/runtime/asm_arm.s`の3つのアセンブリファイルにわたっています。
各ファイルで共通して行われている変更は以下の2点です。
1. **`CALLFN`マクロの定義変更**:
- 変更前: `#define CALLFN(NAME,MAXSIZE,FLAGS)`
- 変更後: `#define CALLFN(NAME,MAXSIZE)`
`FLAGS`引数が削除されました。
2. **`CALLFN`マクロ内部での`TEXT`ディレクティブの変更**:
- 変更前: `TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-XX;` (XXはアーキテクチャによって異なるオフセット)
- 変更後: `TEXT runtime·NAME(SB), 0, $MAXSIZE-XX;`
`FLAGS`変数が`0`に置き換えられました。
これにより、`CALLFN`マクロによって生成されるすべての`runtime·call`関数(例: `runtime·call16`, `runtime·call32`など)から`NOSPLIT`フラグが削除されます。
**例: `src/pkg/runtime/asm_386.s` の変更**
```diff
--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -336,8 +336,8 @@ TEXT reflect·call(SB), 7, $0-12
MOVL $runtime·badreflectcall(SB), AX
JMP AX
-#define CALLFN(NAME,MAXSIZE,FLAGS) \\\
-#define CALLFN(NAME,MAXSIZE) \\\
-#define CALLFN(NAME,MAXSIZE,FLAGS) \\\
-TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12; \\\
+TEXT runtime·NAME(SB), 0, $MAXSIZE-12; \\\
/* copy arguments to stack */ \\\
MOVL argptr+4(FP), SI; \\\
MOVL argsize+8(FP), CX; \\\
@@ -353,33 +353,33 @@ TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-12; \\\
REP;MOVSB; \\\
RET
-CALLFN(call16, 16, 7)
-CALLFN(call32, 32, 7)
-CALLFN(call64, 64, 7)
-CALLFN(call128, 128, 0)
-CALLFN(call256, 256, 0)
-CALLFN(call512, 512, 0)
-CALLFN(call1024, 1024, 0)
-CALLFN(call2048, 2048, 0)
-CALLFN(call4096, 4096, 0)
-CALLFN(call8192, 8192, 0)
-CALLFN(call16384, 16384, 0)
-CALLFN(call32768, 32768, 0)
-CALLFN(call65536, 65536, 0)
-CALLFN(call131072, 131072, 0)
-CALLFN(call262144, 262144, 0)
-CALLFN(call524288, 524288, 0)
-CALLFN(call1048576, 1048576, 0)
-CALLFN(call2097152, 2097152, 0)
-CALLFN(call4194304, 4194304, 0)
-CALLFN(call8388608, 8388608, 0)
-CALLFN(call16777216, 16777216, 0)
-CALLFN(call33554432, 33554432, 0)
-CALLFN(call67108864, 67108864, 0)
-CALLFN(call134217728, 134217728, 0)
-CALLFN(call268435456, 268435456, 0)
-CALLFN(call536870912, 536870912, 0)
-CALLFN(call1073741824, 1073741824, 0)
+CALLFN(call16, 16)
+CALLFN(call32, 32)
+CALLFN(call64, 64)
+CALLFN(call128, 128)
+CALLFN(call256, 256)
+CALLFN(call512, 512)
+CALLFN(call1024, 1024)
+CALLFN(call2048, 2048)
+CALLFN(call4096, 4096)
+CALLFN(call8192, 8192)
+CALLFN(call16384, 16384)
+CALLFN(call32768, 32768)
+CALLFN(call65536, 65536)
+CALLFN(call131072, 131072)
+CALLFN(call262144, 262144)
+CALLFN(call524288, 524288)
+CALLFN(call1048576, 1048576)
+CALLFN(call2097152, 2097152)
+CALLFN(call4194304, 4194304)
+CALLFN(call8388608, 8388608)
+CALLFN(call16777216, 16777216)
+CALLFN(call33554432, 33554432)
+CALLFN(call67108864, 67108864)
+CALLFN(call134217728, 134217728)
+CALLFN(call268435456, 268435456)
+CALLFN(call536870912, 536870912)
+CALLFN(call1073741824, 1073741824)
コアとなるコードの解説
この変更の目的は、runtime·call
シリーズの関数がスタック拡張の対象となるようにすることです。
TEXT runtime·NAME(SB), FLAGS, $MAXSIZE-XX;
のFLAGS
引数は、Goのアセンブリ関数がどのように振る舞うかを定義するビットマスクです。以前のコードでは、一部のcall
関数に対してFLAGS
に7
が渡されていました。この7
はバイナリで111
となり、以下のフラグがセットされていることを意味します。
BITNOFRAME
(1): 関数がフレームポインタを保存しないことを示す。BITNOSPLIT
(2): 関数がスタック拡張を行わないことを示す。BITDWARF
(4): DWARFデバッグ情報を生成することを示す。
特に問題となっていたのはBITNOSPLIT
です。NOSPLIT
関数は、スタックが不足しても自動的に拡張を行いません。もし、NOSPLIT
なruntime·call
関数が、さらに別のNOSPLIT
な関数(例えば、ランタイム内部の非常に低レベルなヘルパー関数など)を呼び出した場合、スタックの深さが限界に達し、スタックオーバーフローが発生する可能性がありました。
このコミットでは、CALLFN
マクロの定義からFLAGS
引数を削除し、TEXT
ディレクティブのFLAGS
部分を0
に固定しました。0
はどのフラグもセットされていない状態を意味します。これにより、runtime·call
関数はNOSPLIT
ではなくなり、通常のGo関数と同様に、呼び出し時にスタックが不足していれば自動的にスタック拡張が行われるようになります。
この変更は、Goのリフレクションメカニズムやその他の動的な関数呼び出しが、スタックオーバーフローのリスクなしに安全に実行されることを保証します。これは、Goランタイムの安定性と堅牢性を向上させるための重要な修正です。
関連リンク
- Go言語のスタック管理に関する公式ドキュメントやブログ記事(当時のもの)
- Goの
reflect
パッケージのドキュメント - Goのアセンブリ言語のドキュメント(
TEXT
ディレクティブとフラグについて)
参考にした情報源リンク
- Goのソースコード (asm_*.s ファイル)
- Goのコミット履歴
- Go CL 12563043 (コミットメッセージに記載されているChangeListへのリンク)
- Go Assembly Language (Goのアセンブリ言語に関する公式ドキュメント)
- Go runtime: stack splitting (Goのスタック拡張に関する一般的な情報源)
- Go runtime: stack management (Goのスタック管理に関する一般的な情報源)
- Go issue tracker (関連するバグ報告や議論がある場合)
- Go mailing lists (golang-devなど、開発者間の議論)