[インデックス 10265] ファイルの概要
このコミットは、GoランタイムにおけるWindows/386アーキテクチャでのシグナルハンドラが、正しいゴルーチン(g
)コンテキストを使用するように修正するものです。
コミット
commit 603d80c28d72c5eae4a0f6a4a8054caf62c5d228
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Mon Nov 7 11:00:14 2011 +1100
runtime: windows_386 sighandler to use correct g
Fixes #2403.
R=hectorchu
CC=golang-dev
https://golang.org/cl/5309071
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/603d80c28d72c5eae4a0f6a4a8054caf62c5d228
元コミット内容
GoランタイムのWindows/386版において、シグナルハンドラが正しいゴルーチン(g
)のコンテキストを使用するように修正。Issue #2403を修正。
変更の背景
このコミットは、GoランタイムがWindows/386環境でシグナル(例外)を処理する際に発生していた問題、具体的にはシグナルハンドラが誤ったゴルーチンコンテキストを参照してしまうバグ(Issue #2403)を修正するために導入されました。
Go言語のランタイムは、OSからのシグナル(例えば、不正なメモリアクセスやゼロ除算などの例外)を受け取った際に、それを適切に処理する必要があります。特に、Goのゴルーチンモデルでは、各ゴルーチンが独自のスタックと実行コンテキストを持っています。シグナルハンドラが呼び出された際、それが現在実行中のゴルーチンのコンテキストで動作することが極めて重要です。もし誤ったゴルーチンのコンテキストで動作してしまうと、スタックの破損、不正なデータアクセス、あるいはプログラムのクラッシュといった深刻な問題を引き起こす可能性があります。
このコミット以前は、Windows/386環境のシグナルハンドラが、シグナル発生時のゴルーチンではなく、別の(おそらくはm->curg
から取得される)ゴルーチンを参照してしまう可能性がありました。これにより、シグナル発生時のスタックトレースが正しく取得できなかったり、パニック処理が適切に行われなかったりする不具合が生じていました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルな部分です。ガベージコレクション、スケジューリング、メモリ管理、システムコール、シグナルハンドリングなど、Goプログラムが動作するために必要な多くの機能を提供します。
- ゴルーチン (Goroutine): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。各ゴルーチンは独自のスタックを持ちます。
g
(Goroutine構造体): Goランタイム内部でゴルーチンを表すデータ構造です。現在のゴルーチンのスタックポインタ、状態、関連するM(Machine/OSスレッド)へのポインタなどが含まれます。m
(Machine/OSスレッド構造体): Goランタイム内部でOSスレッドを表すデータ構造です。m
はg
を実行し、m->curg
は現在そのOSスレッドで実行中のゴルーチンを指します。- シグナルハンドリング (Signal Handling): オペレーティングシステムがプログラムに送信する非同期イベント(シグナル)を処理するメカニズムです。例えば、
SIGSEGV
(セグメンテーション違反)やSIGILL
(不正な命令)などがあります。Windowsでは、これらは「例外」として扱われます。 - Windows例外処理 (Windows Exception Handling): Windowsでは、ハードウェア例外やソフトウェア例外が発生すると、OSがアプリケーションに通知します。アプリケーションは、構造化例外ハンドリング(SEH: Structured Exception Handling)メカニズムを使用してこれらの例外を捕捉し、処理することができます。
CONTEXT
構造体: Windows APIで定義される構造体で、特定の時点でのプロセッサのレジスタの状態(スタックポインタ、命令ポインタ、汎用レジスタなど)を保持します。シグナルハンドラが呼び出される際、このコンテキスト情報が提供されます。ExceptionRecord
構造体: Windows APIで定義される構造体で、発生した例外に関する詳細情報(例外コード、例外アドレスなど)を保持します。- アセンブリ言語 (Assembly Language): CPUが直接実行できる機械語命令を人間が読める形式で記述した低レベル言語です。Goランタイムの特にパフォーマンスが要求される部分やOSとのインタフェース部分は、アセンブリ言語で記述されることがあります。このコミットでは、Intel 386アーキテクチャ向けのアセンブリコード(
sys.s
)が変更されています。 FS
レジスタ: x86アーキテクチャのセグメントレジスタの一つで、通常はスレッドローカルストレージ(TLS: Thread Local Storage)へのポインタを保持するために使用されます。Goランタイムでは、現在のゴルーチン(g
)へのポインタをTLSに格納することが一般的です。
技術的詳細
このコミットの核心は、Windows/386環境におけるシグナルハンドラruntime·sighandler
が、呼び出し元から直接正しいゴルーチン(g
)へのポインタを受け取るように変更された点です。
以前の実装では、runtime·sighandler
はm->curg
(現在のOSスレッドで実行中のゴルーチン)を使用していました。しかし、シグナルが発生した時点でのm->curg
が、実際にシグナルを発生させたゴルーチンと異なる場合がありました。これは、Goランタイムのスケジューラがゴルーチンを切り替えるタイミングや、OSがシグナルをディスパッチするタイミングに依存して発生しうる競合状態やタイミングの問題が原因と考えられます。
新しい実装では、runtime·sigtramp
というアセンブリコードのラッパー関数が、シグナルハンドラを呼び出す際に、現在のゴルーチンへのポインタを明示的に引数として渡すように変更されました。これにより、runtime·sighandler
は常にシグナルを発生させたゴルーチンのコンテキストで動作することが保証されます。
具体的には、以下の変更が行われました。
runtime·sighandler
関数のシグネチャ変更:- 変更前:
runtime·sighandler(ExceptionRecord *info, void *frame, Context *r)
- 変更後:
runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
void *frame
引数が削除され、代わりにG *gp
(ゴルーチンへのポインタ)が追加されました。これにより、シグナルハンドラは呼び出し元から直接g
を受け取ることができます。
- 変更前:
runtime·sighandler
内のg
取得ロジックの変更:- 変更前:
if((gp = m->curg) != nil && runtime·issigpanic(info->ExceptionCode))
- 変更後:
if(gp != nil && runtime·issigpanic(info->ExceptionCode))
m->curg
からg
を取得するのではなく、引数として渡されたgp
を直接使用するように変更されました。
- 変更前:
runtime·sigtramp
アセンブリコードの変更:runtime·sigtramp
は、Windowsがシグナル(例外)発生時に呼び出すエントリポイントです。この関数が、runtime·sighandler
を呼び出す前に、現在のスレッドローカルストレージ(TLS)から現在のゴルーチン(g
)へのポインタを取得し、それをruntime·sighandler
の引数としてスタックにプッシュするように変更されました。get_tls(CX)
: TLSから現在のスレッドの情報を取得し、CX
レジスタに格納します。MOVL g(CX), CX
: TLS情報から現在のゴルーチン(g
)へのポインタを取得し、CX
レジスタに格納します。MOVL CX, 8(SP)
: 取得したg
ポインタを、runtime·sighandler
の第3引数としてスタックにプッシュします(386アーキテクチャのcdecl呼び出し規約では、引数は右から左にスタックに積まれます)。
これらの変更により、シグナルハンドラは常にシグナルを発生させたゴルーチンのコンテキストで動作するようになり、Goプログラムの安定性と信頼性が向上しました。
コアとなるコードの変更箇所
src/pkg/runtime/windows/386/signal.c
--- a/src/pkg/runtime/windows/386/signal.c
+++ b/src/pkg/runtime/windows/386/signal.c
@@ -31,12 +31,9 @@ runtime·initsig(int32)
}
uint32
-runtime·sighandler(ExceptionRecord *info, void *frame, Context *r)
+runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
{
uintptr *sp;
- G *gp;
-
- USED(frame);
switch(info->ExceptionCode) {
case EXCEPTION_BREAKPOINT:
@@ -44,7 +41,7 @@ runtime·sighandler(ExceptionRecord *info, void *frame, Context *r)
return 1;
}
- if((gp = m->curg) != nil && runtime·issigpanic(info->ExceptionCode)) {
+ if(gp != nil && runtime·issigpanic(info->ExceptionCode)) {
// Make it look like a call to the signal func.
// Have to pass arguments out of band since
// augmenting the stack frame would break
src/pkg/runtime/windows/386/sys.s
--- a/src/pkg/runtime/windows/386/sys.s
+++ b/src/pkg/runtime/windows/386/sys.s
@@ -48,51 +48,33 @@ TEXT runtime·setlasterror(SB),7,$0
MOVL AX, 0x34(FS)
RET
-TEXT runtime·sigtramp(SB),7,$0
-\tPUSHL\tBP\t\t\t// cdecl
-\tPUSHL\tBX
-\tPUSHL\tSI
-\tPUSHL\tDI
-\tPUSHL\t0(FS)
-\tCALL\truntime·sigtramp1(SB)
-\tPOPL\t0(FS)
-\tPOPL\tDI
-\tPOPL\tSI
-\tPOPL\tBX
-\tPOPL\tBP
-\tRET
-\
-TEXT runtime·sigtramp1(SB),0,$16-40
+TEXT runtime·sigtramp(SB),7,$28
// unwinding?
-\tMOVL\tinfo+24(FP), BX
-\tMOVL\t4(BX), CX\t\t// exception flags
-\tANDL\t$6, CX
+\tMOVL info+0(FP), CX
+\tTESTL $6, 4(CX)\t\t// exception flags
MOVL $1, AX
JNZ sigdone
-\t// place ourselves at the top of the SEH chain to
-\t// ensure SEH frames lie within thread stack bounds
-\tMOVL\tframe+28(FP), CX\t// our SEH frame
-\tMOVL\tCX, 0(FS)
-\
// copy arguments for call to sighandler
-\tMOVL\tBX, 0(SP)
+\tMOVL CX, 0(SP)
+\tMOVL context+8(FP), CX
MOVL CX, 4(SP)
-\tMOVL\tcontext+32(FP), BX
-\tMOVL\tBX, 8(SP)
-\tMOVL\tdispatcher+36(FP), BX
+\tget_tls(CX)
+\tMOVL g(CX), CX
+\tMOVL CX, 8(SP)
+\
MOVL BX, 12(SP)
+\tMOVL BP, 16(SP)
+\tMOVL SI, 20(SP)
+\tMOVL DI, 24(SP)
CALL runtime·sighandler(SB)
-\tTESTL\tAX, AX
-\tJZ\tsigdone
-\
-\t// call windows default handler early
-\tMOVL\t4(SP), BX\t\t// our SEH frame
-\tMOVL\t0(BX), BX\t\t// SEH frame of default handler
-\tMOVL\tBX, 4(SP)\t\t// set establisher frame
-\tCALL\t4(BX)
+\t// AX is set to report result back to Windows
+\tMOVL 24(SP), DI
+\tMOVL 20(SP), SI
+\tMOVL 16(SP), BP
+\tMOVL 12(SP), BX
sigdone:
RET
コアとなるコードの解説
src/pkg/runtime/windows/386/signal.c
の変更点
runtime·sighandler
関数のシグネチャ変更:void *frame
引数が削除され、代わりにG *gp
が追加されました。これは、シグナルハンドラが呼び出される際に、現在のゴルーチンへのポインタを直接受け取るようにするためです。これにより、ハンドラ内でm->curg
を介してゴルーチンを取得する必要がなくなります。USED(frame);
の行も、frame
引数が削除されたため不要となり削除されました。
gp
の取得ロジックの変更:if((gp = m->curg) != nil && runtime·issigpanic(info->ExceptionCode))
からif(gp != nil && runtime·issigpanic(info->ExceptionCode))
に変更されました。 これは、gp
が関数引数として既に渡されているため、m->curg
から取得する必要がなくなったことを意味します。これにより、シグナルハンドラが常にシグナルを発生させたゴルーチンのコンテキストで動作することが保証されます。
src/pkg/runtime/windows/386/sys.s
の変更点
このファイルは、GoランタイムのWindows/386アーキテクチャ向けのアセンブリコードを含んでいます。主な変更はruntime·sigtramp
関数にあります。
runtime·sigtramp
の再構築:- 以前は
runtime·sigtramp
がruntime·sigtramp1
を呼び出す二段階の構造でしたが、これが単一のruntime·sigtramp
関数に統合されました。これにより、コードの複雑さが軽減され、直接的な引数渡しが可能になります。 - スタックフレームのサイズが
$0
から$28
に変更されました。これは、runtime·sighandler
に渡す引数と、レジスタを保存するための追加のスタック領域が必要になったためです。
- 以前は
- 引数の準備と
runtime·sighandler
の呼び出し:MOVL info+0(FP), CX
とMOVL CX, 0(SP)
:ExceptionRecord *info
(例外情報)をスタックの先頭(第1引数)にプッシュします。MOVL context+8(FP), CX
とMOVL CX, 4(SP)
:Context *r
(コンテキスト情報)をスタックの2番目(第2引数)にプッシュします。get_tls(CX)
とMOVL g(CX), CX
とMOVL CX, 8(SP)
:get_tls(CX)
: 現在のスレッドのTLS(Thread Local Storage)へのポインタをCX
レジスタにロードします。Goランタイムでは、現在のゴルーチン(g
)へのポインタがTLSに格納されています。MOVL g(CX), CX
: TLSから現在のゴルーチン(g
)へのポインタをCX
レジスタにロードします。MOVL CX, 8(SP)
: ロードしたg
ポインタをスタックの3番目(第3引数)にプッシュします。これがruntime·sighandler
の新しいG *gp
引数になります。
CALL runtime·sighandler(SB)
: 準備された引数でruntime·sighandler
を呼び出します。
- レジスタの保存と復元:
MOVL BP, 16(SP)
,MOVL SI, 20(SP)
,MOVL DI, 24(SP)
:runtime·sighandler
呼び出し前に、BP, SI, DIレジスタの値をスタックに保存します。これは、runtime·sighandler
がこれらのレジスタを変更する可能性があるため、呼び出し規約に従って保存・復元を行うためです。MOVL 24(SP), DI
,MOVL 20(SP), SI
,MOVL 16(SP), BP
:runtime·sighandler
呼び出し後に、保存しておいたレジスタの値を復元します。
- 不要なコードの削除:
- 以前のSEHチェーン操作や、Windowsデフォルトハンドラを早期に呼び出すためのロジックが削除されました。これは、Goランタイムがシグナルをより直接的に処理するようになったため、これらの低レベルなSEH操作が不要になったことを示唆しています。
これらの変更により、Windows/386環境でのシグナルハンドリングがより堅牢になり、シグナル発生時に常に正しいゴルーチンコンテキストで処理が行われるようになりました。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/603d80c28d72c5eae4a0f6a4a8054caf62c5d228
- Go Change List (CL): https://golang.org/cl/5309071
- (注: このリンクはGoの内部コードレビューシステムへのリンクであり、直接アクセスして内容を確認することはできません。内容を確認するには、Goのソースコードリポジトリをクローンし、該当するCLをフェッチする必要があります。)
- Go Issue 2403: 残念ながら、提供された情報とWeb検索では、このコミットが修正した具体的なGo言語のIssue #2403の詳細を特定できませんでした。Goプロジェクトには多数のリポジトリがあり、Issue番号はプロジェクト間で重複する可能性があります。もし詳細な情報が必要な場合は、Goの公式Issueトラッカー(
go.dev/issue
)で検索するか、より具体的なコンテキスト(例: "Go runtime issue 2403")を提供してください。
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/runtime/windows/386/signal.c
およびsrc/pkg/runtime/windows/386/sys.s
の該当コミット時点のコード) - Go言語のランタイムに関する一般的な知識
- Windowsの構造化例外ハンドリング (SEH) に関するドキュメント
- x86アセンブリ言語の基本
- Go言語のIssueトラッカー (一般的な情報源として)
- Go言語のChange List (CL) システムに関する一般的な情報