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

[インデックス 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ビットの符号付き整数で-111111111と表現されます(2の補数表現)。これを16ビットに符号拡張すると、1111111111111111となります。もし符号拡張が正しく行われない場合、例えば上位ビットが0で埋められてしまうと、0000000011111111となり、これは255という全く異なる値として解釈されてしまいます。

このコミットの文脈では、Plan 9のシステムコールが返す32ビットの-10xFFFFFFFF)が、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型の変数です。

  1. uintptrへの格納: Plan 9のシステムコールは32ビットの戻り値を返します。エラーを示す-1は、32ビットの2の補数表現で0xFFFFFFFFとなります。この値がuintptramd64では64ビット)に格納されると、0x00000000FFFFFFFFとなります(上位32ビットはゼロ埋め)。
  2. int(r0)への変換: 次に、このuintptr値がGoのint型に変換されます。amd64環境では、Goのint型は64ビットです。この変換は、uintptrの値をそのままint64として解釈することになります。0x00000000FFFFFFFFという値は、符号なしの64ビット整数としては4294967295ですが、符号付きの64ビット整数としては正の値です。したがって、int(r0)の結果は-1にはなりません。
  3. 比較の失敗: 結果として、int(r0) == -1という比較は常にfalseとなり、システムコールがエラーを返しても、Goのコードがそれをエラーとして認識できないというバグが発生していました。

このコミットの修正は、この問題を解決するために、明示的にint32(r0)と型変換を行うように変更しました。

  1. int32(r0)への変換: uintptr型のr00x00000000FFFFFFFF)がint32型に変換されます。この変換では、uintptrの下位32ビットがint32として解釈されます。0xFFFFFFFFは、32ビットの符号付き整数としては-1を正確に表します。
  2. 比較の成功: したがって、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つのファイルに集中しています。

  1. src/pkg/syscall/mksyscall.pl:

    • このPerlスクリプトは、Goのsyscallパッケージのシステムコールラッパーコードを自動生成するためのものです。
    • 変更点: 257行目付近の条件分岐内で、Plan 9システムコールからの戻り値r0のエラーチェックを行う部分が変更されています。
      • 変更前: if int(r0) == -1 {
      • 変更後: if int32(r0) == -1 {
    • この変更により、mksyscall.plが生成するすべてのPlan 9 amd64システムコールラッパーにおいて、エラーチェックの型変換がint32に明示されるようになります。
  2. src/pkg/syscall/zsyscall_plan9_amd64.go:

    • このGoファイルは、mksyscall.plによって自動生成されるPlan 9 amd64アーキテクチャ向けのシステムコールラッパーの実装を含んでいます。
    • 変更点:
      • ファイルのヘッダーコメントが更新され、生成元スクリプトの引数が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関数でバイトポインタに変換し、その際に発生する可能性のあるエラーをチェックするロジックが追加されています。これにより、システムコール呼び出し前の引数処理がより堅牢になっています。

コアとなるコードの解説

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は特定できませんでしたが、一般的な知識として参照しました)
  • 符号拡張に関するコンピュータサイエンスの基本概念