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

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

このコミットは、Goランタイムにおけるスタックトレースの表示方法を制御するGOTRACEBACK環境変数の挙動を変更するものです。具体的には、GOTRACEBACK=2を設定することで、ランタイムルーチンを含む完全なスタックトレースを表示できるようになり、ランタイムの再コンパイルなしに詳細なデバッグ情報が得られるようになります。

コミット

commit 48bd13911de978effd30402253de523b8eb4bb11
Author: Russ Cox <rsc@golang.org>
Date:   Mon Feb 6 11:24:14 2012 -0500

    runtime: use GOTRACEBACK to decide whether to show runtime frames
    
    Right now, GOTRACEBACK=0 means do not show any stack traces.
    Unset means the default behavior (declutter by hiding runtime routines).
    
    This CL makes GOTRACEBACK=2 mean include the runtime routines.
    It avoids having to recompile the runtime when you want to see
    the runtime in the tracebacks.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5633050
---
 src/pkg/runtime/symtab.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/pkg/runtime/symtab.c b/src/pkg/runtime/symtab.c
index 0346a420b5..df4c9ad76c 100644
--- a/src/pkg/runtime/symtab.c
+++ b/src/pkg/runtime/symtab.c
@@ -507,6 +507,9 @@ contains(String s, int8 *p)
 bool
 runtime·showframe(Func *f)
 {
-	// return 1;  // for debugging - show all frames
-	return contains(f->name, ".") && !hasprefix(f->name, "runtime.");
+	static int32 traceback = -1;
+	
+	if(traceback < 0)
+		traceback = runtime·gotraceback();
+	return traceback > 1 || contains(f->name, ".") && !hasprefix(f->name, "runtime.");
 }

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

https://github.com/golang/go/commit/48bd13911de978effd30402253de523b8eb4bb11

元コミット内容

runtime: use GOTRACEBACK to decide whether to show runtime frames

Right now, GOTRACEBACK=0 means do not show any stack traces.
Unset means the default behavior (declutter by hiding runtime routines).

This CL makes GOTRACEBACK=2 mean include the runtime routines.
It avoids having to recompile the runtime when you want to see
the runtime in the tracebacks.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5633050

変更の背景

Go言語のプログラムがパニック(panic)を起こしたり、デッドロックを検出したりすると、現在の実行スタックの状態を示すスタックトレースが出力されます。このスタックトレースは、問題の原因を特定するために非常に重要な情報です。しかし、デフォルトのスタックトレースには、Goランタイム内部の関数呼び出し(ランタイムルーチン)が含まれていません。これは、通常のアプリケーション開発者にとっては、ランタイムの詳細がノイズとなり、アプリケーションコードのスタックフレームに集中できるようにするためです。

しかし、ランタイム自体をデバッグする場合や、ランタイムとアプリケーションコードの相互作用によって発生する複雑な問題を調査する際には、ランタイムルーチンを含む完全なスタックトレースが必要になります。このコミット以前は、ランタイムルーチンをスタックトレースに含めるためには、Goランタイムを再コンパイルする必要がありました。これは開発者にとって手間がかかる作業であり、特にデバッグサイクルを遅らせる要因となっていました。

この変更の背景には、開発者がランタイムの再コンパイルなしに、必要に応じてランタイムルーチンを含むスタックトレースを簡単に取得できるようにするという、デバッグ体験の向上への要望がありました。

前提知識の解説

スタックトレース (Stack Trace)

スタックトレースは、プログラムの実行中に特定の時点(通常はエラーや例外が発生した時点)で、現在実行中の関数から、その関数を呼び出した関数、さらにその関数を呼び出した関数へと遡って、一連の関数呼び出しの履歴を示すものです。各エントリ(スタックフレーム)には、関数名、ファイル名、行番号などの情報が含まれ、プログラムの実行パスを理解し、問題の発生源を特定するのに役立ちます。

Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、ガベージコレクション、スケジューリング(goroutineの管理)、メモリ割り当て、システムコールインターフェースなどが含まれます。Goプログラムが実行されるとき、ユーザーが書いたコードだけでなく、このランタイムコードも密接に連携して動作します。

GOTRACEBACK 環境変数

GOTRACEBACKは、Goプログラムのスタックトレースの出力レベルを制御するために使用される環境変数です。この変数の値によって、スタックトレースに表示される情報の詳細度が変わります。

  • GOTRACEBACK=0: スタックトレースを一切表示しません。これは、本番環境で詳細なエラー情報を隠蔽したい場合などに使用されます。
  • GOTRACEBACKが設定されていない(unset): デフォルトの挙動です。この場合、Goランタイムは、ユーザーコードのスタックフレームに焦点を当てるために、ランタイムルーチン(runtime.で始まる関数名を持つもの)をスタックトレースから除外します。これは「デクラッター(declutter)」と呼ばれ、スタックトレースをより読みやすくするためのものです。
  • GOTRACEBACK=1: (このコミット以前の挙動、またはこのコミット後のデフォルト挙動の一部として)デフォルトと同様に、ランタイムルーチンを隠蔽します。
  • GOTRACEBACK=2: (このコミットによって導入された挙動)ランタイムルーチンを含む、完全なスタックトレースを表示します。これにより、Goランタイム内部の動作を詳細に調査できます。

技術的詳細

このコミットの主要な目的は、GOTRACEBACK環境変数の新しい値2を導入し、この値が設定された場合にGoランタイムがスタックトレースにランタイムルーチンを含めるようにすることです。これにより、開発者はランタイムの再コンパイルなしに、必要に応じて詳細なスタックトレースを取得できるようになります。

変更は、Goランタイムのsrc/pkg/runtime/symtab.cファイル内のruntime·showframe関数に集中しています。この関数は、特定のスタックフレームをスタックトレースに表示するかどうかを決定する役割を担っています。

変更前は、runtime·showframe関数は以下のロジックに基づいていました。

bool
runtime·showframe(Func *f)
{
	// return 1;  // for debugging - show all frames
	return contains(f->name, ".") && !hasprefix(f->name, "runtime.");
}

このロジックは、「関数名にドット(.)が含まれており、かつruntime.で始まらない関数名を持つフレームのみを表示する」というものでした。これは、ユーザーコードの関数名には通常ドットが含まれ(例: main.mainpackage.Function)、ランタイム関数はruntime.で始まるというGoの命名規則に基づいています。したがって、このロジックはデフォルトでランタイムルーチンを隠蔽していました。コメントアウトされた行は、デバッグ時にすべてのフレームを表示するためのフックとして存在していましたが、これはコンパイル時に変更する必要がありました。

このコミットでは、runtime·showframe関数にGOTRACEBACKの値を考慮するロジックが追加されました。

bool
runtime·showframe(Func *f)
{
	static int32 traceback = -1;
	
	if(traceback < 0)
		traceback = runtime·gotraceback();
	return traceback > 1 || contains(f->name, ".") && !hasprefix(f->name, "runtime.");
}

新しいロジックでは、tracebackという静的変数が導入され、runtime·gotraceback()関数を呼び出してGOTRACEBACK環境変数の値を取得します。この値は一度だけ取得され、以降の呼び出しではキャッシュされた値が使用されます。

そして、スタックフレームを表示するかどうかの条件がtraceback > 1 || (contains(f->name, ".") && !hasprefix(f->name, "runtime."))に変更されました。

この条件は次のように解釈されます。

  • traceback > 1: もしGOTRACEBACKの値が2以上であれば(つまりGOTRACEBACK=2が設定されていれば)、この条件が真となり、ランタイムルーチンであるかどうかにかかわらず、すべてのフレームが表示されます。
  • contains(f->name, ".") && !hasprefix(f->name, "runtime."): もしGOTRACEBACKの値が2未満であれば(つまりデフォルトの挙動やGOTRACEBACK=0GOTRACEBACK=1の場合)、以前のロジックが適用され、ランタイムルーチンは隠蔽されます。

この変更により、GOTRACEBACK=2を設定するだけで、ランタイムの再コンパイルなしに、ランタイムルーチンを含む完全なスタックトレースを取得できるようになりました。これは、Goプログラムのデバッグ能力を大幅に向上させるものです。

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

変更はsrc/pkg/runtime/symtab.cファイル内のruntime·showframe関数にあります。

--- a/src/pkg/runtime/symtab.c
+++ b/src/pkg/runtime/symtab.c
@@ -507,6 +507,9 @@ contains(String s, int8 *p)
 bool
 runtime·showframe(Func *f)
 {
-	// return 1;  // for debugging - show all frames
-	return contains(f->name, ".") && !hasprefix(f->name, "runtime.");
+	static int32 traceback = -1;
+	
+	if(traceback < 0)
+		traceback = runtime·gotraceback();
+	return traceback > 1 || contains(f->name, ".") && !hasprefix(f->name, "runtime.");
 }

コアとなるコードの解説

runtime·showframe関数は、Goランタイムがスタックトレースを生成する際に、個々の関数フレームを表示するかどうかを決定するために呼び出されます。

変更前は、この関数は単純に以下の条件に基づいていました。 contains(f->name, ".") && !hasprefix(f->name, "runtime.") これは、「関数名にドットが含まれており、かつruntime.で始まらない」という条件で、ユーザーコードのフレームのみを表示し、ランタイムの内部フレームを隠蔽していました。

変更後、以下のロジックが追加されました。

  1. static int32 traceback = -1;: tracebackという静的変数が導入されました。静的変数であるため、この関数が複数回呼び出されても、その値は保持されます。初期値-1は、まだGOTRACEBACKの値が読み込まれていないことを示します。
  2. if(traceback < 0) traceback = runtime·gotraceback();: この条件は、traceback変数がまだ初期化されていない場合(つまり、関数が初めて呼び出された場合)にのみ実行されます。runtime·gotraceback()関数は、GOTRACEBACK環境変数の現在の値を読み取り、その整数値を返します。この値はtraceback変数に格納され、以降の呼び出しで再利用されます。これにより、環境変数を何度も読み取るオーバーヘッドが回避されます。
  3. return traceback > 1 || contains(f->name, ".") && !hasprefix(f->name, "runtime.");: これが新しいフレーム表示の決定ロジックです。
    • traceback > 1: もしGOTRACEBACKの値が2以上であれば(例: GOTRACEBACK=2)、この部分が真となり、後続の条件に関わらず、そのフレームは表示されます。これは、ランタイムルーチンを含むすべてのフレームを表示するという新しい挙動を実装しています。
    • ||: 論理OR演算子です。
    • contains(f->name, ".") && !hasprefix(f->name, "runtime."): traceback > 1が偽の場合(つまり、GOTRACEBACK01、または設定されていない場合)、この以前のロジックが適用されます。これにより、デフォルトの挙動(ランタイムルーチンを隠蔽する)が維持されます。

この変更により、GOTRACEBACK環境変数の値に応じて、スタックトレースの冗長性を動的に制御できるようになり、特にデバッグ時にランタイムの挙動を詳細に調査する柔軟性が向上しました。

関連リンク

参考にした情報源リンク

  • コミットメッセージの内容
  • Go言語のドキュメント(GOTRACEBACK環境変数に関する一般的な情報)
  • Go言語のソースコード(src/pkg/runtime/symtab.c
  • スタックトレースに関する一般的なプログラミング概念