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

[インデックス 15932] ファイルの概要

このコミットは、GoランタイムにおけるARMアーキテクチャでのCgoコールバック処理のバグ修正に関するものです。具体的には、runtime.cgocallback_gofunc関数がGoの呼び出し規約から逸脱し、スタックのトップに不要なデータ(ガベージ)を残し、リターンPC(プログラムカウンタ)をローカル変数の上に保存していた問題を解決します。これにより、スタックのアンワインド(コールスタックの遡り)が正しく行われないという問題が発生していました。この変更は、リターンPCをスタックのトップに正しく配置し、不要なセーブ領域を削除することで、ARMにおけるスタックアンワインドの正確性を向上させます。

コミット

commit 4cb921bbf1f5d7d161e45e362be1cce35b73fc8b
Author: Carl Shapiro <cshapiro@google.com>
Date:   Mon Mar 25 14:10:28 2013 -0700

    runtime: store asmcgocall return PC where the ARM unwind expects it
    
    The ARM implementation of runtime.cgocallback_gofunc diverged
    from the calling convention by leaving a word of garbage at
    the top of the stack and storing the return PC above the
    locals.  This change stores the return PC at the top of the
    stack and removes the save area above the locals.
    
    Update #5124
    This CL fixes first part of the ARM issues and added the unwind test.
    
    R=golang-dev, bradfitz, minux.ma, cshapiro, rsc
    CC=golang-dev
    https://golang.org/cl/7728045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b

元コミット内容

runtime: store asmcgocall return PC where the ARM unwind expects it

The ARM implementation of runtime.cgocallback_gofunc diverged
from the calling convention by leaving a word of garbage at
the top of the stack and storing the return PC above the
locals. This change stores the return PC at the top of the
stack and removes the save area above the locals.

Update #5124
This CL fixes first part of the ARM issues and added the unwind test.

変更の背景

この変更は、GoのランタイムがCgo(C言語との相互運用機能)を介してCコードからGoコードへコールバックする際の、ARMアーキテクチャ特有の問題を解決するために行われました。具体的には、GoのIssue #5124で報告された問題に対応しています。

Issue #5124は、ARM環境でCgoコールバックを使用すると、runtime.Callers関数(現在のゴルーチンのコールスタックをトレースする関数)が正しく機能しないというバグを指摘していました。これは、runtime.cgocallback_gofuncというアセンブリ関数が、Goの標準的な呼び出し規約に準拠していなかったためです。この関数は、CコードからGo関数が呼び出される際にGoランタイムが使用するゲートウェイのような役割を果たします。

問題の核心は、runtime.cgocallback_gofuncがスタックフレームを構築する際に、リターンPC(呼び出し元に戻るアドレス)の配置とスタックポインタの管理に誤りがあったことです。これにより、スタックのトップに不要な「ガベージ」データが残り、リターンPCが予期しない場所に保存されていました。結果として、スタックアンワインド機構(デバッガやプロファイラがコールスタックを遡るために使用するメカニズム)が混乱し、runtime.Callersが不正確なコールスタック情報を提供する原因となっていました。

このコミットは、Issue #5124の最初の部分を修正し、スタックアンワインドのテストを追加することで、この問題に対処しています。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

  • Goランタイム (Go Runtime): Goプログラムの実行を管理するシステム。スケジューラ、ガベージコレクタ、スタック管理などが含まれます。
  • Cgo: GoプログラムからC言語の関数を呼び出したり、C言語からGoの関数を呼び出したりするためのGoの機能です。GoとCの間のインターフェースを提供します。
  • スタック (Stack): プログラム実行中に、関数呼び出しの引数、ローカル変数、リターンアドレスなどを一時的に保存するために使用されるメモリ領域です。関数が呼び出されるたびにスタックフレームがプッシュされ、関数から戻るたびにポップされます。
  • スタックフレーム (Stack Frame): 関数が呼び出されたときにスタック上に確保される領域で、その関数の実行に必要な情報(引数、ローカル変数、リターンアドレス、保存されたレジスタなど)を格納します。
  • リターンPC (Return Program Counter): 関数が呼び出された後、その関数が終了したときに実行を再開すべき命令のアドレス(プログラムカウンタの値)です。通常、スタックフレームの一部として保存されます。
  • スタックアンワインド (Stack Unwinding): コールスタックを逆方向に辿り、現在実行中の関数から呼び出し元の関数へと遡っていくプロセスです。デバッグ、エラーハンドリング(パニック/例外処理)、プロファイリングなどで使用されます。スタックフレームの構造とリターンPCの正確な配置に依存します。
  • 呼び出し規約 (Calling Convention): 関数が呼び出される際に、引数をどのように渡し、戻り値をどのように返し、レジスタをどのように保存・復元するかなど、関数呼び出しに関する取り決めです。アーキテクチャやコンパイラによって異なります。
  • ARMアーキテクチャ: モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのCPUアーキテクチャです。GoはARMを含む複数のアーキテクチャをサポートしています。
  • アセンブリ言語 (Assembly Language): 特定のCPUアーキテクチャの命令セットに直接対応する低レベルのプログラミング言語です。Goランタイムのパフォーマンスクリティカルな部分や、OSとのインターフェース部分はアセンブリで記述されることがあります。
  • **runtime.cgocallback_gofunc**: Cgoコールバックにおいて、CコードからGo関数が呼び出される際にGoランタイムが使用するアセンブリ関数です。この関数は、CスタックからGoスタックへの切り替えや、Goスケジューラへの連携など、重要な役割を担います。
  • **runtime.Callers**: Goの標準ライブラリruntimeパッケージに含まれる関数で、現在のゴルーチンのコールスタックをトレースし、各スタックフレームのリターンPCを取得するために使用されます。

技術的詳細

このコミットの技術的な詳細は、ARMアーキテクチャにおけるスタックフレームの管理と呼び出し規約の遵守に焦点を当てています。

Goのruntime.cgocallback_gofunc関数は、CコードからGo関数が呼び出されたときに実行されます。この関数は、CスタックからGoスタックへの切り替えを行い、Goのスケジューラに制御を渡す役割を担います。しかし、ARMの実装では、以下の問題がありました。

  1. スタックトップのガベージワード: runtime.cgocallback_gofuncがスタックフレームをセットアップする際に、スタックのトップに1ワード分の不要なデータ(ガベージ)を残していました。これは、スタックポインタ(SP)の調整が不適切だったためと考えられます。
  2. リターンPCの誤った配置: 通常、リターンPCはスタックフレームの特定のオフセットに保存され、スタックアンワインド機構がそれを参照して呼び出し元を特定します。しかし、ARMのruntime.cgocallback_gofuncでは、リターンPCがローカル変数の上に保存されており、これはGoの呼び出し規約やスタックアンワインド機構が期待する場所ではありませんでした。

これらの問題により、runtime.Callersのようなスタックトレースを行うツールが、Cgoコールバックを介したGo関数呼び出しのスタックを正しく遡ることができませんでした。特に、runtime.cgocall(GoからCを呼び出す関数)のフレームを越えてトレースすることが困難でした。

このコミットは、これらの問題を解決するために、src/pkg/runtime/asm_arm.s内のruntime·cgocallback_gofuncアセンブリコードを修正します。

  • スタックフレームサイズの調整: 関数のフレームサイズを$16から$12に減らしています。これは、不要なガベージワードの削除と、スタック上のセーブ領域の最適化を示唆しています。
  • リターンPCの配置修正: リターンPC(gobuf.pc)をスタックの正しい位置(MOVW.W R5, -16(R4))にプッシュするように変更しています。これにより、スタックアンワインド機構が期待する場所にリターンPCが配置されるようになります。
  • スタックポインタの調整: スタックポインタ(R13)の調整ロジックが変更され、不要なオフセットが削除されています(例: ADD $(12+4), R13, R4)。これにより、スタックがクリーンに保たれ、ガベージデータが残らないようになります。
  • 引数のロード順序の変更: fn, frame, framesizeといった引数のロード順序とオフセットが変更されています。これは、スタックフレームのレイアウト変更に伴う調整です。

これらの変更により、runtime.cgocallback_gofuncはGoの呼び出し規約に準拠し、スタックアンワインドが正しく機能するようになります。また、この修正を検証するために、misc/cgo/test/callback.goに新しいテストケースtestCallbackCallersが追加されました。このテストは、Cgoコールバックを介したGo関数呼び出しのスタックトレースが正しく行われることを確認します。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は、以下のファイルに集中しています。

  1. src/pkg/runtime/asm_arm.s: ARMアーキテクチャ向けのアセンブリコードファイル。runtime·cgocallback_gofunc関数の実装が変更されています。
  2. misc/cgo/test/callback.go: Cgoコールバック機能のテストファイル。新しいテスト関数testCallbackCallersが追加されています。
  3. misc/cgo/test/cgo_test.go: Cgoテストスイートのエントリポイント。testCallbackCallersがテストスイートに追加されています。

src/pkg/runtime/asm_arm.s の変更点

runtime·cgocallback_gofunc関数の定義と内部のスタック操作が変更されています。

--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -326,7 +326,7 @@ TEXT runtime·cgocallback(SB),7,$12
 
 // cgocallback_gofunc(void (*fn)(void*), void *frame, uintptr framesize)
 // See cgocall.c for more details.
-TEXT	runtime·cgocallback_gofunc(SB),7,$16
+TEXT	runtime·cgocallback_gofunc(SB),7,$12
 	// Load m and g from thread-local storage.
 	MOVW	_cgo_load_gm(SB), R0
 	CMP	$0, R0
@@ -337,7 +337,7 @@ TEXT	runtime·cgocallback_gofunc(SB),7,$16
 	// In this case, we're running on the thread stack, so there's
 	// lots of space, but the linker doesn't know. Hide the call from
 	// the linker analysis by using an indirect call.
-	MOVW	m, savedm-16(SP)
+	MOVW	m, savedm-12(SP)
 	CMP	$0, m
 	B.NE havem
 	MOVW	$runtime·needm(SB), R0
@@ -348,10 +348,6 @@ havem:
 	// Save current m->g0->sched.sp on stack and then set it to SP.
 	// Save current sp in m->g0->sched.sp in preparation for
 	// switch back to m->curg stack.
-	MOVW	fn+0(FP), R0
-	MOVW	frame+4(FP), R1
-	MOVW	framesize+8(FP), R2
-
 	MOVW	m_g0(m), R3
 	MOVW	(g_sched+gobuf_sp)(R3), R4
 	MOVW.W	R4, -4(R13)
@@ -368,23 +364,23 @@ havem:
 	// This has the added benefit that it looks to the traceback
 	// routine like cgocallbackg is going to return to that
 	// PC (because we defined cgocallbackg to have
-	// a frame size of 16, the same amount that we use below),
+	// a frame size of 12, the same amount that we use below),
 	// so that the traceback will seamlessly trace back into
 	// the earlier calls.
+\tMOVW	fn+4(FP), R0
+\tMOVW	frame+8(FP), R1
+\tMOVW	framesize+12(FP), R2
 
-\t// Save current m->g0->sched.sp on stack and then set it to SP.
 	MOVW	m_curg(m), g
 	MOVW	(g_sched+gobuf_sp)(g), R4 // prepare stack as R4
 
 	// Push gobuf.pc
 	MOVW	(g_sched+gobuf_pc)(g), R5
-\tSUB	$4, R4
-\tMOVW	R5, 0(R4)
+\tMOVW.W	R5, -16(R4)
 
 	// Push arguments to cgocallbackg.
 	// Frame size here must match the frame size above
 	// to trick traceback routines into doing the right thing.
-\tSUB	$16, R4
 	MOVW	R0, 4(R4)
 	MOVW	R1, 8(R4)
 	MOVW	R2, 12(R4)
@@ -394,10 +390,10 @@ havem:
 	BL	runtime·cgocallbackg(SB)
 
 	// Restore g->gobuf (== m->curg->gobuf) from saved values.
-\tMOVW	16(R13), R5
+\tMOVW	0(R13), R5
 	MOVW	R5, (g_sched+gobuf_pc)(g)
-\tADD	$(16+4), R13 // SP clobbered! It is ok!
-\tMOVW	R13, (g_sched+gobuf_sp)(g)
+\tADD	$(12+4), R13, R4
+\tMOVW	R4, (g_sched+gobuf_sp)(g)
 
 	// Switch back to m->g0's stack and restore m->g0->sched.sp.
 	// (Unlike m->curg, the g0 goroutine never uses sched.pc,
@@ -411,7 +407,7 @@ havem:
 
 	// If the m on entry was nil, we called needm above to borrow an m
 	// for the duration of the call. Since the call is over, return it with dropm.
-\tMOVW	savedm-16(SP), R6
+\tMOVW	savedm-12(SP), R6
 	CMP	$0, R6
 	B.NE	3(PC)
 	MOVW	$runtime·dropm(SB), R0

misc/cgo/test/callback.go の変更点

testCallbackCallers関数が追加され、pathstringsパッケージがインポートされています。

--- a/misc/cgo/test/callback.go
+++ b/misc/cgo/test/callback.go
@@ -12,7 +12,9 @@ import "C"
 
 import (
 	"./backdoor"
+	"path"
 	"runtime"
+	"strings"
 	"testing"
 	"unsafe"
 )
@@ -136,3 +138,51 @@ func testBlocking(t *testing.T) {
 		}
 	})
 }
+
+// Test that the stack can be unwound through a call out and call back
+// into Go.
+func testCallbackCallers(t *testing.T) {
+	pc := make([]uintptr, 100)
+	n := 0
+	name := []string{
+		"test.goCallback",
+		"runtime.cgocallbackg",
+		"runtime.cgocallback_gofunc",
+		"return",
+		"runtime.cgocall",
+		"test._Cfunc_callback",
+		"test.nestedCall",
+		"test.testCallbackCallers",
+		"test.TestCallbackCallers",
+		"testing.tRunner",
+		"runtime.goexit",
+	}
+	nestedCall(func() {
+		n = runtime.Callers(2, pc)
+	})
+	// The ARM cannot unwind all the way down to runtime.goexit.
+	// See issue 5124.
+	if n != len(name) && runtime.GOARCH != "arm" {
+		t.Errorf("expected %d frames, got %d", len(name), n)
+	}
+	for i := 0; i < n; i++ {
+		f := runtime.FuncForPC(pc[i])
+		if f == nil {
+			t.Fatalf("expected non-nil Func for pc %p", pc[i])
+		}
+		fname := f.Name()
+		// Remove the prepended pathname from automatically
+		// generated cgo function names.
+		if strings.HasPrefix(fname, "_") {
+			fname = path.Base(f.Name()[1:])
+		}
+		if fname != name[i] {
+			t.Errorf("expected function name %s, got %s", name[i], fname)
+		}
+		// The ARM cannot unwind frames past runtime.cgocall.
+		// See issue 5124.
+		if runtime.GOARCH == "arm" && i == 4 {
+			break
+		}
+	}
+}

misc/cgo/test/cgo_test.go の変更点

TestCallbackCallersがテストスイートに追加されています。

--- a/misc/cgo/test/cgo_test.go
+++ b/misc/cgo/test/cgo_test.go
@@ -36,5 +36,6 @@ func TestBoolAlign(t *testing.T)           { testBoolAlign(t) }\n func Test3729(t *testing.T)                { test3729(t) }\n func Test3775(t *testing.T)                { test3775(t) }\n func TestCthread(t *testing.T)             { testCthread(t) }\n+func TestCallbackCallers(t *testing.T)     { testCallbackCallers(t) }\n \n func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }\n```

## コアとなるコードの解説

`src/pkg/runtime/asm_arm.s`における`runtime·cgocallback_gofunc`の変更がこのコミットの核心です。

1.  **`TEXT runtime·cgocallback_gofunc(SB),7,$16` から `TEXT runtime·cgocallback_gofunc(SB),7,$12` への変更**:
    *   これは、`runtime·cgocallback_gofunc`関数のスタックフレームサイズが16バイトから12バイトに削減されたことを示しています。アセンブリ言語では、`TEXT`ディレクティブの最後の引数は関数のフレームサイズ(ローカル変数や保存されたレジスタのために確保されるスタック領域のサイズ)を示します。この削減は、以前のフレームに存在した不要な「ガベージ」ワードが削除されたことを意味します。

2.  **`MOVW m, savedm-16(SP)` から `MOVW m, savedm-12(SP)` への変更**:
    *   これは、`m`(現在のM、OSスレッドを表すGoランタイムの構造体)をスタックに保存する際のオフセットが変更されたことを示しています。フレームサイズが16バイトから12バイトに減ったことに伴い、スタック上の相対的な位置も調整されています。

3.  **引数ロードの変更**:
    *   変更前は、`fn`, `frame`, `framesize`の引数を`fn+0(FP)`, `frame+4(FP)`, `framesize+8(FP)`からロードしていました。
    *   変更後は、これらのロードが一度削除され、後で`MOVW fn+4(FP), R0`, `MOVW frame+8(FP), R1`, `MOVW framesize+12(FP), R2`として再導入されています。これは、スタックフレームのレイアウトが変更され、引数のオフセットが調整されたことを示しています。特に、`fn`が`+0(FP)`から`+4(FP)`に移動していることから、スタックフレームの先頭に何らかの新しいデータが追加されたか、既存のデータが再配置されたことが推測されます。

4.  **`gobuf.pc`のプッシュ方法の変更**:
    *   変更前:
        ```asm
        MOVW	(g_sched+gobuf_pc)(g), R5
        SUB	$4, R4
        MOVW	R5, 0(R4)
        ```
        ここでは、`gobuf.pc`(ゴルーチンのプログラムカウンタ)をスタックにプッシュする際に、まずスタックポインタ`R4`を4バイト減らし、そのアドレスに`R5`(`gobuf.pc`の値)を保存していました。これは、スタックのトップにリターンPCを配置しようとしていたものの、その後のスタック操作との整合性が取れていなかった可能性があります。
    *   変更後:
        ```asm
        MOVW	(g_sched+gobuf_pc)(g), R5
        MOVW.W	R5, -16(R4)
        ```
        この変更では、`SUB $4, R4`が削除され、代わりに`MOVW.W R5, -16(R4)`が使用されています。これは、`gobuf.pc`を現在のスタックポインタ`R4`から16バイトオフセットした位置に保存することを意味します。このオフセットは、`cgocallbackg`のフレームサイズ(12バイト)と、その後のスタック操作(4バイト)を考慮したもので、スタックアンワインド機構がリターンPCを正しく見つけられるように調整されています。`MOVW.W`は、ワード(4バイト)を書き込む命令です。

5.  **スタックポインタの復元ロジックの変更**:
    *   変更前:
        ```asm
        MOVW	16(R13), R5
        MOVW	R5, (g_sched+gobuf_pc)(g)
        ADD	$(16+4), R13 // SP clobbered! It is ok!
        MOVW	R13, (g_sched+gobuf_sp)(g)
        ```
    *   変更後:
        ```asm
        MOVW	0(R13), R5
        MOVW	R5, (g_sched+gobuf_pc)(g)
        ADD	$(12+4), R13, R4
        MOVW	R4, (g_sched+gobuf_sp)(g)
        ```
        スタックポインタ`R13`の調整と`gobuf.pc`の復元ロジックが変更されています。特に、`ADD $(16+4), R13`が`ADD $(12+4), R13, R4`に変更され、スタックフレームサイズの変更(16から12)が反映されています。これにより、スタックポインタが正しく調整され、スタックの整合性が保たれます。

これらのアセンブリコードの変更は、ARMアーキテクチャにおけるGoのCgoコールバックのスタックフレームレイアウトを修正し、リターンPCがスタックアンワインド機構によって正しく認識されるようにすることを目的としています。これにより、`runtime.Callers`のようなスタックトレース機能が、Cgoを介したGo関数呼び出しのコンテキストでも正確に動作するようになります。

## 関連リンク

*   GitHubコミットページ: [https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b](https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b)
*   Go CL (Change List): [https://golang.org/cl/7728045](https://golang.org/cl/7728045)
*   Go Issue #5124: [https://github.com/golang/go/issues/5124](https://github.com/golang/go/issues/5124)

## 参考にした情報源リンク

*   Go Issue #5124: "runtime: Callers doesn't work through cgocallback on arm" - [https://github.com/golang/go/issues/5124](https://github.com/golang/go/issues/5124)
*   Goのソースコード(特に`src/pkg/runtime/asm_arm.s`)
*   GoのCgoに関するドキュメント
*   ARMアーキテクチャの呼び出し規約に関する一般的な情報 (例: ARM Procedure Call Standard)
*   Goのスタック管理に関する一般的な情報
*   Goのテストフレームワークに関する情報 (testingパッケージ)
*   Goの`runtime.Callers`関数のドキュメント# [インデックス 15932] ファイルの概要

このコミットは、GoランタイムにおけるARMアーキテクチャでのCgoコールバック処理のバグ修正に関するものです。具体的には、`runtime.cgocallback_gofunc`関数がGoの呼び出し規約から逸脱し、スタックのトップに不要なデータ(ガベージ)を残し、リターンPC(プログラムカウンタ)をローカル変数の上に保存していた問題を解決します。これにより、スタックのアンワインド(コールスタックの遡り)が正しく行われないという問題が発生していました。この変更は、リターンPCをスタックのトップに正しく配置し、不要なセーブ領域を削除することで、ARMにおけるスタックアンワインドの正確性を向上させます。

## コミット

commit 4cb921bbf1f5d7d161e45e362be1cce35b73fc8b Author: Carl Shapiro cshapiro@google.com Date: Mon Mar 25 14:10:28 2013 -0700

runtime: store asmcgocall return PC where the ARM unwind expects it

The ARM implementation of runtime.cgocallback_gofunc diverged
from the calling convention by leaving a word of garbage at
the top of the stack and storing the return PC above the
locals.  This change stores the return PC at the top of the
stack and removes the save area above the locals.

Update #5124
This CL fixes first part of the ARM issues and added the unwind test.

R=golang-dev, bradfitz, minux.ma, cshapiro, rsc
CC=golang-dev
https://golang.org/cl/7728045

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b](https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b)

## 元コミット内容

runtime: store asmcgocall return PC where the ARM unwind expects it

The ARM implementation of runtime.cgocallback_gofunc diverged from the calling convention by leaving a word of garbage at the top of the stack and storing the return PC above the locals. This change stores the return PC at the top of the stack and removes the save area above the locals.

Update #5124 This CL fixes first part of the ARM issues and added the unwind test.


## 変更の背景

この変更は、GoのランタイムにおけるCgo(C言語との相互運用機能)を介してCコードからGoコードへコールバックする際の、ARMアーキテクチャ特有の問題を解決するために行われました。具体的には、GoのIssue #5124で報告された問題に対応しています。

Issue #5124は、ARM環境でCgoコールバックを使用すると、`runtime.Callers`関数(現在のゴルーチンのコールスタックをトレースする関数)が正しく機能しないというバグを指摘していました。これは、`runtime.cgocallback_gofunc`というアセンブリ関数が、Goの標準的な呼び出し規約に準拠していなかったためです。この関数は、CコードからGo関数が呼び出される際にGoランタイムが使用するゲートウェイのような役割を果たします。

問題の核心は、`runtime.cgocallback_gofunc`がスタックフレームを構築する際に、リターンPC(呼び出し元に戻るアドレス)の配置とスタックポインタの管理に誤りがあったことです。これにより、スタックのトップに不要な「ガベージ」データが残り、リターンPCが予期しない場所に保存されていました。結果として、スタックアンワインド機構(デバッガやプロファイラがコールスタックを遡るために使用するメカニズム)が混乱し、`runtime.Callers`が不正確なコールスタック情報を提供する原因となっていました。

このコミットは、Issue #5124の最初の部分を修正し、スタックアンワインドのテストを追加することで、この問題に対処しています。

## 前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

*   **Goランタイム (Go Runtime)**: Goプログラムの実行を管理するシステム。スケジューラ、ガベージコレクタ、スタック管理などが含まれます。
*   **Cgo**: GoプログラムからC言語の関数を呼び出したり、C言語からGoの関数を呼び出したりするためのGoの機能です。GoとCの間のインターフェースを提供します。
*   **スタック (Stack)**: プログラム実行中に、関数呼び出しの引数、ローカル変数、リターンアドレスなどを一時的に保存するために使用されるメモリ領域です。関数が呼び出されるたびにスタックフレームがプッシュされ、関数から戻るたびにポップされます。
*   **スタックフレーム (Stack Frame)**: 関数が呼び出されたときにスタック上に確保される領域で、その関数の実行に必要な情報(引数、ローカル変数、リターンアドレス、保存されたレジスタなど)を格納します。
*   **リターンPC (Return Program Counter)**: 関数が呼び出された後、その関数が終了したときに実行を再開すべき命令のアドレス(プログラムカウンタの値)です。通常、スタックフレームの一部として保存されます。
*   **スタックアンワインド (Stack Unwinding)**: コールスタックを逆方向に辿り、現在実行中の関数から呼び出し元の関数へと遡っていくプロセスです。デバッグ、エラーハンドリング(パニック/例外処理)、プロファイリングなどで使用されます。スタックフレームの構造とリターンPCの正確な配置に依存します。
*   **呼び出し規約 (Calling Convention)**: 関数が呼び出される際に、引数をどのように渡し、戻り値をどのように返し、レジスタをどのように保存・復元するかなど、関数呼び出しに関する取り決めです。アーキテクチャやコンパイラによって異なります。
*   **ARMアーキテクチャ**: モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのCPUアーキテクチャです。GoはARMを含む複数のアーキテクチャをサポートしています。
*   **アセンブリ言語 (Assembly Language)**: 特定のCPUアーキテクチャの命令セットに直接対応する低レベルのプログラミング言語です。Goランタイムのパフォーマンスクリティカルな部分や、OSとのインターフェース部分はアセンブリで記述されることがあります。
*   `**runtime.cgocallback_gofunc**`: Cgoコールバックにおいて、CコードからGo関数が呼び出される際にGoランタイムが使用するアセンブリ関数です。この関数は、CスタックからGoスタックへの切り替えや、Goスケジューラへの連携など、重要な役割を担います。
*   `**runtime.Callers**`: Goの標準ライブラリ`runtime`パッケージに含まれる関数で、現在のゴルーチンのコールスタックをトレースし、各スタックフレームのリターンPCを取得するために使用されます。

## 技術的詳細

このコミットの技術的な詳細は、ARMアーキテクチャにおけるスタックフレームの管理と呼び出し規約の遵守に焦点を当てています。

Goの`runtime.cgocallback_gofunc`関数は、CコードからGo関数が呼び出されたときに実行されます。この関数は、CスタックからGoスタックへの切り替えを行い、Goのスケジューラに制御を渡す役割を担います。しかし、ARMの実装では、以下の問題がありました。

1.  **スタックトップのガベージワード**: `runtime.cgocallback_gofunc`がスタックフレームをセットアップする際に、スタックのトップに1ワード分の不要なデータ(ガベージ)を残していました。これは、スタックポインタ(SP)の調整が不適切だったためと考えられます。
2.  **リターンPCの誤った配置**: 通常、リターンPCはスタックフレームの特定のオフセットに保存され、スタックアンワインド機構がそれを参照して呼び出し元を特定します。しかし、ARMの`runtime.cgocallback_gofunc`では、リターンPCがローカル変数の上に保存されており、これはGoの呼び出し規約やスタックアンワインド機構が期待する場所ではありませんでした。

これらの問題により、`runtime.Callers`のようなスタックトレースを行うツールが、Cgoコールバックを介したGo関数呼び出しのスタックを正しく遡ることができませんでした。特に、`runtime.cgocall`(GoからCを呼び出す関数)のフレームを越えてトレースすることが困難でした。

このコミットは、これらの問題を解決するために、`src/pkg/runtime/asm_arm.s`内の`runtime·cgocallback_gofunc`アセンブリコードを修正します。

*   **スタックフレームサイズの調整**: 関数のフレームサイズを`$16`から`$12`に減らしています。これは、不要なガベージワードの削除と、スタック上のセーブ領域の最適化を示唆しています。
*   **リターンPCの配置修正**: リターンPC(`gobuf.pc`)をスタックの正しい位置(`MOVW.W R5, -16(R4)`)にプッシュするように変更しています。これにより、スタックアンワインド機構が期待する場所にリターンPCが配置されるようになります。
*   **スタックポインタの調整**: スタックポインタ(`R13`)の調整ロジックが変更され、不要なオフセットが削除されています(例: `ADD $(12+4), R13, R4`)。これにより、スタックがクリーンに保たれ、ガベージデータが残らないようになります。
*   **引数のロード順序の変更**: `fn`, `frame`, `framesize`といった引数のロード順序とオフセットが変更されています。これは、スタックフレームのレイアウト変更に伴う調整です。

これらの変更により、`runtime.cgocallback_gofunc`はGoの呼び出し規約に準拠し、スタックアンワインドが正しく機能するようになります。また、この修正を検証するために、`misc/cgo/test/callback.go`に新しいテストケース`testCallbackCallers`が追加されました。このテストは、Cgoコールバックを介したGo関数呼び出しのスタックトレースが正しく行われることを確認します。

## コアとなるコードの変更箇所

このコミットにおける主要なコード変更は、以下のファイルに集中しています。

1.  `src/pkg/runtime/asm_arm.s`: ARMアーキテクチャ向けのアセンブリコードファイル。`runtime·cgocallback_gofunc`関数の実装が変更されています。
2.  `misc/cgo/test/callback.go`: Cgoコールバック機能のテストファイル。新しいテスト関数`testCallbackCallers`が追加されています。
3.  `misc/cgo/test/cgo_test.go`: Cgoテストスイートのエントリポイント。`testCallbackCallers`がテストスイートに追加されています。

### `src/pkg/runtime/asm_arm.s` の変更点

`runtime·cgocallback_gofunc`関数の定義と内部のスタック操作が変更されています。

```diff
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -326,7 +326,7 @@ TEXT runtime·cgocallback(SB),7,$12
 
 // cgocallback_gofunc(void (*fn)(void*), void *frame, uintptr framesize)
 // See cgocall.c for more details.
-TEXT	runtime·cgocallback_gofunc(SB),7,$16
+TEXT	runtime·cgocallback_gofunc(SB),7,$12
 	// Load m and g from thread-local storage.
 	MOVW	_cgo_load_gm(SB), R0
 	CMP	$0, R0
@@ -337,7 +337,7 @@ TEXT	runtime·cgocallback_gofunc(SB),7,$16
 	// In this case, we're running on the thread stack, so there's
 	// lots of space, but the linker doesn't know. Hide the call from
 	// the linker analysis by using an indirect call.
-	MOVW	m, savedm-16(SP)
+	MOVW	m, savedm-12(SP)
 	CMP	$0, m
 	B.NE havem
 	MOVW	$runtime·needm(SB), R0
@@ -348,10 +348,6 @@ havem:
 	// Save current m->g0->sched.sp on stack and then set it to SP.
 	// Save current sp in m->g0->sched.sp in preparation for
 	// switch back to m->curg stack.
-	MOVW	fn+0(FP), R0
-	MOVW	frame+4(FP), R1
-	MOVW	framesize+8(FP), R2
-
 	MOVW	m_g0(m), R3
 	MOVW	(g_sched+gobuf_sp)(R3), R4
 	MOVW.W	R4, -4(R13)
@@ -368,23 +364,23 @@ havem:
 	// This has the added benefit that it looks to the traceback
 	// routine like cgocallbackg is going to return to that
 	// PC (because we defined cgocallbackg to have
-	// a frame size of 16, the same amount that we use below),
+	// a frame size of 12, the same amount that we use below),
 	// so that the traceback will seamlessly trace back into
 	// the earlier calls.
+\tMOVW	fn+4(FP), R0
+\tMOVW	frame+8(FP), R1
+\tMOVW	framesize+12(FP), R2
 
-\t// Save current m->g0->sched.sp on stack and then set it to SP.
 	MOVW	m_curg(m), g
 	MOVW	(g_sched+gobuf_sp)(g), R4 // prepare stack as R4
 
 	// Push gobuf.pc
 	MOVW	(g_sched+gobuf_pc)(g), R5
-\tSUB	$4, R4
-\tMOVW	R5, 0(R4)
+\tMOVW.W	R5, -16(R4)
 
 	// Push arguments to cgocallbackg.
 	// Frame size here must match the frame size above
 	// to trick traceback routines into doing the right thing.
-\tSUB	$16, R4
 	MOVW	R0, 4(R4)
 	MOVW	R1, 8(R4)
 	MOVW	R2, 12(R4)
@@ -394,10 +390,10 @@ havem:
 	BL	runtime·cgocallbackg(SB)
 
 	// Restore g->gobuf (== m->curg->gobuf) from saved values.
-\tMOVW	16(R13), R5
+\tMOVW	0(R13), R5
 	MOVW	R5, (g_sched+gobuf_pc)(g)
-\tADD	$(16+4), R13 // SP clobbered! It is ok!
-\tMOVW	R13, (g_sched+gobuf_sp)(g)
+\tADD	$(12+4), R13, R4
+\tMOVW	R4, (g_sched+gobuf_sp)(g)
 
 	// Switch back to m->g0's stack and restore m->g0->sched.sp.
 	// (Unlike m->curg, the g0 goroutine never uses sched.pc,
@@ -411,7 +407,7 @@ havem:
 
 	// If the m on entry was nil, we called needm above to borrow an m
 	// for the duration of the call. Since the call is over, return it with dropm.
-\tMOVW	savedm-16(SP), R6
+\tMOVW	savedm-12(SP), R6
 	CMP	$0, R6
 	B.NE	3(PC)
 	MOVW	$runtime·dropm(SB), R0

misc/cgo/test/callback.go の変更点

testCallbackCallers関数が追加され、pathstringsパッケージがインポートされています。

--- a/misc/cgo/test/callback.go
+++ b/misc/cgo/test/callback.go
@@ -12,7 +12,9 @@ import "C"
 
 import (
 	"./backdoor"
+	"path"
 	"runtime"
+	"strings"
 	"testing"
 	"unsafe"
 )
@@ -136,3 +138,51 @@ func testBlocking(t *testing.T) {
 		}
 	})
 }
+
+// Test that the stack can be unwound through a call out and call back
+// into Go.
+func testCallbackCallers(t *testing.T) {
+	pc := make([]uintptr, 100)
+	n := 0
+	name := []string{
+		"test.goCallback",
+		"runtime.cgocallbackg",
+		"runtime.cgocallback_gofunc",
+		"return",
+		"runtime.cgocall",
+		"test._Cfunc_callback",
+		"test.nestedCall",
+		"test.testCallbackCallers",
+		"test.TestCallbackCallers",
+		"testing.tRunner",
+		"runtime.goexit",
+	}
+	nestedCall(func() {
+		n = runtime.Callers(2, pc)
+	})
+	// The ARM cannot unwind all the way down to runtime.goexit.
+	// See issue 5124.
+	if n != len(name) && runtime.GOARCH != "arm" {
+		t.Errorf("expected %d frames, got %d", len(name), n)
+	}
+	for i := 0; i < n; i++ {
+		f := runtime.FuncForPC(pc[i])
+		if f == nil {
+			t.Fatalf("expected non-nil Func for pc %p", pc[i])
+		}
+		fname := f.Name()
+		// Remove the prepended pathname from automatically
+		// generated cgo function names.
+		if strings.HasPrefix(fname, "_") {
+			fname = path.Base(f.Name()[1:])
+		}
+		if fname != name[i] {
+			t.Errorf("expected function name %s, got %s", name[i], fname)
+		}
+		// The ARM cannot unwind frames past runtime.cgocall.
+		// See issue 5124.
+		if runtime.GOARCH == "arm" && i == 4 {
+			break
+		}
+	}
+}

misc/cgo/test/cgo_test.go の変更点

TestCallbackCallersがテストスイートに追加されています。

--- a/misc/cgo/test/cgo_test.go
+++ b/misc/cgo/test/cgo_test.go
@@ -36,5 +36,6 @@ func TestBoolAlign(t *testing.T)           { testBoolAlign(t) }\n func Test3729(t *testing.T)                { test3729(t) }\n func Test3775(t *testing.T)                { test3775(t) }\n func TestCthread(t *testing.T)             { testCthread(t) }\n+func TestCallbackCallers(t *testing.T)     { testCallbackCallers(t) }\n \n func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }\n```

## コアとなるコードの解説

`src/pkg/runtime/asm_arm.s`における`runtime·cgocallback_gofunc`の変更がこのコミットの核心です。

1.  **`TEXT runtime·cgocallback_gofunc(SB),7,$16` から `TEXT runtime·cgocallback_gofunc(SB),7,$12` への変更**:
    *   これは、`runtime·cgocallback_gofunc`関数のスタックフレームサイズが16バイトから12バイトに削減されたことを示しています。アセンブリ言語では、`TEXT`ディレクティブの最後の引数は関数のフレームサイズ(ローカル変数や保存されたレジスタのために確保されるスタック領域のサイズ)を示します。この削減は、以前のフレームに存在した不要な「ガベージ」ワードが削除されたことを意味します。

2.  **`MOVW m, savedm-16(SP)` から `MOVW m, savedm-12(SP)` への変更**:
    *   これは、`m`(現在のM、OSスレッドを表すGoランタイムの構造体)をスタックに保存する際のオフセットが変更されたことを示しています。フレームサイズが16バイトから12バイトに減ったことに伴い、スタック上の相対的な位置も調整されています。

3.  **引数ロードの変更**:
    *   変更前は、`fn`, `frame`, `framesize`の引数を`fn+0(FP)`, `frame+4(FP)`, `framesize+8(FP)`からロードしていました。
    *   変更後は、これらのロードが一度削除され、後で`MOVW fn+4(FP), R0`, `MOVW frame+8(FP), R1`, `MOVW framesize+12(FP), R2`として再導入されています。これは、スタックフレームのレイアウトが変更され、引数のオフセットが調整されたことを示しています。特に、`fn`が`+0(FP)`から`+4(FP)`に移動していることから、スタックフレームの先頭に何らかの新しいデータが追加されたか、既存のデータが再配置されたことが推測されます。

4.  **`gobuf.pc`のプッシュ方法の変更**:
    *   変更前:
        ```asm
        MOVW	(g_sched+gobuf_pc)(g), R5
        SUB	$4, R4
        MOVW	R5, 0(R4)
        ```
        ここでは、`gobuf.pc`(ゴルーチンのプログラムカウンタ)をスタックにプッシュする際に、まずスタックポインタ`R4`を4バイト減らし、そのアドレスに`R5`(`gobuf.pc`の値)を保存していました。これは、スタックのトップにリターンPCを配置しようとしていたものの、その後のスタック操作との整合性が取れていなかった可能性があります。
    *   変更後:
        ```asm
        MOVW	(g_sched+gobuf_pc)(g), R5
        MOVW.W	R5, -16(R4)
        ```
        この変更では、`SUB $4, R4`が削除され、代わりに`MOVW.W R5, -16(R4)`が使用されています。これは、`gobuf.pc`を現在のスタックポインタ`R4`から16バイトオフセットした位置に保存することを意味します。このオフセットは、`cgocallbackg`のフレームサイズ(12バイト)と、その後のスタック操作(4バイト)を考慮したもので、スタックアンワインド機構がリターンPCを正しく見つけられるように調整されています。`MOVW.W`は、ワード(4バイト)を書き込む命令です。

5.  **スタックポインタの復元ロジックの変更**:
    *   変更前:
        ```asm
        MOVW	16(R13), R5
        MOVW	R5, (g_sched+gobuf_pc)(g)
        ADD	$(16+4), R13 // SP clobbered! It is ok!
        MOVW	R13, (g_sched+gobuf_sp)(g)
        ```
    *   変更後:
        ```asm
        MOVW	0(R13), R5
        MOVW	R5, (g_sched+gobuf_pc)(g)
        ADD	$(12+4), R13, R4
        MOVW	R4, (g_sched+gobuf_sp)(g)
        ```
        スタックポインタ`R13`の調整と`gobuf.pc`の復元ロジックが変更されています。特に、`ADD $(16+4), R13`が`ADD $(12+4), R13, R4`に変更され、スタックフレームサイズの変更(16から12)が反映されています。これにより、スタックポインタが正しく調整され、スタックの整合性が保たれます。

これらのアセンブリコードの変更は、ARMアーキテクチャにおけるGoのCgoコールバックのスタックフレームレイアウトを修正し、リターンPCがスタックアンワインド機構によって正しく認識されるようにすることを目的としています。これにより、`runtime.Callers`のようなスタックトレース機能が、Cgoを介したGo関数呼び出しのコンテキストでも正確に動作するようになります。

## 関連リンク

*   GitHubコミットページ: [https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b](https://github.com/golang/go/commit/4cb921bbf1f5d7d161e45e362be1cce35b73fc8b)
*   Go CL (Change List): [https://golang.org/cl/7728045](https://golang.org/cl/7728045)
*   Go Issue #5124: [https://github.com/golang/go/issues/5124](https://github.com/golang/go/issues/5124)

## 参考にした情報源リンク

*   Go Issue #5124: "runtime: Callers doesn't work through cgocallback on arm" - [https://github.com/golang/go/issues/5124](https://github.com/golang/go/issues/5124)
*   Goのソースコード(特に`src/pkg/runtime/asm_arm.s`)
*   GoのCgoに関するドキュメント
*   ARMアーキテクチャの呼び出し規約に関する一般的な情報 (例: ARM Procedure Call Standard)
*   Goのスタック管理に関する一般的な情報
*   Goのテストフレームワークに関する情報 (testingパッケージ)
*   Goの`runtime.Callers`関数のドキュメント