[インデックス 13993] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるPlan 9アーキテクチャ向けのシステムコールエラーチェックの修正に関するものです。特に、64-bitシステム(amd64)において、システムコールからの戻り値の型変換が原因で、エラーを示す-1
が正しく検出されない問題を解決しています。
コミット
commit af582674b0b1d4a06ca9c8bda8f66ce9e9846696
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Mon Oct 1 10:09:08 2012 +1000
pkg/syscall: Plan 9, 64-bit: Update error checks from sys calls.
This change updates CL 6576057 for exceptional cases where
return values from Syscall/RawSyscall functions are used.
The system calls return 32-bit integers. With the recent change
in size of `int' in Go for amd64, the type conversion was not
catching `-1' return values. This change makes the conversion
explicitly `int32'.
R=rsc, r
CC=golang-dev
https://golang.org/cl/6590047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/af582674b0b1d4a06ca9c8bda8f66ce9e9846696
元コミット内容
このコミットは、pkg/syscall
パッケージ、特にPlan 9オペレーティングシステム向けのコードにおいて、システムコールからの戻り値のエラーチェックを更新するものです。Syscall
やRawSyscall
関数からの戻り値が、特定の例外的なケースで正しく処理されない問題に対処しています。
問題の根源は、システムコールが32-bit整数を返すのに対し、Goのamd64
アーキテクチャにおけるint
型のサイズ変更(またはその解釈の変更)により、-1
というエラー戻り値が正しく捕捉されなくなった点にあります。この変更は、戻り値を明示的にint32
に型変換することで、この問題を解決しています。
変更の背景
Go言語はクロスプラットフォーム対応を重視しており、様々なアーキテクチャやオペレーティングシステムで動作します。このコミットが対象としているのは、ベル研究所で開発された分散オペレーティングシステムであるPlan 9です。Goのsyscall
パッケージは、Goプログラムが基盤となるOSのシステムコールを呼び出すためのインターフェースを提供します。
コミットメッセージにある「With the recent change in size of int
in Go for amd64」という記述は、Goのint
型がamd64
アーキテクチャで64ビット幅を持つようになったこと、またはその挙動が変更されたことを示唆しています。一般的に、Goのint
型はプラットフォーム依存であり、amd64
では64ビットです。システムコールは通常、固定長の整数(例えば32ビット)を戻り値として使用し、エラーを示すために-1
のような特定の値を返します。
問題は、システムコールが返す32ビットの-1
が、Goの64ビットint
型に暗黙的に変換された際に、その比較ロジックが期待通りに機能しなくなったことにあります。具体的には、int(r1) == -1
という比較が、本来エラーであるはずの-1
を検出できない状況が発生していました。これは、uintptr
型のr1
が、システムコールからの戻り値(32ビット)を保持しているにもかかわらず、Goのint
(64ビット)として解釈される際に、符号拡張やビットパターン解釈の違いにより、-1
として認識されなかった可能性があります。
このコミットは、この誤ったエラー検出を防ぐために、システムコールの戻り値を明示的にint32
にキャストすることで、32ビットの符号付き整数としての-1
を正確に比較できるように修正しています。
前提知識の解説
- システムコール (System Call): オペレーティングシステムが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、プロセス管理、メモリ管理など、OSのカーネルが提供する低レベルな機能にアクセスするために使用されます。システムコールは通常、成功時には非負の値を返し、エラー時には負の値(特に
-1
)を返します。 - Go言語の
syscall
パッケージ: GoプログラムからOSのシステムコールを直接呼び出すための機能を提供します。これにより、OS固有の低レベルな操作を行うことができます。 Syscall
とRawSyscall
:syscall
パッケージ内でシステムコールを呼び出すための関数です。RawSyscall
は、Goのスケジューラをブロックしない(プリエンプションされない)という点でSyscall
と異なります。uintptr
型: Go言語におけるポインタを保持できる整数型です。システムコール関数は、引数や戻り値にuintptr
を使用することがよくあります。これは、ポインタや整数値を柔軟に扱えるようにするためです。- Go言語の
int
型のサイズ: Goのint
型は、その実行環境のCPUアーキテクチャに依存してサイズが変わります。32ビットシステムでは32ビット、64ビットシステム(amd64
など)では64ビットになります。これは、そのアーキテクチャで最も効率的な整数演算を可能にするためです。 - Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Unixの概念をさらに推し進め、すべてのリソースをファイルとして表現するという特徴を持ちます。Go言語はPlan 9の影響を強く受けており、Goの初期開発環境としてもPlan 9が使われていました。
- 符号拡張 (Sign Extension): 整数値をより広いビット幅の型に変換する際に、元の値の符号を保持するために、新しい上位ビットを元の値の最上位ビット(符号ビット)で埋める処理です。例えば、8ビットの
-1
(11111111
)を16ビットに符号拡張すると、1111111111111111
となります。正しく符号拡張されないと、値が変化してしまう可能性があります。
技術的詳細
このコミットの核心は、Goのamd64
環境におけるint
型の挙動と、Plan 9システムコールが返す32ビットの戻り値との間の不整合を解消することです。
Plan 9のシステムコールは、エラーを示すために-1
を返します。この-1
は32ビットの符号付き整数として解釈されるべきです。しかし、GoのSyscall
やRawSyscall
関数は戻り値をuintptr
として返します。uintptr
は符号なし整数型であり、そのサイズはアーキテクチャに依存します(amd64
では64ビット)。
元のコードでは、uintptr
型の戻り値r1
をint(r1)
としてGoのint
型に変換し、その結果を-1
と比較していました。
if int(r1) == -1 { ... }
ここで問題となるのは、uintptr
からint
への変換が、システムコールが返した32ビットの-1
を正しく64ビットの符号付き-1
として解釈しなかった可能性です。
例えば、32ビットの-1
はバイナリで0xFFFFFFFF
です。
もしこれがuintptr
として0x00000000FFFFFFFF
(64ビット)と解釈され、その後int(r1)
で64ビットのint
に変換された場合、その値は4294967295
(符号なしの最大値)となり、-1
とは一致しません。
しかし、Goの型変換は通常、このような符号の解釈を正しく行います。したがって、より可能性が高いのは、特定のコンパイラ最適化、またはuintptr
がシステムコールから受け取った32ビット値をどのように内部的に保持し、それがint
に変換される際に、期待される符号拡張が正しく行われなかった、あるいは比較のコンテキストで何らかのビットパターン解釈のずれが生じた、というシナリオです。
このコミットは、この問題を解決するために、比較を行う前に明示的にint32(r1)
とキャストしています。
if int32(r1) == -1 { ... }
これにより、r1
(uintptr
)が保持する値がまず32ビットの符号付き整数として解釈され、その結果が-1
と比較されます。32ビットの-1
はバイナリで0xFFFFFFFF
であり、これをint32
として解釈すれば確実に-1
となります。この明示的なキャストにより、Goのint
型が64ビットであることによる潜在的な解釈のずれが回避され、システムコールからのエラー戻り値が確実に検出されるようになります。
この修正は、特にforkAndExecInChild
関数内の様々なシステムコール呼び出し(SYS_RFORK
, SYS_CLOSE
, SYS_CREATE
, SYS_PWRITE
, SYS_CHDIR
, SYS_DUP
など)と、StartProcess
関数内のSYS_RFORK
、そしてUnmount
関数内のSYS_UNMOUNT
に適用されています。これらのシステムコールは、エラー時に-1
を返す可能性があり、その検出がプロセスの正常な動作に不可欠です。
コアとなるコードの変更箇所
変更は主にsrc/pkg/syscall/exec_plan9.go
とsrc/pkg/syscall/syscall_plan9.go
の2つのファイルにわたります。
src/pkg/syscall/exec_plan9.go
このファイルでは、forkAndExecInChild
関数内でRawSyscall
の戻り値r1
をチェックする箇所が複数修正されています。
--- a/src/pkg/syscall/exec_plan9.go
+++ b/src/pkg/syscall/exec_plan9.go
@@ -207,7 +207,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
r1, _, _ = RawSyscall(SYS_RFORK, uintptr(RFPROC|RFFDG|RFREND|clearenv|rflag), 0, 0)
if r1 != 0 {
- if int(r1) == -1 {
+ if int32(r1) == -1 {
return 0, NewError(errstr())
}
// parent; return PID
@@ -219,7 +219,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
// Close fds we don't need.
for i = 0; i < len(fdsToClose); i++ {
r1, _, _ = RawSyscall(SYS_CLOSE, uintptr(fdsToClose[i]), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -229,7 +229,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
for i = 0; i < len(envv); i++ {
r1, _, _ = RawSyscall(SYS_CREATE, uintptr(unsafe.Pointer(envv[i].name)), uintptr(O_WRONLY), uintptr(0666))
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
@@ -238,13 +238,13 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
r1, _, _ = RawSyscall6(SYS_PWRITE, uintptr(envfd), uintptr(unsafe.Pointer(envv[i].value)), uintptr(envv[i].nvalue),
^uintptr(0), ^uintptr(0), 0)
- if int(r1) == -1 || int(r1) != envv[i].nvalue {
+ if int32(r1) == -1 || int(r1) != envv[i].nvalue {
goto childerror
}
r1, _, _ = RawSyscall(SYS_CLOSE, uintptr(envfd), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -253,7 +253,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
// Chdir
if dir != nil {
r1, _, _ = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -263,7 +263,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
nextfd = int(len(fd))
if pipe < nextfd {
r1, _, _ = RawSyscall(SYS_DUP, uintptr(pipe), uintptr(nextfd), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
pipe = nextfd
@@ -272,7 +272,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
for i = 0; i < len(fd); i++ {
if fd[i] >= 0 && fd[i] < int(i) {
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(nextfd), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
@@ -294,7 +294,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
continue
}
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(i), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -519,7 +519,7 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
func Exec(argv0 string, argv []string, envv []string) (err error) {
if envv != nil {
r1, _, _ := RawSyscall(RFCENVG, 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
return NewError(errstr())
}
src/pkg/syscall/syscall_plan9.go
このファイルでは、Unmount
関数内でSyscall
の戻り値r0
をチェックする箇所が修正されています。
--- a/src/pkg/syscall/syscall_plan9.go
+++ b/src/pkg/syscall/syscall_plan9.go
@@ -255,7 +255,7 @@ func Unmount(name, old string) (err error) {
r0, _, e = Syscall(SYS_UNMOUNT, uintptr(unsafe.Pointer(namep)), oldptr, 0)
}
- if int(r0) == -1 {
+ if int32(r0) == -1 {
err = e
}
return
コアとなるコードの解説
変更のパターンは一貫しており、int(rX) == -1
という形式の比較がint32(rX) == -1
に変更されています。ここでrX
はSyscall
またはRawSyscall
関数からの戻り値(uintptr
型)です。
r1, _, _ = RawSyscall(...)
またはr0, _, e = Syscall(...)
: システムコールを呼び出し、その戻り値をr1
またはr0
(いずれもuintptr
型)に格納しています。if int(rX) == -1
(変更前):uintptr
型のrX
をGoのデフォルトのint
型(amd64
では64ビット)に変換し、その結果が-1
と等しいかどうかをチェックしていました。前述の通り、この暗黙的な変換と比較が、特定の条件下で32ビットの-1
を正しく検出できない問題がありました。if int32(rX) == -1
(変更後):uintptr
型のrX
を明示的にint32
型に変換し、その結果が-1
と等しいかどうかをチェックしています。これにより、システムコールが返す32ビットの符号付き整数としての-1
が確実に認識されるようになります。この修正は、Goのint
型のサイズが64ビットであることによる影響を打ち消し、Plan 9システムコールのエラーセマンティクスに合致するようになります。
この変更は、Goのsyscall
パッケージが、異なるアーキテクチャやOSのシステムコールインターフェースと正しく連携するための、堅牢性と正確性を向上させるものです。
関連リンク
- Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Plan 9 from Bell Labs: https://9p.io/plan9/
- Go言語の型に関する公式ドキュメント: https://go.dev/ref/spec#Numeric_types
参考にした情報源リンク
- コミットメッセージ内のCLリンク: https://golang.org/cl/6590047
- Go言語の
int
型のサイズに関するStack Overflowの議論など(Web検索結果より) - Go言語の
uintptr
に関する情報: https://pkg.go.dev/unsafe#Pointer (unsafe.Pointerのドキュメント内でuintptr
について言及)