[インデックス 19474] ファイルの概要
コミット
runtime: fix error check in freebsd/386 i386_set_ldt
このコミットは、GoランタイムのFreeBSD/386アーキテクチャにおけるi386_set_ldt
システムコールのエラーチェックのバグを修正します。具体的には、Linux/386のエラーチェックロジックが誤ってFreeBSD/386に適用されていた問題を解決します。また、sysctl
システムコールに関連するコードで、JCC
命令をJAE
命令に置き換えることで、ファイル全体のコードスタイルの一貫性を向上させています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/19c8f67e25dc25abdca34b1ebbfa6ed8439a7071
元コミット内容
runtime: fix error check in freebsd/386 i386_set_ldt
Update #2675
The code here was using the error check for Linux/386,
not the one for FreeBSD/386. Most of the time it worked.
Thanks to Neel Natu (FreeBSD developer) for finding this.
The s/JCC/JAE/ a few lines later is a no-op but makes the
test match the rest of the file. Why we write JAE instead of JCC
I don't know, but the two are equivalent and the file might
as well be consistent.
LGTM=bradfitz, minux
R=golang-codereviews, bradfitz, minux
CC=golang-codereviews
https://golang.org/cl/99680044
変更の背景
この変更の主な背景は、GoランタイムがFreeBSD/386システム上でi386_set_ldt
システムコールを呼び出す際のエラー処理に誤りがあったことです。元々のコードでは、Linux/386向けに設計されたエラーチェックロジックが使用されており、これはFreeBSDのシステムコールがエラーを返す際の慣習とは異なっていました。コミットメッセージによると、この問題はFreeBSDの開発者であるNeel Natu氏によって発見されました。
このバグは、GoプログラムがFreeBSD/386環境で特定の低レベル操作(LDTの操作)を実行しようとした際に、システムコールがエラーを返してもGoランタイムがそれを正しく認識せず、予期せぬ動作やクラッシュを引き起こす可能性がありました。コミットメッセージで参照されている#2675
は、この問題に関するバグ報告であると推測されます。
また、JCC
命令をJAE
命令に置き換える変更は、機能的な影響はほとんどありませんが、コードベース全体での一貫性を保つためのものです。これは、Goのコードベースが特定のコーディングスタイルやアセンブリ命令の使用慣習に従っていることを示唆しています。
前提知識の解説
1. x86アセンブリ言語と条件分岐命令
x86アセンブリ言語では、プログラムの実行フローを制御するために様々な条件分岐命令(Conditional Jump Instructions)が使用されます。これらの命令は、直前の算術演算や論理演算の結果によってCPUのEFLAGSレジスタに設定されるフラグ(キャリーフラグ (CF)、ゼロフラグ (ZF)、サインフラグ (SF) など)の状態に基づいてジャンプを実行します。
CMP
命令: 2つのオペランドを比較する命令ですが、実際には一方からもう一方を減算し、その結果に基づいてEFLAGSレジスタのフラグを設定します。減算結果は破棄されます。JLS
(Jump if Less or Signed): 符号付き比較において「より小さい」場合にジャンプします。JCC
(Jump if Carry Clear): キャリーフラグ (CF) がクリア(0)の場合にジャンプします。JAE
(Jump if Above or Equal): 符号なし比較において「より大きいか等しい」場合にジャンプします。これはJNB
(Jump if Not Below) やJNC
(Jump if No Carry) と機能的に同等です。CMP
命令の後に使用される場合、CF=0は最初のオペランドが2番目のオペランド以上であることを示します。
このコミットでは、JLS
がJAE
に、そしてJCC
がJAE
に置き換えられています。特にJCC
からJAE
への変更は、JCC
がキャリーフラグの状態のみを見るのに対し、JAE
は「より大きいか等しい」というより意味的な条件を表現するため、コードの意図をより明確にする効果があります。コミットメッセージにあるように、特定のコンテキストではJCC
とJAE
が同じ結果をもたらすことがあります。
2. システムコールとINT 0x80
オペレーティングシステム(OS)のカーネルが提供する機能(ファイル操作、メモリ管理、プロセス管理など)をユーザープログラムから利用するためのインターフェースを「システムコール」と呼びます。x86アーキテクチャでは、ソフトウェア割り込み(Software Interrupt)を利用してシステムコールを呼び出すのが一般的です。
INT 0x80
: 32ビットx86システム(i386)において、LinuxやFreeBSDなどのUnix系OSでシステムコールを呼び出すための一般的なソフトウェア割り込み命令です。この命令が実行されると、CPUはユーザーモードからカーネルモードに切り替わり、OSの割り込みハンドラが制御を引き継ぎます。- システムコール番号: どのシステムコールを呼び出すかは、通常、
EAX
レジスタにシステムコール番号を設定することで指定されます。 - 引数の渡し方: システムコールへの引数の渡し方はOSやアーキテクチャによって異なります。
- FreeBSD (i386): デフォルトでは、引数はスタックにプッシュされます。
- Linux (i386): 引数はレジスタ(
EBX
,ECX
,EDX
など)に設定されます。 この違いが、今回のエラーチェックのバグの根本原因の一つです。
3. i386_set_ldt
システムコール
i386_set_ldt
は、x86アーキテクチャにおけるLocal Descriptor Table (LDT) を操作するためのシステムコールです。LDTは、セグメンテーションメモリ管理モデルにおいて、特定のプロセス(タスク)に固有のメモリセグメントを定義するために使用されるデータ構造です。現代のOSではページングが主流ですが、一部のレガシーなアプリケーションや特定の低レベルな処理ではLDTが使用されることがあります。
4. システムコールにおけるエラー処理
Unix系OSのシステムコールは、通常、成功時には非負の値(例えば、読み書きされたバイト数やファイルディスクリプタなど)を返し、失敗時には-1
を返します。システムコールが失敗した場合、具体的なエラーの原因はグローバル変数errno
に設定されるエラーコード(例: EINVAL
(不正な引数), EACCES
(アクセス拒否))によって示されます。
Linuxでは、システムコールがエラーを返した場合、EAX
レジスタに-4095
から-1
までの範囲の値が設定される慣習があります。このため、AX
が-4095
より小さいかどうかをチェックすることでエラーを検出できます。しかし、FreeBSDではこの慣習が異なるため、Linuxのエラーチェックをそのまま適用すると問題が発生します。
5. sysctl
システムコール
sysctl
はFreeBSDにおけるシステム制御フレームワークであり、カーネルの様々なパラメータやシステムの状態を動的に参照・変更するためのメカニズムを提供します。コマンドラインツールとしても提供されていますが、プログラムから直接カーネルのsysctl
インターフェースを呼び出すことも可能です。sys___sysctl
は、このsysctl
機能を提供するシステムコールであると考えられます。
技術的詳細
このコミットは、src/pkg/runtime/sys_freebsd_386.s
ファイル、すなわちGoランタイムのFreeBSD/386アーキテクチャ向けアセンブリコードに焦点を当てています。
runtime·i386_set_ldt
のエラーチェック修正
変更前のコードでは、runtime·i386_set_ldt
関数内でINT $0x80
システムコールが呼び出された後、返り値が格納されるAX
レジスタに対して以下のエラーチェックが行われていました。
CMPL AX, $0xfffff001 // AXと-4095を比較
JLS 2(PC) // AXが-4095より小さい(符号付き)場合にジャンプ
ここで0xfffff001
は32ビット符号付き整数で-4095
を表します。このCMPL AX, $0xfffff001
とJLS
の組み合わせは、Linux/386システムコールがエラーを返す際の慣習(返り値が-4095
から-1
の範囲にある場合にエラーを示す)に基づいています。しかし、FreeBSD/386ではシステムコールのエラー返り値の慣習が異なるため、このチェックはFreeBSDでは正しく機能しませんでした。
修正後のコードでは、この2行が以下の1行に置き換えられました。
JAE 2(PC) // AXが「より大きいか等しい」(符号なし)場合にジャンプ
この変更は、INT $0x80
システムコールが成功した場合(非負の値を返す場合)に、その後のエラー処理ロジックをスキップすることを意図しています。FreeBSDのシステムコールは成功時に非負の値を返し、失敗時に負の値を返すため、JAE
(Jump if Above or Equal)命令は、AX
が非負である(つまり、エラーではない)場合にジャンプし、エラー処理をスキップするという、FreeBSDのエラー処理慣習に合致したロジックを提供します。これにより、FreeBSD環境でのi386_set_ldt
システムコールのエラーが正しく検出されるようになります。
runtime·sysctl
のJCC
からJAE
への変更
runtime·sysctl
関数内でも同様に、INT $0x80
システムコール呼び出し後の条件分岐命令が変更されています。
変更前:
JCC 3(PC) // キャリーフラグがクリアの場合にジャンプ
変更後:
JAE 3(PC) // AXが「より大きいか等しい」(符号なし)場合にジャンプ
コミットメッセージにあるように、この変更は機能的には「no-op」(何もしない)であるとされています。これは、この特定のコンテキストにおいて、JCC
(Jump if Carry Clear)とJAE
(Jump if Above or Equal)が同じ条件(例えば、直前の操作でキャリーフラグがクリアになった場合、それが「より大きいか等しい」という条件と一致する)でジャンプを実行するためです。この変更の主な目的は、ファイル全体のアセンブリコードにおける条件分岐命令の記述スタイルの一貫性を保つことです。特に、システムコールの成功/失敗判定にはJAE
のような「非負」をチェックする命令がより直感的であるため、コードの可読性向上にも寄与します。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/sys_freebsd_386.s
ファイルにあります。
--- a/src/pkg/runtime/sys_freebsd_386.s
+++ b/src/pkg/runtime/sys_freebsd_386.s
@@ -309,8 +309,7 @@ TEXT runtime·i386_set_ldt(SB),NOSPLIT,$16
MOVL AX, 8(SP)
MOVL $165, AX
INT $0x80
- CMPL AX, $0xfffff001
- JLS 2(PC)
+ JAE 2(PC)
INT $3
RET
@@ -326,7 +325,7 @@ TEXT runtime·sysctl(SB),NOSPLIT,$28
MOVSL
MOVL $202, AX // sys___sysctl
INT $0x80
- JCC 3(PC)
+ JAE 3(PC)
NEGL AX
RET
MOVL $0, AX
コアとなるコードの解説
TEXT runtime·i386_set_ldt(SB),NOSPLIT,$16
セクション
このセクションは、Goランタイムのi386_set_ldt
関数を定義しています。
MOVL AX, 8(SP)
: システムコールから返された値(AX
レジスタに格納されている)をスタック上の特定のオフセットに移動します。MOVL $165, AX
:AX
レジスタにシステムコール番号165
を設定します。これはFreeBSDにおけるi386_set_ldt
システムコールの番号です。INT $0x80
: ソフトウェア割り込み0x80
を発生させ、システムコールを実行します。- 変更点:
- 削除された行:
CMPL AX, $0xfffff001
とJLS 2(PC)
CMPL AX, $0xfffff001
:AX
レジスタの値と即値-4095
(0xfffff001
)を比較します。これはLinuxのシステムコールエラーチェックの慣習です。JLS 2(PC)
: 比較結果に基づいて、AX
が-4095
より小さい(符号付き)場合に、現在の命令ポインタから2バイト先にジャンプします。これはエラーが発生したと判断し、その後のINT $3
(ブレークポイント)命令にジャンプしてデバッグを助けるためのものです。
- 追加された行:
JAE 2(PC)
JAE 2(PC)
:AX
が「より大きいか等しい」(符号なし)場合に、現在の命令ポインタから2バイト先にジャンプします。これは、システムコールが成功し、AX
が非負の値を返した場合に、エラー処理部分(INT $3
)をスキップしてRET
(リターン)命令に直接進むためのものです。これにより、FreeBSDのシステムコールエラー処理の慣習に合致するようになりました。
- 削除された行:
INT $3
: デバッグ用のブレークポイント命令です。システムコールがエラーを返した場合にここに到達します。RET
: 関数からリターンします。
TEXT runtime·sysctl(SB),NOSPLIT,$28
セクション
このセクションは、Goランタイムのsysctl
関数を定義しています。
MOVL $202, AX
:AX
レジスタにシステムコール番号202
を設定します。これはFreeBSDにおけるsys___sysctl
システムコールの番号です。INT $0x80
: ソフトウェア割り込み0x80
を発生させ、システムコールを実行します。- 変更点:
- 削除された行:
JCC 3(PC)
JCC 3(PC)
: キャリーフラグがクリアの場合に、現在の命令ポインタから3バイト先にジャンプします。
- 追加された行:
JAE 3(PC)
JAE 3(PC)
:AX
が「より大きいか等しい」(符号なし)場合に、現在の命令ポインタから3バイト先にジャンプします。 この変更は、機能的には同等ですが、コードの一貫性を保つために行われました。システムコールが成功した場合(非負の値を返す場合)に、その後のエラー処理(NEGL AX
など)をスキップしてリターンパスに進むことを意図しています。
- 削除された行:
NEGL AX
:AX
レジスタの値を符号反転します。これは、システムコールがエラーを返した場合に、負のエラーコードを返すための処理の一部です。RET
: 関数からリターンします。MOVL $0, AX
:AX
レジスタに0
を設定します。これは、システムコールが成功した場合に0
を返すための処理の一部です。
関連リンク
- Goの公式リポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go.dev/cl/99680044 (コミットメッセージに記載されているCLリンク)
参考にした情報源リンク
- x86アセンブリ言語の条件分岐命令に関する情報 (JCC, JAE, JLSなど)
- システムコールと
INT 0x80
に関する情報 (FreeBSD, Linuxの違い) i386_set_ldt
システムコールに関する情報sysctl
システムコールに関する情報 (FreeBSD)- Go issue #2675 (直接的な情報は見つからなかったが、コミットメッセージからの推測に基づく)