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

[インデックス 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 の安定性とユーザビリティを向上させるために行われました。

  1. エラーハンドリングのバグ修正: sample 関数内でレジスタの読み込みに失敗した場合、ctlproc(pid, "start"); という行が実行されていました。これは、エラーが発生したにもかかわらず、プロファイリング対象のプロセスを「開始」しようとする不適切な動作でした。このバグにより、エラー発生時にプロファイラが予期せぬ状態に陥ったり、正しく終了できなかったりする可能性がありました。このコミットでは、エラー発生時には単に処理を終了する(return 0;)ように修正することで、エラーハンドリングのロジックを改善し、プロファイラの堅牢性を高めています。

  2. スタックトレース出力の可読性向上: 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つの異なる関数 samplestacktracepcsp に変更を加えています。

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 の信頼性とユーザビリティを向上させるための、具体的かつ効果的な修正です。

関連リンク

参考にした情報源リンク