[インデックス 18785] ファイルの概要
このコミットは、Goランタイムがフォールト(実行時エラー)を検出した際に生成するトレースバック出力に、ゴルーチンのヘッダー情報(ゴルーチンIDと状態)を含めるように変更するものです。これにより、デバッグ時の情報がより豊富になり、一貫性が向上します。
コミット
commit 2fb87ddb59218797686f8d725a5accb195ccbaca
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Mar 7 00:01:24 2014 +0400
runtime: print goroutine header on fault
I've just needed the G status on fault to debug runtime bug.
For some reason we print everything except header here.
Make it more informative and consistent.
R=rsc
CC=golang-codereviews
https://golang.org/cl/67870056
---
src/pkg/runtime/signal_386.c | 1 +
src/pkg/runtime/signal_amd64.c | 1 +
src/pkg/runtime/signal_arm.c | 1 +
3 files changed, 3 insertions(+)
diff --git a/src/pkg/runtime/signal_386.c b/src/pkg/runtime/signal_386.c
index 9f3f52179c..829f389cc2 100644
--- a/src/pkg/runtime/signal_386.c
+++ b/src/pkg/runtime/signal_386.c
@@ -112,6 +112,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_EIP(info, ctxt), SIG_ESP(info, ctxt), 0, gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
diff --git b/src/pkg/runtime/signal_amd64.c b/src/pkg/runtime/signal_amd64.c
index 2184b7f64b..01af0e7edf 100644
--- a/src/pkg/runtime/signal_amd64.c
+++ b/src/pkg/runtime/signal_amd64.c
@@ -122,6 +122,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_RIP(info, ctxt), SIG_RSP(info, ctxt), 0, gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
diff --git a/src/pkg/runtime/signal_arm.c b/src/pkg/runtime/signal_arm.c
index 4f797346c8..563f1f2bef 100644
--- a/src/pkg/runtime/signal_arm.c
+++ b/src/pkg/runtime/signal_arm.c
@@ -112,6 +112,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_PC(info, ctxt), SIG_SP(info, ctxt), SIG_LR(info, ctxt), gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2fb87ddb59218797686f8d725a5accb195ccbaca
元コミット内容
runtime: print goroutine header on fault
I've just needed the G status on fault to debug runtime bug.
For some reason we print everything except header here.
Make it more informative and consistent.
変更の背景
この変更の背景には、Goランタイムのデバッグにおける情報不足がありました。コミットメッセージによると、作者はランタイムのバグをデバッグする際に、フォールト発生時のゴルーチンの状態(G status)が必要であると感じていました。しかし、当時のトレースバック出力では、ゴルーチンのヘッダー情報(ゴルーチンIDと状態)が欠落しており、他の情報は出力されているにもかかわらず、この重要な部分が不足しているという不整合がありました。
このコミットは、デバッグの効率を高め、フォールト発生時のトレースバック出力がより包括的で一貫性のあるものになるように、ゴルーチンヘッダーの出力を追加することを目的としています。これにより、開発者は問題発生時のゴルーチンの状況をより迅速かつ正確に把握できるようになります。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムに関する前提知識が必要です。
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステムです。ガベージコレクション、ゴルーチン管理、スケジューリング、チャネル通信、メモリ割り当てなど、Go言語の並行処理モデルと効率的な実行を支える多くの機能を提供します。C言語とGo言語のアセンブリで記述されており、Goプログラムに静的にリンクされます。
- ゴルーチン (Goroutine): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数千から数百万のゴルーチンを同時に実行できます。GoランタイムがこれらのゴルーチンをOSスレッドにマッピングし、効率的にスケジューリングします。
- フォールト (Fault) / パニック (Panic): Goにおける実行時エラーのハンドリングメカニズムです。
- パニック (Panic): プログラムの回復不可能なエラーを示すために使用されます。例えば、nilポインタのデリファレンス、配列の範囲外アクセス、ゼロ除算などがパニックを引き起こします。パニックが発生すると、通常の実行フローは停止し、遅延関数(
defer
)が実行されます。recover
関数で捕捉されない限り、プログラムは終了し、詳細なトレースバック(スタックトレース)が出力されます。 - フォールト (Fault): より広範な意味で、プログラムの異常終了を引き起こす可能性のある低レベルのイベントを指します。パニックはその一種であり、OSからのシグナル(例: SIGSEGV, SIGBUS)によって引き起こされることもあります。
- パニック (Panic): プログラムの回復不可能なエラーを示すために使用されます。例えば、nilポインタのデリファレンス、配列の範囲外アクセス、ゼロ除算などがパニックを引き起こします。パニックが発生すると、通常の実行フローは停止し、遅延関数(
- トレースバック (Traceback) / スタックトレース (Stack Trace): プログラムがパニックまたはフォールトによって異常終了した際に、その時点での関数呼び出しの履歴(コールスタック)を表示するものです。これにより、どの関数がどの順序で呼び出され、どこでエラーが発生したかを特定できます。デバッグにおいて非常に重要な情報です。
- ゴルーチンヘッダー (Goroutine Header): トレースバック出力の各ゴルーチンセクションの冒頭に表示される情報です。通常、
goroutine X [state]:
の形式で表示されます。X
: ゴルーチンに割り当てられた一意のID番号。[state]
: フォールト発生時のゴルーチンの状態。例えば、running
(実行中)、runnable
(実行可能だが待機中)、waiting
(I/Oやチャネル操作などでブロック中)、syscall
(システムコール実行中)などがあります。このヘッダーは、問題発生時にゴルーチンが何をしていたかを理解する上で不可欠です。
runtime·printf
: Goランタイム内部で使用される、C言語のprintf
に似た出力関数です。デバッグ情報やエラーメッセージの出力に利用されます。runtime·goroutineheader(gp)
: Goランタイム内部の関数で、指定されたゴルーチン(gp
はゴルーチン構造体へのポインタ)のヘッダー情報を出力します。runtime·traceback
: Goランタイム内部の関数で、指定されたゴルーチンのスタックトレースを生成し出力します。- シグナルハンドリング (Signal Handling): オペレーティングシステムからのシグナル(例: SIGSEGV, SIGBUS, SIGILLなど)を捕捉し、それに応じてプログラムが特定の処理を行うメカニズムです。Goランタイムは、これらのシグナルを捕捉してパニックに変換し、トレースバックを生成する役割を担っています。このコミットで変更されている
signal_386.c
,signal_amd64.c
,signal_arm.c
は、それぞれ32ビットx86、64ビットx86、ARMアーキテクチャにおけるシグナルハンドリングのコードです。
技術的詳細
このコミットは、Goランタイムのシグナルハンドリング部分に、ゴルーチンヘッダーの出力を明示的に追加することで、フォールト発生時のトレースバックの可読性と情報量を向上させています。
具体的には、src/pkg/runtime/signal_386.c
、src/pkg/runtime/signal_amd64.c
、src/pkg/runtime/signal_arm.c
の3つのファイルが変更されています。これらはそれぞれ、32ビットx86、64ビットx86、ARMアーキテクチャにおけるシグナル処理を担当するGoランタイムのC言語ソースファイルです。
各ファイルのThrow:
ラベルの付いたセクション(これはシグナルによってプログラムが異常終了する際の共通のエントリポイントと考えられます)内で、runtime·gotraceback(&crash)
が真である条件分岐の中に、runtime·goroutineheader(gp);
という行が追加されています。
runtime·gotraceback(&crash)
: この関数は、トレースバックを生成する必要があるかどうかを判断します。通常、パニックが発生した場合や、GOTRACEBACK
環境変数の設定に応じて真を返します。gp
: これは、現在フォールトを引き起こしたゴルーチン(g
構造体)へのポインタです。runtime·goroutineheader(gp);
: この追加された呼び出しにより、gp
が指すゴルーチンのヘッダー情報(例:goroutine 1 [running]:
)が、そのゴルーチンのスタックトレースが出力される直前に標準エラー出力に書き込まれるようになります。
これにより、以前はスタックトレースの前にゴルーチンヘッダーが出力されていなかった状況が改善され、デバッグ時にどのゴルーチンがどのような状態でフォールトを引き起こしたのかが一目でわかるようになります。これは、特に複数のゴルーチンが絡む複雑な並行処理のバグを特定する際に非常に役立ちます。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/signal_386.c b/src/pkg/runtime/signal_386.c
index 9f3f52179c..829f389cc2 100644
--- a/src/pkg/runtime/signal_386.c
+++ b/src/pkg/runtime/signal_386.c
@@ -112,6 +112,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_EIP(info, ctxt), SIG_ESP(info, ctxt), 0, gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
diff --git b/src/pkg/runtime/signal_amd64.c b/src/pkg/runtime/signal_amd64.c
index 2184b7f64b..01af0e7edf 100644
--- a/src/pkg/runtime/signal_amd64.c
+++ b/src/pkg/runtime/signal_amd64.c
@@ -122,6 +122,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_RIP(info, ctxt), SIG_RSP(info, ctxt), 0, gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
diff --git a/src/pkg/runtime/signal_arm.c b/src/pkg/runtime/signal_arm.c
index 4f797346c8..563f1f2bef 100644
--- a/src/pkg/runtime/signal_arm.c
+++ b/src/pkg/runtime/signal_arm.c
@@ -112,6 +112,7 @@ Throw:
runtime·printf("\n");
if(runtime·gotraceback(&crash)){
+ runtime·goroutineheader(gp);
runtime·traceback(SIG_PC(info, ctxt), SIG_SP(info, ctxt), SIG_LR(info, ctxt), gp);
runtime·tracebackothers(gp);
runtime·printf("\n");
コアとなるコードの解説
上記の変更箇所は、Goランタイムがシグナル(例えば、セグメンテーション違反など)を受け取ってプログラムが異常終了する際に、トレースバックを出力するロジックに手を加えています。
各アーキテクチャ(386, amd64, arm)のシグナルハンドリングファイルにおいて、Throw:
ラベルの直後のコードブロック、特にif(runtime·gotraceback(&crash))
の条件が真である場合に実行される部分に注目してください。
追加された行は以下の通りです。
runtime·goroutineheader(gp);
gp
: これは、現在パニックまたはフォールトを引き起こしたゴルーチン(g
構造体)へのポインタです。Goランタイム内部では、各ゴルーチンはg
という構造体で表現され、その状態やスタック情報などが管理されています。runtime·goroutineheader()
: この関数は、引数として渡されたゴルーチンポインタgp
に基づいて、そのゴルーチンの識別子(ID)と現在の状態を整形して標準エラー出力に書き出す役割を担っています。例えば、goroutine 123 [running]:
のような形式で出力されます。
この変更により、フォールト発生時にトレースバックが生成される際、まず最初に問題のゴルーチンのヘッダー情報が明確に表示されるようになります。その後に、runtime·traceback
によってそのゴルーチンの詳細なスタックトレースが続き、さらにruntime·tracebackothers
によって他のゴルーチンの情報が出力されます。
これにより、デバッグ時にトレースバックを見た際に、どのゴルーチンがどのような状況でクラッシュしたのかを即座に把握できるようになり、デバッグ作業の効率が大幅に向上します。これは、特に並行処理のバグや、複数のゴルーチンが複雑に絡み合う問題の解析において非常に価値のある改善です。
関連リンク
- Go Code Review 67870056: https://golang.org/cl/67870056
参考にした情報源リンク
- Goにおけるパニックとリカバリ: https://sesamedisk.com
- Goのスタックトレースとゴルーチンヘッダー: https://digitalocean.com
- Goのトレースバック制御 (GOTRACEBACK): https://go.dev/doc/diagnose#godebug
- Goのデバッグパッケージ (runtime/debug): https://go.dev/pkg/runtime/debug/
- Goのパニックとスタックトレースの理解: https://ardanlabs.com
- Goのパニックとリカバリの例: https://yourbasic.org
- Goのパニックとエラーハンドリング: https://codesignal.com
- Goのスタックトレースの読み方: https://dev.to
- Goのゴルーチン状態: https://amyangfei.me
- Goのパニックとリカバリのメカニズム: https://medium.com