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

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

このコミットは、GoランタイムにおけるFreeBSD/amd64環境でのmadviseシステムコールのエラーハンドリングの修正に関するものです。具体的には、システムコールからの戻り値のチェック方法を改善し、キャリーフラグの状態を正しく利用することで、エラー検出のロジックを修正しています。

コミット

commit 1fbe3090d8382176d1f5075cecd2fa578fe363a4
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sun Nov 25 18:46:41 2012 +0900

    runtime: fix madvise for freebsd/amd64
    
    Make use of carry clear condition instead of low or same.
    
    R=minux.ma, jsing, dave
    CC=golang-dev
    https://golang.org/cl/6844080

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

https://github.com/golang/go/commit/1fbe3090d8382176d1f5075cecd2fa578fe363a4

元コミット内容

runtime: fix madvise for freebsd/amd64

Make use of carry clear condition instead of low or same.

R=minux.ma, jsing, dave
CC=golang-dev
https://golang.org/cl/6844080

変更の背景

GoランタイムがFreeBSD/amd64環境でmadviseシステムコールを呼び出す際、その戻り値のエラーチェックに誤りがありました。FreeBSDを含む多くのUnix系システムでは、システムコールがエラーを返した場合、通常は戻り値として-1を設定し、同時にCPUのEFLAGSレジスタ内のキャリーフラグ(CF)をセットします。成功した場合は戻り値が0(またはその他の成功を示す値)となり、キャリーフラグはクリアされます。

しかし、このコミット以前のGoランタイムの実装では、madviseシステムコールのエラーチェックにCMPQ AX, $0xfffffffffffff001AXレジスタの値と特定のマジックナンバーを比較)とJLS(Jump if Less or Same)命令を使用していました。このロジックは、システムコールのエラーを示すキャリーフラグの状態を直接確認するのではなく、戻り値の数値的な比較に依存していました。結果として、特定のエラーケースで誤ってエラーではないと判断したり、逆にエラーではないのにエラーと判断したりする可能性がありました。

この修正は、madviseシステムコールのエラーハンドリングがFreeBSD/amd64のABI(Application Binary Interface)に準拠し、キャリーフラグを正しく利用するようにするために行われました。

前提知識の解説

madviseシステムコール

madviseは、プロセスがメモリ領域の使用方法についてカーネルに助言(advice)を与えるためのシステムコールです。例えば、特定のメモリ領域が今後アクセスされないことをカーネルに伝え、その領域を解放してもらうことで、システム全体のメモリ効率を向上させることができます。主な用途としては、メモリの解放(MADV_DONTNEED)、アクセスパターンのヒント(MADV_SEQUENTIAL, MADV_RANDOM)などがあります。

FreeBSDにおけるシステムコールの戻り値とエラーハンドリング

FreeBSDを含む多くのUnix系OSのx86-64アーキテクチャでは、システムコールが成功した場合、通常はRAXレジスタ(またはAXレジスタ)に0または成功を示す値を返します。エラーが発生した場合、RAXレジスタには-1が設定され、同時にEFLAGSレジスタのキャリーフラグ(CF)がセットされます。エラーの種類は、errno変数(Goランタイムでは通常、RAXレジスタにエラーコードが直接返されるか、errnoに相当する値が設定される)で示されます。したがって、システムコールのエラーを正確に検出するためには、RAXレジスタの値だけでなく、キャリーフラグの状態を確認することが重要です。

x86-64アセンブリにおける条件分岐命令

  • SYSCALL: x86-64アーキテクチャでシステムコールを呼び出すための命令です。システムコール番号はRAXレジスタに、引数はRDI, RSI, RDX, R10, R8, R9レジスタに渡されます。システムコール実行後、戻り値はRAXレジスタに格納され、エラーが発生した場合はキャリーフラグがセットされます。
  • CMPQ (Compare Quadword): 2つの64ビット値を比較し、その結果に基づいてEFLAGSレジスタのフラグ(ゼロフラグ、キャリーフラグなど)を設定します。この命令自体はレジスタの内容を変更しません。
  • JLS (Jump if Less or Same): 符号なし比較において「より小さいか等しい」場合にジャンプする条件分岐命令です。これはJBE (Jump if Below or Equal) の別名であり、CF=1(キャリーフラグがセットされている)またはZF=1(ゼロフラグがセットされている)の場合にジャンプします。システムコールのエラーチェックにおいては、キャリーフラグがセットされているかどうかを直接確認する目的には適していません。
  • JCC (Jump if Carry Clear): キャリーフラグ(CF)がクリアされている(CF=0)場合にジャンプする条件分岐命令です。システムコールが成功した場合、キャリーフラグはクリアされるため、この命令はシステムコールの成功をチェックするのに適しています。

技術的詳細

元のコードでは、madviseシステムコールが返した値(AXレジスタに格納される)を0xfffffffffffff001と比較し、その結果に基づいてJLS命令で分岐していました。0xfffffffffffff001-4095に相当する値であり、これはFreeBSDのシステムコールがエラー時に返す可能性のある値(例えばEFAULTなど)をチェックしようとしていた可能性があります。しかし、この比較とJLSの組み合わせは、システムコールのエラーを示すキャリーフラグの状態を直接的かつ正確に反映していませんでした。

FreeBSDのシステムコール規約では、エラーが発生した場合にキャリーフラグがセットされることが標準的な方法です。したがって、システムコールが成功したかどうかを判断するには、SYSCALL命令の直後にキャリーフラグがクリアされているか(つまり、エラーが発生していないか)をチェックするのが最も適切です。

このコミットでは、CMPQ AX, $0xfffffffffffff001JLSの組み合わせをJCC命令に置き換えることで、この問題を解決しています。JCC命令はキャリーフラグがクリアされている場合にジャンプするため、SYSCALL命令の直後に使用することで、システムコールがエラーなく成功した場合にのみ後続の処理に進むようにロジックが変更されました。これにより、GoランタイムはFreeBSD/amd64環境でのmadviseシステムコールのエラーをより正確に検出できるようになりました。

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

--- a/src/pkg/runtime/sys_freebsd_amd64.s
+++ b/src/pkg/runtime/sys_freebsd_amd64.s
@@ -190,8 +190,7 @@ TEXT runtime·madvise(SB),7,$0
 	MOVQ	24(SP), DX
 	MOVQ	$75, AX	// madvise
 	SYSCALL
-	CMPQ	AX, $0xfffffffffffff001
-	JLS	2(PC)
+	JCC	2(PC)
 	MOVL	$0xf1, 0xf1  // crash
 	RET
 	

コアとなるコードの解説

変更されたアセンブリコードは、runtime·madvise関数の内部にあります。

  • 変更前:

    	SYSCALL
    	CMPQ	AX, $0xfffffffffffff001
    	JLS	2(PC)
    

    SYSCALL命令の実行後、AXレジスタにシステムコールの戻り値が格納されます。その後、AXレジスタの値と0xfffffffffffff001(-4095)を比較し、その結果に基づいてJLS(Jump if Less or Same)命令で分岐していました。このロジックは、システムコールがエラーを返した場合にAXが負の値になることを期待していましたが、キャリーフラグの状態を直接確認するものではありませんでした。

  • 変更後:

    	SYSCALL
    	JCC	2(PC)
    

    SYSCALL命令の実行後、CMPQJLSの行が削除され、代わりにJCC(Jump if Carry Clear)命令が追加されました。 JCC命令は、SYSCALL命令によって設定されるキャリーフラグの状態を直接チェックします。

    • システムコールが成功した場合、キャリーフラグはクリアされます(CF=0)。このとき、JCC命令はジャンプし、エラー処理(MOVL $0xf1, 0xf1 // crash)をスキップしてRET(リターン)に進みます。
    • システムコールがエラーを返した場合、キャリーフラグはセットされます(CF=1)。このとき、JCC命令はジャンプせず、次の命令であるMOVL $0xf1, 0xf1 // crashが実行され、プログラムがクラッシュするようになっています。これは、Goランタイムがシステムコールエラーを致命的なものとして扱うことを示しています。

この変更により、GoランタイムはFreeBSD/amd64環境でのmadviseシステムコールのエラーを、OSのABIに準拠した形で正確に検出できるようになりました。

関連リンク

参考にした情報源リンク