[インデックス 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, $0xfffffffffffff001
(AX
レジスタの値と特定のマジックナンバーを比較)と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, $0xfffffffffffff001
とJLS
の組み合わせを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
命令の実行後、CMPQ
とJLS
の行が削除され、代わりに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に準拠した形で正確に検出できるようになりました。