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

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

このコミットは、Go言語のsyscallパッケージにおけるPlan9およびamd64アーキテクチャ向けのアセンブリコードの修正に関するものです。具体的には、システムコールからのエラーハンドリングのロジックが改善され、特に32ビットと64ビットの-1表現の違いに起因する問題と、runtime.Syscall6におけるエラー文字列処理の算術的な誤りが修正されています。

コミット

commit b6e322dcf50675ad9ae3f9e587ca9b738004d035
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date:   Mon Feb 25 22:40:14 2013 +0100

    syscall: Plan9, amd64: fix syscall error handling in assembly
    
    Syscalls return `-1' on error and the representation is always
    32-bits. The `$-1' literal in 64-bit assembly is always the
    64-bit representation. So this change makes sure that we
    always do a 32-bit comparison when checking for error.
    Also makes sure that in the error case, we return a 64-bit
    `-1' from runtime.seek.
    
    Fixes the arithmetic for handling the error-string in
    runtime.Syscall6.
    
    R=golang-dev, rminnich, rsc, ality, minux.ma
    CC=golang-dev
    https://golang.org/cl/7399052

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

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

元コミット内容

このコミットは、Go言語のsyscallパッケージ内のsrc/pkg/syscall/asm_plan9_amd64.sファイルに対して行われた修正です。主な内容は以下の通りです。

  1. システムコールがエラー時に返す-1の値の比較方法を修正。システムコールは32ビット表現で-1を返すが、64ビットアセンブリでは$-1リテラルが64ビット表現となるため、比較を32ビットで行うように変更。
  2. エラー発生時にruntime.seekが64ビットの-1を返すように修正。
  3. runtime.Syscall6におけるエラー文字列処理の算術的な誤りを修正。

変更の背景

このコミットの背景には、Go言語がPlan9オペレーティングシステム上でamd64アーキテクチャを使用する際の、システムコールエラーハンドリングにおける微妙な問題がありました。

システムコールは、処理が成功した場合は結果を返し、失敗した場合は特定のエラー値(多くのシステムで-1)を返します。Plan9のシステムコールは、エラー時に32ビットの-1を返すという規約がありました。しかし、Goのamd64アセンブリコード内でこの-1を比較する際、アセンブラが$-1というリテラルを64ビットの-1として解釈してしまうという問題が発生していました。

これにより、システムコールが実際にエラーを返しているにもかかわらず、アセンブリコードがそれを正しくエラーとして認識できない可能性がありました。例えば、システムコールが32ビットの-1を返しても、64ビットの-1と比較すると一致しないため、エラーパスに進まないといった状況が考えられます。これは、予期せぬ動作やプログラムのクラッシュにつながる可能性のある、深刻なバグでした。

また、runtime.Syscall6という特定のシステムコールラッパーにおいて、エラー発生時にエラー文字列を処理するための算術ロジックに誤りがあったことも、このコミットで対処されています。これは、エラーメッセージの取得やGoのerrorインターフェースへの変換が正しく行われない原因となっていました。

これらの問題は、GoプログラムがPlan9上で安定して動作するために修正が必要な、低レベルかつ重要なバグでした。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。

  1. システムコール (Syscall): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御など、OSのカーネル機能にアクセスするために使用されます。Go言語では、syscallパッケージを通じてこれらの低レベルな操作が抽象化されています。

  2. アセンブリ言語 (Assembly Language): CPUが直接実行できる機械語に非常に近い低レベルなプログラミング言語です。特定のCPUアーキテクチャ(例: amd64)に特化しており、レジスタ操作、メモリアクセス、ジャンプ命令などを直接記述します。Go言語のランタイムや標準ライブラリの一部は、パフォーマンスやOSとの連携のためにアセンブリ言語で記述されています。Goのアセンブリは、Plan9アセンブリという独自の構文を使用しています。

  3. Plan9オペレーティングシステム: ベル研究所で開発された分散型オペレーティングシステムです。Unixの思想をさらに推し進め、すべてのリソースをファイルとして扱うという特徴があります。Go言語は、Plan9の設計思想に影響を受けており、Plan9への移植も行われています。

  4. amd64アーキテクチャ: IntelとAMDが開発した64ビットのCPUアーキテクチャです。x86アーキテクチャの64ビット拡張版であり、より大きなメモリ空間を扱え、より多くの汎用レジスタを利用できます。

  5. レジスタ (Registers): CPU内部にある高速な記憶領域です。演算の対象となるデータや、命令の実行に必要な情報を一時的に保持します。amd64アーキテクチャでは、AX, BX, CX, DX, SP, BP, SI, DI, R8R15などの汎用レジスタがあります。

    • AXレジスタ: 多くのシステムコールで戻り値を格納するために使用されます。
  6. データ表現 (Data Representation): コンピュータ内部で数値がどのように表現されるかです。特に、符号付き整数(例: -1)は、2の補数表現で表されることが一般的です。

    • 32ビット表現: 32ビット(4バイト)で数値を表現します。-10xFFFFFFFFとなります。
    • 64ビット表現: 64ビット(8バイト)で数値を表現します。-10xFFFFFFFFFFFFFFFFとなります。 同じ-1という値でも、ビット幅が異なるとそのバイナリ表現は異なります。
  7. アセンブリ命令:

    • MOVQ: 64ビットの値を移動する命令(Move Quadword)。
    • CMPQ: 64ビットの値を比較する命令(Compare Quadword)。
    • CMPL: 32ビットの値を比較する命令(Compare Longword)。
    • JNE: 比較結果が等しくない場合にジャンプする命令(Jump Not Equal)。
    • SYSCALL: システムコールを実行する命令。
    • TEXT: Goアセンブリにおける関数の開始を示すディレクティブ。
    • SB (Static Base): グローバルシンボルや外部シンボルを参照するための擬似レジスタ。
    • SP (Stack Pointer): スタックの現在位置を示すレジスタ。関数呼び出し時の引数やローカル変数はスタックに配置されます。+オフセット(SP)はスタック上の特定のアドレスを参照します。
    • LEAQ: アドレスをレジスタにロードする命令(Load Effective Address Quadword)。
    • CLD: 方向フラグをクリアする命令。文字列操作命令の方向を前方(アドレス増加方向)に設定します。
    • MOVSL: 32ビットの文字列を移動する命令(Move String Longword)。
    • MOVSQ: 64ビットの文字列を移動する命令(Move String Quadword)。

技術的詳細

このコミットの技術的な核心は、amd64アーキテクチャにおける32ビットと64ビットの数値表現の違い、特に-1というエラー値の扱いにあります。

問題点: Plan9のシステムコールは、エラーを示すために32ビットの-1(バイナリでは0xFFFFFFFF)を返します。この戻り値は通常、AXレジスタに格納されます。 しかし、Goのアセンブリコード内でこのAXレジスタの値と-1を比較する際、CMPQ AX, $-1という命令が使われていました。ここで問題となるのは、アセンブラが$-1というリテラルを64ビットの-1(バイナリでは0xFFFFFFFFFFFFFFFF)として解釈してしまう点です。

AXレジスタは64ビットレジスタですが、システムコールが返す32ビットの-1は、AXレジスタの上位32ビットがゼロクリアされているか、あるいは符号拡張されているかによって、その値が異なります。もし上位32ビットがゼロクリアされている場合、AX0x00000000FFFFFFFFとなり、これは64ビットの-1である0xFFFFFFFFFFFFFFFFとは一致しません。結果として、システムコールがエラーを返しているにもかかわらず、CMPQ命令が「等しくない」と判断し、エラーハンドリングのパスが実行されないという論理的な誤りが発生していました。

解決策: この問題を解決するために、比較命令をCMPQ(64ビット比較)からCMPL(32ビット比較)に変更しました。 CMPL AX, $-1とすることで、AXレジスタの下位32ビットのみが$-1(32ビット表現の0xFFFFFFFF)と比較されます。これにより、システムコールが返す32ビットの-1が正しくエラーとして認識されるようになります。

また、runtime.seek関数では、エラー時にGoのランタイムに64ビットの-1を返す必要がありました。以前はシステムコールからの戻り値AXをそのまま使用していましたが、これも32ビットの-1が64ビットのコンテキストで正しく扱われない可能性があったため、明示的にMOVQ $-1, newoffset+40(SP)として64ビットの-1を返すように修正されました。

さらに、runtime.Syscall6におけるエラー文字列の処理も修正されました。MOVSL命令(32ビット文字列移動)がMOVSQ命令(64ビット文字列移動)に変更されたことで、エラー文字列のコピーが64ビット単位で行われるようになり、アドレス計算やデータ転送の正確性が向上しました。これは、特にamd64環境でのポインタや文字列のサイズが64ビットであることを考慮した修正です。

これらの変更により、GoのPlan9/amd64環境におけるシステムコールエラーハンドリングの堅牢性が大幅に向上しました。

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

変更はsrc/pkg/syscall/asm_plan9_amd64.sファイルに集中しています。

--- a/src/pkg/syscall/asm_plan9_amd64.s
+++ b/src/pkg/syscall/asm_plan9_amd64.s
@@ -28,7 +28,7 @@ TEXT	·Syscall(SB),7,$0
 	SYSCALL
 	MOVQ	AX, r1+40(SP)
 	MOVQ	$0, r2+48(SP)
-	CMPQ	AX, $-1
+	CMPL	AX, $-1
 	JNE	ok3
 
 	SUBQ	$16, SP
@@ -67,7 +67,7 @@ TEXT	·Syscall6(SB),7,$0
 	SYSCALL
 	MOVQ	AX, r1+64(SP)
 	MOVQ	$0, r2+72(SP)
-	CMPQ	AX, $-1
+	CMPL	AX, $-1
 	JNE	ok4
 	
 	SUBQ	$16, SP
@@ -83,8 +83,8 @@ copyresult4:
 	LEAQ	err+80(SP), DI
 
 	CLD
-	MOVSL
-	MOVSL
+	MOVSQ
+	MOVSQ
 
 	CALL	runtime·exitsyscall(SB)
 	RET
@@ -135,9 +135,9 @@ TEXT ·seek(SB),7,$0
 	MOVQ	$SYS_SEEK, BP	// syscall entry
 	SYSCALL
 	
-	CMPQ	AX, $-1
+	CMPL	AX, $-1
 	JNE	ok6
-	MOVQ	AX, 40(SP)\t// newoffset
+	MOVQ	$-1, newoffset+40(SP)
 	
 	SUBQ	$16, SP
 	CALL	syscall·errstr(SB)

コアとなるコードの解説

このコミットにおける主要なコード変更は、以下の3つのセクションに分けられます。

  1. TEXT ·Syscall(SB) および TEXT ·Syscall6(SB) 内の比較命令の変更:

    -	CMPQ	AX, $-1
    +	CMPL	AX, $-1
    
    • 変更前 (CMPQ AX, $-1): CMPQは64ビットの比較命令です。AXレジスタの64ビット値と、アセンブラが64ビットとして解釈した$-10xFFFFFFFFFFFFFFFF)を比較していました。システムコールが返す32ビットの-10x00000000FFFFFFFF)は、この64ビットの-1とは異なるため、エラーが正しく検出されない可能性がありました。
    • 変更後 (CMPL AX, $-1): CMPLは32ビットの比較命令です。これにより、AXレジスタの下位32ビットのみが、32ビットとして解釈された$-10xFFFFFFFF)と比較されます。システムコールが返す32ビットの-1は、この比較で正しく一致するため、エラーパス(JNEの逆、つまり等しい場合にエラー処理に進む)が適切に実行されるようになります。これは、SyscallSyscall6の両方で同様に修正されています。
  2. copyresult4: ラベル下の文字列移動命令の変更:

    -	MOVSL
    -	MOVSL
    +	MOVSQ
    +	MOVSQ
    
    • このセクションは、runtime.Syscall6がエラーを処理する際に、エラー文字列をコピーする部分です。
    • 変更前 (MOVSL): MOVSLは32ビット(Longword)単位でデータを移動する命令です。amd64アーキテクチャではポインタやサイズが64ビットであるため、32ビット単位での移動は、特にアドレス計算や文字列の終端処理において問題を引き起こす可能性がありました。
    • 変更後 (MOVSQ): MOVSQは64ビット(Quadword)単位でデータを移動する命令です。これにより、amd64環境のデータ幅に合わせた効率的かつ正確な文字列コピーが可能となり、runtime.Syscall6におけるエラー文字列処理の算術的な誤りが修正されました。
  3. TEXT ·seek(SB) 内の戻り値の変更:

    -	MOVQ	AX, 40(SP)\t// newoffset
    +	MOVQ	$-1, newoffset+40(SP)
    
    • この部分は、runtime.seek関数がシステムコールを実行し、その結果を返すロジックです。
    • 変更前 (MOVQ AX, 40(SP)): システムコールからの戻り値であるAXレジスタの値をそのままスタック上のnewoffsetに格納していました。前述の通り、AXが32ビットの-1を含んでいる場合、これが64ビットのコンテキストで正しく-1として解釈されない可能性がありました。
    • 変更後 (MOVQ $-1, newoffset+40(SP)): エラーが発生した場合(CMPL AX, $-1が真の場合)、明示的に64ビットの-1リテラルをスタック上のnewoffsetに格納するように変更されました。これにより、runtime.seekがエラー時に常に正しい64ビットの-1を返すことが保証されます。

これらの変更は、Goの低レベルなシステムコールインターフェースにおける、数値表現とアセンブリ命令の厳密な整合性を確保し、エラーハンドリングの信頼性を向上させるために不可欠でした。

関連リンク

参考にした情報源リンク