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

[インデックス 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.newstackruntime.morestackに制御を戻し、runtime.morestackは元の関数呼び出し元に制御を戻します。

問題は、runtime.morestackruntime.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関数のアセンブリコード内で行われています。

  1. MOVW (g_sched+gobuf_sp)(g), SP: これは、現在のゴルーチン構造体gのスケジューラ情報(g_sched)から、スタックポインタ(gobuf_sp)をロードし、それを現在のスタックポインタレジスタSPに設定しています。これは、newstackを呼び出す前に、スタックをゴルーチンのスケジューラが管理する状態に設定するものです。
  2. BL runtime·newstack(SB): BL (Branch with Link) 命令は、runtime·newstack関数を呼び出します。この命令は、次の命令のアドレス(つまり、BL命令の直後の命令のアドレス)をリンクレジスタ(LR)に保存し、runtime·newstackにジャンプします。
  3. // Not reached, but make sure the return PC from the call to newstack: このコメントは、追加されたRET命令が通常の実行パスでは到達しないことを示しています。
  4. // is still in this function, and not the beginning of the next.: このコメントは、RET命令の目的を説明しています。newstackからのリターンPCが、morestack関数内、具体的にはこのRET命令の場所を指すようにすることで、トレースバックの正確性を保証します。
  5. RET: この命令が追加された主要な変更点です。RET命令は、リンクレジスタ(LR)に保存されているアドレスにジャンプします。newstackが完了してmorestackに戻った際、このRET命令が実行されることで、morestackが正しく元の呼び出し元に戻ることを保証します。これにより、スタックトレースがmorestackを跨いで正確に生成されるようになります。

この修正は、newstackからのリターン後に、morestackがスタックトレースの観点から「正しい」場所で終了し、その後の命令が誤ってトレースバックに含まれることを防ぐためのものです。

関連リンク

  • Go言語のスタック管理に関する詳細な情報:
    • Go's Execution Tracer (Goの実行トレーサーに関するブログ記事。スタック管理の側面にも触れられている可能性があります。)
    • Go runtime source code (Goランタイムのソースコードリポジトリ)

参考にした情報源リンク