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

[インデックス 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スレッドを表すデータ構造です。mgを実行し、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·sighandlerm->curg(現在のOSスレッドで実行中のゴルーチン)を使用していました。しかし、シグナルが発生した時点でのm->curgが、実際にシグナルを発生させたゴルーチンと異なる場合がありました。これは、Goランタイムのスケジューラがゴルーチンを切り替えるタイミングや、OSがシグナルをディスパッチするタイミングに依存して発生しうる競合状態やタイミングの問題が原因と考えられます。

新しい実装では、runtime·sigtrampというアセンブリコードのラッパー関数が、シグナルハンドラを呼び出す際に、現在のゴルーチンへのポインタを明示的に引数として渡すように変更されました。これにより、runtime·sighandlerは常にシグナルを発生させたゴルーチンのコンテキストで動作することが保証されます。

具体的には、以下の変更が行われました。

  1. runtime·sighandler関数のシグネチャ変更:
    • 変更前: runtime·sighandler(ExceptionRecord *info, void *frame, Context *r)
    • 変更後: runtime·sighandler(ExceptionRecord *info, Context *r, G *gp) void *frame引数が削除され、代わりにG *gp(ゴルーチンへのポインタ)が追加されました。これにより、シグナルハンドラは呼び出し元から直接gを受け取ることができます。
  2. runtime·sighandler内のg取得ロジックの変更:
    • 変更前: if((gp = m->curg) != nil && runtime·issigpanic(info->ExceptionCode))
    • 変更後: if(gp != nil && runtime·issigpanic(info->ExceptionCode)) m->curgからgを取得するのではなく、引数として渡されたgpを直接使用するように変更されました。
  3. 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·sigtrampruntime·sigtramp1を呼び出す二段階の構造でしたが、これが単一のruntime·sigtramp関数に統合されました。これにより、コードの複雑さが軽減され、直接的な引数渡しが可能になります。
    • スタックフレームのサイズが$0から$28に変更されました。これは、runtime·sighandlerに渡す引数と、レジスタを保存するための追加のスタック領域が必要になったためです。
  • 引数の準備とruntime·sighandlerの呼び出し:
    • MOVL info+0(FP), CXMOVL CX, 0(SP): ExceptionRecord *info(例外情報)をスタックの先頭(第1引数)にプッシュします。
    • MOVL context+8(FP), CXMOVL CX, 4(SP): Context *r(コンテキスト情報)をスタックの2番目(第2引数)にプッシュします。
    • get_tls(CX)MOVL g(CX), CXMOVL 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) システムに関する一般的な情報