[インデックス 18814] ファイルの概要
このコミットは、Goランタイムにおけるamd64p32
アーキテクチャ上でのcgocallback
関数のスタックサイズに関するバグ修正です。具体的には、cgocallback
関数のスタックフレームサイズが誤って設定されていた問題を修正し、正しい値に調整することで、Cgoコールバックが正しく機能するようにしています。
コミット
commit 4bc632cead0cd2a3c92a22e47c587ae8b76c400b
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Mon Mar 10 07:57:58 2014 +0100
runtime: fix cgocallback stack size on amd64p32.
LGTM=dave
R=rsc, dave, iant
CC=golang-codereviews
https://golang.org/cl/73160043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4bc632cead0cd2a3c92a22e47c587ae8b76c400b
元コミット内容
runtime: fix cgocallback stack size on amd64p32.
LGTM=dave
R=rsc, dave, iant
CC=golang-codereviews
https://golang.org/cl/73160043
変更の背景
このコミットは、amd64p32
という特定のアーキテクチャにおいて、Cgo(GoとC言語の相互運用機能)のコールバック処理におけるスタックサイズの問題を修正するために行われました。
GoプログラムがC言語のコードを呼び出す際(C.func()
のような形式)、またはC言語のコードからGoの関数が呼び出される際(Cgoコールバック)、Goランタイムはこれらの呼び出しを適切に処理するためのメカニズムを提供します。特にCgoコールバックの場合、CコードからGoコードへの遷移が発生するため、スタックの切り替えや引数の受け渡しなど、低レベルな処理が必要となります。
amd64p32
は、64ビットのレジスタと命令セットを使用しながらも、ポインタサイズが32ビットであるという特殊な環境です。このような環境では、通常の64ビットシステムとは異なるメモリレイアウトやアライメントの考慮が必要になることがあります。
元のコードでは、runtime·cgocallback
関数のスタックフレームサイズが誤って$12-12
と設定されていました。これは、関数が引数を受け取らない場合や、スタック上にローカル変数を確保しない場合に用いられる表記ですが、実際にはCgoコールバックの処理に必要なスタック領域が確保されていなかった可能性があります。この不正確なスタックサイズの設定は、Cgoコールバックが実行される際にスタックオーバーフローやメモリ破壊を引き起こす可能性があり、プログラムのクラッシュや予期せぬ動作につながる重大なバグでした。
この修正は、amd64p32
環境でCgoコールバックが安定して動作するために不可欠なものでした。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Goランタイム (Go Runtime): Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、スタック管理、Cgoインターフェースなどが含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、Goランタイムの制御下で実行されます。
-
Cgo: Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。Cgoは、GoとCの異なる呼び出し規約、メモリモデル、スタック管理などを橋渡しする役割を担います。
-
スタック (Stack): スタックは、プログラムの実行中に一時的なデータを格納するために使用されるメモリ領域です。関数が呼び出されるたびに、その関数のローカル変数、引数、戻りアドレスなどがスタックフレームとしてスタックにプッシュされます。関数が終了すると、そのスタックフレームはポップされ、スタックポインタが元の位置に戻ります。スタックはLIFO(Last-In, First-Out)の原則で動作します。
-
スタックサイズ (Stack Size): 関数が実行される際に必要となるスタック領域の量です。これは、関数の引数の数、ローカル変数のサイズ、およびその関数が呼び出す他の関数のスタック使用量によって決まります。スタックサイズが不足すると、スタックオーバーフローが発生し、プログラムがクラッシュする原因となります。
-
amd64p32
アーキテクチャ:amd64p32
は、64ビットのAMD64命令セットを使用しながらも、ポインタのサイズが32ビットに制限されている特殊な実行環境です。これは、主に一部の組み込みシステムや、メモリ使用量を抑えつつ64ビットの計算能力を利用したい場合に用いられることがあります。通常のamd64
(またはx86-64
)アーキテクチャではポインタは64ビットですが、amd64p32
では32ビットであるため、メモリのアドレス指定やデータ構造のレイアウトにおいて特別な考慮が必要になります。 -
アセンブリ言語 (Assembly Language): このコミットで変更されているファイルはアセンブリ言語で書かれています。アセンブリ言語は、CPUが直接理解できる機械語に非常に近い低レベルのプログラミング言語です。Goランタイムのコア部分は、パフォーマンスや特定のハードウェア操作のためにアセンブリ言語で記述されることがあります。Goのアセンブリは、Plan 9アセンブラの構文に似ています。
-
TEXT
ディレクティブ: Goのアセンブリ言語におけるTEXT
ディレクティブは、関数の定義を開始するために使用されます。その構文は通常TEXT symbol(SB), flags, args_size-frame_size
のようになります。symbol(SB)
: 関数のシンボル名。SB
は静的ベースポインタ(Static Base pointer)を表し、グローバルシンボルであることを示します。flags
: 関数の特性を示すフラグ(例:NOSPLIT
はスタックの分割を許可しないことを示します)。args_size-frame_size
: これは非常に重要で、args_size
は関数が受け取る引数の合計サイズ(バイト単位)、frame_size
は関数自身のスタックフレーム(ローカル変数やレジスタ退避領域など)のサイズ(バイト単位)を示します。このコミットでは、このframe_size
が問題となっていました。
技術的詳細
このコミットの技術的な核心は、amd64p32
アーキテクチャにおけるcgocallback
関数のスタックフレームサイズの正確な設定にあります。
Goランタイムでは、Cgoコールバック(CコードからGo関数を呼び出す)を処理するために、runtime·cgocallback
というアセンブリ関数が使用されます。この関数は、Cの呼び出し規約からGoの呼び出し規約への変換、スタックの切り替え、そしてGoのスケジューラへの制御の引き渡しなど、複雑な低レベル処理を行います。
Goのアセンブリ言語では、TEXT
ディレクティブのargs_size-frame_size
という部分で、関数の引数とローカルスタックフレームのサイズを明示的に指定します。
args_size
: 関数が呼び出し元から受け取る引数の合計サイズ。frame_size
: 関数自身のローカル変数や、レジスタを退避するために使用するスタック領域のサイズ。
元のコードでは、runtime·cgocallback
の定義が以下のようになっていました。
TEXT runtime·cgocallback(SB),NOSPLIT,$12-12
ここで、$12-12
は、引数のサイズが12バイト、スタックフレームのサイズも12バイトであることを示しています。しかし、cgocallback
関数は実際には引数を受け取らず、また、その内部で特別なスタック領域を必要としない場合、スタックフレームサイズは0であるべきです。
$12-12
という表記は、Goのアセンブリにおいて、引数サイズとフレームサイズが同じ値である場合に、フレームサイズが0であることを意味する慣例的な表現として使われることがあります。しかし、これは誤解を招きやすく、特にamd64p32
のような特殊な環境では、コンパイラやアセンブラがこれを正しく解釈しない可能性がありました。
このコミットでは、この部分を以下のように修正しています。
TEXT runtime·cgocallback(SB),NOSPLIT,$0-12
この変更により、引数のサイズが$0
(0バイト)と明示され、スタックフレームのサイズが$12
(12バイト)と設定されました。
なぜ$0-12
なのかというと、cgocallback
は実際には引数を受け取らないにもかかわらず、GoのABI(Application Binary Interface)や特定のレジスタ退避の必要性から、内部的に12バイトのスタック領域を必要とするためと考えられます。この12バイトは、おそらく戻りアドレスや特定のレジスタの退避、あるいはCgoコールバックの内部処理に必要な最小限のスタックアライメントを確保するためのものです。
この修正により、amd64p32
環境でcgocallback
が呼び出された際に、必要なスタック領域が正しく確保されるようになり、スタックオーバーフローや関連するメモリの問題が解消されました。これは、Goランタイムの安定性とCgoの信頼性を向上させる上で重要な修正です。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/asm_amd64p32.s
ファイルの一箇所のみです。
--- a/src/pkg/runtime/asm_amd64p32.s
+++ b/src/pkg/runtime/asm_amd64p32.s
@@ -617,7 +617,7 @@ TEXT runtime·asmcgocall(SB),NOSPLIT,$0-8
// cgocallback(void (*fn)(void*), void *frame, uintptr framesize)
// Not implemented.
-TEXT runtime·cgocallback(SB),NOSPLIT,$12-12
+TEXT runtime·cgocallback(SB),NOSPLIT,$0-12
MOVL 0, AX
RET
コアとなるコードの解説
変更された行は、runtime·cgocallback
関数の定義部分です。
元のコード:
TEXT runtime·cgocallback(SB),NOSPLIT,$12-12
修正後のコード:
TEXT runtime·cgocallback(SB),NOSPLIT,$0-12
この変更のポイントは、TEXT
ディレクティブの最後の部分であるargs_size-frame_size
の値を$12-12
から$0-12
に変更したことです。
-
$12-12
(変更前): これは、関数が12バイトの引数を受け取り、かつ12バイトのスタックフレームを持つことを示唆していました。しかし、cgocallback
はGoの内部関数であり、Cgoのメカニズムを通じて呼び出されるため、Goの観点からは直接的な引数を受け取らない場合があります。また、$X-X
という形式は、フレームサイズが0であることを示す慣例として使われることもありますが、amd64p32
のような特殊な環境では、これが正しく解釈されず、スタックフレームが適切に確保されない問題を引き起こしていた可能性があります。 -
$0-12
(変更後):$0
: これは、関数がGoの観点から0バイトの引数を受け取ることを明確に示しています。$12
: これは、関数が自身の実行のために12バイトのスタックフレームを必要とすることを明示しています。この12バイトは、おそらくCgoコールバックの処理に必要な最小限のスタックアライメントや、内部的なレジスタ退避などのために確保される領域です。
この修正により、amd64p32
環境においてruntime·cgocallback
関数が呼び出された際に、必要なスタック領域が正確に確保されるようになり、スタック関連のバグが解消されました。これにより、Cgoコールバックの安定性と信頼性が向上しました。
関連リンク
- Go言語のCgoに関する公式ドキュメント: https://go.dev/blog/c-go-is-not-c (Cgoの概念的な理解に役立ちます)
- Goのアセンブリ言語に関するドキュメント: https://go.dev/doc/asm (Goのアセンブリ構文と
TEXT
ディレクティブについて詳しく解説されています) - GoのIssueトラッカー (Gerrit CLへのリンク): https://golang.org/cl/73160043 (このコミットの元のレビュープロセスを確認できます)
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/runtime/asm_amd64p32.s
): https://github.com/golang/go/blob/master/src/runtime/asm_amd64p32.s - Goのコミット履歴: https://github.com/golang/go/commits/master
- GoのCgoに関するブログ記事やドキュメント (一般的なCgoの仕組みを理解するため):
- AMD64アーキテクチャおよびABIに関する一般的な情報 (特に
amd64p32
の文脈で):- https://en.wikipedia.org/wiki/X86-64
- https://en.wikipedia.org/wiki/X32_ABI (amd64p32はx32 ABIに関連する概念です)
- スタックと関数呼び出し規約に関する一般的なコンピュータサイエンスの知識。
- Goのアセンブリ言語の構文と慣例に関する情報源。