[インデックス 15528] ファイルの概要
このコミットは、Go言語のランタイムにおけるCgo(C言語との相互運用機能)のcrosscall2
関数がARMアーキテクチャ上で正しく動作しない問題を修正するものです。具体的には、x_cgo_load_gm
関数の呼び出し方法を修正し、より堅牢な方法で関数アドレスをロードするように変更しています。
コミット
commit 776b51850b7cb4b1f24f9c4c4de329d4f439d1e5
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 1 05:02:41 2013 -0800
runtime/cgo: fix crosscall2 on arm
This time for sure.
R=golang-dev
CC=golang-dev
https://golang.org/cl/7449045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/776b51850b7cb4b1f24f9c4c4de329d4f439d1e5
元コミット内容
runtime/cgo: fix crosscall2 on arm
「今度こそは確実に。」というコメントが添えられており、以前にも同様の修正が試みられたが、完全には解決していなかったことを示唆しています。
変更の背景
Go言語は、C言語で書かれたライブラリを呼び出すためのCgoというメカニズムを提供しています。このCgoの内部では、GoのランタイムとCのコード間でスタックやレジスタの状態を適切に切り替えるための「クロスコール」と呼ばれる処理が行われます。crosscall2
はそのようなクロスコールの一つで、特にGoからCへ、またはCからGoへ制御が移る際に重要な役割を果たします。
ARMアーキテクチャにおいて、このcrosscall2
の動作に問題がありました。具体的な問題は、x_cgo_load_gm
という関数(おそらくGoのグローバル変数やランタイムの状態をロードするためのCgo内部関数)を呼び出す際に、その関数のアドレスの解決方法に起因していたと考えられます。
コミットメッセージの「This time for sure.(今度こそは確実に)」という表現から、この問題が以前にも修正が試みられたものの、完全に解決されていなかったか、あるいは特定の条件下で再発していたことが伺えます。このコミットは、その根本的な原因に対処し、ARM上でのCgoの安定性を向上させることを目的としています。
前提知識の解説
Cgo (C Foreign Function Interface for Go)
Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリをGoプロジェクトで再利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。Cgoは、Goのビルドプロセスの一部として、GoとCの間のブリッジコードを生成します。
Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなどが含まれます。Cgoを使用する場合、GoランタイムはCコードとの間でコンテキストの切り替え(スタックの切り替え、レジスタの保存と復元など)を調整する役割を担います。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。ARMプロセッサは、その電力効率の高さと性能のバランスから、スマートフォン、タブレット、IoTデバイスなどで普及しています。
ARMアーキテクチャのプログラミングモデルでは、レジスタの使用方法、関数呼び出し規約(引数の渡し方、戻り値の受け取り方)、スタックフレームの管理などが定義されています。アセンブリ言語でプログラミングする際には、これらの規約を厳密に守る必要があります。
アセンブリ言語 (ARM)
アセンブリ言語は、CPUが直接実行できる機械語に1対1で対応する低レベルのプログラミング言語です。ARMアセンブリは、ARMプロセッサの命令セットに基づいています。
- レジスタ: ARMプロセッサには、R0からR15までの汎用レジスタがあります。
- R0-R3: 関数呼び出しの引数や戻り値に使用されることが多い。
- R13 (SP): スタックポインタ。スタックの現在のトップを指す。
- R14 (LR): リンクレジスタ。関数呼び出し時に戻りアドレスを保存する。
- R15 (PC): プログラムカウンタ。次に実行される命令のアドレスを指す。
- 命令:
MOVM
: 複数のレジスタをメモリに保存(MOVM.WP
)またはメモリから復元(MOVM.IAW
)する命令。スタックフレームの保存と復元によく使われます。MOVM.WP [Rn], {reglist}
:Rn
が指すアドレスにレジスタリストreglist
の内容を書き込み、Rn
を更新(書き込んだバイト数だけ減らす)。スタックにプッシュする際に使用。MOVM.IAW [Rn], {reglist}
:Rn
が指すアドレスからレジスタリストreglist
の内容を読み込み、Rn
を更新(読み込んだバイト数だけ増やす)。スタックからポップする際に使用。
BL (Branch with Link)
: 関数呼び出し命令。指定されたアドレスにジャンプし、現在のプログラムカウンタ(PC)の次の命令のアドレスをリンクレジスタ(LR, R14)に保存します。これにより、関数から戻る際に元の実行フローに戻ることができます。MOVW
: 32ビットの値をレジスタにロードする命令。(SB)
: シンボルベースのアドレス指定。Goのアセンブリでは、SB
は静的セグメントの開始アドレスを指し、グローバル変数や関数のアドレスを相対的に指定するために使用されます。(R0)
: レジスタR0に格納されているアドレスへの間接ジャンプ。
Position-Independent Code (PIC)
PIC(位置独立コード)は、メモリ上のどこにロードされても正しく実行できるコードのことです。共有ライブラリ(DLLや.soファイル)では、ロードされるアドレスが実行時までわからないため、PICが不可欠です。PICでは、絶対アドレスではなく、相対アドレスやレジスタを介した間接アドレス指定が多用されます。
技術的詳細
このコミットの核心は、ARMアーキテクチャにおける関数呼び出しのメカニズム、特にグローバルシンボル(関数アドレス)の解決方法に関するものです。
元のコードでは、BL x_cgo_load_gm(SB)
という命令を使用していました。これは、x_cgo_load_gm
というシンボル(関数)に直接ジャンプし、その戻りアドレスをリンクレジスタ(R14)に保存する命令です。しかし、ARMアーキテクチャ、特に特定のコンテキスト(例えば、位置独立コードが要求される場合や、シンボルが非常に遠いアドレスにある場合)では、BL
命令が直接シンボルアドレスを解決できない、または正しく解決できないケースが存在します。
GoのランタイムやCgoのような低レベルのコードでは、アセンブリ言語が使用されます。アセンブリ言語では、命令が直接CPUの動作を制御するため、アーキテクチャ固有の特性や命令セットの制約を深く理解している必要があります。
修正後のコードでは、MOVW _cgo_load_gm(SB), R0
とBL (R0)
という2つの命令に分割されています。
MOVW _cgo_load_gm(SB), R0
:_cgo_load_gm(SB)
は、x_cgo_load_gm
関数のアドレスを指すシンボルです。- この命令は、
_cgo_load_gm
のアドレスをレジスタR0にロードします。MOVW
命令は、32ビットの値をレジスタにロードするために使用され、シンボルの絶対アドレスを正確に取得できます。
BL (R0)
:- この命令は、レジスタR0に格納されているアドレスにジャンプします。つまり、
x_cgo_load_gm
関数のアドレスがR0にロードされた後、そのアドレスに対して間接的にBL
命令を実行します。
- この命令は、レジスタR0に格納されているアドレスにジャンプします。つまり、
この変更の技術的な利点は以下の通りです。
- 堅牢なアドレス解決:
MOVW
命令は、BL
命令が直接ジャンプできないような遠いアドレスや、位置独立コードのコンテキストでも、シンボルの絶対アドレスを確実にレジスタにロードできます。 - ARMの命令セットの特性への対応: ARMの
BL
命令は、相対アドレス指定に制限がある場合があります。特に、ジャンプ先が現在のPCから一定の範囲外にある場合、直接BL
でジャンプすることはできません。このような場合、まずアドレスをレジスタにロードし、そのレジスタを介して間接的にジャンプする方法が一般的かつ推奨されます。 - Cgoの安定性向上: CgoはGoとCの間の複雑なインターフェースであり、正確なスタックとレジスタの管理が不可欠です。この修正により、
crosscall2
がARM上で常に正しい関数アドレスを呼び出すことが保証され、Cgoの安定性と信頼性が向上します。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/cgo/asm_arm.s b/src/pkg/runtime/cgo/asm_arm.s
index 1aa760e8b7..40f0300841 100644
--- a/src/pkg/runtime/cgo/asm_arm.s
+++ b/src/pkg/runtime/cgo/asm_arm.s
@@ -16,7 +16,8 @@ TEXT crosscall2(SB),7,$-4
* nevertheless.
*/
MOVM.WP [R0, R1, R2, R4, R5, R6, R7, R8, R9, R10, R11, R12, R14], (R13)
- BL x_cgo_load_gm(SB)
+ MOVW _cgo_load_gm(SB), R0
+ BL (R0)
MOVW PC, R14
MOVW -4(R13), PC
MOVM.IAW (R13), [R0, R1, R2, R4, R5, R6, R7, R8, R9, R10, R11, R12, PC]
コアとなるコードの解説
変更はsrc/pkg/runtime/cgo/asm_arm.s
ファイル内のcrosscall2
関数のアセンブリコードにあります。
変更前:
BL x_cgo_load_gm(SB)
この命令は、x_cgo_load_gm
というシンボル(関数)に直接ジャンプします。ARMのBL
命令は、PC相対アドレス指定を使用し、ジャンプできる範囲に制限があります。もしx_cgo_load_gm
が現在のPCから遠すぎる場合や、位置独立コードのコンテキストで絶対アドレスが実行時に解決される必要がある場合、この直接ジャンプは失敗するか、予期せぬ動作を引き起こす可能性がありました。
変更後:
MOVW _cgo_load_gm(SB), R0
BL (R0)
MOVW _cgo_load_gm(SB), R0
:_cgo_load_gm(SB)
は、x_cgo_load_gm
関数のアドレスを指すシンボルです。- この命令は、
_cgo_load_gm
の絶対アドレスをレジスタR0にロードします。MOVW
命令は、32ビットの即値またはシンボルアドレスをレジスタにロードする際に使用され、アドレスの距離に関わらず正確にロードできます。
BL (R0)
:- レジスタR0に格納されているアドレス(つまり、
x_cgo_load_gm
のアドレス)にジャンプします。これは間接ジャンプと呼ばれます。
- レジスタR0に格納されているアドレス(つまり、
この変更により、x_cgo_load_gm
関数のアドレスがまずレジスタR0に確実にロードされ、その後、そのレジスタを介して間接的に関数が呼び出されるようになります。これにより、ARMアーキテクチャにおけるアドレス解決の制約を回避し、crosscall2
が常に正しいx_cgo_load_gm
関数を呼び出すことが保証されます。これは、特に共有ライブラリや動的にロードされるコードにおいて、位置独立性を確保するために重要なパターンです。
関連リンク
- GoLang CL: https://golang.org/cl/7449045
参考にした情報源リンク
- Go言語のCgoに関する公式ドキュメントやブログ記事
- ARMアーキテクチャのリファレンスマニュアル(特に命令セットと関数呼び出し規約に関するセクション)
- Goのアセンブリ言語に関するドキュメント
- Position-Independent Code (PIC) に関する一般的な情報