[インデックス 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
の値の比較方法を修正。システムコールは32ビット表現で-1
を返すが、64ビットアセンブリでは$-1
リテラルが64ビット表現となるため、比較を32ビットで行うように変更。 - エラー発生時に
runtime.seek
が64ビットの-1
を返すように修正。 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上で安定して動作するために修正が必要な、低レベルかつ重要なバグでした。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。
-
システムコール (Syscall): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御など、OSのカーネル機能にアクセスするために使用されます。Go言語では、
syscall
パッケージを通じてこれらの低レベルな操作が抽象化されています。 -
アセンブリ言語 (Assembly Language): CPUが直接実行できる機械語に非常に近い低レベルなプログラミング言語です。特定のCPUアーキテクチャ(例: amd64)に特化しており、レジスタ操作、メモリアクセス、ジャンプ命令などを直接記述します。Go言語のランタイムや標準ライブラリの一部は、パフォーマンスやOSとの連携のためにアセンブリ言語で記述されています。Goのアセンブリは、Plan9アセンブリという独自の構文を使用しています。
-
Plan9オペレーティングシステム: ベル研究所で開発された分散型オペレーティングシステムです。Unixの思想をさらに推し進め、すべてのリソースをファイルとして扱うという特徴があります。Go言語は、Plan9の設計思想に影響を受けており、Plan9への移植も行われています。
-
amd64アーキテクチャ: IntelとAMDが開発した64ビットのCPUアーキテクチャです。x86アーキテクチャの64ビット拡張版であり、より大きなメモリ空間を扱え、より多くの汎用レジスタを利用できます。
-
レジスタ (Registers): CPU内部にある高速な記憶領域です。演算の対象となるデータや、命令の実行に必要な情報を一時的に保持します。amd64アーキテクチャでは、
AX
,BX
,CX
,DX
,SP
,BP
,SI
,DI
,R8
〜R15
などの汎用レジスタがあります。AX
レジスタ: 多くのシステムコールで戻り値を格納するために使用されます。
-
データ表現 (Data Representation): コンピュータ内部で数値がどのように表現されるかです。特に、符号付き整数(例:
-1
)は、2の補数表現で表されることが一般的です。- 32ビット表現: 32ビット(4バイト)で数値を表現します。
-1
は0xFFFFFFFF
となります。 - 64ビット表現: 64ビット(8バイト)で数値を表現します。
-1
は0xFFFFFFFFFFFFFFFF
となります。 同じ-1
という値でも、ビット幅が異なるとそのバイナリ表現は異なります。
- 32ビット表現: 32ビット(4バイト)で数値を表現します。
-
アセンブリ命令:
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ビットがゼロクリアされている場合、AX
は0x00000000FFFFFFFF
となり、これは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つのセクションに分けられます。
-
TEXT ·Syscall(SB)
およびTEXT ·Syscall6(SB)
内の比較命令の変更:- CMPQ AX, $-1 + CMPL AX, $-1
- 変更前 (
CMPQ AX, $-1
):CMPQ
は64ビットの比較命令です。AX
レジスタの64ビット値と、アセンブラが64ビットとして解釈した$-1
(0xFFFFFFFFFFFFFFFF
)を比較していました。システムコールが返す32ビットの-1
(0x00000000FFFFFFFF
)は、この64ビットの-1
とは異なるため、エラーが正しく検出されない可能性がありました。 - 変更後 (
CMPL AX, $-1
):CMPL
は32ビットの比較命令です。これにより、AX
レジスタの下位32ビットのみが、32ビットとして解釈された$-1
(0xFFFFFFFF
)と比較されます。システムコールが返す32ビットの-1
は、この比較で正しく一致するため、エラーパス(JNE
の逆、つまり等しい場合にエラー処理に進む)が適切に実行されるようになります。これは、Syscall
とSyscall6
の両方で同様に修正されています。
- 変更前 (
-
copyresult4:
ラベル下の文字列移動命令の変更:- MOVSL - MOVSL + MOVSQ + MOVSQ
- このセクションは、
runtime.Syscall6
がエラーを処理する際に、エラー文字列をコピーする部分です。 - 変更前 (
MOVSL
):MOVSL
は32ビット(Longword)単位でデータを移動する命令です。amd64アーキテクチャではポインタやサイズが64ビットであるため、32ビット単位での移動は、特にアドレス計算や文字列の終端処理において問題を引き起こす可能性がありました。 - 変更後 (
MOVSQ
):MOVSQ
は64ビット(Quadword)単位でデータを移動する命令です。これにより、amd64環境のデータ幅に合わせた効率的かつ正確な文字列コピーが可能となり、runtime.Syscall6
におけるエラー文字列処理の算術的な誤りが修正されました。
- このセクションは、
-
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の低レベルなシステムコールインターフェースにおける、数値表現とアセンブリ命令の厳密な整合性を確保し、エラーハンドリングの信頼性を向上させるために不可欠でした。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
syscall
パッケージ: https://pkg.go.dev/syscall - Go言語のPlan9ポートに関する情報: https://go.dev/doc/go1.0#plan9 (Go 1.0リリースノートの一部)
- Go言語のアセンブリに関するドキュメント: https://go.dev/doc/asm
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/7399052
は、このGerritの変更リストへのリンクです。) - Plan9オペレーティングシステムに関する情報: https://9p.io/plan9/
- amd64アセンブリ命令セットリファレンス (一般的な情報源):
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals
- AMD64 Architecture Programmer’s Manuals
- 2の補数表現に関する一般的な情報源 (例: Wikipediaなど)
- GoのPlan9アセンブリに関する議論や記事 (Web検索結果より)
- https://ycombinator.com/item?id=2987609 (GoのPlan9アセンブリに関する議論)
- https://symbolcrash.com/2012/07/go-assembly-language/ (Goアセンブリ言語の紹介)