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

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

このコミットは、Go言語のランタイムにおけるWindows AMD64アーキテクチャ向けのシステムコールおよび例外処理に関連するアセンブリコードファイル src/pkg/runtime/sys_windows_amd64.s に加えられた変更を扱っています。このファイルは、GoプログラムがWindows 64ビット環境で動作するために必要な低レベルの操作、特にシグナルハンドリングや例外処理の「トランポリン」関数を定義しています。

コミット

  • コミットハッシュ: 2d3cc97c9c7ced9d13d99611c3f3a69b4c81fbba
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2012年3月12日 月曜日 16:48:16 -0400
  • コミットメッセージ:
    runtime: fix windows/amd64
    
    Maybe.
    
    TBR=bradfitz
    CC=golang-dev
    https://golang.org/cl/5754091
    

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

https://github.com/golang/go/commit/2d3cc97c9c7ced9d13d99611c3f3a69b4c81fbba

元コミット内容

runtime: fix windows/amd64

Maybe.

TBR=bradfitz
CC=golang-dev
https://golang.org/cl/5754091

変更の背景

このコミットは、GoランタイムがWindows AMD64環境で動作する際の潜在的な問題を修正することを目的としています。コミットメッセージの「Maybe.」という言葉は、この修正が試行的なものであり、問題の根本原因に対する確実な解決策ではない可能性を示唆しています。

変更の核心は、runtime·sigtramp という関数、すなわちシグナル/例外ハンドラに関連するスタックフレームの管理にあります。Goのリンカ(6l)が、シグナルハンドラのような特殊なコンテキストで実行される関数のスタック使用量を正確に理解できない、または適切に処理できないという問題があったと考えられます。シグナルハンドラは、オペレーティングシステムによって提供される、通常の関数呼び出しとは異なる「本質的に無制限のスタック」上で実行されることが多いため、リンカの通常のスタックフレーム解析が適用できない場合があります。このコミットは、リンカの誤解を避けるためのワークアラウンドとして、スタックフレームの宣言と実際のスタック操作を調整しています。

前提知識の解説

Go Runtime (Goランタイム)

Goランタイムは、Go言語で書かれたプログラムの実行を管理する低レベルのシステムです。これには、ガベージコレクション、ゴルーチンのスケジューリング、チャネル通信、システムコールインターフェース、そしてシグナル/例外処理などが含まれます。Goプログラムは、OSの機能と直接やり取りするために、多くの場合、アセンブリ言語で書かれたランタイムコードを使用します。

アセンブリ言語 (x86-64/AMD64)

アセンブリ言語は、コンピュータのプロセッサが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。x86-64(またはAMD64)は、現代の64ビットプロセッサで使用される命令セットアーキテクチャです。

  • レジスタ: プロセッサ内部の高速な記憶領域。
    • SP (Stack Pointer): 現在のスタックの最上位アドレスを指すレジスタ。
    • CX, R8, AX, BP, SI, DI: 汎用レジスタ。関数呼び出しの引数や戻り値、一時的なデータ格納などに使用されます。
  • スタック: プログラムが一時的なデータを格納するために使用するメモリ領域。関数呼び出しの際に、戻りアドレス、引数、ローカル変数などがスタックにプッシュ(追加)され、関数終了時にポップ(削除)されます。スタックは通常、高アドレスから低アドレスに向かって成長します。
  • スタック操作:
    • SUBQ $N, SP: スタックポインタ SP から N を減算します。これにより、スタック上に N バイトの領域が確保されます(スタックが低アドレスに成長するため)。
    • ADDQ $N, SP: スタックポインタ SPN を加算します。これにより、スタック上の N バイトの領域が解放されます。
    • MOVQ src, dest: src の値を dest に移動します。0(SP)8(SP) は、SP が指すアドレスからのオフセットを示し、スタック上のメモリ位置を指します。
  • TEXT ディレクティブ: Goのアセンブリでは、TEXT symbol(SB), flags, $framesize の形式で関数を定義します。
    • symbol(SB): 関数のシンボル名。SB は静的ベース(Static Base)を示し、グローバルシンボルであることを意味します。
    • flags: 関数の特性を示すフラグ。
    • $framesize: この関数が使用するスタックフレームのサイズ(ローカル変数やレジスタ退避領域など)。

Windowsの例外処理/シグナルハンドリング

Windowsでは、プログラムの実行中に発生するエラーやイベント(例: ゼロ除算、無効なメモリアクセス)を「例外」として扱います。Unix系のシステムにおける「シグナル」に相当します。これらの例外が発生すると、OSは通常、登録された例外ハンドラを呼び出します。sigtramp のような「トランポリン」関数は、OSから例外通知を受け取り、Goランタイムのより高レベルなシグナルハンドラに処理を橋渡しする役割を担います。

Goリンカ (6l)

6l は、Go言語のツールチェーンの一部であるリンカです。コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成します。この過程で、関数間の呼び出し規約の整合性を確認したり、スタックフレームのサイズを計算したりする役割も果たします。リンカがスタックフレームのサイズを誤って解釈すると、スタックオーバーフローや不正なメモリアクセスなどのランタイムエラーにつながる可能性があります。

技術的詳細

このコミットの主要な変更は、runtime·sigtramp 関数の定義と、その内部でのスタックポインタの操作にあります。

元のコードでは、runtime·sigtramp 関数は TEXT runtime·sigtramp(SB),7,$56 と定義されていました。これは、この関数が56バイトのスタックフレームを使用するとリンカに宣言していることを意味します。しかし、シグナルハンドラはOSによって特殊なスタックコンテキストで呼び出されるため、Goのリンカ 6l がこの56バイトのスタックフレームの宣言を誤解し、問題を引き起こす可能性がありました。特に、コメントにある「6lがsigtrampが本質的に無制限のスタック上で実行されることを理解していない」という点が重要です。これは、リンカが通常の関数呼び出しのスタックフレームの制約をシグナルハンドラにも適用しようとすることで、不整合が生じることを示唆しています。

この修正では、TEXT runtime·sigtramp(SB),7,$0 と変更することで、リンカに対してこの関数が「0バイトのスタックフレーム」を使用すると宣言します。これにより、リンカがこの関数のスタック使用量を過度に厳しくチェックしたり、誤った最適化を行ったりするのを防ぎます。

しかし、sigtramp 関数自体は引数をスタックにコピーするために実際にスタック領域を必要とします。このため、リンカへの宣言を $0 に変更した上で、関数内部で明示的にスタックポインタ SP を調整しています。

  1. SUBQ $56, SP: 関数が実行される直前に、スタックポインタを56バイト分減算し、必要なスタック領域を手動で確保します。これにより、引数を格納するためのスペースが確保されます。
  2. MOVQ CX, 0(SP) および MOVQ R8, 8(SP): 確保したスタック領域に、例外レコード(CX)とコンテキスト(R8)の値をコピーします。これらは、Goランタイムのより高レベルなシグナルハンドラに渡される引数です。
  3. ADDQ $56, SP: 関数が終了する直前(sigdone ラベルの直前)に、スタックポインタを56バイト分加算し、手動で確保したスタック領域を解放します。

このアプローチは、リンカのスタックフレーム解析の制約を回避しつつ、関数が必要とするスタック領域を正しく管理するための一般的なテクニックです。これにより、GoランタイムがWindows AMD64環境でシグナル/例外をより安定して処理できるようになります。

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

--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -116,7 +116,7 @@ TEXT runtime·setlasterror(SB),7,$0
 	MOVL	AX, 0x68(CX)
 	RET
 
-TEXT runtime·sigtramp(SB),7,$56
+TEXT runtime·sigtramp(SB),7,$0
 	// CX: exception record
 	// R8: context
 
@@ -125,7 +125,12 @@ TEXT runtime·sigtramp(SB),7,$56
 	MOVL	$1, AX
 	JNZ	sigdone
 
-\t// copy arguments for call to sighandler
+\t// copy arguments for call to sighandler.
+\n+\t// Stack adjustment is here to hide from 6l,
+\t// which doesn't understand that sigtramp
+\t// runs on essentially unlimited stack.
+\tSUBQ	$56, SP
 \tMOVQ	CX, 0(SP)
 \tMOVQ	R8, 8(SP)
 
@@ -151,6 +156,8 @@ TEXT runtime·sigtramp(SB),7,$56
 	MOVQ	32(SP), BP
 	MOVQ	40(SP), SI
 	MOVQ	48(SP), DI
+\tADDQ	$56, SP
+\n sigdone:
 \tRET
 

コアとなるコードの解説

  1. TEXT runtime·sigtramp(SB),7,$56 から TEXT runtime·sigtramp(SB),7,$0 への変更:

    • これは runtime·sigtramp 関数の定義行です。
    • 元の $56 は、この関数が56バイトのスタックフレームを必要とするとリンカに伝えていました。
    • 変更後の $0 は、この関数がリンカに対して「0バイトのスタックフレーム」を使用すると宣言します。これは、リンカがこの特殊なシグナルハンドラのスタック使用量を誤って解釈するのを防ぐためのワークアラウンドです。
  2. SUBQ $56, SP の追加:

    • この命令は、runtime·sigtramp 関数が実際に必要とする56バイトのスタック領域を、関数が実行される直前に手動で確保します。
    • この操作は、リンカのスタックフレーム解析から「隠す」ために行われます。リンカは $0 の宣言を見るため、この手動のスタック調整を関数のローカルスタックフレームの一部とは見なしません。
  3. ADDQ $56, SP の追加:

    • この命令は、runtime·sigtramp 関数が終了する直前に、手動で確保した56バイトのスタック領域を解放します。
    • これにより、関数が呼び出し元に戻る際にスタックポインタが正しい位置に戻り、スタックの整合性が保たれます。

これらの変更により、runtime·sigtramp 関数は、リンカのスタックフレーム解析の制約を受けずに、必要なスタック操作を正確に実行できるようになります。これは、GoランタイムがWindows AMD64環境でシグナルや例外を安定して処理するために不可欠な修正です。

関連リンク

参考にした情報源リンク

  • Go言語のランタイムに関する公式ドキュメント (Goのソースコード、特にsrc/runtimeディレクトリ内のアセンブリファイルやC/Goファイル)
  • x86-64アセンブリ言語の命令セットリファレンス
  • Windowsの例外処理メカニズムに関するドキュメント
  • Goリンカ(cmd/link)の動作に関する情報 (Goのソースコードや関連する設計ドキュメント)
  • 一般的なスタックフレーム管理と関数呼び出し規約に関するコンピュータサイエンスの知識