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

[インデックス 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番目のオペランド以上であることを示します。

このコミットでは、JLSJAEに、そしてJCCJAEに置き換えられています。特にJCCからJAEへの変更は、JCCがキャリーフラグの状態のみを見るのに対し、JAEは「より大きいか等しい」というより意味的な条件を表現するため、コードの意図をより明確にする効果があります。コミットメッセージにあるように、特定のコンテキストではJCCJAEが同じ結果をもたらすことがあります。

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, $0xfffff001JLSの組み合わせは、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·sysctlJCCから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, $0xfffff001JLS 2(PC)
      • CMPL AX, $0xfffff001: AXレジスタの値と即値-40950xfffff001)を比較します。これは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を返すための処理の一部です。

関連リンク

参考にした情報源リンク