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

[インデックス 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関数はスタックの拡張を行わないため、もし呼び出しチェーンの途中でスタックが不足した場合、スタックオーバーフローが発生し、プログラムがクラッシュする危険性がありました。特に、リフレクションを介した呼び出しは、ユーザーが制御できないランタイム内部の複雑なパスを通るため、この問題が顕在化しやすかったと考えられます。

このコミットは、このような潜在的なスタックオーバーフローのリスクを排除し、ランタイムの堅牢性を向上させることを目的としています。

前提知識の解説

  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.ssrc/pkg/runtime/asm_amd64.ssrc/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デバッグ情報を生成する

したがって、FLAGS7の場合、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に設定されるようになりました。FLAGS0ということは、NOSPLITフラグがクリアされることを意味します。

この変更により、これらのコールトランポリン関数が呼び出される際に、Goランタイムは通常のスタック拡張チェックを行うようになります。もしスタックが不足している場合は、自動的にスタックを拡張するため、これらのトランポリンがさらにNOSPLITな関数を呼び出したとしても、スタックオーバーフローが発生するリスクがなくなります。

この修正は、Goのスタック管理モデルの堅牢性を高め、特にリフレクションのような動的なコードパスにおける予期せぬクラッシュを防ぐ上で重要です。

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

変更は、src/pkg/runtime/asm_386.ssrc/pkg/runtime/asm_amd64.ssrc/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 の変更

--- 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関数に対してFLAGS7が渡されていました。この7はバイナリで111となり、以下のフラグがセットされていることを意味します。

  • BITNOFRAME (1): 関数がフレームポインタを保存しないことを示す。
  • BITNOSPLIT (2): 関数がスタック拡張を行わないことを示す。
  • BITDWARF (4): DWARFデバッグ情報を生成することを示す。

特に問題となっていたのはBITNOSPLITです。NOSPLIT関数は、スタックが不足しても自動的に拡張を行いません。もし、NOSPLITruntime·call関数が、さらに別のNOSPLITな関数(例えば、ランタイム内部の非常に低レベルなヘルパー関数など)を呼び出した場合、スタックの深さが限界に達し、スタックオーバーフローが発生する可能性がありました。

このコミットでは、CALLFNマクロの定義からFLAGS引数を削除し、TEXTディレクティブのFLAGS部分を0に固定しました。0はどのフラグもセットされていない状態を意味します。これにより、runtime·call関数はNOSPLITではなくなり、通常のGo関数と同様に、呼び出し時にスタックが不足していれば自動的にスタック拡張が行われるようになります。

この変更は、Goのリフレクションメカニズムやその他の動的な関数呼び出しが、スタックオーバーフローのリスクなしに安全に実行されることを保証します。これは、Goランタイムの安定性と堅牢性を向上させるための重要な修正です。

関連リンク

  • Go言語のスタック管理に関する公式ドキュメントやブログ記事(当時のもの)
  • Goのreflectパッケージのドキュメント
  • Goのアセンブリ言語のドキュメント(TEXTディレクティブとフラグについて)

参考にした情報源リンク

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関数に対してFLAGS7が渡されていました。この7はバイナリで111となり、以下のフラグがセットされていることを意味します。

  • BITNOFRAME (1): 関数がフレームポインタを保存しないことを示す。
  • BITNOSPLIT (2): 関数がスタック拡張を行わないことを示す。
  • BITDWARF (4): DWARFデバッグ情報を生成することを示す。

特に問題となっていたのはBITNOSPLITです。NOSPLIT関数は、スタックが不足しても自動的に拡張を行いません。もし、NOSPLITruntime·call関数が、さらに別のNOSPLITな関数(例えば、ランタイム内部の非常に低レベルなヘルパー関数など)を呼び出した場合、スタックの深さが限界に達し、スタックオーバーフローが発生する可能性がありました。

このコミットでは、CALLFNマクロの定義からFLAGS引数を削除し、TEXTディレクティブのFLAGS部分を0に固定しました。0はどのフラグもセットされていない状態を意味します。これにより、runtime·call関数はNOSPLITではなくなり、通常のGo関数と同様に、呼び出し時にスタックが不足していれば自動的にスタック拡張が行われるようになります。

この変更は、Goのリフレクションメカニズムやその他の動的な関数呼び出しが、スタックオーバーフローのリスクなしに安全に実行されることを保証します。これは、Goランタイムの安定性と堅牢性を向上させるための重要な修正です。

関連リンク

  • Go言語のスタック管理に関する公式ドキュメントやブログ記事(当時のもの)
  • Goのreflectパッケージのドキュメント
  • Goのアセンブリ言語のドキュメント(TEXTディレクティブとフラグについて)

参考にした情報源リンク