[インデックス 10960] ファイルの概要
このコミットは、GoランタイムにおけるSIGILL
(不正命令シグナル)のハンドリング方法を変更するものです。具体的には、SIGILL
が発生した場合にGoのパニック機構を介さずに、直接プログラムをクラッシュさせるように修正されています。これにより、不正な命令実行という深刻なエラーに対するGoプログラムの挙動がより直接的かつ予測可能になります。
コミット
commit 5690ddc7fa033e10961c728ddd6bccf4903707d4
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Dec 21 15:45:36 2011 -0800
runtime: don't panic on SIGILL, just crash
R=rsc
CC=golang-dev
https://golang.org/cl/5504067
---
src/pkg/runtime/signals_linux.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n
diff --git a/src/pkg/runtime/signals_linux.h b/src/pkg/runtime/signals_linux.h
index 919b80ea29..1fc5f8c87c 100644
--- a/src/pkg/runtime/signals_linux.h
+++ b/src/pkg/runtime/signals_linux.h
@@ -13,7 +13,7 @@ SigTab runtime·sigtab[] = {
/* 1 */ Q+R, \"SIGHUP: terminal line hangup\",
/* 2 */ Q+R, \"SIGINT: interrupt\",
/* 3 */ C, \"SIGQUIT: quit\",
-\t/* 4 */\tC+P, \"SIGILL: illegal instruction\",
+\t/* 4 */\tC, \"SIGILL: illegal instruction\",
/* 5 */ C, \"SIGTRAP: trace trap\",
/* 6 */ C, \"SIGABRT: abort\",
/* 7 */ C+P, \"SIGBUS: bus error\",
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5690ddc7fa033e10961c728ddd6bccf4903707d4
元コミット内容
このコミットの目的は、GoランタイムがSIGILL
シグナルを受け取った際の挙動を変更することです。以前はSIGILL
によってGoのパニックが発生していましたが、この変更により、パニックを発生させずに直接プログラムをクラッシュさせるようになります。
変更の背景
SIGILL
シグナルは、プログラムが不正なCPU命令を実行しようとした際にOSによって発行されるシグナルです。これは通常、実行可能ファイルの破損、メモリの破壊、または深刻なプログラミングエラーなど、回復が極めて困難な状況を示します。
Goのパニック(panic
)は、プログラムの通常の実行フローを停止させ、スタックを巻き戻し、defer
関数を実行するメカニズムです。recover
関数を使用することで、パニックから回復し、プログラムの実行を継続することも可能です。しかし、SIGILL
のような低レベルで致命的なエラーの場合、パニックを発生させてrecover
を試みることは、かえって問題を複雑にしたり、さらなる不安定性を招いたりする可能性があります。
このコミットの背景には、SIGILL
のような深刻なエラーに対しては、Goのパニック機構を介さずに、より迅速かつ明確にプログラムを終了させるべきであるという判断があったと考えられます。これにより、デバッグ時の挙動がより予測しやすくなり、システム全体の安定性向上に寄与します。
前提知識の解説
1. Unixシグナル
Unix系OSでは、シグナルはプロセスに対して非同期的にイベントを通知するメカニズムです。例えば、SIGINT
(Ctrl+Cによる割り込み)、SIGTERM
(終了要求)、SIGSEGV
(セグメンテーション違反)、SIGILL
(不正命令)などがあります。プログラムはこれらのシグナルを捕捉し、特定のハンドラ関数を実行することで、シグナルに応じた処理を行うことができます。
2. SIGILL
(Illegal Instruction Signal)
SIGILL
は、プロセスがCPUによって認識されない、または現在のCPUモードでは実行できない命令を実行しようとしたときに発生するシグナルです。主な原因としては以下が挙げられます。
- 実行可能ファイルの破損: プログラムのバイナリコードが何らかの理由で壊れている場合。
- メモリ破壊: プログラムが自身のコードセグメントを誤って上書きしてしまい、その結果として不正な命令が実行される場合。
- プログラミングエラー: 関数ポインタが不正なアドレスを指していたり、データ領域をコードとして実行しようとしたりする場合。
3. Goにおけるpanic
とcrash
panic
(パニック): Go言語に組み込まれたエラーハンドリングメカニズムの一つです。panic()
関数を明示的に呼び出すか、ランタイムエラー(例: nilポインタ参照、配列の範囲外アクセス)によって発生します。パニックが発生すると、現在の関数の実行が停止し、defer
された関数が実行されながら呼び出しスタックを遡ります。recover()
関数をdefer
内で使用することで、パニックを捕捉し、プログラムの終了を防ぐことができます。パニックは、通常、プログラムが安全に続行できないような、予期せぬ例外的な状況のために使用されます。crash
(クラッシュ): Goプログラムが完全に終了することを指します。パニックがrecover
されなかった場合、最終的にプログラムはクラッシュします。OSシグナルがGoランタイムによって処理され、それがパニックに変換されずに直接プログラムを終了させる場合もクラッシュと見なされます。
4. GoランタイムのシグナルハンドリングとSigTab
Goランタイムは、OSからのシグナルを内部的に処理し、Goプログラムの挙動を制御します。src/pkg/runtime/signals_linux.h
のようなファイルには、各シグナルに対するGoランタイムのデフォルトの挙動を定義するSigTab
(シグナルテーブル)のような構造が存在します。
このテーブル内のフラグは、シグナルが受信されたときにランタイムがどのように応答するかを示します。コミット内容から推測されるフラグの意味は以下の通りです。
C
: Crash - シグナルを受け取るとプログラムをクラッシュ(終了)させます。P
: Panic - シグナルを受け取るとGoのパニックを発生させます。Q
: Quit - プログラムを終了させますが、通常はコアダンプを生成します(SIGQUIT
のデフォルト挙動)。R
: Restart - シグナルハンドラから戻った後、システムコールを再開します。
したがって、C+P
は「クラッシュさせ、かつパニックも発生させる」という挙動を意味し、C
は「パニックを発生させずにクラッシュさせる」という挙動を意味します。
技術的詳細
このコミットは、Goランタイムのシグナルハンドリングロジックの核心部分であるSigTab
配列を変更しています。SigTab
は、Linuxシステムにおける各シグナル番号に対応するGoランタイムの挙動を定義するテーブルです。
変更前、SIGILL
(シグナル番号4)のエントリはC+P
と定義されていました。これは、SIGILL
が発生した場合に、Goランタイムがプログラムをクラッシュさせると同時に、Goのパニックも発生させることを意味していました。この挙動は、SIGILL
が通常、回復不可能な低レベルのエラーを示すにもかかわらず、Goのパニック/リカバリメカニズムを介そうとするものでした。
変更後、SIGILL
のエントリはC
のみに変更されました。これにより、SIGILL
が発生した際には、Goのパニックを発生させることなく、直接プログラムをクラッシュさせるようになります。この変更の技術的な意図は、SIGILL
のような深刻なエラーに対しては、Goのパニックハンドリングのオーバーヘッドや複雑さを避け、よりクリーンかつ迅速にプログラムを終了させることにあります。
具体的には、SIGILL
はCPUレベルでの不正な状態を示すため、Goのランタイムがその状態から健全に回復することは非常に困難です。パニックを発生させても、その後のrecover
が意味をなさないか、あるいはさらなる未定義の挙動を引き起こす可能性が高いです。そのため、このような状況では、単にプログラムを終了させ、OSレベルでのクラッシュレポート(もし設定されていればコアダンプなど)に任せる方が、問題の診断やシステムの安定性にとって望ましいと判断されたと考えられます。
この変更は、Goプログラムが不正な命令に遭遇した際の挙動を簡素化し、より堅牢なエラー処理戦略に貢献します。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/signals_linux.h
ファイル内のSigTab
配列の1行のみです。
--- a/src/pkg/runtime/signals_linux.h
+++ b/src/pkg/runtime/signals_linux.h
@@ -13,7 +13,7 @@ SigTab runtime·sigtab[] = {
/* 1 */ Q+R, \"SIGHUP: terminal line hangup\",
/* 2 */ Q+R, \"SIGINT: interrupt\",
/* 3 */ C, \"SIGQUIT: quit\",
-\t/* 4 */\tC+P, \"SIGILL: illegal instruction\",
+\t/* 4 */\tC, \"SIGILL: illegal instruction\",
/* 5 */ C, \"SIGTRAP: trace trap\",
/* 6 */ C, \"SIGABRT: abort\",
/* 7 */ C+P, \"SIGBUS: bus error\",
コアとなるコードの解説
このコードスニペットは、GoランタイムがLinuxシステム上でシグナルをどのように処理するかを定義するSigTab
配列の一部です。各行は特定のシグナル番号に対応し、そのシグナルが受信された際のGoランタイムの挙動をフラグの組み合わせで指定しています。
変更された行は以下の通りです。
-\t/* 4 */\tC+P, \"SIGILL: illegal instruction\",
+\t/* 4 */\tC, \"SIGILL: illegal instruction\",
/* 4 */
: これはシグナル番号4、すなわちSIGILL
に対応します。C+P
: 変更前のフラグです。C
(Crash): プログラムをクラッシュ(終了)させます。P
(Panic): Goのパニックを発生させます。- したがって、
C+P
は「SIGILL
を受け取ったら、プログラムをクラッシュさせ、かつGoのパニックも発生させる」という意味になります。
C
: 変更後のフラグです。C
(Crash): プログラムをクラッシュ(終了)させます。- この変更により、「
SIGILL
を受け取ったら、Goのパニックを発生させることなく、直接プログラムをクラッシュさせる」という挙動に変わります。
この修正は、SIGILL
が示すエラーの性質(通常は回復不能な低レベルのコード実行エラー)を考慮し、Goのパニック機構を介するよりも、直接的なプログラム終了が適切であるという判断に基づいています。これにより、SIGILL
発生時のGoプログラムの挙動がより明確かつ効率的になります。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Goの
os/signal
パッケージ: https://pkg.go.dev/os/signal - Goの
runtime
パッケージ: https://pkg.go.dev/runtime
参考にした情報源リンク
- Goにおける
panic
とrecover
に関する解説記事: - Unixシグナルに関する一般的な情報:
SIGILL
に関する情報:- Goランタイムのシグナルハンドリングに関する議論やドキュメント(一般的な情報源からの推測を含む)
- https://go.dev/src/runtime/signal_unix.go (関連するソースコード)
- https://go.dev/src/runtime/proc.go (関連するソースコード)
- https://medium.com/@juliensalinas/go-signal-handling-a-deep-dive-into-os-signal-and-runtime-packages-101f2a3b4d5e (Goのシグナルハンドリングに関する解説記事)
- https://victoriametrics.com/blog/go-signal-handling/ (Goのシグナルハンドリングに関する解説記事)