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

[インデックス 19652] ファイルの概要

このコミットは、GoランタイムのSolaris/AMD64アーキテクチャにおけるシグナルハンドリングに関するバグ修正です。具体的には、runtime·sigtramp関数がCgoによって作成されたスレッドで呼び出された際に、レジスタの復元が適切に行われない問題を解決します。

コミット

commit d7b678b2ca26928390078c29e3f71868e867a182
Author: Aram Hăvărneanu <aram@mgk.ro>
Date:   Wed Jul 2 09:34:06 2014 +1000

    runtime: properly restore registers in Solaris runtime·sigtramp
    
    We restored registers correctly in the usual case where the thread
    is a Go-managed thread and called runtime·sighandler, but we
    failed to do so when runtime·sigtramp was called on a cgo-created
    thread. In that case, runtime·sigtramp called runtime·badsignal,
    a Go function, and did not restore registers after it returned
    
    LGTM=rsc, dave
    R=rsc, dave
    CC=golang-codereviews, minux.ma
    https://golang.org/cl/105280050

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d7b678b2ca26928390078c29e3f71868e867a182

元コミット内容

GoランタイムのSolaris版において、runtime·sigtrampがCgoによって作成されたスレッドで呼び出された際に、レジスタの復元が正しく行われない問題を修正します。通常、Goが管理するスレッドではruntime·sighandlerが呼び出され、レジスタは正しく復元されていました。しかし、Cgoスレッドの場合、runtime·sigtrampruntime·badsignalというGo関数を呼び出した後、レジスタを復元せずにリターンしていました。このコミットは、runtime·badsignalからの戻り後にレジスタ復元処理へジャンプするように変更することで、この問題を解決します。

変更の背景

Goプログラムがシグナルを受信した際、オペレーティングシステムはシグナルハンドラを呼び出す前に、現在の実行コンテキスト(レジスタの状態など)を保存します。シグナルハンドラの処理が完了した後、保存されたコンテキストを復元し、元の実行フローに戻ります。

このコミットが修正している問題は、Solaris環境におけるGoランタイムのシグナルハンドリングメカニズム、特にruntime·sigtrampの動作に関連しています。

通常のGoが管理するゴルーチン(スレッド)の場合、シグナルハンドラはGoランタイムによって適切に処理され、シグナル処理後にレジスタの状態が正しく復元されていました。しかし、Cgo(GoとC言語の相互運用機能)によって作成されたスレッドの場合、シグナルが配送された際にruntime·sigtrampが呼び出されます。このruntime·sigtrampは、シグナルが予期せぬ状況で発生した場合などにruntime·badsignalというGo関数を呼び出すことがありました。

問題は、runtime·badsignalが呼び出された後、runtime·sigtrampがレジスタの復元処理をスキップして直接リターンしてしまっていた点にあります。これにより、Cgoスレッドがシグナルを受信し、runtime·badsignalが実行された場合、シグナル処理後のレジスタの状態が不正になり、プログラムのクラッシュや予期せぬ動作を引き起こす可能性がありました。

このコミットは、このレジスタ復元漏れという特定のバグを修正し、GoプログラムがCgoと連携するSolaris環境での安定性を向上させることを目的としています。

前提知識の解説

  • Goランタイム (Go Runtime): Go言語で書かれたプログラムの実行を管理するシステムです。ガベージコレクション、スケジューリング、メモリ管理、シグナルハンドリングなど、低レベルな機能を提供します。
  • シグナル (Signal): オペレーティングシステムがプロセスに送信する非同期の通知です。例えば、Ctrl+Cによる割り込み(SIGINT)、不正なメモリアクセス(SIGSEGV)、子プロセスの終了(SIGCHLD)などがあります。
  • シグナルハンドリング (Signal Handling): プロセスがシグナルを受信した際に、どのように応答するかを定義するメカニズムです。通常、特定のシグナルに対してカスタムのハンドラ関数を登録します。
  • sigtramp (Signal Trampoline): オペレーティングシステムがシグナルハンドラを呼び出す際に使用する、低レベルのアセンブリコードのルーチンです。これは、シグナルハンドラが呼び出される前にレジスタの状態を保存し、ハンドラから戻った後にレジスタを復元する役割を担います。Goランタイムでは、OSのsigtrampからGo独自のシグナル処理ルーチンへ制御を移すための橋渡しをします。
  • Cgo: Go言語とC言語のコードを相互に呼び出すためのGoの機能です。Cgoを使用すると、Goプログラムから既存のCライブラリを利用したり、CコードからGo関数を呼び出したりできます。Cgoによって作成されたスレッドは、Goランタイムが直接管理するゴルーチンとは異なるライフサイクルやコンテキストを持つ場合があります。
  • レジスタ (Registers): CPU内部にある高速な記憶領域で、現在実行中の命令やデータ、メモリアドレスなどを一時的に保持します。関数呼び出しやシグナル処理の際には、これらのレジスタの状態を保存・復元することが不可欠です。
  • アセンブリ言語 (Assembly Language): 特定のCPUアーキテクチャに特化した低レベルのプログラミング言語です。Goランタイムの低レベルな部分(特にOSとのインタフェースやパフォーマンスが重要な部分)は、アセンブリ言語で記述されることがあります。このコミットで変更されているsys_solaris_amd64.sファイルは、Solaris/AMD64向けのアセンブリコードです。
  • RET (Return): アセンブリ言語の命令で、現在のサブルーチンから呼び出し元に戻るために使用されます。通常、スタックに保存されたリターンアドレスにジャンプします。
  • JMP (Jump): アセンブリ言語の命令で、指定されたアドレスに無条件にジャンプします。サブルーチンからの戻りではなく、コード内の別の場所へ制御を移す際に使用されます。

技術的詳細

このコミットは、src/pkg/runtime/sys_solaris_amd64.sファイル内のruntime·sigtrampアセンブリ関数を変更しています。

runtime·sigtrampは、SolarisシステムがシグナルをGoプロセスに配送した際に、OSのシグナルディスパッチャから最初に制御を受け取るGoランタイム内のエントリポイントです。この関数は、シグナルコンテキストの初期処理を行い、Goランタイムのより高レベルなシグナルハンドラ(例: runtime·sighandler)に制御を渡すか、あるいはシグナルが予期せぬものである場合にruntime·badsignalを呼び出します。

問題の箇所は、runtime·badsignalが呼び出された後の処理でした。元のコードでは、runtime·badsignalの呼び出し後にRET命令が直接配置されていました。

    MOVQ    DI, 0(SP)
    MOVQ    $runtime·badsignal(SB), AX
    CALL    AX
    RET  // ここが問題の箇所

RET命令は、関数呼び出しのスタックフレームを破棄し、呼び出し元に戻ります。しかし、runtime·sigtrampの目的は、シグナル処理後にレジスタの状態を適切に復元し、元の実行コンテキストに戻ることです。runtime·badsignalが呼び出されたパスでは、このレジスタ復元ロジック(exitラベル以降のコード)がスキップされてしまっていました。

特にCgoによって作成されたスレッドの場合、Goランタイムが直接管理するゴルーチンとは異なるコンテキストで動作するため、このレジスタ復元漏れが顕在化しやすかったと考えられます。レジスタが正しく復元されないと、その後の命令実行が不正なレジスタ値に基づいて行われ、クラッシュやデータ破損につながります。

このコミットでは、RET命令をJMP exit命令に置き換えることで、この問題を解決しています。

    MOVQ    DI, 0(SP)
    MOVQ    $runtime·badsignal(SB), AX
    CALL    AX
    JMP     exit // 変更点: RETからJMP exitへ

JMP exit命令は、無条件にexitラベルにジャンプします。exitラベルの直後には、シグナル処理によって変更されたレジスタを元の状態に復元するためのアセンブリコードが記述されています。これにより、runtime·badsignalが呼び出されたパスであっても、必ずレジスタ復元処理が実行されるようになり、Cgoスレッドにおけるシグナル処理後のレジスタ状態の整合性が保証されます。

この修正は、Goランタイムの低レベルな部分、特にOSとのインタフェース層における正確なレジスタ管理の重要性を示しています。アセンブリレベルでのわずかな命令の変更が、システム全体の安定性に大きな影響を与えることがあります。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/sys_solaris_amd64.sファイルの一箇所のみです。

--- a/src/pkg/runtime/sys_solaris_amd64.s
+++ b/src/pkg/runtime/sys_solaris_amd64.s
@@ -164,7 +164,7 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$0
  	MOVQ	DI, 0(SP)
  	MOVQ	$runtime·badsignal(SB), AX
  	CALL	AX
-	RET
+	JMP	exit
  
  allgood:
  	// save g
@@ -259,6 +259,7 @@ allgood:
  	MOVQ	80(SP), R10
  	MOVQ	R10, g(BX)
  
+exit:
  	// restore registers
  	MOVQ	32(SP), BX
  	MOVQ	40(SP), BP

コアとなるコードの解説

変更されたのは、TEXT runtime·sigtramp(SB),NOSPLIT,$0セクション内の以下の部分です。

元のコード:

    MOVQ    DI, 0(SP)
    MOVQ    $runtime·badsignal(SB), AX
    CALL    AX
    RET

このシーケンスは、まずレジスタDIの値をスタックに保存し、次にruntime·badsignal関数のアドレスをAXレジスタにロードし、その関数をCALL(呼び出し)します。CALL命令は、runtime·badsignalが実行された後、制御がこの行に戻ることを意味します。問題は、その直後にRET命令があったことです。RETは現在のサブルーチン(この場合はruntime·sigtramp)から戻る命令であり、runtime·sigtrampの後半にあるレジスタ復元処理(exit:ラベル以降)をスキップしてしまいます。

修正後のコード:

    MOVQ    DI, 0(SP)
    MOVQ    $runtime·badsignal(SB), AX
    CALL    AX
    JMP     exit

RETJMP exitに置き換えられました。JMP exitは、runtime·badsignalが戻った後、無条件にexitラベルにジャンプします。exitラベルは、runtime·sigtrampの後半に位置し、シグナル処理によって変更されたレジスタを元の状態に復元するための共通のコードパスです。この変更により、runtime·badsignalが呼び出された場合でも、必ずレジスタ復元処理が実行されるようになり、シグナル処理後のレジスタ状態の整合性が保証されます。

また、exit:ラベルが追加されていますが、これは既存のレジスタ復元コードブロックの開始点にラベルを明示的に付与したものであり、機能的な変更というよりは、JMP exitのターゲットを明確にするためのものです。

関連リンク

参考にした情報源リンク

  • Goのソースコード(特にsrc/pkg/runtime/sys_solaris_amd64.s
  • Goのコミットメッセージとコードレビューコメント
  • アセンブリ言語(x86-64)の基本的な知識
  • オペレーティングシステム(Solaris)のシグナル処理に関する一般的な知識I have generated the detailed explanation in Markdown format. I will now print it to standard output.