[インデックス 16238] ファイルの概要
このコミットは、GoランタイムにおけるARMアーキテクチャ特有のスタックポインタ破損バグを修正するものです。具体的には、runtime.cgocallback_gofunc()
内で発生する問題で、runtime.setmg()
関数が別の関数(cgo_save_gm
)を呼び出す際に、リンクレジスタ(LR)をスタックに保存していなかったことが原因です。この修正により、Cgoコールバック時の安定性が向上し、関連するテストが再有効化されました。
コミット
commit 5b78cee3764ea71722a56dc2e1b33ae7e90e5427
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Apr 30 04:13:32 2013 +0800
runtime: fix stack pointer corruption in runtime.cgocallback_gofunc()
runtime.setmg() calls another function (cgo_save_gm), so it must save
LR onto stack.
Re-enabled TestCthread test in misc/cgo/test.
Fixes #4863.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/9019043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5b78cee3764ea71722a56dc2e1b33ae7e90e5427
元コミット内容
このコミットは、Goランタイムのruntime.cgocallback_gofunc()
関数におけるスタックポインタの破損を修正します。runtime.setmg()
関数がcgo_save_gm
という別の関数を呼び出す際に、ARMアーキテクチャのリンクレジスタ(LR)をスタックに保存していなかったため、関数呼び出しからの復帰時にスタックポインタが不正な状態になる問題がありました。この修正により、runtime.setmg()
がLRを適切に保存するようになり、問題が解決されました。また、このバグのために無効化されていたmisc/cgo/test
ディレクトリ内のTestCthread
テストが再有効化されています。この修正は、Issue #4863を解決します。
変更の背景
Go言語は、C言語で書かれたライブラリを呼び出すためのCgoというメカニズムを提供しています。Cgoは、GoとCの間の相互運用性を可能にしますが、異なるランタイム環境間でのスタック管理やレジスタの保存・復元は非常に複雑です。
このコミットの背景には、ARMアーキテクチャにおけるCgoコールバックの特定のシナリオで発生するスタックポインタの破損バグがありました。具体的には、CコードからGoコードへのコールバック(runtime.cgocallback_gofunc
が関与)の際に、Goランタイムが現在のM(OSスレッド)とG(Goroutine)を設定するためにruntime.setmg()
を呼び出します。このsetmg()
関数が内部でcgo_save_gm
という別の関数を呼び出す際、ARMの関数呼び出し規約に従って、呼び出し元のアドレスを保持するリンクレジスタ(LR)をスタックに保存する必要がありました。しかし、これが適切に行われていなかったため、setmg()
から戻る際にLRが不正な値を指し、結果としてスタックポインタが破損し、プログラムがクラッシュする可能性がありました。
この問題は、GoのIssue #4863として報告されており、特にARM環境でのCgoを利用するアプリケーションの安定性に影響を与えていました。このバグのため、misc/cgo/test/cthread.go
内のTestCthread
テストがARMアーキテクチャでスキップされるように一時的に無効化されていました。このコミットは、根本原因であるLRの保存漏れを修正し、テストを再有効化することで、Cgoの堅牢性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が必要です。
-
Goランタイム (Go Runtime):
- Goプログラムの実行を管理する低レベルのシステムです。スケジューラ、メモリ管理(ガベージコレクション)、Goroutineの管理、Cgoとの連携などを担当します。
- M (Machine): OSスレッドを表します。Goランタイムは、OSスレッド上でGoroutineを実行します。
- G (Goroutine): Go言語の軽量な並行処理単位です。OSスレッドとは異なり、Goランタイムによってスケジューリングされます。
- P (Processor): 論理プロセッサを表し、MとGの間の仲介役となります。Pは実行可能なGoroutineのキューを持ち、MにGoroutineを供給します。
- スタック (Stack): 関数呼び出しの際に、ローカル変数、引数、戻りアドレスなどを一時的に保存するメモリ領域です。GoのGoroutineはそれぞれ独自のスタックを持ちます。
-
Cgo:
- Go言語からC言語のコードを呼び出したり、C言語のコードからGo言語のコードを呼び出したりするためのGoの機能です。
- 異なる言語間の呼び出しでは、それぞれの言語の関数呼び出し規約(Calling Convention)やメモリ管理の違いを吸収するための特別な処理が必要です。
- Cgoコールバック: CコードからGoコードの関数を呼び出すことを指します。このプロセスは特に複雑で、GoランタイムがCスタックからGoスタックへの切り替えや、レジスタの状態保存・復元を適切に行う必要があります。
-
ARMアーキテクチャとアセンブリ (ARM Architecture and Assembly):
- ARMは、モバイルデバイスなどで広く使われているCPUアーキテクチャです。
- レジスタ (Registers): CPU内部にある高速な記憶領域です。関数呼び出しの際に引数を渡したり、計算結果を一時的に保持したりします。
- スタックポインタ (SP: Stack Pointer): 現在のスタックの最上位(または最下位)のアドレスを指すレジスタです。関数呼び出しやローカル変数の確保によって値が変化します。
- リンクレジスタ (LR: Link Register): 関数呼び出しの際に、呼び出し元に戻るためのアドレス(戻りアドレス)を保存するレジスタです。関数が別の関数を呼び出す場合、LRの値をスタックに保存しないと、元の呼び出し元に戻れなくなり、スタックポインタが破損する原因となります。
TEXT
ディレクティブ: Goのアセンブリ言語で関数を定義する際に使用されます。TEXT runtime·setmg(SB), 7, $-4
のような形式で、関数の名前、フラグ、スタックフレームサイズなどを指定します。$-4
や$0
は、関数のスタックフレームサイズを示します。これは、関数がローカル変数やレジスタの保存のために確保するスタック領域のサイズです。負の値は、スタックフレームが動的に決定されることを示唆することがあります。
これらの概念が、Cgoコールバック時のスタック管理の複雑さと、なぜLRの保存が重要であるかを理解する上で不可欠です。
技術的詳細
このバグは、GoランタイムがCgoコールバックを処理する際の、ARMアーキテクチャ特有のスタック管理の不備に起因していました。
-
Cgoコールバックのフロー:
- CコードがGo関数を呼び出す際、Goランタイムは特別なエントリポイント(
runtime.cgocallback_gofunc
など)を通じて制御を受け取ります。 - このエントリポイントでは、CスタックからGoスタックへの切り替え、Goroutineのコンテキストの確立、そして現在のOSスレッド(M)とGoroutine(G)の関連付けが行われます。
- この関連付けを行うのが
runtime.setmg(m, g)
関数です。
- CコードがGo関数を呼び出す際、Goランタイムは特別なエントリポイント(
-
runtime.setmg()
の役割と問題点:runtime.setmg(m, g)
は、現在のMとGレジスタ(Goランタイムが内部的に使用するレジスタ)を更新し、現在のOSスレッドがどのGoroutineを実行しているかをランタイムに知らせます。- 問題は、
runtime.setmg()
が内部でcgo_save_gm
という別の関数を呼び出していた点にありました。cgo_save_gm
は、CgoのコンテキストでMとGの情報を保存するための関数です。 - ARMアーキテクチャの関数呼び出し規約では、関数が別の関数を呼び出す場合、呼び出し元のアドレスを保持するリンクレジスタ(LR)の値をスタックに保存する必要があります。これにより、呼び出された関数から戻った際に、元の実行フローに戻ることができます。
- しかし、
runtime.setmg()
のアセンブリコード(src/pkg/runtime/asm_arm.s
)では、cgo_save_gm
を呼び出す前にLRをスタックに保存していませんでした。
-
スタックポインタの破損:
- LRが保存されないまま
cgo_save_gm
が呼び出され、その後にruntime.setmg()
が終了しようとすると、LRにはcgo_save_gm
からの戻りアドレスが残ったままになります。 runtime.setmg()
が本来戻るべきアドレス(つまり、runtime.cgocallback_gofunc
内のsetmg
呼び出しの次の命令のアドレス)が失われてしまいます。- これにより、
runtime.setmg()
からの復帰時に不正なアドレスにジャンプしたり、スタックポインタ(SP)が期待される位置に戻らなかったりして、スタックが破損し、最終的にプログラムのクラッシュや予期せぬ動作を引き起こしていました。
- LRが保存されないまま
-
修正内容:
- 修正は、
src/pkg/runtime/asm_arm.s
内のruntime·setmg
関数の定義を変更することです。 - 元の定義は
TEXT runtime·setmg(SB), 7, $-4
でした。ここで$-4
は、スタックフレームサイズが動的に決定されることを示唆していました。 - 修正後は
TEXT runtime·setmg(SB), 7, $0
となります。$0
は、この関数自体がスタックフレームを確保しないことを意味します。これは、setmg
がcgo_save_gm
を呼び出す際に、cgo_save_gm
がLRを保存する責任を持つか、あるいはsetmg
の呼び出し元がLRを保存する責任を持つように、呼び出し規約が変更されたことを示唆しています。 - この変更により、
setmg
がcgo_save_gm
を呼び出す際のLRの保存が適切に行われるようになり、スタックポインタの破損が解消されました。
- 修正は、
-
テストの再有効化:
- このバグが修正されたことで、
misc/cgo/test/cthread.go
内のTestCthread
テストがARMアーキテクチャでスキップされる必要がなくなりました。 runtime.GOARCH == "arm"
のチェックとt.Skip()
の行が削除され、テストが再び実行されるようになりました。これにより、将来的に同様の回帰バグが発生しないことを保証できます。
- このバグが修正されたことで、
この修正は、Goランタイムの低レベルな部分、特にアセンブリ言語とアーキテクチャ固有の挙動を深く理解している必要がある、非常に技術的なものです。
コアとなるコードの変更箇所
このコミットによるコードの変更は、主に以下の2つのファイルにあります。
misc/cgo/test/cthread.go
src/pkg/runtime/asm_arm.s
misc/cgo/test/cthread.go
の変更
--- a/misc/cgo/test/cthread.go
+++ b/misc/cgo/test/cthread.go
@@ -8,7 +8,6 @@ package cgotest
import "C"
import (
- "runtime"
"sync"
"testing"
)
@@ -31,10 +30,6 @@ func Add(x int) {
}
func testCthread(t *testing.T) {
- if runtime.GOARCH == "arm" {
- t.Skip("testCthread disabled on arm")
- }
-
sum.i = 0
C.doAdd(10, 6)
runtime
パッケージのインポートが削除されました。testCthread
関数内の、ARMアーキテクチャでテストをスキップする条件分岐(if runtime.GOARCH == "arm" { t.Skip("testCthread disabled on arm") }
)が削除されました。
src/pkg/runtime/asm_arm.s
の変更
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -417,7 +417,7 @@ havem:
RET
// void setmg(M*, G*); set m and g. for use by needm.
-TEXT runtime·setmg(SB), 7, $-4
+TEXT runtime·setmg(SB), 7, $0
MOVW mm+0(FP), m
MOVW gg+4(FP), g
runtime·setmg
関数のTEXT
ディレクティブのスタックフレームサイズ指定が変更されました。- 変更前:
TEXT runtime·setmg(SB), 7, $-4
- 変更後:
TEXT runtime·setmg(SB), 7, $0
- 変更前:
コアとなるコードの解説
misc/cgo/test/cthread.go
の変更の解説
このファイルは、Cgoのcthread
テストケースです。このテストは、CスレッドからGo関数を呼び出すシナリオを検証します。
-
import "runtime"
の削除:- 以前は
runtime.GOARCH
を使用して現在のアーキテクチャをチェックしていたため、runtime
パッケージをインポートする必要がありました。 - テストスキップのロジックが削除されたため、
runtime
パッケージは不要になり、インポートリストから削除されました。これはクリーンアップの一環です。
- 以前は
-
ARMアーキテクチャでのテストスキップロジックの削除:
if runtime.GOARCH == "arm" { t.Skip("testCthread disabled on arm") }
の行が削除されました。- この行は、以前のGoランタイムのバグ(本コミットで修正されるスタックポインタ破損)のために、ARM環境でこのテストが失敗するのを避けるために一時的に追加されていました。
- バグが修正されたため、このテストはARM環境でも正しく動作するようになり、スキップする必要がなくなりました。これにより、Cgoの重要なテストがすべてのサポート対象アーキテクチャで実行されるようになり、回帰テストの網羅性が向上します。
src/pkg/runtime/asm_arm.s
の変更の解説
このファイルは、ARMアーキテクチャ用のGoランタイムのアセンブリコードを含んでいます。
TEXT runtime·setmg(SB), 7, $-4
からTEXT runtime·setmg(SB), 7, $0
への変更:- これは、
runtime·setmg
関数の定義におけるスタックフレームサイズの指定を変更するものです。 TEXT
ディレクティブの3番目の引数は、関数のスタックフレームサイズ(関数がローカル変数やレジスタの保存のために確保するスタック領域のサイズ)を示します。$-4
: 負の値は、Goのアセンブリでは特別な意味を持つことがあります。この場合、関数がスタックフレームを動的に決定するか、あるいは特定のレジスタ(この場合はLR)の保存を呼び出し元に依存していることを示唆している可能性があります。以前のコードでは、setmg
がcgo_save_gm
を呼び出す際に、LRをスタックに保存する責任が明確でなかったか、あるいは誤って省略されていました。$0
: これは、runtime·setmg
関数自体が、その関数内でローカル変数やレジスタを保存するためのスタック領域を明示的に確保しないことを意味します。この変更は、setmg
がcgo_save_gm
を呼び出す際のLRの保存方法に関するGoランタイムの内部的な規約変更を反映しています。具体的には、setmg
がcgo_save_gm
を呼び出す前にLRをスタックに保存する責任を負うか、あるいはcgo_save_gm
がLRを保存する責任を負うように、呼び出し規約が調整されたことを示唆しています。この修正により、cgo_save_gm
への呼び出し時にLRが適切に保存されるようになり、関数からの復帰時にスタックポインタが破損する問題が解決されました。
- これは、
このアセンブリレベルの変更は、GoランタイムのCgoコールバックメカニズムの堅牢性を確保するために不可欠であり、ARMアーキテクチャにおける低レベルなスタック管理の正確性を保証します。
関連リンク
- Go Issue #4863: https://github.com/golang/go/issues/4863
- Go CL 9019043: https://golang.org/cl/9019043 (Goのコードレビューシステムにおける変更リスト)
参考にした情報源リンク
- Go言語の公式ドキュメント (Cgo): https://go.dev/blog/cgo
- Go言語のランタイムに関する資料 (M, G, Pモデルなど): https://go.dev/doc/effective_go#concurrency
- ARMアーキテクチャの関数呼び出し規約 (Procedure Call Standard for the ARM Architecture): (一般的な情報源、特定のリンクは提供しないが、ARM開発者向けドキュメントを参照)
- スタックポインタとリンクレジスタの概念 (一般的なコンピュータアーキテクチャの教科書やオンラインリソースを参照)
- Goのアセンブリ言語に関する資料: https://go.dev/doc/asm
- Goのソースコード (特に
src/pkg/runtime/
ディレクトリ): https://github.com/golang/go/tree/master/src/runtime - GoのIssueトラッカー: https://github.com/golang/go/issues
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/