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

[インデックス 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におけるpaniccrash

  • 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プログラムの挙動がより明確かつ効率的になります。

関連リンク

参考にした情報源リンク