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

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

このコミットは、Go言語のFreeBSD/ARM環境におけるシステムコール処理の修正に関するものです。特に、アラインメントされていない引数を持つシステムコールが正しく機能するように改善されています。

コミット

commit b0db7e870c50a38239c2bd541a862fe544b05350
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri Feb 14 12:22:13 2014 +0900

    syscall: fix system calls with misaligned arguments on freebsd/arm
    
    This CL enables the current tree to work with FreeBSD 10-STABLE
    on ARM EABI platforms, though there are still a few test fails.
    
    Also updates documentation.
    
    LGTM=iant
    R=iant, dave
    CC=golang-codereviews
    https://golang.org/cl/61060044

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

https://github.com/golang/go/commit/b0db7e870c50a38239c2bd541a862fe544b05350

元コミット内容

syscall: fix system calls with misaligned arguments on freebsd/arm

この変更は、FreeBSD 10-STABLE上のARM EABIプラットフォームで、アラインメントされていない引数を持つシステムコールが正しく動作するようにします。これにより、現在のGoのコードベースがFreeBSD 10-STABLEで動作するようになりますが、いくつかのテストはまだ失敗する可能性があります。また、関連するドキュメントも更新されています。

変更の背景

このコミットの背景には、FreeBSD 10-STABLEにおけるARMアーキテクチャでのシステムコール呼び出し規約、特に引数のアラインメントに関する問題がありました。ARMアーキテクチャでは、データアクセスにおいて特定のメモリアドレスアラインメントが要求されることが多く、これが守られないとパフォーマンスの低下や、場合によっては不正なメモリアクセスによるクラッシュを引き起こす可能性があります。

Go言語のランタイムは、システムコールを直接呼び出すためにアセンブリコードを使用しています。これは、GoのユーザーレベルのコードとOSカーネルの間の橋渡しをする重要な部分です。FreeBSD 10-STABLEのARM EABI(Embedded Application Binary Interface)環境において、Goランタイムがシステムコールに渡す引数のアラインメントがOSの期待するものと異なっていたため、システムコールが正しく機能しない、あるいは予期せぬ動作をする問題が発生していました。

この問題は、特にスタック上に配置される引数において顕著でした。ARM EABIでは、スタックポインタ(SP)が8バイト境界にアラインされていることを期待する場合があります。Goのシステムコールラッパーがこのアラインメントを維持せずに引数をスタックにプッシュすると、カーネル側で引数を読み取る際に問題が生じ、システムコールが失敗したり、誤った値を処理したりする可能性がありました。

このコミットは、FreeBSD 10-STABLE上でのGoの安定性と互換性を向上させることを目的としています。

前提知識の解説

1. システムコール (Syscall)

システムコールは、ユーザープログラムがオペレーティングシステム(OS)のカーネルが提供するサービスを利用するための唯一の手段です。ファイルI/O、ネットワーク通信、メモリ管理、プロセス制御など、OSの機能にアクセスするために使用されます。Go言語では、syscallパッケージを通じてこれらのOS機能にアクセスします。

2. ARMアーキテクチャとEABI (Embedded Application Binary Interface)

ARMは、スマートフォン、タブレット、組み込みシステムなど、幅広いデバイスで使用されているCPUアーキテクチャです。EABIは、特定のCPUアーキテクチャ(この場合はARM)上でソフトウェアがどのようにコンパイルされ、実行されるかを定義する標準です。これには、レジスタの使用方法、スタックフレームのレイアウト、関数呼び出し規約、データ型のアラインメントなどが含まれます。

特に、ARM EABIでは、スタックポインタ(SP)が8バイト境界にアラインされていることが期待される場合があります。これは、ARMプロセッサがダブルワード(8バイト)アクセスを効率的に行うため、または特定の命令が8バイトアラインメントを要求するためです。

3. メモリアラインメント

メモリアラインメントとは、データがメモリ内で特定の境界(例えば、4バイト境界、8バイト境界など)に配置されることを指します。CPUは、アラインされたデータにアクセスする方が、アラインされていないデータにアクセスするよりも効率的です。アラインメントされていないアクセスは、パフォーマンスの低下を引き起こしたり、一部のアーキテクチャではハードウェア例外(アラインメントフォルト)を発生させたりすることがあります。

システムコールにおいて引数がスタックを通じて渡される場合、OSカーネルは特定のメモリアラインメントを期待することがあります。Goのランタイムがこの期待に応えられない場合、システムコールは失敗する可能性があります。

4. Goのアセンブリコード (.sファイル)

Go言語は、パフォーマンスが重要な部分や、OSとの直接的なインターフェースが必要な部分でアセンブリコードを使用します。src/pkg/syscall/asm_freebsd_arm.sのようなファイルは、FreeBSD/ARMプラットフォームにおけるシステムコール呼び出しのためのアセンブリルーチンを定義しています。これらのルーチンは、Goの関数呼び出し規約とOSのシステムコール呼び出し規約の間で引数と戻り値を変換する役割を担います。

5. スタックポインタ (SP) とフレームポインタ (FP)

  • スタックポインタ (SP): 現在のスタックの最上位(または最下位、アーキテクチャによる)を指すレジスタです。関数呼び出し時に引数をプッシュしたり、ローカル変数を確保したりする際にSPが移動します。
  • フレームポインタ (FP): 現在のスタックフレームの基点を指すレジスタです。引数やローカル変数へのアクセスを相対アドレスで行うために使用されます。

Goのアセンブリコードでは、0(FP)4(FP)などの表記で、フレームポインタからのオフセットで引数や戻り値にアクセスします。

技術的詳細

このコミットの主要な変更点は、FreeBSD/ARMにおけるシステムコール呼び出しのアセンブリコード、具体的にはsrc/pkg/syscall/asm_freebsd_arm.sファイル内のスタックポインタ(R13レジスタ)の操作方法にあります。

以前の実装では、Syscall6Syscall9のような、より多くの引数を取るシステムコールにおいて、スタックに引数をプッシュする際にADD $24, R13のような命令を使用していました。これは、スタックポインタを直接操作して、スタック上の引数領域を調整しようとするものです。しかし、この方法では、OSが期待するスタックのアラインメント(特に8バイトアラインメント)が維持されない可能性がありました。

新しい実装では、このスタックポインタの操作方法が変更されています。具体的には、MOVW R13, R4で現在のスタックポインタの値を一時的にR4レジスタに保存し、MOVW $20(FP), R13のように、フレームポインタからのオフセットを使ってスタックポインタを直接目的の引数領域に設定しています。システムコール呼び出し後には、MOVW R4, R13で元のスタックポインタに戻しています。

この変更の意図は、システムコールに渡される引数がスタック上に正しくアラインされるようにすることです。ARM EABIでは、関数呼び出し規約やシステムコール規約において、スタックポインタが特定の境界(例えば8バイト)にアラインされていることを要求することがあります。以前のADDSUBによる相対的なスタックポインタ操作では、このアラインメントが崩れる可能性がありましたが、フレームポインタからの絶対的なオフセットでスタックポインタを設定することで、より確実にアラインメントを維持できるようになります。

また、コメントの変更も行われています。以前はint32型で定義されていたSyscall関数の引数と戻り値が、uintptr型に変更されています。これは、Goのuintptr型がポインタやメモリアドレスを表すために使用され、システムコール引数としてより適切であることを示しています。errerrnoに変わっているのも、システムコールが返すエラーコードが通常errnoとして知られているためです。

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

変更はsrc/pkg/syscall/asm_freebsd_arm.sファイルに集中しています。

--- a/src/pkg/syscall/asm_freebsd_arm.s
+++ b/src/pkg/syscall/asm_freebsd_arm.s
@@ -8,13 +8,13 @@
 // System call support for ARM, FreeBSD
 //
 
-// func Syscall(trap int32, a1, a2, a3 int32) (r1, r2, err int32);
-// func Syscall6(trap int32, a1, a2, a3, a4, a5, a6 int32) (r1, r2, err int32);
-// func Syscall9(trap int32, a1, a2, a3, a4, a5, a6, a7, a8, a9 int64) (r1, r2, err int32)
+// func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, errno uintptr);
+// func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr);
+// func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, errno uintptr)
 
 TEXT	·Syscall(SB),NOSPLIT,$0-28
 	BL runtime·entersyscall(SB)
-	MOVW 0(FP), R7 // sigcall num
+	MOVW 0(FP), R7 // syscall number
 	MOVW 4(FP), R0 // a1
 	MOVW 8(FP), R1 // a2
 	MOVW 12(FP), R2 // a3
@@ -23,69 +23,71 @@ TEXT	·Syscall(SB),NOSPLIT,$0-28
 	BCS error
 	MOVW R0, 16(FP) // r1
 	MOVW R1, 20(FP) // r2
-	MOVW R2, 24(FP) // err
+	MOVW R2, 24(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 error:
 	MOVW $-1, R3
 	MOVW R3, 16(FP) // r1
 	MOVW R2, 20(FP) // r2
-	MOVW R0, 24(FP) // err
+	MOVW R0, 24(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 
 TEXT	·Syscall6(SB),NOSPLIT,$0-40
 	BL runtime·entersyscall(SB)
-	MOVW 0(FP), R7 // sigcall num
+	MOVW 0(FP), R7 // syscall number
 	MOVW 4(FP), R0 // a1
 	MOVW 8(FP), R1 // a2
 	MOVW 12(FP), R2 // a3
 	MOVW 16(FP), R3 // a4
-	ADD $24, R13 // a5 to a6 are passed on stack
+	MOVW R13, R4
+	MOVW $20(FP), R13 // a5 to a6 are passed on stack
 	SWI $0 // syscall
-	SUB $24, R13
+	MOVW R4, R13
 	MOVW $0, R2
 	BCS error6
 	MOVW R0, 28(FP) // r1
 	MOVW R1, 32(FP) // r2
-	MOVW R2, 36(FP) // err
+	MOVW R2, 36(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 error6:
 	MOVW $-1, R3
 	MOVW R3, 28(FP) // r1
 	MOVW R2, 32(FP) // r2
-	MOVW R0, 36(FP) // err
+	MOVW R0, 36(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 
 TEXT	·Syscall9(SB),NOSPLIT,$0-52
 	BL runtime·entersyscall(SB)
-	MOVW 0(FP), R7 // sigcall num
+	MOVW 0(FP), R7 // syscall number
 	MOVW 4(FP), R0 // a1
 	MOVW 8(FP), R1 // a2
 	MOVW 12(FP), R2 // a3
 	MOVW 16(FP), R3 // a4
-	ADD $24, R13 // a5 to a9 are passed on stack
+	MOVW R13, R4
+	MOVW $20(FP), R13 // a5 to a9 are passed on stack
 	SWI $0 // syscall
-	SUB $24, R13
+	MOVW R4, R13
 	MOVW $0, R2
 	BCS error9
 	MOVW R0, 40(FP) // r1
 	MOVW R1, 44(FP) // r2
-	MOVW R2, 48(FP) // err
+	MOVW R2, 48(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 error9:
 	MOVW $-1, R3
 	MOVW R3, 40(FP) // r1
 	MOVW R2, 44(FP) // r2
-	MOVW R0, 48(FP) // err
+	MOVW R0, 48(FP) // errno
 	BL runtime·exitsyscall(SB)
 	RET
 
 TEXT	·RawSyscall(SB),NOSPLIT,$0-28
-	MOVW 0(FP), R7 // sigcall num
+	MOVW 0(FP), R7 // syscall number
 	MOVW 4(FP), R0 // a1
 	MOVW 8(FP), R1 // a2
 	MOVW 12(FP), R2 // a3
@@ -94,33 +96,34 @@ TEXT	·RawSyscall(SB),NOSPLIT,$0-28
 	BCS errorr
 	MOVW R0, 16(FP) // r1
 	MOVW R1, 20(FP) // r2
-	MOVW R2, 24(FP) // err
+	MOVW R2, 24(FP) // errno
 	RET
 errorr:
 	MOVW $-1, R3
 	MOVW R3, 16(FP) // r1
 	MOVW R2, 20(FP) // r2
-	MOVW R0, 24(FP) // err
+	MOVW R0, 24(FP) // errno
 	RET
 
 TEXT	·RawSyscall6(SB),NOSPLIT,$0-40
-	MOVW 0(FP), R7 // sigcall num
+	MOVW 0(FP), R7 // syscall number
 	MOVW 4(FP), R0 // a1
 	MOVW 8(FP), R1 // a2
 	MOVW 12(FP), R2 // a3
 	MOVW 16(FP), R3 // a4
-	ADD $24, R13 // a5 to a6 are passed on stack
+	MOVW R13, R4
+	MOVW $20(FP), R13 // a5 to a6 are passed on stack
 	SWI $0 // syscall
-	SUB $24, R13
+	MOVW R4, R13
 	MOVW $0, R2
 	BCS errorr6
 	MOVW R0, 28(FP) // r1
 	MOVW R1, 32(FP) // r2
-	MOVW R2, 36(FP) // err
+	MOVW R2, 36(FP) // errno
 	RET
 errorr6:
 	MOVW $-1, R3
 	MOVW R3, 28(FP) // r1
 	MOVW R2, 32(FP) // r2
-	MOVW R0, 36(FP) // err
+	MOVW R0, 36(FP) // errno
 	RET

コアとなるコードの解説

このコミットの核心は、Syscall6Syscall9RawSyscall6といった複数の引数を取るシステムコールのアセンブリルーチンにおけるスタックポインタ(R13)の操作方法の変更です。

変更前:

	ADD $24, R13 // a5 to a6 are passed on stack
	SWI $0 // syscall
	SUB $24, R13

このコードは、システムコールに渡す引数の一部(a5以降)がスタックに配置されることを想定し、スタックポインタR13を直接ADDおよびSUB命令で操作していました。ADD $24, R13はスタックポインタを24バイト分進める(スタックが下方に伸びる場合)ことで、引数用の領域を確保しようとしています。しかし、この相対的な操作では、R13が常に8バイト境界にアラインされているという保証がありませんでした。FreeBSD/ARMのシステムコール規約が8バイトアラインメントを要求する場合、このアラインメントの不一致が問題を引き起こしていました。

変更後:

	MOVW R13, R4
	MOVW $20(FP), R13 // a5 to a6 are passed on stack
	SWI $0 // syscall
	MOVW R4, R13

この新しいアプローチでは、以下の点が異なります。

  1. スタックポインタの保存: MOVW R13, R4命令により、現在のスタックポインタR13の値を一時的にR4レジスタに保存します。これは、システムコール呼び出し後に元のスタック状態に戻すために必要です。
  2. フレームポインタからの絶対オフセット: MOVW $20(FP), R13命令が導入されました。これは、フレームポインタ(FP)からの絶対的なオフセット20(FP)を計算し、そのアドレスを新しいスタックポインタR13として設定します。フレームポインタは通常、関数エントリ時に8バイト境界にアラインされているため、この方法でスタックポインタを設定することで、システムコールに渡される引数領域が確実にアラインされるようになります。20(FP)は、Syscall6の場合、trap(0), a1(4), a2(8), a3(12), a4(16) の後に続くa5の開始位置を示唆しています。
  3. スタックポインタの復元: システムコール呼び出し後、MOVW R4, R13命令で、システムコール呼び出し前の元のスタックポインタR13の値をR4から復元します。これにより、Goランタイムのスタックフレームが正しく維持されます。

この変更により、FreeBSD/ARM環境において、GoのシステムコールがOSの期待するスタックアラインメント規約に準拠するようになり、アラインメントに起因する問題が解決されました。

また、関数のシグネチャに関するコメントも更新されています。

  • func Syscall(trap int32, a1, a2, a3 int32) (r1, r2, err int32); から func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, errno uintptr); へ変更。
  • sigcall num から syscall number へ変更。
  • err から errno へ変更。

これらの変更は、GoのシステムコールAPIがより汎用的なuintptr型を使用し、エラー戻り値が標準的なerrnoという用語で表現されるように、ドキュメントとコードの整合性を高めるものです。uintptrは、ポインタを保持できる整数型であり、システムコール引数としてアドレスやサイズなど様々な値を渡すのに適しています。

関連リンク

  • Go言語のsyscallパッケージ: https://pkg.go.dev/syscall
  • ARM EABI (Wikipedia): https://en.wikipedia.org/wiki/ARM_architecture#EABI
  • FreeBSD ARM (公式ドキュメントなど): 関連するFreeBSDの公式ドキュメントやメーリングリストの議論が参考になる可能性がありますが、特定のURLはコミットメッセージには含まれていません。

参考にした情報源リンク


I have generated the comprehensive technical explanation in Markdown format, following all the instructions, including the chapter structure and detailed explanations. The output is provided directly to standard output.I have generated the comprehensive technical explanation in Markdown format, following all the instructions, including the chapter structure and detailed explanations. The output is provided directly to standard output.