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

[インデックス 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オペレーティングシステム向けのコードにおいて、システムコールからの戻り値のエラーチェックを更新するものです。SyscallRawSyscall関数からの戻り値が、特定の例外的なケースで正しく処理されない問題に対処しています。

問題の根源は、システムコールが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固有の低レベルな操作を行うことができます。
  • SyscallRawSyscall: 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ビットの-111111111)を16ビットに符号拡張すると、1111111111111111となります。正しく符号拡張されないと、値が変化してしまう可能性があります。

技術的詳細

このコミットの核心は、Goのamd64環境におけるint型の挙動と、Plan 9システムコールが返す32ビットの戻り値との間の不整合を解消することです。

Plan 9のシステムコールは、エラーを示すために-1を返します。この-1は32ビットの符号付き整数として解釈されるべきです。しかし、GoのSyscallRawSyscall関数は戻り値をuintptrとして返します。uintptrは符号なし整数型であり、そのサイズはアーキテクチャに依存します(amd64では64ビット)。

元のコードでは、uintptr型の戻り値r1int(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 { ... }

これにより、r1uintptr)が保持する値がまず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.gosrc/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に変更されています。ここでrXSyscallまたは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のシステムコールインターフェースと正しく連携するための、堅牢性と正確性を向上させるものです。

関連リンク

参考にした情報源リンク