[インデックス 13979] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるPlan 9アーキテクチャ向けの64ビットシステムコールに関するエラーチェックの修正を目的としています。具体的には、システムコールからの戻り値が32ビット整数であるにもかかわらず、Goのamd64
アーキテクチャにおけるint
型のサイズ変更により、-1
というエラーを示す戻り値が正しく捕捉されない問題を解決します。この修正は、型変換を明示的にint32
にすることで、この問題を解消しています。
コミット
commit 8fdc90acf413be6063f791c43bcb65b3d75d1059
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date: Fri Sep 28 09:56:44 2012 +1000
pkg/syscall: Plan 9, 64-bit: Update error checks from sys calls.
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, rminnich, npe, r
CC=golang-dev
https://golang.org/cl/6576057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8fdc90acf413be6063f791c43bcb65b3d75d1059
元コミット内容
pkg/syscall: Plan 9, 64-bit: Update error checks from sys calls.
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'.
変更の背景
この変更の背景には、Go言語のamd64
アーキテクチャにおけるint
型のサイズ変更があります。以前のGoのバージョンでは、int
型は32ビットアーキテクチャでは32ビット、64ビットアーキテクチャでは64ビットのサイズを持つことが一般的でした。しかし、特定の変更(おそらくGo 1.0リリース前後)により、amd64
環境においてもint
型が32ビットに固定される、あるいはその逆の変更があった可能性があります。
Plan 9オペレーティングシステムでは、システムコールが返すエラー値は慣習的に32ビット整数で表現され、特に-1
がエラーを示す一般的な値として使用されます。Goのsyscall
パッケージは、これらの低レベルなシステムコールをGoプログラムから呼び出すためのインターフェースを提供しています。
amd64
環境でGoのint
型のサイズが変更された際、システムコールから返される32ビットの-1
という値が、Goの新しいint
型に暗黙的に変換される際に、その値が保持されず、結果としてエラーチェックが正しく機能しなくなる問題が発生しました。例えば、64ビットのint
型に32ビットの-1
が変換されると、符号拡張の挙動によっては期待する-1
とは異なる値として解釈される可能性がありました。このコミットは、この型変換の不整合によって引き起こされるバグを修正するために導入されました。
前提知識の解説
1. システムコール (System Call)
システムコールは、オペレーティングシステム(OS)のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイルI/O、メモリ管理、プロセス制御など、OSの基本的な機能はシステムコールを通じてアクセスされます。システムコールは通常、特定のレジスタに引数を設定し、特別な命令(例: SYSCALL
命令)を実行することで呼び出されます。システムコールの戻り値は、操作の成功/失敗や結果のデータを示します。多くのUNIX系システムでは、エラーを示すために-1
が返され、エラーの具体的な種類はerrno
などのグローバル変数に設定されます。
2. Plan 9 from Bell Labs
Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。UNIXの設計思想をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという「すべてはファイルである」という原則を徹底しています。Plan 9のシステムコールインターフェースは、UNIX系システムとは異なる独自の規約を持っていますが、エラーを示すために-1
を返すという慣習は共通しています。Go言語は、その設計思想や開発者の背景から、Plan 9の影響を強く受けています。
3. Go言語のint
型とアーキテクチャ依存性
Go言語の組み込み型であるint
は、そのサイズが実行されるシステムのアーキテクチャに依存します。
- 32ビットアーキテクチャ (例:
386
,arm
):int
型は32ビット幅を持ちます。 - 64ビットアーキテクチャ (例:
amd64
,arm64
):int
型は64ビット幅を持ちます。
これは、C言語のint
型が通常32ビットであるのとは対照的です。Goのint
型がアーキテクチャに依存する設計は、パフォーマンスとメモリ効率を考慮したものです。しかし、この柔軟性が、低レベルなシステムコールとの連携において、予期せぬ型変換の問題を引き起こすことがあります。特に、システムコールが固定のビット幅(例: 32ビット)で値を返す場合、Goのint
型がそれよりも大きいビット幅を持つ環境では、暗黙的な型変換が問題となる可能性があります。
4. 符号拡張 (Sign Extension)
符号拡張とは、より小さいビット幅の符号付き整数を、より大きいビット幅の符号付き整数に変換する際に、元の数値の符号(正負)を保持するために、新しい上位ビットを元の数値の最上位ビット(符号ビット)と同じ値で埋める操作です。
例えば、8ビットの符号付き整数で-1
は11111111
と表現されます(2の補数表現)。これを16ビットに符号拡張すると、1111111111111111
となります。もし符号拡張が正しく行われない場合、例えば上位ビットが0
で埋められてしまうと、0000000011111111
となり、これは255
という全く異なる値として解釈されてしまいます。
このコミットの文脈では、Plan 9のシステムコールが返す32ビットの-1
(0xFFFFFFFF
)が、Goのamd64
環境で64ビットのint
型(int64
に相当)に変換される際に、符号拡張が正しく行われない、あるいは期待する挙動と異なるために、int(r0) == -1
という比較が失敗していたと考えられます。明示的にint32(r0)
とすることで、r0
がまず32ビットの符号付き整数として解釈され、その上で-1
と比較されるため、この問題が回避されます。
技術的詳細
このコミットの核心は、Goのsyscall
パッケージがシステムコールからの戻り値を処理する方法にあります。Goのsyscall
パッケージは、低レベルなシステムコールをラップし、Goの関数として提供します。システムコールは通常、複数の戻り値を持ち、その中にはエラーコードや結果を示す値が含まれます。
Goのsyscall
パッケージでは、システムコールの戻り値はuintptr
型として受け取られることが一般的です。uintptr
はポインタを保持できる十分な大きさの符号なし整数型であり、アーキテクチャに依存して32ビットまたは64ビットになります。システムコールが返す値(この場合は32ビット整数)は、このuintptr
に格納されます。
問題は、このuintptr
に格納された値をGoのint
型に変換し、その結果を-1
と比較する際に発生しました。
元のコードでは、int(r0) == -1
という形式で比較が行われていました。ここでr0
はシステムコールからの戻り値を保持するuintptr
型の変数です。
uintptr
への格納: Plan 9のシステムコールは32ビットの戻り値を返します。エラーを示す-1
は、32ビットの2の補数表現で0xFFFFFFFF
となります。この値がuintptr
(amd64
では64ビット)に格納されると、0x00000000FFFFFFFF
となります(上位32ビットはゼロ埋め)。int(r0)
への変換: 次に、このuintptr
値がGoのint
型に変換されます。amd64
環境では、Goのint
型は64ビットです。この変換は、uintptr
の値をそのままint64
として解釈することになります。0x00000000FFFFFFFF
という値は、符号なしの64ビット整数としては4294967295
ですが、符号付きの64ビット整数としては正の値です。したがって、int(r0)
の結果は-1
にはなりません。- 比較の失敗: 結果として、
int(r0) == -1
という比較は常にfalse
となり、システムコールがエラーを返しても、Goのコードがそれをエラーとして認識できないというバグが発生していました。
このコミットの修正は、この問題を解決するために、明示的にint32(r0)
と型変換を行うように変更しました。
int32(r0)
への変換:uintptr
型のr0
(0x00000000FFFFFFFF
)がint32
型に変換されます。この変換では、uintptr
の下位32ビットがint32
として解釈されます。0xFFFFFFFF
は、32ビットの符号付き整数としては-1
を正確に表します。- 比較の成功: したがって、
int32(r0) == -1
という比較は、システムコールがエラーを示す-1
を返した場合に正しくtrue
となり、エラーが捕捉されるようになります。
この修正は、mksyscall.pl
スクリプトと、それによって生成されるzsyscall_plan9_amd64.go
ファイルの両方に適用されています。mksyscall.pl
は、システムコールラッパーコードを自動生成するためのスクリプトであり、このスクリプト自体が生成するコードのテンプレートを修正することで、将来生成されるすべてのPlan 9 amd64
システムコールラッパーにこの修正が適用されるようになります。
また、Open
, Create
, Remove
, Chdir
, Bind
, Mount
, Stat
, Wstat
などの関数では、文字列引数をシステムコールに渡す前にBytePtrFromString
関数を使用してバイトポインタに変換する処理が追加されています。これは、文字列をシステムコールに渡す際の安全性を高めるための一般的なパターンであり、エラーハンドリングも強化されています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下の2つのファイルに集中しています。
-
src/pkg/syscall/mksyscall.pl
:- このPerlスクリプトは、Goの
syscall
パッケージのシステムコールラッパーコードを自動生成するためのものです。 - 変更点: 257行目付近の条件分岐内で、Plan 9システムコールからの戻り値
r0
のエラーチェックを行う部分が変更されています。- 変更前:
if int(r0) == -1 {
- 変更後:
if int32(r0) == -1 {
- 変更前:
- この変更により、
mksyscall.pl
が生成するすべてのPlan 9amd64
システムコールラッパーにおいて、エラーチェックの型変換がint32
に明示されるようになります。
- このPerlスクリプトは、Goの
-
src/pkg/syscall/zsyscall_plan9_amd64.go
:- このGoファイルは、
mksyscall.pl
によって自動生成されるPlan 9amd64
アーキテクチャ向けのシステムコールラッパーの実装を含んでいます。 - 変更点:
- ファイルのヘッダーコメントが更新され、生成元スクリプトの引数が
syscall_plan9_386.go
からsyscall_plan9_amd64.go
に変更されています。 exits
関数の定義が削除されています。これは、このコミットの直接的な目的とは異なるクリーンアップまたはリファクタリングの一部である可能性があります。fd2path
,pipe
,await
,Dup
,Open
,Create
,Remove
,Pread
,Pwrite
,Close
,Chdir
,Bind
,Mount
,Stat
,Fstat
,Wstat
,Fwstat
といった複数のシステムコールラッパー関数において、システムコールからの戻り値r0
のエラーチェック部分が、int(r0) == -1
からint32(r0) == -1
に変更されています。Open
,Create
,Remove
,Chdir
,Bind
,Mount
,Stat
,Wstat
などの関数では、文字列引数をBytePtrFromString
関数でバイトポインタに変換し、その際に発生する可能性のあるエラーをチェックするロジックが追加されています。これにより、システムコール呼び出し前の引数処理がより堅牢になっています。
- ファイルのヘッダーコメントが更新され、生成元スクリプトの引数が
- このGoファイルは、
コアとなるコードの解説
src/pkg/syscall/mksyscall.pl
の変更
--- a/src/pkg/syscall/mksyscall.pl
+++ b/src/pkg/syscall/mksyscall.pl
@@ -257,7 +257,7 @@ while(<>) {
$text .= $body;
if ($plan9 && $ret[2] eq "e1") {
- $text .= "\\tif int(r0) == -1 {\\n";
+ $text .= "\\tif int32(r0) == -1 {\\n";
$text .= "\\t\\terr = e1\\n";
$text .= "\\t}\\n";
} elsif ($do_errno) {
このPerlスクリプトの変更は、Goのシステムコールラッパーコードを生成する際のテンプレートを修正しています。$plan9
が真(Plan 9ターゲット)であり、かつ戻り値がエラー変数e1
に関連付けられている場合、生成されるGoコードにエラーチェックのロジックが挿入されます。
変更前はint(r0) == -1
というGoのコードが生成されていましたが、変更後はint32(r0) == -1
が生成されるようになります。これにより、amd64
環境でint
が64ビット幅であっても、システムコールが返す32ビットのエラー値-1
が正しく比較されるようになります。
src/pkg/syscall/zsyscall_plan9_amd64.go
の変更
このファイルはmksyscall.pl
によって生成されるため、上記のPerlスクリプトの変更が反映された結果として、多くのシステムコールラッパー関数で同様の変更が見られます。
例として、fd2path
関数の変更を見てみましょう。
--- a/src/pkg/syscall/zsyscall_plan9_amd64.go
+++ b/src/pkg/syscall/zsyscall_plan9_amd64.go
@@ -22,7 +15,7 @@ func fd2path(fd int, buf []byte) (err error) {
_p0 = unsafe.Pointer(&_zero)
}
r0, _, e1 := Syscall(SYS_FD2PATH, uintptr(fd), uintptr(_p0), uintptr(len(buf)))
- if int(r0) == -1 {
+ if int32(r0) == -1 {
err = e1
}
return
Syscall
関数は、実際のシステムコールを実行し、その戻り値をr0
, r1
, e1
として返します。r0
は通常、システムコールの主要な戻り値(成功時の結果やエラーコード)を保持します。
変更前はint(r0) == -1
でエラーをチェックしていましたが、これは前述の通りamd64
環境でのint
型のサイズ変更により正しく機能しなくなっていました。
変更後はint32(r0) == -1
とすることで、r0
の値を明示的に32ビット符号付き整数として解釈し、-1
と比較します。これにより、Plan 9システムコールが返す32ビットのエラー値-1
が正確に検出されるようになります。
また、Open
, Create
, Remove
などの関数では、文字列引数をシステムコールに渡す前にBytePtrFromString
関数を呼び出し、その戻り値であるエラーをチェックするロジックが追加されています。
--- a/src/pkg/syscall/zsyscall_plan9_amd64.go
+++ b/src/pkg/syscall/zsyscall_plan9_amd64.go
@@ -69,9 +62,14 @@ func Dup(oldfd int, newfd int) (fd int, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Open(path string, mode int) (fd int, err error) {
-\tr0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), 0)
+\tvar _p0 *byte
+\t_p0, err = BytePtrFromString(path)
+\tif err != nil {
+\t\treturn
+\t}
+\tr0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
\tfd = int(r0)
-\tif int(r0) == -1 {
+\tif int32(r0) == -1 {
\t\terr = e1
\t}\n \treturn
BytePtrFromString(path)
は、Goの文字列をCスタイルのヌル終端バイト配列へのポインタに変換する関数です。この変換処理自体がエラーを返す可能性があるため、そのエラーをチェックし、エラーがあればシステムコールを呼び出す前にreturn
するようになっています。これにより、無効なパスが渡された場合などに、より早期にエラーを捕捉できるようになり、堅牢性が向上しています。
関連リンク
- Go言語の
syscall
パッケージに関する公式ドキュメント: https://pkg.go.dev/syscall - Go言語の
int
型のサイズに関する議論(Go 1.0リリースノートなど) - Plan 9オペレーティングシステムに関する情報: https://9p.io/plan9/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Plan 9のシステムコールに関する資料
- Go言語の
int
型の挙動に関するコミュニティの議論やブログ記事(具体的なURLは特定できませんでしたが、一般的な知識として参照しました) - 符号拡張に関するコンピュータサイエンスの基本概念