[インデックス 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
: スタックポインタSP
にN
を加算します。これにより、スタック上の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
を調整しています。
SUBQ $56, SP
: 関数が実行される直前に、スタックポインタを56バイト分減算し、必要なスタック領域を手動で確保します。これにより、引数を格納するためのスペースが確保されます。MOVQ CX, 0(SP)
およびMOVQ R8, 8(SP)
: 確保したスタック領域に、例外レコード(CX
)とコンテキスト(R8
)の値をコピーします。これらは、Goランタイムのより高レベルなシグナルハンドラに渡される引数です。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
コアとなるコードの解説
-
TEXT runtime·sigtramp(SB),7,$56
からTEXT runtime·sigtramp(SB),7,$0
への変更:- これは
runtime·sigtramp
関数の定義行です。 - 元の
$56
は、この関数が56バイトのスタックフレームを必要とするとリンカに伝えていました。 - 変更後の
$0
は、この関数がリンカに対して「0バイトのスタックフレーム」を使用すると宣言します。これは、リンカがこの特殊なシグナルハンドラのスタック使用量を誤って解釈するのを防ぐためのワークアラウンドです。
- これは
-
SUBQ $56, SP
の追加:- この命令は、
runtime·sigtramp
関数が実際に必要とする56バイトのスタック領域を、関数が実行される直前に手動で確保します。 - この操作は、リンカのスタックフレーム解析から「隠す」ために行われます。リンカは
$0
の宣言を見るため、この手動のスタック調整を関数のローカルスタックフレームの一部とは見なしません。
- この命令は、
-
ADDQ $56, SP
の追加:- この命令は、
runtime·sigtramp
関数が終了する直前に、手動で確保した56バイトのスタック領域を解放します。 - これにより、関数が呼び出し元に戻る際にスタックポインタが正しい位置に戻り、スタックの整合性が保たれます。
- この命令は、
これらの変更により、runtime·sigtramp
関数は、リンカのスタックフレーム解析の制約を受けずに、必要なスタック操作を正確に実行できるようになります。これは、GoランタイムがWindows AMD64環境でシグナルや例外を安定して処理するために不可欠な修正です。
関連リンク
- Go Change List: https://golang.org/cl/5754091
参考にした情報源リンク
- Go言語のランタイムに関する公式ドキュメント (Goのソースコード、特に
src/runtime
ディレクトリ内のアセンブリファイルやC/Goファイル) - x86-64アセンブリ言語の命令セットリファレンス
- Windowsの例外処理メカニズムに関するドキュメント
- Goリンカ(
cmd/link
)の動作に関する情報 (Goのソースコードや関連する設計ドキュメント) - 一般的なスタックフレーム管理と関数呼び出し規約に関するコンピュータサイエンスの知識