[インデックス 19074] ファイルの概要
このコミットは、GoランタイムにおけるPlan 9環境でのGOTRACEBACK
環境変数の処理に関するバグ修正です。具体的には、GOTRACEBACK
の値を読み取る際にGetenv()
関数が動的なメモリ確保(malloc
)を行うことで発生する潜在的な問題を解決し、代わりに静的なバッファを使用するように変更しています。
コミット
commit a07f6adda8663f86701356bca341112846ed251f
Author: David du Colombier <0intro@gmail.com>
Date: Wed Apr 9 06:41:14 2014 +0200
runtime: fix GOTRACEBACK on Plan 9
Getenv() should not call malloc when called from
gotraceback(). Instead, we return a static buffer
in this case, with enough room to hold the longest
value.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/85680043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a07f6adda8663f86701356bca341112846ed251f
元コミット内容
runtime: fix GOTRACEBACK on Plan 9
Getenv() should not call malloc when called from
gotraceback(). Instead, we return a static buffer
in this case, with enough room to hold the longest
value.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/85680043
変更の背景
この変更の背景には、Goプログラムがクラッシュした際にスタックトレースを生成するメカニズムと、その際のメモリ管理の安全性に関する考慮があります。
GOTRACEBACK
環境変数は、Goプログラムがパニック(ランタイムエラー)を起こした際に、どの程度の詳細なスタックトレースを出力するかを制御するために使用されます。例えば、GOTRACEBACK=all
を設定すると、すべてのゴルーチンのスタックトレースが出力されます。
問題は、このGOTRACEBACK
の値を読み取るためにGoランタイム内部のGetenv()
関数が呼び出される際に、動的なメモリ確保関数であるmalloc
が使用されていた点にありました。プログラムがパニックを起こしている状況、つまりすでに不安定な状態にある中で、動的なメモリ確保を行うことは非常に危険です。
具体的には、以下のような問題が考えられます。
- メモリ破損: パニックの原因がメモリ破損である場合、
malloc
が不正なメモリ領域にアクセスしようとして、さらなるクラッシュや未定義の動作を引き起こす可能性があります。 - デッドロック:
malloc
が内部的にロックを使用している場合、パニック発生時にすでにそのロックが保持されていたり、あるいはロックの取得に失敗したりすることで、デッドロックが発生し、スタックトレースの出力自体が停止してしまう可能性があります。 - リソース不足: システムが極度のリソース不足に陥っている状況でパニックが発生した場合、
malloc
がメモリを確保できずに失敗し、適切なスタックトレースが出力されない可能性があります。
特にPlan 9のような、リソース管理が厳格で、低レベルな処理が重視される環境では、このような動的なメモリ確保の副作用はより顕著になる可能性があります。
このコミットは、このような不安定な状況下でのmalloc
呼び出しを回避し、GOTRACEBACK
の処理をより堅牢にするために行われました。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識があると役立ちます。
-
Goランタイム (Go Runtime): Go言語で書かれたプログラムは、Goランタイムと呼ばれる低レベルなシステムによって実行が管理されます。これは、ガベージコレクション、ゴルーチンのスケジューリング、システムコール、メモリ管理など、プログラムの実行に必要な多くの基盤機能を提供します。C言語で書かれた部分も多く含まれており、OSとのインタフェースを担当します。
-
GOTRACEBACK 環境変数: Goプログラムの実行時に設定できる環境変数の一つです。プログラムがパニックを起こした際に、Goランタイムが生成するスタックトレースの出力レベルを制御します。
0
またはnone
: スタックトレースを出力しない。1
またはsingle
: 現在のゴルーチンのスタックトレースのみを出力する(デフォルト)。2
またはall
: すべてのゴルーチンのスタックトレースを出力する。crash
: すべてのゴルーチンのスタックトレースを出力し、さらにOS固有のクラッシュダンプを生成しようとする(通常はデバッグ目的)。 この変数は、デバッグや問題診断において非常に重要です。
-
Plan 9 (オペレーティングシステム): ベル研究所で開発された分散オペレーティングシステムです。Go言語の開発者の一部(特にRob PikeやKen Thompson)がPlan 9の開発にも深く関わっていたため、Go言語の設計思想に大きな影響を与えています。Plan 9は、すべてのリソースをファイルとして扱うというユニークな設計思想を持ち、シンプルさと堅牢性を重視しています。Goランタイムには、Plan 9を含む様々なOS向けの特定の実装が含まれています。
-
malloc (メモリ確保関数): C言語の標準ライブラリ関数で、指定されたサイズのメモリブロックをヒープから動的に確保するために使用されます。Goランタイムの低レベルな部分では、C言語で書かれたコードが
malloc
のような関数を直接使用することがあります。動的なメモリ確保は柔軟性を提供しますが、メモリの断片化、パフォーマンスオーバーヘッド、そして特にマルチスレッド環境やエラー処理中にはデッドロックやメモリ破損のリスクを伴うことがあります。 -
スタックトレース (Stack Trace): プログラムの実行中にエラーや例外が発生した際に、その時点までに呼び出された関数のリストと、それぞれの呼び出し元の情報(ファイル名、行番号など)を示すものです。これにより、エラーがプログラムのどの部分で、どのような関数の呼び出し順序で発生したのかを特定でき、デバッグ作業に不可欠な情報となります。
技術的詳細
このコミットの技術的な核心は、GoランタイムがGOTRACEBACK
環境変数の値を読み取る際のメモリ確保戦略の変更にあります。
-
問題の特定: Goランタイムの
src/pkg/runtime/env_plan9.c
ファイルにあるruntime·getenv
関数は、環境変数の値を取得する役割を担っています。この関数は、取得する値のサイズに応じてruntime·malloc
を呼び出し、動的にメモリを確保していました。 しかし、GOTRACEBACK
環境変数は、プログラムがすでにパニック状態にある、つまり非常に不安定な状況で参照されることが想定されます。このような状況下でmalloc
を呼び出すことは、前述の通り、メモリ破損、デッドロック、またはさらなるクラッシュを引き起こす潜在的なリスクを伴います。これは、malloc
が内部的に複雑なデータ構造やロックを使用しているため、不安定な状態ではそれらが正しく機能しない可能性があるためです。 -
解決策の導入: このコミットでは、
runtime·getenv
関数が"GOTRACEBACK"
という特定の環境変数を読み取ろうとしている場合に限り、動的なmalloc
呼び出しを回避するロジックが導入されました。- 静的バッファの導入:
static byte b[128];
という128バイトの静的(グローバルスコープに似た)バッファが導入されました。このバッファは、プログラムの実行開始時に一度だけ確保され、以降は再利用されます。 - 条件付きメモリ確保:
runtime·strcmp((byte*)s, (byte*)"GOTRACEBACK") == 0
という条件分岐が追加され、要求された環境変数が"GOTRACEBACK"
であるかどうかをチェックします。 - 静的バッファの利用: もし
"GOTRACEBACK"
であれば、動的なmalloc
の代わりに、新しく導入された静的バッファb
を指すようにポインタp
を設定します。 - バッファのクリア:
runtime·memclr(b, sizeof b);
を呼び出して、静的バッファの内容をゼロクリアします。これは、以前のGOTRACEBACK
の値が残っていて、新しい値がそれよりも短い場合に、古い値の残骸が混入するのを防ぐためです。 - サイズチェック:
if(n >= sizeof b)
というチェックが追加され、GOTRACEBACK
の値が静的バッファのサイズ(128バイト)を超える場合は、nil
(ヌルポインタ)を返します。これは、バッファオーバーフローを防ぐための安全策です。GOTRACEBACK
の値が128バイトを超えることは通常想定されませんが、万が一の場合に備えています。
- 静的バッファの導入:
この変更により、GOTRACEBACK
の処理中にmalloc
が呼び出されることがなくなり、Goプログラムがパニック状態にある際のランタイムの堅牢性が向上しました。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/env_plan9.c
ファイルに集中しています。
--- a/src/pkg/runtime/env_plan9.c
+++ b/src/pkg/runtime/env_plan9.c
@@ -12,6 +12,7 @@ runtime·getenv(int8 *s)
intgo len;
byte file[128];
byte *p;
+ static byte b[128]; // 新しく追加された静的バッファ
len = runtime·findnull((byte*)s);
if(len > sizeof file-6)
@@ -25,7 +26,14 @@ runtime·getenv(int8 *s)
if(fd < 0)
return nil;
n = runtime·seek(fd, 0, 2);
- p = runtime·malloc(n+1); // 変更前: 無条件にmallocを呼び出す
+ if(runtime·strcmp((byte*)s, (byte*)"GOTRACEBACK") == 0){ // GOTRACEBACKの場合の条件分岐
+ // should not call malloc
+ if(n >= sizeof b) // バッファサイズを超える場合はnilを返す
+ return nil;
+ runtime·memclr(b, sizeof b); // 静的バッファをクリア
+ p = b; // 静的バッファを使用
+ }else
+ p = runtime·malloc(n+1); // GOTRACEBACK以外の場合はこれまで通りmalloc
r = runtime·pread(fd, p, n, 0);
runtime·close(fd);
if(r < 0)
コアとなるコードの解説
src/pkg/runtime/env_plan9.c
内のruntime·getenv
関数は、Goランタイムが環境変数の値を取得するために使用する低レベルなC言語関数です。
変更前は、この関数は環境変数の値の長さに応じて、常にruntime·malloc(n+1)
を呼び出して動的にメモリを確保していました。ここでn
は環境変数の値の長さです。
このコミットによる変更は、以下のロジックを追加しています。
-
static byte b[128];
: これは、runtime·getenv
関数が呼び出されるたびに再利用される、128バイトの静的(static
キーワードにより、プログラムの実行期間中メモリに存在し続ける)バッファを宣言しています。このバッファは、GOTRACEBACK
環境変数の値を格納するために特別に用意されました。 -
if(runtime·strcmp((byte*)s, (byte*)"GOTRACEBACK") == 0)
: この条件文は、現在取得しようとしている環境変数の名前が文字列"GOTRACEBACK"
と一致するかどうかをチェックしています。runtime·strcmp
はGoランタイム内部の文字列比較関数です。 -
if(n >= sizeof b)
: もし環境変数が"GOTRACEBACK"
であり、かつその値の長さn
が静的バッファb
のサイズ(128バイト)以上である場合、この条件が真となります。これは、静的バッファに収まらないほど長い値が設定されている場合に、バッファオーバーフローを防ぐための安全策です。この場合、nil
(ヌルポインタ)を返して、値の取得を諦めます。 -
runtime·memclr(b, sizeof b);
: 静的バッファb
をゼロクリアしています。これは、runtime·getenv
が複数回呼び出され、GOTRACEBACK
の値を読み取る際に、以前の呼び出しでバッファに残っていたデータが新しい値に混入するのを防ぐためです。これにより、常にクリーンな状態でバッファが使用されます。 -
p = b;
:p
は環境変数の値を格納するメモリ領域を指すポインタです。GOTRACEBACK
の場合、このポインタを動的に確保されたメモリではなく、静的バッファb
の先頭を指すように変更しています。これにより、malloc
の呼び出しが回避されます。 -
else p = runtime·malloc(n+1);
: もし取得しようとしている環境変数が"GOTRACEBACK"
ではない場合、これまでの動作通りruntime·malloc(n+1)
を呼び出して、動的にメモリを確保します。
この一連の変更により、GOTRACEBACK
の処理というクリティカルなパスにおいて、動的なメモリ確保に伴う潜在的なリスクが排除され、ランタイムの安定性が向上しました。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/85680043
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/env_plan9.c
) - Go言語のドキュメント(
GOTRACEBACK
環境変数に関する情報) - 一般的なオペレーティングシステムとメモリ管理の概念
- Plan 9オペレーティングシステムに関する一般的な知識