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

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

このコミットは、Go言語のランタイムにおけるWindows/AMD64アーキテクチャ用の例外ハンドラ(sigtramp)のバグ修正に関するものです。具体的には、スレッドローカルストレージ(TLS)からm(machine)構造体へのアクセスに使用されるレジスタが誤っていた点を修正しています。

コミット

commit 16ade99d9d57a84a754bfa8e9a52f5911d9cf5e4
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Mon Mar 12 22:42:55 2012 -0400

    runtime: fix windows/amd64 exception handler
    
    R=golang-dev, rsc
    CC=golang-dev, hectorchu
    https://golang.org/cl/5797077

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

https://github.com/golang/go/commit/16ade99d9d57a84a754bfa8e9a52f5911d9cf5e4

元コミット内容

このコミットは、Goランタイムのsrc/pkg/runtime/sys_windows_amd64.sファイルにおいて、Windows/AMD64環境での例外ハンドラ(sigtramp関数)内のレジスタ使用に関する誤りを修正しています。具体的には、m(machine)構造体へのポインタを取得する際に、誤ってBXレジスタを使用していた箇所を、正しいCXレジスタを使用するように変更しています。

変更の背景

Go言語のランタイムは、プログラムの実行を管理し、ガベージコレクション、スケジューリング、システムコールなど、低レベルの操作を処理します。オペレーティングシステム(OS)やアーキテクチャに依存する部分は、各プラットフォーム固有のアセンブリコードで実装されています。

Windows/AMD64環境において、GoランタイムはOSからのシグナル(例外)を受け取った際に、sigtrampという関数を通じてそのシグナルを処理します。この処理の中で、現在のOSスレッドに対応するGoランタイムのm(machine)構造体へのポインタにアクセスする必要があります。m構造体は、現在のスレッドの状態、スタック情報、スケジューラ関連データなど、スレッド固有の重要な情報を含んでいます。

このコミットが行われる前は、sigtramp内でm構造体へのポインタを取得する際に、誤ったレジスタ(BX)が使用されていました。これは、Goランタイムがスレッドローカルストレージ(TLS)からm構造体へのポインタをCXレジスタにロードするget_tls(CX)という命令の直後に発生していました。結果として、m構造体へのアクセスが不正なメモリ位置を参照し、ランタイムエラーやクラッシュを引き起こす可能性がありました。このコミットは、このレジスタの誤用を修正し、ランタイムの安定性と正確性を向上させることを目的としています。

前提知識の解説

Goランタイムのm(machine)構造体

Go言語のランタイムは、独自の軽量なスレッド(ゴルーチン)を管理するために、OSスレッドの上に抽象化レイヤーを構築しています。この抽象化の中心となるのが、以下の3つの主要な構造体です。

  • G (Goroutine): Go言語の軽量スレッド。スタック、プログラムカウンタ、状態など、ゴルーチン固有の情報を保持します。
  • M (Machine): OSスレッドに対応するGoランタイムの構造体。各Mは1つのOSスレッドにバインドされ、そのOSスレッド上でGを実行します。Mは、OSスレッドのスタック、レジスタ、スレッドローカルストレージ(TLS)へのポインタ、現在のGへのポインタなどを保持します。
  • P (Processor): 論理プロセッサ。MとGの間の仲介役となり、Gの実行をMにディスパッチします。Pは、実行可能なGのキューや、ガベージコレクション関連のデータなどを保持します。

このコミットで問題となっているm構造体は、OSスレッドとGoランタイムの間の重要なリンクであり、シグナルハンドリングのようなOSレベルのイベントを処理する際に、現在のスレッドのGoランタイムの状態にアクセスするために不可欠です。

スレッドローカルストレージ (TLS)

スレッドローカルストレージ(Thread-Local Storage, TLS)は、各スレッドが独自のデータコピーを持つことができるメモリ領域です。グローバル変数とは異なり、TLSに格納されたデータは、そのデータを作成したスレッドからのみアクセス可能です。Goランタイムでは、m構造体へのポインタなど、スレッド固有の重要な情報をTLSに格納して、高速にアクセスできるようにしています。

x86-64アーキテクチャのレジスタ

x86-64(またはAMD64)は、64ビットの命令セットアーキテクチャです。CPUには、データを一時的に保持するための汎用レジスタが多数あります。このコミットに関連するレジスタは以下の通りです。

  • AX (Accumulator Register): 汎用レジスタの一つで、通常、算術演算の結果や関数の戻り値を保持するために使用されます。
  • BX (Base Register): 汎用レジスタの一つで、通常、メモリのアドレス計算のベースアドレスとして使用されます。
  • CX (Count Register): 汎用レジスタの一つで、ループカウンタや、特定の命令(例: シフト命令)の繰り返し回数を指定するために使用されます。また、関数呼び出し規約によっては、引数を渡すためにも使用されます。

Goランタイムのアセンブリコードでは、これらのレジスタが特定の目的のために使用されます。特に、get_tls(CX)命令は、TLSから取得したm構造体へのポインタをCXレジスタに格納することを意図しています。

Windowsにおける例外ハンドリングとsigtramp

Windowsオペレーティングシステムでは、プログラムの実行中に発生するエラーや予期せぬイベント(例: ゼロ除算、無効なメモリアクセス)は「例外」として扱われます。OSは、例外が発生すると、その例外を処理するために登録された例外ハンドラを呼び出します。

Goランタイムは、Windowsの例外処理メカニズムと連携し、Goプログラム内で発生した例外を適切に処理します。sigtrampは、GoランタイムがOSからのシグナル(例外)を受け取った際に、Goのシグナルハンドラを呼び出すための橋渡しをするアセンブリ関数です。この関数は、OSが提供する例外コンテキストから必要な情報を抽出し、Goランタイムの内部状態を適切に設定してから、Goのコードで書かれたシグナルハンドラに制御を渡します。

技術的詳細

このコミットの技術的詳細は、GoランタイムがWindows/AMD64環境でどのようにスレッドローカルストレージとm構造体を扱っているか、そしてアセンブリコードレベルでのレジスタの役割に集約されます。

src/pkg/runtime/sys_windows_amd64.sファイルは、GoランタイムのWindows/AMD64アーキテクチャ固有のアセンブリコードを含んでいます。このファイルには、runtime·sigtrampという関数が定義されています。この関数は、WindowsがGoプログラムに例外を通知する際に呼び出されるエントリポイントです。

sigtramp関数の目的の一つは、現在のOSスレッドに対応するGoランタイムのm構造体へのポインタを取得し、そのm構造体が有効であるか(つまり、nilではないか)を確認することです。この確認は、不正な状態でのシグナル処理を防ぐために重要です。

問題のコードは以下の部分です。

	get_tls(CX)

	// check that m exists
	MOVQ	m(BX), AX
	CMPQ	AX, $0
	JNE	2(PC)
	CALL	runtime·badsignal(SB)
  1. get_tls(CX): この命令は、スレッドローカルストレージから現在のm構造体へのポインタを取得し、それをCXレジスタに格納します。これは、GoランタイムがWindows/AMD64上でTLSを管理する方法の一部です。
  2. MOVQ m(BX), AX: この命令は、BXレジスタが指すアドレスからm構造体の内容をAXレジスタに移動しようとしています。しかし、直前のget_tls(CX)命令によってm構造体へのポインタはCXレジスタに格納されています。したがって、BXレジスタを使用することは誤りであり、m構造体への不正なメモリアクセスを引き起こす可能性があります。
  3. CMPQ AX, $0: AXレジスタの内容(m構造体へのポインタ、または不正な値)が0nil)であるかを比較します。
  4. JNE 2(PC): AXnilでなければ、次の命令をスキップします。
  5. CALL runtime·badsignal(SB): AXnilであれば、runtime·badsignal関数を呼び出し、不正なシグナル処理を報告します。

このバグは、get_tls(CX)CXレジスタに正しいポインタを置いているにもかかわらず、その後のMOVQ命令が誤ってBXレジスタを参照していたために発生していました。これにより、m構造体の存在チェックが正しく機能せず、ランタイムの不安定性につながっていました。

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

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

--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -137,7 +137,7 @@ TEXT runtime·sigtramp(SB),7,$0
  	get_tls(CX)
 
  	// check that m exists
-	MOVQ	m(BX), AX
+	MOVQ	m(CX), AX
  	CMPQ	AX, $0
  	JNE	2(PC)
  	CALL	runtime·badsignal(SB)

コアとなるコードの解説

変更された行は以下の通りです。

- MOVQ m(BX), AX + MOVQ m(CX), AX

  • MOVQ: x86-64アセンブリ命令で、64ビットの値を移動(Move Quadword)します。
  • m(BX) / m(CX): これは、Goのアセンブリ構文で、m構造体へのポインタが格納されているレジスタ(BXまたはCX)を間接参照し、そのアドレスにあるm構造体の内容にアクセスすることを意味します。
  • AX: m構造体の内容が移動される先のレジスタです。

修正前は、m構造体へのポインタがCXレジスタに格納されているにもかかわらず、MOVQ m(BX), AXという命令が使用されていました。これは、BXレジスタが指すアドレスからデータを読み込もうとするため、m構造体の正しいアドレスではない場所からデータを読み込むことになり、結果としてAXレジスタには無効な値がロードされる可能性がありました。

修正後は、MOVQ m(CX), AXに変更されました。これにより、get_tls(CX)によってCXレジスタに格納された正しいm構造体へのポインタが使用され、m構造体の内容が正確にAXレジスタにロードされるようになります。この修正により、m構造体の存在チェックが正しく機能し、Windows/AMD64環境での例外ハンドラの堅牢性が向上しました。

この変更は、Goランタイムの低レベルな部分における正確なレジスタ使用の重要性を示しています。アセンブリコードレベルでのわずかな誤りでも、ランタイムの安定性に大きな影響を与える可能性があります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/runtime/ディレクトリ)
  • Go言語のIssueトラッカーおよびコードレビューシステム(golang.org/cl/5797077
  • x86-64アセンブリ言語に関する一般的なドキュメントおよびチュートリアル
  • Windowsオペレーティングシステムの例外処理に関するMicrosoftのドキュメント
  • Go言語のランタイムに関するブログ記事や技術解説
  • Stack OverflowなどのプログラミングQ&Aサイト(特定のレジスタ使用やアセンブリ命令の挙動に関する情報)# [インデックス 12596] ファイルの概要

このコミットは、Go言語のランタイムにおけるWindows/AMD64アーキテクチャ用の例外ハンドラ(sigtramp)のバグ修正に関するものです。具体的には、スレッドローカルストレージ(TLS)からm(machine)構造体へのアクセスに使用されるレジスタが誤っていた点を修正しています。

コミット

commit 16ade99d9d57a84a754bfa8e9a52f5911d9cf5e4
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Mon Mar 12 22:42:55 2012 -0400

    runtime: fix windows/amd64 exception handler
    
    R=golang-dev, rsc
    CC=golang-dev, hectorchu
    https://golang.org/cl/5797077

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

https://github.com/golang/go/commit/16ade99d9d57a84a754bfa8e9a52f5911d9cf5e4

元コミット内容

このコミットは、Goランタイムのsrc/pkg/runtime/sys_windows_amd64.sファイルにおいて、Windows/AMD64環境での例外ハンドラ(sigtramp関数)内のレジスタ使用に関する誤りを修正しています。具体的には、m(machine)構造体へのポインタを取得する際に、誤ってBXレジスタを使用していた箇所を、正しいCXレジスタを使用するように変更しています。

変更の背景

Go言語のランタイムは、プログラムの実行を管理し、ガベージコレクション、スケジューリング、システムコールなど、低レベルの操作を処理します。オペレーティングシステム(OS)やアーキテクチャに依存する部分は、各プラットフォーム固有のアセンブリコードで実装されています。

Windows/AMD64環境において、GoランタイムはOSからのシグナル(例外)を受け取った際に、sigtrampという関数を通じてそのシグナルを処理します。この処理の中で、現在のOSスレッドに対応するGoランタイムのm(machine)構造体へのポインタにアクセスする必要があります。m構造体は、現在のスレッドの状態、スタック情報、スケジューラ関連データなど、スレッド固有の重要な情報を含んでいます。

このコミットが行われる前は、sigtramp内でm構造体へのポインタを取得する際に、誤ったレジスタ(BX)が使用されていました。これは、Goランタイムがスレッドローカルストレージ(TLS)からm構造体へのポインタをCXレジスタにロードするget_tls(CX)という命令の直後に発生していました。結果として、m構造体へのアクセスが不正なメモリ位置を参照し、ランタイムエラーやクラッシュを引き起こす可能性がありました。このコミットは、このレジスタの誤用を修正し、ランタイムの安定性と正確性を向上させることを目的としています。

前提知識の解説

Goランタイムのm(machine)構造体

Go言語のランタイムは、独自の軽量なスレッド(ゴルーチン)を管理するために、OSスレッドの上に抽象化レイヤーを構築しています。この抽象化の中心となるのが、以下の3つの主要な構造体です。

  • G (Goroutine): Go言語の軽量スレッド。スタック、プログラムカウンタ、状態など、ゴルーチン固有の情報を保持します。
  • M (Machine): OSスレッドに対応するGoランタイムの構造体。各Mは1つのOSスレッドにバインドされ、そのOSスレッド上でGを実行します。Mは、OSスレッドのスタック、レジスタ、スレッドローカルストレージ(TLS)へのポインタ、現在のGへのポインタなどを保持します。
  • P (Processor): 論理プロセッサ。MとGの間の仲介役となり、Gの実行をMにディスパッチします。Pは、実行可能なGのキューや、ガベージコレクション関連のデータなどを保持します。

このコミットで問題となっているm構造体は、OSスレッドとGoランタイムの間の重要なリンクであり、シグナルハンドリングのようなOSレベルのイベントを処理する際に、現在のスレッドのGoランタイムの状態にアクセスするために不可欠です。

スレッドローカルストレージ (TLS)

スレッドローカルストレージ(Thread-Local Storage, TLS)は、各スレッドが独自のデータコピーを持つことができるメモリ領域です。グローバル変数とは異なり、TLSに格納されたデータは、そのデータを作成したスレッドからのみアクセス可能です。Goランタイムでは、m構造体へのポインタなど、スレッド固有の重要な情報をTLSに格納して、高速にアクセスできるようにしています。

x86-64アーキテクチャのレジスタ

x86-64(またはAMD64)は、64ビットの命令セットアーキテクチャです。CPUには、データを一時的に保持するための汎用レジスタが多数あります。このコミットに関連するレジスタは以下の通りです。

  • AX (Accumulator Register): 汎用レジスタの一つで、通常、算術演算の結果や関数の戻り値を保持するために使用されます。
  • BX (Base Register): 汎用レジスタの一つで、通常、メモリのアドレス計算のベースアドレスとして使用されます。
  • CX (Count Register): 汎用レジスタの一つで、ループカウンタや、特定の命令(例: シフト命令)の繰り返し回数を指定するために使用されます。また、関数呼び出し規約によっては、引数を渡すためにも使用されます。

Goランタイムのアセンブリコードでは、これらのレジスタが特定の目的のために使用されます。特に、get_tls(CX)命令は、TLSから取得したm構造体へのポインタをCXレジスタに格納することを意図しています。

Windowsにおける例外ハンドリングとsigtramp

Windowsオペレーティングシステムでは、プログラムの実行中に発生するエラーや予期せぬイベント(例: ゼロ除算、無効なメモリアクセス)は「例外」として扱われます。OSは、例外が発生すると、その例外を処理するために登録された例外ハンドラを呼び出します。

Goランタイムは、Windowsの例外処理メカニズムと連携し、Goプログラム内で発生した例外を適切に処理します。sigtrampは、GoランタイムがOSからのシグナル(例外)を受け取った際に、Goのシグナルハンドラを呼び出すための橋渡しをするアセンブリ関数です。この関数は、OSが提供する例外コンテキストから必要な情報を抽出し、Goランタイムの内部状態を適切に設定してから、Goのコードで書かれたシグナルハンドラに制御を渡します。

技術的詳細

このコミットの技術的詳細は、GoランタイムがWindows/AMD64環境でどのようにスレッドローカルストレージとm構造体を扱っているか、そしてアセンブリコードレベルでのレジスタの役割に集約されます。

src/pkg/runtime/sys_windows_amd64.sファイルは、GoランタイムのWindows/AMD64アーキテクチャ固有のアセンブリコードを含んでいます。このファイルには、runtime·sigtrampという関数が定義されています。この関数は、WindowsがGoプログラムに例外を通知する際に呼び出されるエントリポイントです。

sigtramp関数の目的の一つは、現在のOSスレッドに対応するGoランタイムのm構造体へのポインタを取得し、そのm構造体が有効であるか(つまり、nilではないか)を確認することです。この確認は、不正な状態でのシグナル処理を防ぐために重要です。

問題のコードは以下の部分です。

	get_tls(CX)

	// check that m exists
	MOVQ	m(BX), AX
	CMPQ	AX, $0
	JNE	2(PC)
	CALL	runtime·badsignal(SB)
  1. get_tls(CX): この命令は、スレッドローカルストレージから現在のm構造体へのポインタを取得し、それをCXレジスタに格納します。これは、GoランタイムがWindows/AMD64上でTLSを管理する方法の一部です。
  2. MOVQ m(BX), AX: この命令は、BXレジスタが指すアドレスからm構造体の内容をAXレジスタに移動しようとしています。しかし、直前のget_tls(CX)命令によってm構造体へのポインタはCXレジスタに格納されています。したがって、BXレジスタを使用することは誤りであり、m構造体への不正なメモリアクセスを引き起こす可能性があります。
  3. CMPQ AX, $0: AXレジスタの内容(m構造体へのポインタ、または不正な値)が0nil)であるかを比較します。
  4. JNE 2(PC): AXnilでなければ、次の命令をスキップします。
  5. CALL runtime·badsignal(SB): AXnilであれば、runtime·badsignal関数を呼び出し、不正なシグナル処理を報告します。

このバグは、get_tls(CX)CXレジスタに正しいポインタを置いているにもかかわらず、その後のMOVQ命令が誤ってBXレジスタを参照していたために発生していました。これにより、m構造体の存在チェックが正しく機能せず、ランタイムの不安定性につながっていました。

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

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

--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -137,7 +137,7 @@ TEXT runtime·sigtramp(SB),7,$0
  	get_tls(CX)
 
  	// check that m exists
-	MOVQ	m(BX), AX
+	MOVQ	m(CX), AX
  	CMPQ	AX, $0
  	JNE	2(PC)
  	CALL	runtime·badsignal(SB)

コアとなるコードの解説

変更された行は以下の通りです。

- MOVQ m(BX), AX + MOVQ m(CX), AX

  • MOVQ: x86-64アセンブリ命令で、64ビットの値を移動(Move Quadword)します。
  • m(BX) / m(CX): これは、Goのアセンブリ構文で、m構造体へのポインタが格納されているレジスタ(BXまたはCX)を間接参照し、そのアドレスにあるm構造体の内容にアクセスすることを意味します。
  • AX: m構造体の内容が移動される先のレジスタです。

修正前は、m構造体へのポインタがCXレジスタに格納されているにもかかわらず、MOVQ m(BX), AXという命令が使用されていました。これは、BXレジスタが指すアドレスからデータを読み込もうとするため、m構造体の正しいアドレスではない場所からデータを読み込むことになり、結果としてAXレジスタには無効な値がロードされる可能性がありました。

修正後は、MOVQ m(CX), AXに変更されました。これにより、get_tls(CX)によってCXレジスタに格納された正しいm構造体へのポインタが使用され、m構造体の内容が正確にAXレジスタにロードされるようになります。この修正により、m構造体の存在チェックが正しく機能し、Windows/AMD64環境での例外ハンドラの堅牢性が向上しました。

この変更は、Goランタイムの低レベルな部分における正確なレジスタ使用の重要性を示しています。アセンブリコードレベルでのわずかな誤りでも、ランタイムの安定性に大きな影響を与える可能性があります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/runtime/ディレクトリ)
  • Go言語のIssueトラッカーおよびコードレビューシステム(golang.org/cl/5797077
  • x86-64アセンブリ言語に関する一般的なドキュメントおよびチュートリアル
  • Windowsオペレーティングシステムの例外処理に関するMicrosoftのドキュメント
  • Go言語のランタイムに関するブログ記事や技術解説
  • Stack OverflowなどのプログラミングQ&Aサイト(特定のレジスタ使用やアセンブリ命令の挙動に関する情報)