[インデックス 19706] ファイルの概要
このコミットは、GoランタイムがWindows上で動作する際に、Goランタイム自身が管理していない「外部スレッド(foreign threads)」から発生した例外を無視するように変更を加えるものです。これにより、外部スレッドが引き起こす予期せぬクラッシュを防ぎ、Goプログラムの安定性を向上させます。
コミット
commit a1778ec1462c2f3f8865e02e5fd7e72ee25c2b64
Author: Shenghou Ma <minux@golang.org>
Date: Wed Jul 9 23:55:35 2014 -0400
runtime: ignore exceptions from foreign threads.
Fixes #8224.
LGTM=alex.brainman, rsc
R=alex.brainman, rsc, dave
CC=golang-codereviews
https://golang.org/cl/104200046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a1778ec1462c2f3f8865e02e5fd7e7e72ee25c2b64
元コミット内容
runtime: ignore exceptions from foreign threads.
Fixes #8224.
LGTM=alex.brainman, rsc
R=alex.brainman, rsc, dave
CC=golang-codereviews
https://golang.org/cl/104200046
変更の背景
Goランタイムは、自身のゴルーチン(goroutine)をOSスレッドに多重化して実行します。Windows環境では、Goランタイムが管理するスレッドとは別に、外部のライブラリやC/C++コードなどによって作成された「外部スレッド(foreign threads)」が存在する場合があります。これらの外部スレッドが例外(例えば、アクセス違反やゼロ除算など)を発生させた場合、Goランタイムの例外ハンドリングメカニズムがその例外を捕捉しようとします。
しかし、Goランタイムの例外ハンドラは、Goが管理するスレッドのコンテキスト(特にGoのスケジューラが認識するg
構造体、つまり現在のゴルーチン情報)に依存しています。外部スレッドはGoランタイムの管理下にないため、Goランタイムが期待するg
構造体を持っていません。この状態で外部スレッドからの例外がGoランタイムの例外ハンドラに渡されると、g
構造体へのアクセスが不正となり、結果としてGoプログラム全体がクラッシュする可能性がありました。
このコミットは、この問題を解決するために導入されました。コミットメッセージにある Fixes #8224
は、この問題に関連するバグトラッカーのイシューを参照していますが、現在の公開されているGoのイシュートラッカーでは直接的な詳細を見つけることはできませんでした。しかし、この変更の目的は、Goランタイムが外部スレッドからの例外を適切に識別し、それらを無視することで、Goプログラムの堅牢性を高めることにあります。
前提知識の解説
Goランタイムとゴルーチン、OSスレッド
- ゴルーチン (Goroutine): Go言語の軽量な並行処理単位です。OSスレッドよりもはるかに軽量で、数百万個のゴルーチンを同時に実行することも可能です。GoランタイムのスケジューラがゴルーチンをOSスレッドにマッピングし、実行を管理します。
- OSスレッド (Operating System Thread): オペレーティングシステムが管理する実行単位です。Goランタイムは、複数のゴルーチンを少数のOSスレッドに多重化して実行します。Goプログラムがシステムコールを実行したり、
runtime.LockOSThread()
を呼び出したりすると、特定のゴルーチンが特定のOSスレッドに「ピン留め」されることがあります。 - 外部スレッド (Foreign Threads): Goランタイム自身が作成・管理していないOSスレッドを指します。例えば、C言語で書かれたライブラリをGoから呼び出し、そのライブラリが内部で独自のスレッドを作成した場合、そのスレッドはGoランタイムにとっての「外部スレッド」となります。
Windows Structured Exception Handling (SEH)
Windowsオペレーティングシステムは、プログラム実行中に発生する例外(ハードウェア例外、ソフトウェア例外など)を処理するための独自のメカニズムとして「構造化例外処理(Structured Exception Handling, SEH)」を提供しています。
- 例外の種類: アクセス違反(メモリの不正アクセス)、ゼロ除算、スタックオーバーフローなど、様々な種類の例外があります。
- 例外ハンドラ: プログラムは、
__try
,__except
,__finally
などのキーワード(C/C++の場合)を使用して、例外発生時に実行されるコードブロック(例外ハンドラ)を登録できます。 - VEH (Vectored Exception Handling): SEHとは別に、Windows XP以降で導入された例外処理メカニズムです。VEHハンドラは、SEHハンドラよりも先に呼び出されるため、より広範な例外処理を可能にします。Goランタイムの
sigtramp
関数は、Windows上では主にVEHとして機能します。
sigtramp
関数
Goランタイムにおけるsigtramp
(シグナルトランポリン)は、OSからのシグナルや例外を捕捉し、Goランタイムの例外処理メカニズムに橋渡しをする低レベルの関数です。Windows環境では、POSIXシグナルが存在しないため、sigtramp
は主にWindowsのSEHやVEHと連携して動作します。
- 役割: ハードウェア例外が発生した際に、OSによって呼び出されます。Goランタイムのスタックに切り替えたり、Windowsの例外をGoのパニック(panic)に変換したりする役割を担います。
- 実装: 非常に低レベルな処理であるため、通常はアセンブリ言語で実装されています。
技術的詳細
このコミットの核心は、Goランタイムの例外ハンドラであるruntime·sigtramp
が、例外を処理する前に、その例外を発生させたスレッドがGoランタイムによって管理されているスレッドであるかどうかをチェックする点にあります。
Goランタイムが管理するスレッドは、スレッドローカルストレージ(TLS: Thread Local Storage)に現在のゴルーチン(g
構造体)へのポインタを格納しています。このg
ポインタは、Goランタイムがスレッドのコンテキストを識別するために不可欠です。
変更前のsigtramp
は、例外が発生すると無条件にTLSからg
ポインタを取得しようとしました。しかし、外部スレッドはGoランタイムによって初期化されていないため、TLSに有効なg
ポインタを持っていません。この状態でg
ポインタを読み取ろうとすると、不正なメモリアクセスが発生し、プログラムがクラッシュする原因となっていました。
このコミットでは、sigtramp
の冒頭でTLSから取得した値(g
ポインタが格納されるべき場所)がゼロであるかどうかをチェックするコードが追加されました。
- もしTLSの値がゼロであれば、それはGoランタイムが管理していない外部スレッドからの例外であると判断されます。
- この場合、Goランタイムはその例外を無視し、Windowsに処理を継続するように指示します(
AX
レジスタに0
を設定してリターン)。これにより、外部スレッドが引き起こした例外がGoランタイムの内部処理を妨害することを防ぎます。 - TLSの値がゼロでなければ、それはGoランタイムが管理するスレッドからの例外であると判断され、通常の例外処理フロー(
runtime·sighandler
の呼び出しなど)に進みます。
この変更により、Goランタイムは自身の管理外のスレッドからの例外に対して堅牢になり、Goプログラム全体の安定性が向上しました。
コアとなるコードの変更箇所
src/pkg/runtime/sys_windows_386.s
および src/pkg/runtime/sys_windows_amd64.s
の runtime·sigtramp
関数に以下の変更が加えられました。
--- a/src/pkg/runtime/sys_windows_386.s
+++ b/src/pkg/runtime/sys_windows_386.s
@@ -88,6 +88,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0
// fetch g
get_tls(DX)
+ CMPL DX, $0
+ JNE 3(PC)
+ MOVL $0, AX // continue
+ JMP done
MOVL g(DX), DX
CMPL DX, $0
JNE 2(PC)
@@ -99,6 +103,7 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0
CALL runtime·sighandler(SB)
// AX is set to report result back to Windows
+done:
// restore callee-saved registers
MOVL 24(SP), DI
MOVL 20(SP), SI
--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -120,6 +120,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0
// fetch g
get_tls(DX)
+ CMPQ DX, $0
+ JNE 3(PC)
+ MOVQ $0, AX // continue
+ JMP done
MOVQ g(DX), DX
CMPQ DX, $0
JNE 2(PC)
@@ -131,6 +135,7 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0-0
CALL runtime·sighandler(SB)
// AX is set to report result back to Windows
+done:
// restore registers as required for windows callback
MOVQ 24(SP), R15
MOVQ 32(SP), R14
コアとなるコードの解説
追加されたアセンブリコードは、32-bit (sys_windows_386.s) と 64-bit (sys_windows_amd64.s) の両方で同様のロジックを実装しています。
sys_windows_386.s
(32-bit) の変更点
// fetch g
get_tls(DX) // スレッドローカルストレージ (TLS) から現在のgポインタのアドレスをDXレジスタに取得
+ CMPL DX, $0 // DXレジスタの値が0と比較
+ JNE 3(PC) // DXが0でなければ、現在のPCから3バイト先にジャンプ (次のMOVL g(DX), DX命令をスキップ)
+ MOVL $0, AX // AXレジスタに0を移動 (Windowsに例外処理を継続させることを伝える)
+ JMP done // doneラベルにジャンプし、関数を終了
MOVL g(DX), DX // DXが0でない場合、TLSからgポインタ自体をDXレジスタにロード
CMPL DX, $0
JNE 2(PC)
get_tls(DX)
: このマクロは、現在のスレッドのTLSから、Goランタイムが管理するg
構造体(現在のゴルーチン)へのポインタが格納されているメモリ位置のアドレスをDX
レジスタにロードします。Goランタイムが管理するスレッドの場合、このアドレスは有効なメモリ領域を指します。外部スレッドの場合、このアドレスは通常ゼロ、または不正な値になります。CMPL DX, $0
:DX
レジスタの値がゼロ($0
)と比較されます。JNE 3(PC)
: もしDX
がゼロでなければ(JNE
はJump if Not Equal)、プログラムカウンタ(PC
)から3バイト先にジャンプします。これは、次の命令であるMOVL g(DX), DX
をスキップすることを意味します。つまり、Goランタイムが管理するスレッドであれば、このチェックを通過して通常のg
ポインタのロードに進みます。MOVL $0, AX
: もしDX
がゼロであれば(つまり、外部スレッドからの例外である場合)、AX
レジスタにゼロをロードします。Windowsの例外処理メカニズムでは、例外ハンドラが0
を返すと、OSは例外処理を継続(つまり、次のハンドラを探すか、デフォルトの処理を行う)します。JMP done
:done
ラベルに無条件にジャンプします。これにより、runtime·sigtramp
関数の残りの部分(runtime·sighandler
の呼び出しなど)をスキップし、関数を終了します。done:
: 新しく追加されたラベルで、関数の終了処理(レジスタの復元など)が行われる場所です。
sys_windows_amd64.s
(64-bit) の変更点
64-bit版も同様のロジックですが、レジスタ名と命令が64-bit用に変更されています。
// fetch g
get_tls(DX) // TLSから現在のgポインタのアドレスをDXレジスタに取得
+ CMPQ DX, $0 // DXレジスタの値が0と比較 (64-bit)
+ JNE 3(PC) // DXが0でなければ、現在のPCから3バイト先にジャンプ
+ MOVQ $0, AX // AXレジスタに0を移動 (64-bit)
+ JMP done // doneラベルにジャンプ
MOVQ g(DX), DX // DXが0でない場合、TLSからgポインタ自体をDXレジスタにロード (64-bit)
CMPQ DX, $0
JNE 2(PC)
CMPQ DX, $0
: 64-bitレジスタの比較命令。MOVQ $0, AX
: 64-bitレジスタへの移動命令。
これらの変更により、Goランタイムは、Goが管理していないスレッドから発生した例外を安全に無視し、Goプログラムのクラッシュを防ぐことができるようになりました。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/104200046
- 関連する可能性のあるGoイシュー(ただし、直接的な詳細は見つからず):
Fixes #8224
参考にした情報源リンク
- Go runtime foreign threads Windows:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMZN7cr5taWtdHTjnLuPaJld2zBxZ19viGiXi9ySxoGuk2lmmoFYDFPbNvnjniwzYgoJTI5BaNS6qDqs3IMN9jYF_VLcHXw5Z68aMTq7EzowgN6hSuDHmTRxq3kLy_m7_WN52JjHSvMBYqU_5SMoIxSp55L9130WqGw7oTAP-XPypK8REAWk5Fr_qHjc4IO_EDkSVeMZiCepMjyTBBMm07AOn6eJ66xsQCOQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEywJzNm9vs00rrptyjuhg01okhMv286W6aQ2DxNtjwkaNMlKr9ZlIEyJD-wAv3AC8aYMba_0wFPKtuWqs4aOyZYceTyWm35duIeRkIEX65amEcbcIhk6C_GYd2siQeSm6YkIElsIeYxHcPxxW-HFEAjvnwLiob8wuzI3q7BbPE4rJt2NqMRAS-v-SBRg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGGMC_k-AgYr4L01qnYcih30LR7KYMMK3CZ68ubDMEuTHt79r4CWEt0p8GBXBUUnm69t3DO1e8kKmZKNcZZG2VH6v6OM9n_P1WQvl0fCM6_DR1FYPKLesINS5A9Mw==
- Windows Structured Exception Handling (SEH):
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE2rjdLcMOOG_LyWAME2d0-k5TRvk7WnPlCXfWkfa8fubpa0pSXnyW8zh4XlI4cNDyDYMDs0hrQ7hyctRbh4E1dl6_AK7wHbo0skesNtP9wXapBswMJUlYE5RX2xmh69-6IiC3bvzKV6EX5Xrmcd2WyIrtAwdQhQCl_Vi7Gxl72WIgFsmqlGj2Lpg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHtnA3qPR9XwqEiSdn3aD9tgl3_2NzpnFGBOhAsdBF6UibbWSANSCO0X1rragHkshbpJ-YxBYtq_DlwXwCtmo5Siw3yMF-luR9ElfeQ-lx8fHmYFExkl3zAsak4A3USMgBE7CaPkboPqgy983kHGeRZd5KMFL0hRm98fx_NEpdSuSdodHxa1ygYp1_KB7-1r5UGIroLmhA=
- Go runtime sigtramp Windows:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG-V4CoKmuOln99d0db6u4Q3h3mzEY4JlFDSXb8PDxtsPcI_DoPGzC7APPMZj6Vn3rGBMz2uDRe0XwY5UjUrnYteXeWwFnb_pzoevFaNbO7KQhU0dXjYer2TgYonam0DWi0rcfGdpR0MUjBvM5WkLzkIsl2CwKoFU0qTuX2AsRYX9XoMmXD9ivD4xaXza1d33ykpCiqGuC0qP4Hk0c=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEbZyQh5ILYcISX7Q7FxtyJNRNDdUSJYii1qhAYwuWMwFj0rH04mx-utlRREfaFfmMq4C2_h8TLWIjm2MPBZvKGDTgHE6tCWqABvWZf_9clF19_i2YGvsikQedAIAmlFtgoOMhKLnwTGjK3FQk4SlUpdv4nDA==