[インデックス 1209] ファイルの概要
このコミットは、Go言語のランタイムにおけるトレースバック(スタックトレース)の表示方法を微調整するものです。具体的には、トレースバックで表示されるプログラムカウンタ(PC)の値が、関数呼び出し命令(CALL
命令)の次の行ではなく、CALL
命令自体が記述されている行を指すように修正されています。これにより、デバッグ時の情報がより直感的になります。
コミット
commit ec913c42b3d1a0a7f380aee5c1ce597f0d2f0f07
Author: Rob Pike <r@golang.org>
Date: Thu Nov 20 17:19:45 2008 -0800
tweak pcs in traceback so they point to calling line instead of line after call.
R=rsc
DELTA=2 (0 added, 0 deleted, 2 changed)
OCL=19745
CL=19745
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ec913c42b3d1a0a7f380aee5c1ce597f0d2f0f07
元コミット内容
tweak pcs in traceback so they point to calling line instead of line after call.
変更の背景
この変更の背景には、デバッグ時のユーザーエクスペリエンスの向上が挙げられます。一般的なCPUアーキテクチャ(特にx86/x64)において、CALL
命令が実行される際、リターンアドレスとしてスタックにプッシュされる値は、CALL
命令自体の次の命令のアドレスです。これは、関数から戻ってきたときに実行を再開すべき場所だからです。
しかし、デバッガやトレースバックツールがスタックトレースを表示する際、ユーザーが期待するのは「どの行で関数が呼び出されたか」という情報です。もしPCがCALL
命令の次の行を指している場合、ソースコード上では呼び出し元の行ではなく、その次の行が示されてしまい、混乱を招く可能性があります。
このコミットは、この「PCが指す場所」と「ユーザーが期待する場所」の間のギャップを埋めるために行われました。PCの値を1バイト減算することで、CALL
命令自体のアドレス(またはその直前のバイト)を指すように調整し、結果としてソースコード上の呼び出し元の行に正確に対応させることが目的です。これは、Go言語の初期開発段階におけるランタイムのデバッグ情報表示の改善の一環と考えられます。
前提知識の解説
1. プログラムカウンタ (PC)
プログラムカウンタ(Program Counter, PC)は、CPUのレジスタの一つで、次に実行される命令のアドレスを保持しています。CPUはPCが指すアドレスから命令をフェッチし、実行します。命令が実行されると、PCは通常、次の命令のアドレスに進みます。
2. CALL
命令とリターンアドレス
アセンブリ言語におけるCALL
命令は、サブルーチン(関数)を呼び出すために使用されます。CALL
命令が実行されると、以下の2つの主要な動作が行われます。
- リターンアドレスのプッシュ:
CALL
命令の直後の命令のアドレスがスタックにプッシュされます。これは、呼び出された関数が終了した後に、どこに戻って実行を再開すべきかを示すためです。 - PCの更新: PCが呼び出されるサブルーチンのエントリポイント(開始アドレス)に設定されます。
重要なのは、スタックにプッシュされるリターンアドレスがCALL
命令自体のアドレスではなく、その「次の命令のアドレス」であるという点です。これは、CALL
命令のサイズが可変であることや、パイプライン処理の都合など、CPUアーキテクチャの設計に起因します。
3. トレースバック(スタックトレース)
トレースバック、またはスタックトレースは、プログラムが特定の時点(例えば、エラー発生時やデバッグ時)で実行していた関数呼び出しのシーケンス(履歴)を表示するものです。各エントリは、呼び出し元の関数、その呼び出しが行われたソースコードのファイル名と行番号、そしてプログラムカウンタ(PC)の値などを含みます。これは、プログラムの実行フローを理解し、バグの原因を特定する上で非常に重要なデバッグ情報です。
4. Goランタイム
Go言語は、独自のランタイムシステムを持っています。このランタイムは、ガベージコレクション、ゴルーチン(軽量スレッド)のスケジューリング、スタック管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルの機能を提供します。src/runtime
ディレクトリ内のファイルは、このランタイムのC言語(またはアセンブリ言語)で書かれた部分であり、OSやハードウェアと直接対話します。
技術的詳細
このコミットは、Goランタイムのsrc/runtime/print.c
とsrc/runtime/rt2_amd64.c
の2つのファイルに影響を与えています。これらのファイルは、主にデバッグ目的でプログラムカウンタ(PC)の値を表示する機能に関連しています。
変更の核心は、sys·getcallerpc(p)
またはcallpc
によって取得されたPCの値から1
を減算している点です。
sys·getcallerpc(p)
: この関数は、現在の関数の呼び出し元のPCを取得します。前述の通り、これはCALL
命令の次の命令のアドレスを指します。- 1
: x86/x64アーキテクチャでは、命令は通常1バイト以上で構成されますが、CALL
命令の直前のアドレスを指すように調整することで、ソースコード上のCALL
命令の行にマッピングしやすくなります。これは、多くの場合、CALL
命令が複数バイトで構成されるため、-1
というオフセットが、その命令の開始アドレス(またはその非常に近い位置)を指すようにするための経験的な調整であると考えられます。これにより、デバッグ情報がより正確に、ユーザーが期待する「呼び出し元の行」を指すようになります。
この調整は、特にトレースバックの可読性とデバッグの効率性を向上させるためのものです。
コアとなるコードの変更箇所
src/runtime/print.c
--- a/src/runtime/print.c
+++ b/src/runtime/print.c
@@ -32,7 +32,7 @@ void
sys·printpc(void *p)
{
prints("PC=0x");
- sys·printpointer(sys·getcallerpc(p));
+ sys·printpointer((byte*)sys·getcallerpc(p) - 1);\t// -1 to get to CALL instr.
}
src/runtime/rt2_amd64.c
--- a/src/runtime/rt2_amd64.c
+++ b/src/runtime/rt2_amd64.c
@@ -70,7 +70,7 @@ traceback(uint8 *pc, uint8 *sp, void* r15)
/* print this frame */
prints("0x");
- sys·printpointer(callpc);
+ sys·printpointer(callpc - 1);\t// -1 to get to CALL instr.
prints("?zi\n");
prints("\t");
prints(name);
コアとなるコードの解説
src/runtime/print.c
の変更
sys·printpc(void *p)
関数は、プログラムカウンタ(PC)の値をデバッグ出力として表示するためのものです。- 変更前は
sys·printpointer(sys·getcallerpc(p));
となっており、sys·getcallerpc(p)
が返すPCの値をそのまま表示していました。 - 変更後は
sys·printpointer((byte*)sys·getcallerpc(p) - 1);\t// -1 to get to CALL instr.
となっています。sys·getcallerpc(p)
で取得したPCの値(これはCALL
命令の次の命令のアドレス)をbyte*
にキャストし、そこから1
を減算しています。- コメント
// -1 to get to CALL instr.
が追加されており、この減算がCALL
命令自体のアドレスを指すようにするためのものであることが明示されています。
src/runtime/rt2_amd64.c
の変更
traceback(uint8 *pc, uint8 *sp, void* r15)
関数は、AMD64アーキテクチャにおけるスタックトレースを生成・表示するためのものです。- この関数内で、各スタックフレームの呼び出し元PCを表示する際に、変更前は
sys·printpointer(callpc);
となっていました。 - 変更後は
sys·printpointer(callpc - 1);\t// -1 to get to CALL instr.
となっています。- ここでも同様に、
callpc
(呼び出し元のPC)から1
を減算しています。 - コメントも同様に、
CALL
命令自体のアドレスを指すための調整であることが示されています。
- ここでも同様に、
これらの変更は、Goランタイムが生成するトレースバックのPC表示を、より直感的でデバッグしやすいものにするための、低レベルかつ重要な調整です。
関連リンク
- Go言語の初期コミット履歴: https://github.com/golang/go/commits?author=r@golang.org (Rob Pike氏のコミット履歴)
- Go言語のランタイムソースコード: https://github.com/golang/go/tree/master/src/runtime