[インデックス 1101] ファイルの概要
このコミットは、Go言語のプロファイリングツール prof
におけるエラーハンドリングのバグ修正と、スタックトレース出力の可読性向上を目的とした改修です。具体的には、レジスタ読み込みエラー発生時の不適切なプロセス制御の削除と、スタックトレース出力後に改行を追加することで、プロファイラがより安定して動作し、出力が見やすくなるように改善されています。
コミット
commit 0e544fa0e330c615d290a7d78609d16295b0b946
Author: Rob Pike <r@golang.org>
Date: Mon Nov 10 18:13:20 2008 -0800
fix error-handling bug.
add newline after stack traces.
R=rsc
DELTA=3 (2 added, 1 deleted, 0 changed)
OCL=18945
CL=18953
---
src/cmd/prof/main.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/cmd/prof/main.c b/src/cmd/prof/main.c
index 22a2605c93..a4223e75a8 100644
--- a/src/cmd/prof/main.c
+++ b/src/cmd/prof/main.c
@@ -98,7 +98,6 @@ sample(void)
if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) {
if(n == 1)
fprintf(2, "prof: can't read registers at %d: %r\n", i);
- ctlproc(pid, "start");
return 0;
}
}
@@ -136,6 +135,8 @@ stacktracepcsp(uvlong pc, uvlong sp)
fprintf(2, "no machdata->ctrace\\n");
else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0)
fprintf(2, "no stack frame: pc=%#p sp=%#p\\n", pc, sp);
+else
+ print("\\n");
}
void
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0e544fa0e330c615d290a7d78609d16295b0b946
元コミット内容
fix error-handling bug.
add newline after stack traces.
R=rsc
DELTA=3 (2 added, 1 deleted, 0 changed)
OCL=18945
CL=18953
変更の背景
このコミットは、Go言語の初期段階におけるプロファイリングツール prof
の安定性とユーザビリティを向上させるために行われました。
-
エラーハンドリングのバグ修正:
sample
関数内でレジスタの読み込みに失敗した場合、ctlproc(pid, "start");
という行が実行されていました。これは、エラーが発生したにもかかわらず、プロファイリング対象のプロセスを「開始」しようとする不適切な動作でした。このバグにより、エラー発生時にプロファイラが予期せぬ状態に陥ったり、正しく終了できなかったりする可能性がありました。このコミットでは、エラー発生時には単に処理を終了する(return 0;
)ように修正することで、エラーハンドリングのロジックを改善し、プロファイラの堅牢性を高めています。 -
スタックトレース出力の可読性向上:
stacktracepcsp
関数は、プログラムカウンタ (PC) とスタックポインタ (SP) に基づいてスタックトレースを生成・出力する役割を担っています。以前のバージョンでは、スタックトレースが出力された後に改行が追加されていなかったため、複数のスタックトレースが連続して出力される場合に、それぞれのトレースの区切りが不明瞭になり、出力結果が読みにくくなる問題がありました。このコミットでは、スタックトレースが正常に取得された場合に明示的に改行 (print("\\n");
) を追加することで、出力の視認性を大幅に向上させています。これは、プロファイリング結果を分析する開発者にとって、非常に重要な改善点です。
これらの変更は、Go言語のツールチェーンが成熟していく過程で、初期のバグを修正し、開発者の使い勝手を向上させるための継続的な努力の一環として行われました。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識があると役立ちます。
-
プロファイラ (Profiler): プロファイラは、プログラムの実行中にその動作を監視し、パフォーマンス特性(CPU使用率、メモリ使用量、関数呼び出し回数など)を収集・分析するツールです。
prof
はGo言語の初期のプロファイリングツールの一つであり、プログラムのボトルネックを特定するのに役立ちます。 -
src/cmd/prof/main.c
: このファイルは、Go言語のプロファイリングツールprof
のC言語で書かれた主要な部分です。Go言語自体はGoで書かれていますが、初期のツールやランタイムの一部はC言語で実装されていました。main.c
は、プロファイラのメインロジックやシステムコールとの連携を担当しています。 -
uvlong
:unsigned long long
の略で、符号なし64ビット整数型を指します。これは、メモリのアドレスや大きな数値を扱う際に使用されます。特に、プログラムカウンタ (PC) やスタックポインタ (SP) のようなアドレス情報を格納するのに適しています。 -
pc
(Program Counter): プログラムカウンタは、CPUが次に実行する命令のアドレスを保持するレジスタです。スタックトレースを生成する際には、このPCの値からどの関数が実行されていたかを特定します。 -
sp
(Stack Pointer): スタックポインタは、現在の関数のスタックフレームの最上位(または最下位、アーキテクチャによる)のアドレスを指すレジスタです。スタックトレースでは、SPの値を使って関数の呼び出し履歴を辿ります。 -
map
(Memory Map): プログラムのメモリ空間のマッピング情報を示す概念です。プロファイラは、このメモリマップを利用して、特定のアドレスにあるデータや命令を読み取ります。 -
pid
(Process ID): プロセスIDは、オペレーティングシステムが各実行中のプロセスに割り当てる一意の識別子です。ctlproc
のような関数で、特定のプロセスを操作するために使用されます。 -
ureg
(User Registers): ユーザーレジスタは、CPUの汎用レジスタや特殊レジスタのうち、ユーザーモードのプログラムがアクセスできるものです。プロファイラは、これらのレジスタの値を読み取ることで、プログラムの現在の状態(例えば、どの関数が実行されているか、引数は何かなど)を把握します。 -
get8
: この関数は、おそらく指定されたメモリマップ (map
) から8バイトのデータを読み取るためのユーティリティ関数です。プロファイラが対象プロセスのメモリを検査する際に使用されます。 -
fprintf(2, ...)
:fprintf
はC言語の標準ライブラリ関数で、フォーマットされた出力をファイルストリームに書き込みます。2
は標準エラー出力 (stderr) を指します。エラーメッセージやデバッグ情報を出力する際に使用されます。 -
ctlproc(pid, "start")
: この関数は、特定のプロセス (pid
) に対して制御コマンドを送信するものです。"start"
は、そのプロセスを開始または再開するコマンドを意味します。プロファイラが対象プロセスの実行を制御するために使用されます。 -
machdata->ctrace
:machdata
は、おそらくターゲットマシンのアーキテクチャ固有のデータや関数へのポインタを含む構造体です。ctrace
は、その構造体に含まれる関数ポインタで、スタックトレースを生成するためのアーキテクチャ依存のロジックをカプセル化していると考えられます。 -
xptrace
: これは、おそらくスタックトレースのコールバック関数や、トレース結果を処理するためのコンテキストを指すポインタです。 -
%r
(Plan 9 error string):fprintf
のフォーマット指定子%r
は、Plan 9 オペレーティングシステムのエラー文字列を出力するために使用されます。Go言語の初期はPlan 9の影響を強く受けていたため、このような表記が見られます。
技術的詳細
このコミットは、src/cmd/prof/main.c
ファイル内の2つの異なる関数 sample
と stacktracepcsp
に変更を加えています。
sample
関数における変更
sample
関数は、プロファイラが定期的に呼び出すサンプリングルーチンの一部であり、対象プロセスのレジスタ情報を読み取ろうとします。
変更前:
if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) {
if(n == 1)
fprintf(2, "prof: can't read registers at %d: %r\n", i);
ctlproc(pid, "start"); // エラー時にプロセスを再開しようとする
return 0;
}
変更後:
if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) {
if(n == 1)
fprintf(2, "prof: can't read registers at %d: %r\n", i);
// ctlproc(pid, "start"); // 削除
return 0;
}
詳細:
get8
関数がレジスタの読み込みに失敗した場合(戻り値が負の場合)、エラーメッセージを標準エラー出力に書き込みます。変更前は、このエラー発生時に ctlproc(pid, "start");
が呼び出されていました。これは、レジスタの読み込みに失敗したにもかかわらず、プロファイリング対象のプロセスを「開始」しようとするロジックであり、明らかに不適切です。エラーが発生した状況でプロセスを再開しようとすると、プロファイラが不安定になったり、無限ループに陥ったり、あるいは誤ったプロファイリング結果を生成したりする可能性があります。
このコミットでは、この ctlproc(pid, "start");
の呼び出しを削除しました。これにより、レジスタ読み込みエラーが発生した際には、単にエラーメッセージを出力し、sample
関数から 0
を返して処理を終了するようになります。これは、エラー発生時の適切な振る舞いであり、プロファイラの堅牢性を向上させます。
stacktracepcsp
関数における変更
stacktracepcsp
関数は、与えられたプログラムカウンタ (PC) とスタックポインタ (SP) を基にスタックトレースを生成し、出力する役割を担っています。
変更前:
fprintf(2, "no machdata->ctrace\\n");
else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0)
fprintf(2, "no stack frame: pc=%#p sp=%#p\\n", pc, sp);
// ここに改行がない
}
変更後:
fprintf(2, "no machdata->ctrace\\n");
else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0)
fprintf(2, "no stack frame: pc=%#p sp=%#p\\n", pc, sp);
+else
+ print("\\n"); // スタックトレース出力後に改行を追加
}
詳細:
machdata->ctrace
関数がスタックトレースを正常に生成した場合(戻り値が 0
より大きい場合)、以前のコードでは何も出力されずに stacktracepcsp
関数が終了していました。これにより、複数のスタックトレースが連続して出力される場合、それぞれのトレースが改行なしで連結されてしまい、非常に読みにくい出力となっていました。
このコミットでは、machdata->ctrace
が正常にスタックトレースを生成した場合に、print("\\n");
を追加しました。print
関数は、おそらく標準出力に文字列を書き込むためのユーティリティ関数です。この変更により、各スタックトレースの後に必ず改行が挿入されるようになり、プロファイリング結果の可読性が大幅に向上します。これは、開発者がプロファイリング結果を分析する際のユーザエクスペリエンスを改善する、シンプルながらも効果的な変更です。
コアとなるコードの変更箇所
--- a/src/cmd/prof/main.c
+++ b/src/cmd/prof/main.c
@@ -98,7 +98,6 @@ sample(void)
if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) {
if(n == 1)
fprintf(2, "prof: can't read registers at %d: %r\n", i);
- ctlproc(pid, "start");
return 0;
}
}
@@ -136,6 +135,8 @@ stacktracepcsp(uvlong pc, uvlong sp)
fprintf(2, "no machdata->ctrace\\n");
else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0)
fprintf(2, "no stack frame: pc=%#p sp=%#p\\n", pc, sp);
+else
+ print("\\n");
}
void
コアとなるコードの解説
sample
関数内の変更
- ctlproc(pid, "start");
: この行が削除されました。これは、get8
関数がレジスタの読み込みに失敗した場合(エラー時)に、プロファイリング対象のプロセスを「開始」しようとする不適切な処理でした。エラーが発生している状況でプロセスを再開しようとすると、プロファイラの動作が不安定になる原因となります。この削除により、エラー発生時には単にエラーメッセージを出力し、関数を終了する(return 0;
)という、より堅牢なエラーハンドリングが実現されました。
stacktracepcsp
関数内の変更
+else
:machdata->ctrace
がスタックトレースの生成に成功した場合(machdata->ctrace(...) <= 0
の条件が偽の場合)に実行されるブロックを追加するためのelse
キーワードが追加されました。+ print("\\n");
: この行が追加されました。machdata->ctrace
がスタックトレースを正常に生成し終えた後、明示的に改行文字 (\n
) を出力します。これにより、複数のスタックトレースが連続して出力される際に、それぞれのトレースが独立した行に表示され、出力結果の可読性が大幅に向上します。
これらの変更は、Go言語のプロファイリングツール prof
の信頼性とユーザビリティを向上させるための、具体的かつ効果的な修正です。
関連リンク
- Go言語の公式ドキュメント(プロファイリングに関する情報):
- https://go.dev/doc/diagnostics
- https://go.dev/blog/pprof (Goのプロファイリングツール
pprof
に関するブログ記事)
参考にした情報源リンク
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Plan 9 from Bell Labs: https://9p.io/plan9/ (Go言語の設計に影響を与えたオペレーティングシステム)
- C言語の標準ライブラリ関数に関する情報 (例:
fprintf
): https://www.cplusplus.com/reference/cstdio/fprintf/ - Git Diffの読み方に関する情報: https://git-scm.com/docs/git-diff
- Go言語の初期の歴史に関する情報 (必要に応じて): https://go.dev/doc/history
uvlong
やctlproc
など、Plan 9 系のシステムコールやデータ型に関する情報 (Plan 9 のマニュアルページなど):- http://man.cat-v.org/plan9/2/ctlproc
- http://man.cat-v.org/plan9/6/prof (Plan 9 の
prof
コマンドに関する情報) - http://man.cat-v.org/plan9/6/trace (Plan 9 の
trace
コマンドに関する情報) - http://man.cat-v.org/plan9/2/get8 (Plan 9 の
get8
関数に関する情報) - http://man.cat-v.org/plan9/2/machdata (Plan 9 の
machdata
構造体に関する情報) - http://man.cat-v.org/plan9/2/ureg (Plan 9 の
ureg
構造体に関する情報) - http://man.cat-v.org/plan9/2/map (Plan 9 の
map
構造体に関する情報) - http://man.cat-v.org/plan9/2/pc (Plan 9 の
pc
に関する情報) - http://man.cat-v.org/plan9/2/sp (Plan 9 の
sp
に関する情報) - http://man.cat-v.org/plan9/2/pid (Plan 9 の
pid
に関する情報) - http://man.cat-v.org/plan9/2/xptrace (Plan 9 の
xptrace
に関する情報) - http://man.cat-v.org/plan9/2/print (Plan 9 の
print
関数に関する情報) - http://man.cat-v.org/plan9/2/r (Plan 9 の
%r
フォーマット指定子に関する情報)