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

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

このコミットは、Go言語のランタイムがWindows上でクラッシュした際に表示されるエラーダイアログを抑制するための変更です。具体的には、src/lib9/main.cファイルに、Windows固有のシグナルハンドラとエラーモード設定を追加することで、セグメンテーション違反やバスエラー発生時にシステムのエラーダイアログが表示されないようにしています。

コミット

commit f418c505d0b3b9de226f5c28c2345f00299845fc
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Feb 3 06:11:25 2013 +0800

    lib9/main.c: don't show the crash dialog on windows
    Fixes #3202. (Or rather, work around issue 3202)

    R=alex.brainman, rsc
    CC=golang-dev
    https://golang.org/cl/7202053

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

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

元コミット内容

lib9/main.c: don't show the crash dialog on windows
Fixes #3202. (Or rather, work around issue 3202)

変更の背景

この変更は、Go言語のプログラムがWindows環境で実行中に内部的な致命的エラー(セグメンテーション違反やバスエラーなど)に遭遇した際に、Windowsが自動的に表示するクラッシュダイアログ(Windows Error Reportingダイアログ)を抑制することを目的としています。

Go言語のIssue 3202("windows: crash dialog on internal error")で報告された問題は、特にコマンドラインツールや自動化されたスクリプト、あるいはサーバープロセスとしてGoプログラムが動作している場合に顕著でした。クラッシュダイアログが表示されると、ユーザーの介入が必要になったり、自動化されたプロセスが停止したりする可能性があります。これは、予期せぬクラッシュが発生した場合でも、プログラムが静かに終了するか、あるいはログにエラーを出力するだけで、GUIベースのインタラクションを必要としない挙動が望ましいという設計思想に反します。

このコミットは、問題の根本的な解決ではなく、「ワークアラウンド(回避策)」として、クラッシュダイアログの表示を無効にすることで、このユーザーエクスペリエンスの問題を解決しようとしています。これにより、GoプログラムがWindows上でより「行儀良く」クラッシュし、自動化された環境での運用が容易になります。

前提知識の解説

このコミットを理解するためには、以下の技術的背景知識が必要です。

  1. Go言語のランタイムとC言語の連携: Go言語のランタイムは、一部がC言語やアセンブリ言語で実装されています。特に、OSとの低レベルなインタラクション(メモリ管理、シグナルハンドリング、システムコールなど)は、C言語のコードを通じて行われることが多いです。src/lib9/main.cのようなファイルは、Goプログラムの起動時やOS固有の処理において、C言語のコードが介在する部分を示しています。

  2. Plan 9 from Bell Labs (Plan 9): lib9という名前は、ベル研究所が開発したオペレーティングシステム「Plan 9」に由来します。Go言語は、その設計思想や一部のライブラリにおいてPlan 9の影響を強く受けています。lib9は、Goの初期のランタイムやツールチェーンにおいて、Plan 9のシステムコールやユーティリティ関数を模倣した、あるいはそれらから派生した機能を提供するために使用されていました。p9mainは、Plan 9スタイルのプログラムのエントリポイントを指すことが多く、Goのmain関数が内部的にこれを呼び出すことで、Plan 9の慣習に沿った初期化が行われることがあります。

  3. シグナルハンドリング (Signal Handling): Unix系OSやWindowsにおいて、シグナルはソフトウェア割り込みの一種で、特定のイベント(例: プログラムの異常終了、ユーザーからの割り込み)が発生したことをプロセスに通知するメカニズムです。

    • SIGSEGV (Segmentation Violation): プログラムが不正なメモリアドレスにアクセスしようとしたときに発生するシグナルです。一般的に「セグメンテーション違反」や「セグフォ」と呼ばれ、プログラムのクラッシュの主要な原因の一つです。
    • SIGBUS (Bus Error): プログラムが不正なメモリアドレスにアクセスしようとしたときに発生するシグナルですが、SIGSEGVとは異なり、通常はアラインメント違反や物理メモリのアクセスエラーなど、より低レベルなハードウェア関連の問題に起因します。 signal()関数は、特定のシグナルが発生した際に呼び出されるカスタムのハンドラ関数を設定するために使用されます。
  4. Windowsのエラーレポート機能: Windowsオペレーティングシステムには、アプリケーションがクラッシュした際に、その情報を収集し、Microsoftに送信するかどうかをユーザーに尋ねる「Windows Error Reporting (WER)」という機能があります。これにより、開発者はクラッシュレポートを受け取り、バグの特定と修正に役立てることができます。しかし、前述の通り、コマンドラインツールなどではこのダイアログが邪魔になることがあります。

  5. SetErrorMode関数 (Windows API): Windows APIのSetErrorMode関数は、システムが特定のエラーを処理する方法を変更するために使用されます。この関数に渡されるフラグは、エラーダイアログの表示を抑制したり、エラーをアプリケーションに直接報告させたりするなどの挙動を制御します。

    • SEM_NOGPFAULTERRORBOX: 一般保護違反(GPF、セグメンテーション違反に相当)が発生した際に、システムがエラーメッセージボックスを表示するのを防ぎます。
    • SEM_FAILCRITICALERRORS: システムが、利用できないドライブへのアクセスなど、致命的なエラーが発生した際に、エラーメッセージボックスを表示するのを防ぎます。
    • SEM_NOOPENFILEERRORBOX: ファイルを開く際に発生するエラー(例: ファイルが見つからない)に対して、エラーメッセージボックスを表示するのを防ぎます。

技術的詳細

このコミットの技術的な核心は、Windows環境におけるGoプログラムのクラッシュ時の挙動を制御するために、C言語のコードでWindows APIとシグナルハンドリングを組み合わせている点にあります。

  1. 条件付きコンパイル (#ifdef WIN32): 変更は#ifdef WIN32プリプロセッサディレクティブで囲まれています。これは、コードがWindowsプラットフォーム向けにコンパイルされる場合にのみ、このセクションのコードが有効になることを意味します。これにより、クロスプラットフォーム対応が維持され、Windows以外のOSではこの特定の挙動変更が適用されません。

  2. カスタムクラッシュハンドラ (crashhandler): crashhandlerという静的関数が定義されています。この関数は、SIGSEGVまたはSIGBUSシグナルが発生した際に呼び出されるように設定されます。

    • fprintf(2, "%s: internal fatal error.\n", argv0);: 標準エラー出力(ファイルディスクリプタ2)に「internal fatal error.」というメッセージを出力します。argv0はプログラム名です。これにより、ダイアログが表示されない代わりに、コンソールにエラーメッセージが出力され、問題発生を通知します。
    • exit(1);: プログラムを終了コード1(通常、エラーを示す)で即座に終了させます。これにより、プログラムが異常終了したことをシステムに伝えます。
  3. シグナルハンドラの設定: main関数内で、signal(SIGSEGV, crashhandler);signal(SIGBUS, crashhandler);が呼び出されています。これは、セグメンテーション違反(SIGSEGV)とバスエラー(SIGBUS)が発生した場合に、デフォルトのシグナルハンドラではなく、カスタムで定義したcrashhandler関数が実行されるようにOSに登録するものです。これにより、Windowsのエラーダイアログが表示される前に、Goランタイムが制御を奪い、独自のエラー処理を行うことができます。

  4. エラーモードの設定 (SetErrorMode): SetErrorMode関数は、Windowsのエラーダイアログ表示を抑制する最も重要な部分です。

    • DWORD mode = SetErrorMode(SEM_NOGPFAULTERRORBOX);: まず、現在のエラーモードを取得し、SEM_NOGPFAULTERRORBOXフラグを設定して、一般保護違反(GPF)時のダイアログ表示を抑制します。この行は、既存のエラーモードを取得しつつ、同時にSEM_NOGPFAULTERRORBOXを設定しています。
    • SetErrorMode(mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);: 次に、取得した既存のエラーモードに、さらに以下のフラグをOR演算で追加して設定し直しています。
      • SEM_FAILCRITICALERRORS: クリティカルエラー発生時のダイアログを抑制します。
      • SEM_NOGPFAULTERRORBOX: GPF時のダイアログを抑制します(二重に設定されていますが、これは既存のモードに確実に含めるための冗長な記述か、あるいは以前のモードを上書きする意図があるかもしれません)。
      • SEM_NOOPENFILEERRORBOX: ファイルオープンエラー時のダイアログを抑制します。 これらのフラグを設定することで、Goプログラムがクラッシュした際にWindowsが自動的に表示するエラーダイアログのほとんどを無効化し、代わりにカスタムのcrashhandlerが実行されるか、あるいはプログラムが静かに終了するようになります。

この変更は、GoプログラムがWindows上でより予測可能で、自動化に適した挙動を示すようにするための重要なステップです。

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

変更は src/lib9/main.c ファイルに集中しています。

--- a/src/lib9/main.c
+++ b/src/lib9/main.c
@@ -27,11 +27,28 @@ THE SOFTWARE.
 #define NOPLAN9DEFINES
 #include <libc.h>

+#ifdef WIN32
+#include <windows.h>
+
+static void crashhandler() {
+	fprintf(2, "%s: internal fatal error.\n", argv0);
+	exit(1);
+}
+#endif
+
 extern void p9main(int, char**);

 int
 main(int argc, char **argv)
 {
+#ifdef WIN32
+	signal(SIGSEGV, crashhandler);
+	signal(SIGBUS, crashhandler);
+	// don't display the crash dialog
+	DWORD mode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
+	SetErrorMode(mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+	argv0 = argv[0];
+#endif
 	p9main(argc, argv);
 	exits("main");
 	return 99;

コアとなるコードの解説

このコードは、GoプログラムのC言語で書かれたエントリポイントであるmain関数に、Windows固有のクラッシュハンドリングロジックを追加しています。

  1. #ifdef WIN32 ブロック: このブロック内のコードは、コンパイラがWIN32マクロを定義している場合(つまり、Windows向けにコンパイルされている場合)にのみ含まれます。

  2. ヘッダーファイルのインクルード: #include <windows.h> は、Windows API関数(SetErrorModeなど)を使用するために必要なヘッダーファイルです。

  3. crashhandler 関数の定義: static void crashhandler() は、カスタムのシグナルハンドラ関数です。

    • fprintf(2, "%s: internal fatal error.\n", argv0);: プログラム名と「internal fatal error.」というメッセージを標準エラー出力に書き込みます。これにより、クラッシュダイアログの代わりにコンソールにエラー情報が表示されます。
    • exit(1);: プログラムをエラーコード1で即座に終了させます。
  4. main 関数内の変更: main関数の冒頭に、再び#ifdef WIN32ブロックが追加されています。

    • signal(SIGSEGV, crashhandler);signal(SIGBUS, crashhandler);: SIGSEGV(セグメンテーション違反)とSIGBUS(バスエラー)という2つの重要なシグナルに対して、先ほど定義したcrashhandler関数をシグナルハンドラとして登録しています。これにより、これらのシグナルが発生した際に、Windowsのデフォルトのクラッシュダイアログではなく、このカスタムハンドラが実行されます。
    • // don't display the crash dialog: コメントで、この後のコードがクラッシュダイアログを非表示にする目的であることを示しています。
    • DWORD mode = SetErrorMode(SEM_NOGPFAULTERRORBOX);: SetErrorMode関数を呼び出し、現在のエラーモードを取得しつつ、SEM_NOGPFAULTERRORBOXフラグを設定して、一般保護違反時のダイアログ表示を抑制します。
    • SetErrorMode(mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);: 取得した既存のモードに、さらにSEM_FAILCRITICALERRORS(致命的エラー)、SEM_NOGPFAULTERRORBOX(GPF)、SEM_NOOPENFILEERRORBOX(ファイルオープンエラー)の各フラグを追加して、エラーモードを再設定します。これにより、Windowsが様々な種類のエラーに対してダイアログを表示するのを防ぎます。
    • argv0 = argv[0];: プログラム名をグローバル変数argv0に設定しています。これは、crashhandler関数内でプログラム名を出力するために使用されます。

この一連の変更により、GoプログラムがWindows上で予期せぬクラッシュを起こした場合でも、GUIダイアログが表示されることなく、コンソールにエラーメッセージを出力して静かに終了するようになります。これは、特に自動化された環境やサーバーアプリケーションにおいて、望ましい挙動です。

関連リンク

参考にした情報源リンク