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

[インデックス 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.csrc/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表示を、より直感的でデバッグしやすいものにするための、低レベルかつ重要な調整です。

関連リンク

参考にした情報源リンク