[インデックス 16990] ファイルの概要
このコミットは、Goランタイムにおけるmorestack
呼び出しを跨いだトレースバックの修正に関するものです。具体的には、ARMアーキテクチャのアセンブリコードにおいて、morestack
からnewstack
への呼び出し後のリターンアドレスが正しく設定されるように変更が加えられています。これにより、スタックの拡張が行われた際に、正確なスタックトレースが得られるようになります。
コミット
commit 13507e0697b2749e49341c25b9c1f5414f88d26e
Author: Russ Cox <rsc@golang.org>
Date: Thu Aug 1 18:51:55 2013 -0400
runtime: fix traceback across morestack
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12287043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/13507e0697b2749e49341c25b9c1f5414f88d26e
元コミット内容
runtime: fix traceback across morestack
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12287043
変更の背景
Go言語のランタイムは、ゴルーチン(goroutine)のスタックを動的に管理します。これは、各ゴルーチンが最初は小さなスタックで開始し、必要に応じてスタックサイズを自動的に拡張するという仕組みです。このスタック拡張のプロセスは、runtime.morestack
というアセンブリ関数によって開始され、その後runtime.newstack
というGo関数が呼び出されて実際のスタックの再割り当てとコピーが行われます。
しかし、このmorestack
からnewstack
への呼び出しを跨ぐ際に、スタックトレース(トレースバック)が正しく生成されないという問題がありました。特に、newstack
が完了して元の実行フローに戻る際のリターンアドレスが、期待される場所(morestack
の呼び出し元)ではなく、次の命令の開始点になってしまう可能性がありました。これにより、デバッグ時などに正確なコールスタックを追跡することが困難になるという問題が発生していました。
このコミットは、このトレースバックの不正確さを修正し、morestack
を介したスタック拡張後も、デバッガやパニック発生時に正確なスタックトレースが得られるようにすることを目的としています。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステム。ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、スタック管理などを担当します。
- ゴルーチン (Goroutine): Go言語における軽量な並行実行単位。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。
- スタック (Stack): プログラムの実行中に、関数呼び出しの引数、ローカル変数、リターンアドレスなどを一時的に格納するメモリ領域。
- 動的スタック拡張 (Dynamic Stack Growth): Goのゴルーチンは、固定サイズのスタックを持つOSスレッドとは異なり、必要に応じてスタックサイズを自動的に拡張します。これにより、メモリ効率が向上し、多数のゴルーチンを効率的に実行できます。
runtime.morestack
: Goランタイムのアセンブリコードで実装された関数。ゴルーチンのスタックが不足しそうになったときに呼び出されます。この関数は、現在のゴルーチンのコンテキストを保存し、runtime.newstack
を呼び出してスタックの拡張を要求します。runtime.newstack
: Go言語で実装された関数。runtime.morestack
から呼び出され、新しいより大きなスタック領域を割り当て、古いスタックの内容を新しいスタックにコピーし、関連するポインタを更新します。- トレースバック (Traceback) / コールスタック (Call Stack): プログラムの実行中に、現在実行中の関数から、その関数を呼び出した関数、さらにその関数を呼び出した関数へと遡って、関数呼び出しの履歴をリストアップしたもの。デバッグやエラー解析に不可欠な情報です。
- リターンアドレス (Return Address): 関数が呼び出された際に、その関数が処理を終えた後に実行を再開すべき命令のアドレス。スタックに保存されます。
- ARMアーキテクチャ (ARM Architecture): モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャ。Goランタイムは、様々なアーキテクチャに対応しており、このコミットはARM向けのアセンブリコードに特化しています。
- アセンブリ言語 (Assembly Language): 特定のCPUアーキテクチャの命令セットに直接対応する低レベルのプログラミング言語。Goランタイムのパフォーマンスが重要な部分はアセンブリで記述されることがあります。
技術的詳細
Goランタイムのスタック拡張メカニズムでは、各Go関数のプロローグ(関数の冒頭部分)に「スタックガードチェック」と呼ばれるコードが挿入されます。これは、現在のスタックポインタがスタックガード(スタックの限界を示す値)を超えていないかを確認するものです。もしスタックガードを超えていれば、スタックが不足していると判断され、runtime.morestack
が呼び出されます。
runtime.morestack
はアセンブリで書かれており、現在のゴルーチンのレジスタ状態などを保存した後、runtime.newstack
というGo関数を呼び出します。runtime.newstack
は、より大きなスタックを確保し、古いスタックの内容を新しいスタックにコピーし、スタックポインタを更新します。この一連の処理が完了すると、runtime.newstack
はruntime.morestack
に制御を戻し、runtime.morestack
は元の関数呼び出し元に制御を戻します。
問題は、runtime.morestack
がruntime.newstack
を呼び出した後、そのリターンアドレスがスタック上にどのように配置されるか、そしてnewstack
からのリターン後にmorestack
がどのように元の実行フローに戻るか、という点にありました。特にARMのような一部のアーキテクチャでは、BL
(Branch with Link) 命令がリターンアドレスをリンクレジスタ (LR) に格納しますが、その後の処理でLRが上書きされたり、スタックトレース生成時にLRの正しい値が取得できなかったりする可能性がありました。
このコミットでは、runtime.newstack
の呼び出し後にRET
命令を追加することで、この問題を解決しています。RET
命令は、スタックからリターンアドレスをポップして、そのアドレスにジャンプする命令です。これにより、newstack
からのリターン後、morestack
が明示的に正しいリターンアドレスを使用して元の呼び出し元に戻ることを保証します。コメントにある「Not reached, but make sure the return PC from the call to newstack is still in this function, and not the beginning of the next.」は、このRET
命令が直接実行されることはないが、デバッグ情報やトレースバック生成の際に、newstack
からのリターンPC(プログラムカウンタ)がmorestack
関数内に正しく位置することを保証するためのものであることを示唆しています。これにより、スタックトレースがmorestack
を跨いで正確に追跡できるようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/asm_arm.s
ファイルにあります。
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -204,6 +204,10 @@ TEXT runtime·morestack(SB),7,$-4-0
MOVW (g_sched+gobuf_sp)(g), SP
BL runtime·newstack(SB)
+ // Not reached, but make sure the return PC from the call to newstack
+ // is still in this function, and not the beginning of the next.
+ RET
+
// Called from reflection library. Mimics morestack,
// reuses stack growth code to create a frame
// with the desired args running the desired function.
コアとなるコードの解説
変更はruntime·morestack
関数のアセンブリコード内で行われています。
MOVW (g_sched+gobuf_sp)(g), SP
: これは、現在のゴルーチン構造体g
のスケジューラ情報(g_sched
)から、スタックポインタ(gobuf_sp
)をロードし、それを現在のスタックポインタレジスタSP
に設定しています。これは、newstack
を呼び出す前に、スタックをゴルーチンのスケジューラが管理する状態に設定するものです。BL runtime·newstack(SB)
:BL
(Branch with Link) 命令は、runtime·newstack
関数を呼び出します。この命令は、次の命令のアドレス(つまり、BL
命令の直後の命令のアドレス)をリンクレジスタ(LR)に保存し、runtime·newstack
にジャンプします。// Not reached, but make sure the return PC from the call to newstack
: このコメントは、追加されたRET
命令が通常の実行パスでは到達しないことを示しています。// is still in this function, and not the beginning of the next.
: このコメントは、RET
命令の目的を説明しています。newstack
からのリターンPCが、morestack
関数内、具体的にはこのRET
命令の場所を指すようにすることで、トレースバックの正確性を保証します。RET
: この命令が追加された主要な変更点です。RET
命令は、リンクレジスタ(LR)に保存されているアドレスにジャンプします。newstack
が完了してmorestack
に戻った際、このRET
命令が実行されることで、morestack
が正しく元の呼び出し元に戻ることを保証します。これにより、スタックトレースがmorestack
を跨いで正確に生成されるようになります。
この修正は、newstack
からのリターン後に、morestack
がスタックトレースの観点から「正しい」場所で終了し、その後の命令が誤ってトレースバックに含まれることを防ぐためのものです。
関連リンク
- Go言語のスタック管理に関する詳細な情報:
- Go's Execution Tracer (Goの実行トレーサーに関するブログ記事。スタック管理の側面にも触れられている可能性があります。)
- Go runtime source code (Goランタイムのソースコードリポジトリ)
参考にした情報源リンク
- Go runtime morestack traceback - Google Search Results (この解説の作成にあたり、
runtime.morestack
の役割とトレースバックにおけるその出現に関する一般的な理解を深めるために参照しました。) - https://golang.org/cl/12287043 (元の変更リスト)
- Go's Execution Tracer
- Go runtime source code
- Go's dynamic stack growth (Goの動的スタック成長に関する公式ドキュメントやブログ記事があれば、より詳細な情報源となります。)
- Understanding Go's runtime.morestack (Goの
runtime.morestack
に関する解説記事があれば、より詳細な情報源となります。)