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

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

このコミットは、Go言語のランタイムにおけるWindows/386アーキテクチャ向けのシグナルハンドラ(例外ハンドラ)に関連する変更です。具体的には、src/pkg/runtime/os_windows_386.c ファイル内のコードが対象となっています。このファイルは、GoプログラムがWindows 32-bit環境で実行される際の、低レベルなOSとのインタラクション、特に例外(シグナル)の処理を担当しています。

コミット

commit b2fa6f41a48b141e7307f9640f4ceb748a42b4cd
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 7 14:22:17 2014 -0500

    runtime: comment out breakpoint in windows/386 sighandler
    
    This code being buggy is the only explanation I can come up
    with for issue 7325. It's probably not, but the only alternative
    is a Windows kernel bug. Comment this out to see what breaks
    or gets fixed.
    
    Update #7325
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/72590044

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

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

元コミット内容

runtime: comment out breakpoint in windows/386 sighandler

This code being buggy is the only explanation I can come up
with for issue 7325. It's probably not, but the only alternative
is a Windows kernel bug. Comment this out to see what breaks
or gets fixed.

Update #7325

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/72590044

変更の背景

このコミットは、GoランタイムのWindows/386環境における特定のバグ、issue 7325 に対応するために行われました。コミットメッセージによると、この問題の原因がGoランタイムのコードにあるのか、それともWindowsカーネルのバグにあるのかが不明確でした。

コミッターであるRuss Cox氏は、Goランタイムのコードにバグがある可能性を疑いつつも、それが唯一の説明ではないと考えていました。しかし、Goランタイムのコードに問題がないとすれば、残る可能性はWindowsカーネルのバグであるという極端な状況でした。

このような状況で、問題の切り分けと原因特定のために、Goランタイム内の特定のブレークポイント処理コードを一時的にコメントアウトするという、デバッグ手法が取られました。これは、「何が壊れるか、あるいは何が修正されるかを見るため」という目的で行われ、問題の根本原因を特定するための実験的な変更でした。

前提知識の解説

Goランタイム (Go Runtime)

Goランタイムは、Go言語で書かれたプログラムの実行を管理する低レベルなシステムです。これには、ガベージコレクション、スケジューリング(ゴルーチンの管理)、メモリ管理、システムコール、そしてシグナル(例外)処理などが含まれます。Goプログラムは、OSが提供する機能とGoランタイムが提供する抽象化レイヤーを介して連携します。

Windows/386

これは、Windowsオペレーティングシステム上で動作するIntel 80386(またはそれ以降の互換性のある32ビット)アーキテクチャを指します。Go言語はクロスプラットフォームであり、様々なOSとCPUアーキテクチャの組み合わせをサポートしています。このコミットは、特にWindowsの32ビット環境に特化したランタイムの挙動に関するものです。

シグナルハンドラ (Sighandler / Exception Handler)

Unix系OSでは「シグナルハンドラ」と呼ばれますが、Windowsでは「例外ハンドラ (Exception Handler)」がより適切な用語です。これらは、プログラムの実行中に発生する予期せぬイベント(例: ゼロ除算、無効なメモリアクセス、ブレークポイントなど)をOSが検知し、そのイベントを処理するために登録された特定の関数を呼び出すメカニズムです。Goランタイムは、これらのOSレベルの例外を捕捉し、Goのパニック機構やデバッガとの連携に利用します。

EXCEPTION_BREAKPOINT

これはWindowsの例外コードの一つで、プログラムがブレークポイント命令(通常はINT3命令)を実行したときに発生します。デバッガは通常、この例外を捕捉してプログラムの実行を一時停止し、開発者がコードの状態を検査できるようにします。

INT3 命令

INT3は、x86アーキテクチャの機械語命令で、1バイトの命令コード 0xCC を持ちます。この命令が実行されると、CPUはデバッグ例外(割り込み3)を生成します。デバッガは通常、この命令をプログラムの実行ファイルに挿入することでブレークポイントを設定します。

8l (Go Linker for 386)

8lは、Go言語のツールチェーンの一部であり、386(32ビットx86)アーキテクチャ向けのリンカです。リンカは、コンパイルされたオブジェクトファイルやライブラリを結合して、実行可能なプログラムを生成する役割を担います。コミットメッセージのコメントで「8l generates 2 bytes for INT3」とあるのは、Goのリンカが特定の状況でINT3命令を2バイトとして扱う(または、そのように解釈されるようなコードを生成する)ことを示唆しています。これは、通常のINT3命令が1バイトであることと対照的であり、命令ポインタの調整(r->Eip--)が必要になる理由の一つです。

技術的詳細

このコミットの核心は、GoランタイムがWindows上でブレークポイント例外(EXCEPTION_BREAKPOINT)をどのように処理していたか、そしてその処理がなぜ問題を引き起こしていた可能性があるかという点にあります。

Windowsの例外処理メカニズムでは、プログラムがINT3命令を実行すると、OSはEXCEPTION_BREAKPOINT例外を生成し、登録された例外ハンドラに制御を渡します。Goランタイムのsighandler関数(実質的には例外ハンドラ)は、この例外を捕捉し、デバッガが介入できるようにするか、あるいはGoランタイム自身が適切に処理する責任があります。

問題は、EXCEPTION_BREAKPOINTが発生した際に、命令ポインタ(EIP、Windowsのコンテキスト構造体ではr->Eip)を適切に調整する必要がある点です。通常、INT3命令は1バイトです。しかし、Goのリンカ8lが特定の状況でINT3命令を2バイトとして生成する、あるいはそのように解釈されるようなコードを生成する場合、例外ハンドラが命令ポインタを1バイト分だけデクリメント(r->Eip--)すると、命令ポインタが正しくない位置を指してしまい、無限ループやクラッシュなどの問題を引き起こす可能性があります。

コミットメッセージにある「8l generates 2 bytes for INT3」というコメントは、この命令ポインタの調整がなぜ複雑になるのかを示唆しています。もしリンカが実際に2バイトのINT3命令を生成している場合、例外ハンドラは命令ポインタを2バイト分戻す必要があります。しかし、コメントアウトされたコードではr->Eip--(1バイト分デクリメント)しか行われていませんでした。この不一致がissue 7325の原因である可能性が考えられました。

このコミットでは、このブレークポイント処理ロジック全体をコメントアウトすることで、この部分が本当に問題の原因であるかを検証しようとしました。もしコメントアウトすることで問題が解決すれば、Goランタイムのこの部分のコードにバグがあったことになります。もし問題が解決しない、あるいは別の問題が発生すれば、Windowsカーネルのバグである可能性が高まります。これは、複雑なシステムにおけるデバッグの典型的なアプローチであり、疑わしい部分を一時的に無効化して挙動の変化を観察するものです。

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

--- a/src/pkg/runtime/os_windows_386.c
+++ b/src/pkg/runtime/os_windows_386.c
@@ -30,11 +30,13 @@ runtime·sighandler(ExceptionRecord *info, Context *r, G *gp)
  	bool crash;\n 	uintptr *sp;\n \n+\t/*\n  \tswitch(info->ExceptionCode) {\n  \tcase EXCEPTION_BREAKPOINT:\n  \t\tr->Eip--;\t// because 8l generates 2 bytes for INT3\n  \t\treturn 1;\n  \t}\n+\t*/\n \n  \tif(gp != nil && runtime·issigpanic(info->ExceptionCode)) {\n  \t\t// Make it look like a call to the signal func.\n```

## コアとなるコードの解説

変更されたコードは、`src/pkg/runtime/os_windows_386.c` ファイル内の `runtime·sighandler` 関数の一部です。この関数は、Windowsの例外ハンドラとして機能し、様々な例外コードを処理します。

コメントアウトされたブロックは以下の部分です。

```c
	switch(info->ExceptionCode) {
	case EXCEPTION_BREAKPOINT:
		r->Eip--;	// because 8l generates 2 bytes for INT3
		return 1;
	}

このコードブロックは、info->ExceptionCodeEXCEPTION_BREAKPOINT である場合に実行されるロジックでした。

  1. switch(info->ExceptionCode): 発生した例外の種類をチェックします。
  2. case EXCEPTION_BREAKPOINT:: 例外がブレークポイント(INT3命令の実行によって発生)である場合、このケースが処理されます。
  3. r->Eip--;: ここが重要な部分です。r はコンテキストレコード(Context構造体)へのポインタで、CPUレジスタの状態を含んでいます。r->Eip は命令ポインタ(Extended Instruction Pointer)を表します。Eip-- は、命令ポインタを1バイト分デクリメント(減算)することを意味します。
    • この行のコメント「// because 8l generates 2 bytes for INT3」は、Goのリンカ8lINT3命令を2バイトとして生成するため、命令ポインタを1バイト分戻す必要がある、という意図を示しています。しかし、もしINT3が実際に2バイトとして扱われるなら、命令ポインタは2バイト分戻されるべきであり、1バイトのデクリメントでは不十分である可能性があります。これがバグの原因であると疑われました。
  4. return 1;: 例外が処理されたことをOSに通知し、プログラムの実行を再開させます。

このコミットでは、上記のswitch文全体がC言語のコメントブロック/* ... */で囲まれ、無効化されました。これにより、EXCEPTION_BREAKPOINTが発生しても、Goランタイムはこの特定のロジックで処理せず、次の例外ハンドラ(もしあれば)に処理を委ねるか、OSのデフォルトの例外処理に任せることになります。

この変更は、issue 7325の原因がこのブレークポイント処理のバグにあるのか、それとも他の要因(例えばWindowsカーネルのバグ)にあるのかを切り分けるための、診断的なステップでした。もしこの変更で問題が解決すれば、Goランタイムのこの部分のコードが修正されるべきであることが明確になります。

関連リンク

参考にした情報源リンク