[インデックス 16660] ファイルの概要
このコミットは、Goランタイムにおけるトレースバック時の関数引数表示のバグを修正するものです。具体的には、スタックトレースにおいて関数呼び出しの引数を表示する際に、本来よりも多くの引数を表示してしまったり、引数が省略されることを示す「...」の表示が不正確であったりする問題を解決します。
コミット
runtime: fix argument printing during traceback
Current code can print more arguments than necessary
and also incorrectly prints "...".
Update #5723.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/10689043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eac6bee7c1aba48dd4f677dc354acba003d75d41
元コミット内容
runtime: fix argument printing during traceback
Current code can print more arguments than necessary
and also incorrectly prints "...".
Update #5723.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/10689043
変更の背景
この変更は、Goランタイムが生成するスタックトレース(特にパニック発生時など)において、関数呼び出しの引数情報が正しく表示されないという問題(Issue 5723)に対応するために行われました。
従来のコードでは、関数引数を表示する際に、実際には存在しない余分な引数まで表示してしまう可能性がありました。これは、引数の数を決定するために使用される情報が不正確であったためと考えられます。また、引数が多すぎて全てを表示できない場合に「...」と省略表示するロジックも不適切であり、正しく省略が行われない、あるいは不必要な省略が行われるという問題がありました。
これらの問題は、デバッグ時にスタックトレースから正確な情報を得ることを妨げ、開発者の生産性を低下させる可能性がありました。そのため、トレースバックの正確性を向上させ、より信頼性の高いデバッグ情報を提供するために、この修正が導入されました。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムに関する基本的な知識が必要です。
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステムです。ガベージコレクション、スケジューリング、スタック管理、トレースバック生成など、Go言語の多くの重要な機能を提供します。C言語で書かれた部分が多く、特にパフォーマンスが要求される部分やOSとのインタラクションが必要な部分でCが使用されます。
- トレースバック (Traceback / Stack Trace): プログラムがクラッシュしたり、パニックが発生したりした際に、その時点での関数呼び出しの履歴(コールスタック)を表示する機能です。どの関数がどの関数を呼び出し、最終的にどこで問題が発生したのかを特定するのに役立ちます。
- スタックフレーム (Stack Frame): 関数が呼び出されるたびに、その関数のローカル変数、引数、戻りアドレスなどがスタック上に確保される領域です。トレースバックは、これらのスタックフレームを遡って情報を収集します。
- 関数引数 (Function Arguments): 関数に渡される値です。Goランタイムは、トレースバック時にこれらの引数の値を表示しようとします。
uintptr
: Go言語におけるポインタ型の一つで、任意のポインタ値を保持できる整数型です。主に低レベルの操作や、ポインタと整数間の変換が必要な場合に使用されます。ここでは、引数の値を汎用的に扱うために使用されています。runtime·printf
,runtime·prints
,runtime·printhex
: これらはGoランタイム内部で使用される低レベルの出力関数です。runtime·printf
: フォーマットされた文字列を出力します。runtime·prints
: 文字列を出力します。runtime·printhex
: 16進数形式で値を出力します。トレースバックでは、引数の値を16進数で表示することが一般的です。
f->args
とframe.arglen
:f->args
: 関数(Func
構造体)が持つ引数の合計サイズ(バイト単位)を示すフィールドです。これは関数のシグネチャから静的に決定される情報であり、可変長引数(...
)を持つ関数では、実際に渡された引数の数とは異なる場合があります。frame.arglen
: 現在のスタックフレーム(Stkframe
構造体)における引数の実際の長さ(バイト単位)を示すフィールドです。これは実行時に実際に渡された引数の数に基づいて計算されるため、より正確な情報を提供します。
技術的詳細
このコミットの技術的な核心は、トレースバック時に表示する関数引数の数を決定するロジックの改善と、引数省略表示(...
)の条件の調整にあります。
1. 引数数の正確な取得:
従来のコードでは、関数引数の数を決定するために f->args
を使用していました。f->args
は関数のシグネチャに基づいて静的に決定される引数の合計サイズであり、可変長引数を持つ関数など、実行時に実際に渡される引数の数が異なる場合に不正確な情報となる可能性がありました。
この修正では、f->args/sizeof(uintptr)
を frame.arglen/sizeof(uintptr)
に変更しています。frame.arglen
は現在のスタックフレームにおける引数の実際の長さ(バイト単位)を表しており、実行時に渡された引数の数に基づいて計算されます。これにより、実際に渡された引数の数だけを正確にループ処理できるようになり、余分な引数が表示される問題が解決されます。sizeof(uintptr)
で割ることで、バイト単位の長さを uintptr
型の引数の数に変換しています。
2. 引数省略表示のロジック変更:
従来のコードでは、引数が4つ以上ある場合に ", ..."
を表示し、ループを中断していました (if(i >= 4)
)。この条件が、特定の状況下で不正確な省略表示を引き起こしていました。
修正後では、この条件が if(i >= 5)
に変更されています。これにより、5つ以上の引数がある場合に ", ..."
が表示されるようになります。この変更は、Goの慣習や、より自然な引数表示の閾値に合わせたものと考えられます。
3. 引数間の区切り文字の表示順序:
従来のコードでは、引数の値を出力した後に if(i != 0) runtime·prints(", ");
で区切り文字の ,
を出力していました。これは、最初の引数の前に不要な ,
が表示されるのを防ぐためのロジックですが、if(i >= 4)
のチェックと break
の位置によっては、最後の引数の後に , ...
が表示される際に、その前に余分な ,
が入ってしまう可能性がありました。
修正後では、if(i != 0) runtime·prints(", ");
の行が runtime·printhex(((uintptr*)frame.argp)[i]);
の前に移動されています。これにより、2番目以降の引数を表示する前に必ず ,
が挿入されるようになり、引数間の区切りがより正確かつ一貫して行われるようになります。
これらの変更は、ARMアーキテクチャ (traceback_arm.c
) と x86アーキテクチャ (traceback_x86.c
) の両方に適用されており、Goがサポートする主要なプラットフォームでトレースバックの正確性が向上します。
コアとなるコードの変更箇所
変更は src/pkg/runtime/traceback_arm.c
と src/pkg/runtime/traceback_x86.c
の両ファイルで行われています。両ファイルでの変更内容は同一です。
--- a/src/pkg/runtime/traceback_arm.c
+++ b/src/pkg/runtime/traceback_arm.c
@@ -139,14 +139,14 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,
if(n > 0 && frame.pc > f->entry && !waspanic)
tracepc -= sizeof(uintptr);
runtime·printf("%S(", f->name);
- for(i = 0; i < f->args/sizeof(uintptr); i++) {
- if(i != 0)
- runtime·prints(", ");
- runtime·printhex(((uintptr*)frame.argp)[i]);
- if(i >= 4) {
+ for(i = 0; i < frame.arglen/sizeof(uintptr); i++) {
+ if(i >= 5) {
runtime·prints(", ...");
break;
}
+ if(i != 0)
+ runtime·prints(", ");
+ runtime·printhex(((uintptr*)frame.argp)[i]);
}
runtime·prints(")\\n");
runtime·printf("\t%S:%d", f->src, runtime·funcline(f, tracepc));
コアとなるコードの解説
変更されたループ部分に焦点を当てて解説します。
変更前:
for(i = 0; i < f->args/sizeof(uintptr); i++) { // ループ条件
if(i != 0)
runtime·prints(", ");
runtime·printhex(((uintptr*)frame.argp)[i]);
if(i >= 4) { // 省略表示の条件
runtime·prints(", ...");
break;
}
}
for(i = 0; i < f->args/sizeof(uintptr); i++)
:- ループは
i
を0から開始し、f->args/sizeof(uintptr)
で計算される回数だけ繰り返されます。 f->args
は関数の引数の合計サイズ(バイト単位)であり、sizeof(uintptr)
はuintptr
型のサイズです。この除算は、関数が取りうる引数の最大数をuintptr
の単位で概算しようとしています。しかし、これはあくまで静的な情報であり、可変長引数など、実行時に実際に渡された引数の数とは異なる場合があります。これが「Current code can print more arguments than necessary」の原因でした。
- ループは
if(i >= 4)
:i
が4以上(つまり5番目の引数以降)になった場合に、", ..."
を出力してループを中断します。これは、表示する引数の数を最大4つに制限しようとする意図がありました。
変更後:
for(i = 0; i < frame.arglen/sizeof(uintptr); i++) { // ループ条件
if(i >= 5) { // 省略表示の条件
runtime·prints(", ...");
break;
}
if(i != 0) // 区切り文字の出力
runtime·prints(", ");
runtime·printhex(((uintptr*)frame.argp)[i]);
}
for(i = 0; i < frame.arglen/sizeof(uintptr); i++)
:- ループ条件が
frame.arglen/sizeof(uintptr)
に変更されました。 frame.arglen
は、現在のスタックフレームにおける引数の実際の合計サイズ(バイト単位)です。これは実行時に実際に渡された引数の数に基づいて計算されるため、より正確な引数の数を反映します。これにより、余分な引数を表示する問題が根本的に解決されます。
- ループ条件が
if(i >= 5)
:- 省略表示の条件が
i >= 5
に変更されました。これにより、6番目以降の引数がある場合に", ..."
が表示され、ループが中断されます。つまり、最大5つの引数が表示されるようになります。これは、より多くの引数を表示しつつ、適切な位置で省略を行うための調整です。
- 省略表示の条件が
if(i != 0) runtime·prints(", ");
の移動:- この行が
runtime·printhex
の前に移動されました。これにより、2番目以降の引数を表示する前に必ず,
が挿入されるようになり、引数間の区切りがより正確かつ一貫して行われるようになります。最初の引数の前には,
は表示されません。
- この行が
この変更により、Goランタイムのトレースバック出力がより正確で、デバッグに役立つ情報を提供するようになりました。
関連リンク
- Go Issue 5723: https://code.google.com/p/go/issues/detail?id=5723 (古いGoogle Codeのリンクですが、元のIssueです)
- Go CL 10689043: https://golang.org/cl/10689043 (Gerritの変更リスト)
参考にした情報源リンク
- Go言語のソースコード (特に
src/pkg/runtime/traceback_arm.c
およびsrc/pkg/runtime/traceback_x86.c
の変更履歴) - Go言語のIssueトラッカー (Issue 5723)
- Go言語のGerritコードレビューシステム (CL 10689043)
- Go言語のドキュメントおよびランタイムに関する一般的な知識