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

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

このコミットは、Go言語のsyscallパッケージ内のexec_plan9.goファイルに対する変更です。このファイルは、Plan 9オペレーティングシステム上でのプロセス実行(forkExec関数)に関連するシステムコールを扱うためのGo言語のインターフェースを提供します。具体的には、新しいプロセスをフォークし、指定されたプログラムを実行する際の環境変数の処理に関するバグ修正が含まれています。

コミット

commit 8b5d4c3c0310c1669f8abd0b159985d80771e9f7
Author: Alexey Borzenkov <snaury@gmail.com>
Date:   Mon Aug 6 16:24:08 2012 -0400

    syscall: fix plan9 build broken by CL 6458050
    
    R=golang-dev, rsc
    CC=golang-dev, r, yarikos
    https://golang.org/cl/6454104

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

https://github.com/golang/go/commit/8b5d4c3c0310c1669f8abd0b159985d80771e9f7

元コミット内容

syscall: fix plan9 build broken by CL 6458050

変更の背景

このコミットは、以前の変更リスト(Change List, CL)であるCL 6458050によって引き起こされたPlan 9ビルドの破損を修正するために導入されました。CL 6458050は、Go言語の標準ライブラリ、特にsyscallパッケージにおける文字列とバイトポインタの扱いに関する変更を含んでいた可能性があります。

具体的には、環境変数を処理する際に使用されていたBytePtrFromString関数が、Plan 9環境において問題を引き起こしていました。BytePtrFromStringはGoの文字列をCスタイルのヌル終端バイト配列に変換し、そのポインタを返すことを意図していますが、Plan 9の特定のコンテキストやメモリ管理の挙動と合致せず、ビルドエラーや実行時エラーの原因となっていたと考えられます。

この修正は、Plan 9上でのGoプログラムの安定性と互換性を維持するために不可欠でした。

前提知識の解説

  • syscallパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステムの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。ファイル操作、プロセス管理、ネットワーク通信など、OSに依存する多くの機能がこのパッケージを通じて実現されます。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Unixの概念をさらに推し進め、すべてのリソースをファイルとして表現するという哲学を持っています。Go言語はPlan 9の設計思想に影響を受けており、初期のGo開発環境ではPlan 9が重要な役割を果たしていました。そのため、Goの標準ライブラリにはPlan 9固有のシステムコールを扱うためのコードが含まれています。
  • 環境変数: プロセスが実行される環境に関する情報を提供するキーと値のペアです。例えば、PATH環境変数は実行可能ファイルの検索パスを指定します。プログラムが外部プロセスを起動する際、これらの環境変数を新しいプロセスに引き継ぐことがよくあります。
  • BytePtrFromString: Go言語の文字列(UTF-8エンコードされたバイト列)を、C言語の文字列(ヌル終端バイト配列)として扱うためのポインタに変換する関数です。システムコールの中には、C言語のAPIのようにヌル終端文字列を期待するものがあるため、このような変換が必要になります。しかし、GoのガベージコレクタとCのポインタの相互運用には注意が必要で、特にGoの文字列が一時的なメモリ領域に割り当てられている場合、そのポインタがすぐに無効になる可能性があります。
  • execシステムコール: 新しいプログラムを実行するために使用されるシステムコール群です。既存のプロセスを新しいプログラムイメージで上書きします。forkExecのような関数は、通常、fork(新しいプロセスを作成)とexec(新しいプログラムを実行)の組み合わせを抽象化したものです。

技術的詳細

問題の核心は、syscallパッケージがPlan 9上でプロセスを起動する際に、環境変数をどのように準備して渡すかという点にありました。

以前のコードでは、環境変数の値(v[i+1:])をBytePtrFromStringに渡し、その結果得られるバイトポインタをenvItem構造体に格納していました。BytePtrFromStringはGoの文字列からバイトポインタを生成しますが、このポインタが指すメモリ領域はGoのガベージコレクタによって管理されており、Goの文字列がスコープを外れたり、ガベージコレクションが実行されたりすると、ポインタが指すデータが無効になる可能性があります。

Plan 9のexecシステムコールが環境変数を読み取る前に、BytePtrFromStringによって生成された一時的なバイト配列が解放されてしまう、あるいは移動されてしまうという競合状態やライフサイクル管理の問題が発生していたと考えられます。これにより、execシステムコールが不正なメモリを読み取ろうとし、ビルドの失敗や実行時エラーにつながっていました。

修正では、この問題を回避するために、環境変数の値をBytePtrFromStringで直接ポインタに変換するのではなく、明示的に新しいバイトスライスを作成し、そこに環境変数の値をコピーするように変更されました。

  1. envvalue := make([]byte, len(v)-i): 環境変数の値の長さに合わせて、新しいバイトスライスenvvalueを確保します。これにより、このバイトスライスはGoのヒープ上に独立して存在し、ガベージコレクタによってすぐに回収されることはありません。
  2. copy(envvalue, v[i+1:]): 環境変数の実際の値(v[i+1:])を、新しく確保したenvvalueスライスにコピーします。
  3. &envvalue[0]: 新しいバイトスライスの先頭要素へのポインタを取得し、これをenvItem構造体に格納します。このポインタは、envvalueスライスが有効である限り、有効なメモリ領域を指し続けます。

この変更により、execシステムコールが環境変数を読み取る際に、データが常に有効なメモリ領域に存在することが保証され、Plan 9ビルドの破損が修正されました。これは、GoのガベージコレクタとCスタイルのポインタを扱う際の一般的な注意点を示す良い例です。

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

--- a/src/pkg/syscall/exec_plan9.go
+++ b/src/pkg/syscall/exec_plan9.go
@@ -419,11 +419,9 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)\
 		if err != nil {
 			return 0, err
 		}
-		envvalue, err := BytePtrFromString(v[i+1:])
-		if err != nil {
-			return 0, err
-		}
-		envvParsed = append(envvParsed, envItem{envname, envvalue, len(v) - i})\
+		envvalue := make([]byte, len(v)-i)
+		copy(envvalue, v[i+1:])
+		envvParsed = append(envvParsed, envItem{envname, &envvalue[0], len(v) - i})\
 		}
 	}
 

コアとなるコードの解説

変更されたコードブロックは、forkExec関数内で環境変数を解析し、準備する部分です。

  • 変更前:

    envvalue, err := BytePtrFromString(v[i+1:])
    if err != nil {
        return 0, err
    }
    envvParsed = append(envvParsed, envItem{envname, envvalue, len(v) - i})
    

    ここでは、環境変数の値(v[i+1:])をBytePtrFromString関数に渡していました。この関数は、Goの文字列からCスタイルのヌル終端バイト配列へのポインタを返します。しかし、このポインタが指すメモリはGoのガベージコレクタによって管理されており、forkExecがシステムコールを呼び出す前に、そのメモリが移動または解放される可能性がありました。これがPlan 9ビルドの破損の原因でした。また、BytePtrFromStringがエラーを返す可能性も考慮されていました。

  • 変更後:

    envvalue := make([]byte, len(v)-i)
    copy(envvalue, v[i+1:])
    envvParsed = append(envvParsed, envItem{envname, &envvalue[0], len(v) - i})
    

    この修正では、BytePtrFromStringの使用を完全に削除しました。

    1. envvalue := make([]byte, len(v)-i): まず、環境変数の値の長さに正確に一致する新しいバイトスライスenvvalueを割り当てます。このスライスはGoのヒープ上に確保され、明示的に参照がなくなるまでガベージコレクションの対象になりません。
    2. copy(envvalue, v[i+1:]): 次に、元の環境変数の値(v[i+1:])を、新しく作成したenvvalueスライスにコピーします。これにより、環境変数のデータが安定したメモリ領域に格納されます。
    3. envvParsed = append(envvParsed, envItem{envname, &envvalue[0], len(v) - i}): 最後に、envItem構造体に、新しくコピーされたバイトスライスの先頭要素へのポインタ&envvalue[0]を格納します。このポインタは、envvalueスライスが有効である限り、安全に参照できます。

この変更により、環境変数のデータがシステムコールに渡されるまで確実にメモリ上に保持されるようになり、Plan 9上でのexecシステムコールの安定性が向上し、ビルドの破損が修正されました。

関連リンク

参考にした情報源リンク