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

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

このコミットは、Go言語のランタイムがARMアーキテクチャ上で動作する際に、ハードウェア浮動小数点ユニットの有無をチェックし、もし不足している場合はプログラムの実行を中止するように変更を加えるものです。具体的には、GOARM=7でコンパイルされたバイナリが、浮動小数点ハードウェアを持たないARMv5などの古いCPUで実行された場合に、適切なエラーメッセージを出力して終了するようになります。

コミット

commit 212ce41d004cc9e33d35b64cc13c2c9baf843452
Author: Dave Cheney <dave@cheney.net>
Date:   Fri Sep 7 14:26:42 2012 +1000

    runtime: arm: abort if hardware floating point missing
    
    Fixes #3911.
    
    Requires CL 6449127.
    
    dfc@qnap:~$ ./runtime.test
    runtime: this CPU has no floating point hardware, so it cannot run
    this GOARM=7 binary. Recompile using GOARM=5.
    
    R=rsc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6442109
---
 src/pkg/runtime/asm_arm.s          |  1 +
 src/pkg/runtime/signal_linux_arm.c | 16 ++++++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/src/pkg/runtime/asm_arm.s b/src/pkg/runtime/asm_arm.s
index 2c89139805..57df8c9c63 100644
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -37,6 +37,7 @@ TEXT _rt0_arm(SB),7,$-4
 	MOVW.NE	g, R0 // first argument of initcgo is g
 	BL.NE	(R2) // will clobber R0-R3
 
+	BL	runtime·checkgoarm(SB)
 	BL	runtime·check(SB)
 
 	// saved argc, argv
diff --git a/src/pkg/runtime/signal_linux_arm.c b/src/pkg/runtime/signal_linux_arm.c
index c35d139b27..7f93db5fb0 100644
--- a/src/pkg/runtime/signal_linux_arm.c
+++ b/src/pkg/runtime/signal_linux_arm.c
@@ -147,9 +147,21 @@ runtime·setsig(int32 i, void (*fn)(int32, Siginfo*, void*, G*), bool restart)\n #define AT_PLATFORM	15 // introduced in at least 2.6.11
 #define AT_HWCAP	16 // introduced in at least 2.6.11
 #define AT_RANDOM	25 // introduced in 2.6.29
+#define HWCAP_VFP	(1 << 6)\n static uint32 runtime·randomNumber;\n-uint32 runtime·hwcap;\n-uint8 runtime·armArch = 6; // we default to ARMv6\n+uint8  runtime·armArch = 6;\t// we default to ARMv6\n+uint32 runtime·hwcap;\t// set by setup_auxv\n+uint8  runtime·goarm;\t// set by 5l\n+\n+void\n+runtime·checkgoarm(void)\n+{\n+\tif(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {\n+\t\truntime·printf(\"runtime: this CPU has no floating point hardware, so it cannot run\\n\");\n+\t\truntime·printf(\"this GOARM=%d binary. Recompile using GOARM=5.\\n\", runtime·goarm);\n+\t\truntime·exit(1);\n+\t}\n+}\n \n #pragma textflag 7\n void

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

https://github.com/golang/go/commit/212ce41d004cc9e33d35b64cc13c2c9baf843452

元コミット内容

このコミットは、GoランタイムがARMアーキテクチャ上で動作する際に、ハードウェア浮動小数点ユニットが利用可能かどうかをチェックし、もし利用できない場合はプログラムの実行を中止するというものです。これは、特定のGOARM設定でコンパイルされたバイナリが、その設定が要求するハードウェア機能をサポートしないCPU上で実行された場合に発生する問題を解決することを目的としています。

コミットメッセージには、以下の情報が含まれています。

  • タイトル: runtime: arm: abort if hardware floating point missing (ランタイム: arm: ハードウェア浮動小数点がない場合、中止する)
  • 関連するIssue: Fixes #3911. (Issue 3911を修正)
  • 前提となる変更リスト (CL): Requires CL 6449127. (CL 6449127が必要)
  • 実行例: dfc@qnap:~$ ./runtime.test を実行した際に、ハードウェア浮動小数点がないCPUでGOARM=7のバイナリを実行した場合に表示されるエラーメッセージの例が示されています。
    runtime: this CPU has no floating point hardware, so it cannot run
    this GOARM=7 binary. Recompile using GOARM=5.
    
    (ランタイム: このCPUには浮動小数点ハードウェアがないため、このGOARM=7バイナリを実行できません。GOARM=5を使用して再コンパイルしてください。)
  • レビュー担当者: R=rsc, minux.ma
  • CC: golang-dev
  • 関連するGo Change List (CL): https://golang.org/cl/6442109

変更の背景

この変更の背景には、Go言語がARMアーキテクチャをサポートする上で直面した互換性の問題があります。ARMアーキテクチャには、様々なバージョンと機能セットが存在します。特に、浮動小数点演算のサポートは、CPUのバージョンや実装によって異なります。

  • ARMv5: 浮動小数点ハードウェアを標準では持たず、ソフトウェアエミュレーションに頼ることが多い。
  • ARMv6: VFPv2 (Vector Floating Point Unit version 2) をオプションでサポート。
  • ARMv7: VFPv3/v4を標準でサポートし、NEON (Advanced SIMD) 拡張も利用可能。

Go言語のコンパイラは、GOARM環境変数によって、ターゲットとするARMアーキテクチャのバージョンと、それに伴う浮動小数点サポートの有無を決定します。

  • GOARM=5: 浮動小数点ハードウェアを期待せず、ソフトウェア浮動小数点を使用するバイナリを生成。
  • GOARM=6: ARMv6以降をターゲットとし、VFPv2を期待するバイナリを生成。
  • GOARM=7: ARMv7以降をターゲットとし、VFPv3/v4を期待するバイナリを生成。

問題は、GOARM=7(またはGOARM=6)でコンパイルされたバイナリが、実際には浮動小数点ハードウェアを持たない古いARMv5などのCPUで実行された場合に発生しました。このような場合、バイナリはハードウェア浮動小数点命令を実行しようとしますが、CPUがそれをサポートしていないため、不正な命令例外やクラッシュを引き起こす可能性がありました。

このコミットは、このような互換性の問題を事前に検出し、ユーザーに適切なフィードバック(再コンパイルの推奨)を提供することで、より堅牢なGoアプリケーションのデプロイを可能にすることを目的としています。コミットメッセージに記載されているFixes #3911は、この問題がGoのIssueトラッカーで報告されていたことを示しています。

前提知識の解説

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

1. ARMアーキテクチャと浮動小数点ユニット (FPU)

ARM (Advanced RISC Machine) は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。ARMアーキテクチャには多くのバージョンがあり、それぞれ異なる機能セットと性能特性を持っています。

  • ARMv5: 古いアーキテクチャで、通常はハードウェア浮動小数点ユニットを持たず、浮動小数点演算はソフトウェアでエミュレートされます。これは性能に大きな影響を与えます。
  • ARMv6: ARMv5の後継で、オプションでVFPv2 (Vector Floating Point Unit version 2) をサポートします。VFPは、単精度および倍精度の浮動小数点演算をハードウェアで高速化するためのコプロセッサです。
  • ARMv7: 現在でも広く使われているアーキテクチャで、VFPv3またはVFPv4を標準でサポートします。また、NEONと呼ばれるSIMD (Single Instruction, Multiple Data) 拡張も導入され、マルチメディア処理や信号処理の性能が向上しました。

2. GOARM 環境変数

Go言語のコンパイラは、GOARM環境変数を使用して、ターゲットとするARMプロセッサのバージョンと、それに伴う浮動小数点サポートの有無を決定します。

  • GOARM=5: ARMv5互換のバイナリを生成します。これはハードウェア浮動小数点ユニットを期待せず、ソフトウェア浮動小数点を使用します。
  • GOARM=6: ARMv6互換のバイナリを生成します。これはVFPv2を期待し、ハードウェア浮動小数点を使用します。
  • GOARM=7: ARMv7互換のバイナリを生成します。これはVFPv3/v4を期待し、ハードウェア浮動小数点を使用します。

GOARMの値と実際のCPUの機能が一致しない場合、問題が発生します。特に、GOARM=6GOARM=7でコンパイルされたバイナリを、ハードウェア浮動小数点ユニットを持たないCPUで実行しようとすると、実行時エラーやクラッシュにつながります。

3. AT_HWCAPHWCAP_VFP (LinuxのAuxiliary Vector)

Linuxカーネルは、プロセスの起動時に、実行環境に関する様々な情報を「Auxiliary Vector (補助ベクトル)」と呼ばれるデータ構造を通じてユーザー空間に渡します。この情報には、CPUの機能やシステム設定などが含まれます。

  • AT_HWCAP: Auxiliary Vectorのエントリの一つで、CPUのハードウェア機能を示すビットマスクが含まれています。
  • HWCAP_VFP: AT_HWCAPビットマスク内で、VFP (Vector Floating Point) ユニットの存在を示す特定のビットです。このビットがセットされていれば、CPUはハードウェア浮動小数点ユニットを搭載していることを意味します。

Goランタイムは、setup_auxvのような内部関数を通じて、このAuxiliary VectorからAT_HWCAPの情報を読み取り、CPUのハードウェア機能を検出します。

4. Goランタイムの初期化 (_rt0_arm)

Goプログラムが起動する際、まずランタイムの初期化コードが実行されます。ARMアーキテクチャの場合、この初期化は通常、アセンブリ言語で書かれた_rt0_arm関数から始まります。この関数は、スタックの設定、引数の処理、そしてGoランタイムの主要な初期化ルーチンへのジャンプなど、低レベルのセットアップを行います。このコミットでは、この初期化プロセスの非常に早い段階でハードウェア浮動小数点ユニットのチェックが追加されています。

技術的詳細

このコミットの技術的な詳細を掘り下げていきます。

1. runtime·checkgoarm 関数の導入

コミットの主要な変更点は、src/pkg/runtime/signal_linux_arm.cruntime·checkgoarm という新しいC関数が追加されたことです。この関数は、Goバイナリが期待するGOARMレベルと、実行中のCPUが実際に持つハードウェア浮動小数点機能とを比較します。

void
runtime·checkgoarm(void)
{
	if(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {
		runtime·printf("runtime: this CPU has no floating point hardware, so it cannot run\\n");
		runtime·printf("this GOARM=%d binary. Recompile using GOARM=5.\\n", runtime·goarm);
		runtime·exit(1);
	}
}
  • runtime·goarm: これは、コンパイル時にGoリンカ (5l) によって設定される変数で、バイナリがターゲットとするGOARMのバージョンを示します。
  • runtime·hwcap: これは、LinuxカーネルのAuxiliary Vectorから読み取られたCPUのハードウェア機能を示すビットマスクです。setup_auxvのようなランタイム内部の関数によって設定されます。
  • HWCAP_VFP: (1 << 6) と定義されており、runtime·hwcap 内でVFPユニットの存在を示すビットです。

このif文の条件 runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP) は、以下の状況を検出します。

  1. runtime·goarm > 5: バイナリがGOARM=6またはGOARM=7でコンパイルされていることを意味します。これらのバージョンはハードウェア浮動小数点ユニットを期待します。
  2. !(runtime·hwcap & HWCAP_VFP): 実行中のCPUがHWCAP_VFPビットを持たない、つまりハードウェア浮動小数点ユニットを搭載していないことを意味します。

この両方の条件が真である場合、Goバイナリはハードウェア浮動小数点命令を実行しようとしますが、CPUがそれをサポートしていないため、実行を継続できません。

2. エラーメッセージと終了

条件が満たされた場合、runtime·checkgoarmは以下のエラーメッセージを標準エラー出力に表示します。

runtime: this CPU has no floating point hardware, so it cannot run
this GOARM=%d binary. Recompile using GOARM=5.

このメッセージは非常に具体的で、ユーザーに問題の原因(ハードウェア浮動小数点ユニットの欠如)と解決策(GOARM=5での再コンパイル)を明確に伝えます。メッセージ出力後、runtime·exit(1)が呼び出され、プログラムはエラーコード1で終了します。これにより、不正な命令によるクラッシュを防ぎ、よりユーザーフレンドリーなエラーハンドリングを提供します。

3. _rt0_arm からの呼び出し

runtime·checkgoarm関数は、src/pkg/runtime/asm_arm.s_rt0_arm アセンブリ関数から呼び出されます。

TEXT _rt0_arm(SB),7,$-4
	MOVW.NE	g, R0 // first argument of initcgo is g
	BL.NE	(R2) // will clobber R0-R3

	BL	runtime·checkgoarm(SB)
	BL	runtime·check(SB)

_rt0_armはGoプログラムのエントリポイントの一つであり、ランタイムの初期化の非常に早い段階で実行されます。BL runtime·checkgoarm(SB) 命令は、runtime·checkgoarm関数を呼び出します。この配置により、Goプログラムが本格的な実行を開始する前に、必要なハードウェア機能のチェックが行われることが保証されます。これにより、浮動小数点命令が実行される前に互換性の問題が検出され、早期に終了することができます。

4. 変数の追加と初期化

src/pkg/runtime/signal_linux_arm.c には、runtime·hwcapruntime·goarm という2つの新しいグローバル変数が追加されています。

uint32 runtime·hwcap;	// set by setup_auxv
uint8  runtime·goarm;	// set by 5l
  • runtime·hwcap: CPUのハードウェア機能ビットマスクを保持します。コメントにあるように、setup_auxv関数(Auxiliary Vectorを解析するランタイム内部関数)によって設定されます。
  • runtime·goarm: コンパイル時にリンカ(5l)によって設定されるGOARMの値を保持します。

これらの変数は、runtime·checkgoarm関数が実行時に必要な情報を取得するために使用されます。

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

このコミットにおけるコアとなるコードの変更箇所は以下の2つのファイルです。

  1. src/pkg/runtime/asm_arm.s:

    • _rt0_arm関数の内部に、新しく追加されたruntime·checkgoarm関数を呼び出すアセンブリ命令 BL runtime·checkgoarm(SB) が追加されました。これは、Goプログラムの初期化シーケンスの非常に早い段階でハードウェアチェックが実行されることを保証します。
  2. src/pkg/runtime/signal_linux_arm.c:

    • HWCAP_VFPマクロが定義されました。これは、LinuxのAuxiliary Vectorにおけるハードウェア機能ビットマスクで、VFP (Vector Floating Point) ユニットの存在を示すビットです。
    • runtime·hwcap (uint32) と runtime·goarm (uint8) という2つのグローバル変数が追加されました。これらはそれぞれ、CPUのハードウェア機能と、コンパイル時のGOARM設定を保持します。
    • runtime·checkgoarmという新しいC関数が実装されました。この関数は、runtime·goarmの値が5より大きい(つまり、ハードウェア浮動小数点が必要なバイナリである)にもかかわらず、runtime·hwcapHWCAP_VFPビットがセットされていない(つまり、ハードウェア浮動小数点ユニットがない)場合に、エラーメッセージを出力してプログラムを終了させます。

コアとなるコードの解説

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

--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -37,6 +37,7 @@ TEXT _rt0_arm(SB),7,$-4
 	MOVW.NE	g, R0 // first argument of initcgo is g
 	BL.NE	(R2) // will clobber R0-R3
 
+	BL	runtime·checkgoarm(SB)
 	BL	runtime·check(SB)
 
 	// saved argc, argv

この変更は、GoランタイムのARMアーキテクチャ向けエントリポイントである_rt0_armアセンブリ関数に、runtime·checkgoarm関数への呼び出しを追加しています。

  • TEXT _rt0_arm(SB),7,$-4: _rt0_arm関数の定義。SBはStatic Baseレジスタを示し、7はスタックフレームのサイズ、$-4は引数のオフセットを示します。
  • BL runtime·checkgoarm(SB): BL (Branch with Link) 命令は、指定されたアドレス(ここではruntime·checkgoarm関数のアドレス)にジャンプし、現在のプログラムカウンタ(PC)の次の命令のアドレスをリンクレジスタ(LR)に保存します。これにより、runtime·checkgoarmの実行が完了した後に、元の呼び出し元に戻ることができます。

この変更により、Goプログラムが起動し、基本的なレジスタとスタックが設定された直後に、ハードウェア浮動小数点ユニットのチェックが実行されるようになります。これは、浮動小数点命令が実際に使用される前に互換性の問題を検出するための重要なステップです。

src/pkg/runtime/signal_linux_arm.c の変更

--- a/src/pkg/runtime/signal_linux_arm.c
+++ b/src/pkg/runtime/signal_linux_arm.c
@@ -147,9 +147,21 @@ runtime·setsig(int32 i, void (*fn)(int32, Siginfo*, void*, G*), bool restart)\n #define AT_PLATFORM	15 // introduced in at least 2.6.11
 #define AT_HWCAP	16 // introduced in at least 2.6.11
 #define AT_RANDOM	25 // introduced in 2.6.29
+#define HWCAP_VFP	(1 << 6)\n static uint32 runtime·randomNumber;\n-uint32 runtime·hwcap;\n-uint8 runtime·armArch = 6; // we default to ARMv6\n+uint8  runtime·armArch = 6;\t// we default to ARMv6\n+uint32 runtime·hwcap;\t// set by setup_auxv\n+uint8  runtime·goarm;\t// set by 5l\n+\n+void\n+runtime·checkgoarm(void)\n+{\n+\tif(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {\n+\t\truntime·printf(\"runtime: this CPU has no floating point hardware, so it cannot run\\n\");\n+\t\truntime·printf(\"this GOARM=%d binary. Recompile using GOARM=5.\\n\", runtime·goarm);\n+\t\truntime·exit(1);\n+\t}\n+}\n \n #pragma textflag 7\n void

このファイルでは、主に以下の3つの変更が行われています。

  1. HWCAP_VFP の定義:

    #define HWCAP_VFP	(1 << 6)
    

    これは、LinuxのAuxiliary VectorでCPUのハードウェア機能を示すAT_HWCAPビットマスク内で、VFP (Vector Floating Point) ユニットの存在を示すビットを定義しています。ビット6がVFPに対応します。

  2. 新しいグローバル変数の追加:

    uint32 runtime·hwcap;	// set by setup_auxv
    uint8  runtime·goarm;	// set by 5l
    
    • runtime·hwcap: 実行中のCPUがサポートするハードウェア機能のビットマスクを格納するための変数です。この値は、ランタイムの初期化中にsetup_auxv関数によって、Linuxカーネルから提供されるAuxiliary VectorのAT_HWCAPエントリから読み取られます。
    • runtime·goarm: コンパイル時にGoリンカ(5l)によって設定されるGOARM環境変数の値を格納するための変数です。これにより、バイナリがどのARMアーキテクチャバージョンをターゲットとしているかを知ることができます。
  3. runtime·checkgoarm 関数の実装:

    void
    runtime·checkgoarm(void)
    {
    	if(runtime·goarm > 5 && !(runtime·hwcap & HWCAP_VFP)) {
    		runtime·printf("runtime: this CPU has no floating point hardware, so it cannot run\\n");
    		runtime·printf("this GOARM=%d binary. Recompile using GOARM=5.\\n", runtime·goarm);
    		runtime·exit(1);
    	}
    }
    

    この関数は、以下のロジックを実行します。

    • runtime·goarm > 5: これは、コンパイルされたバイナリがGOARM=6またはGOARM=7をターゲットとしていることを意味します。これらの設定は、ハードウェア浮動小数点ユニットの存在を前提としています。
    • !(runtime·hwcap & HWCAP_VFP): これは、runtime·hwcapビットマスクにHWCAP_VFPビットがセットされていないことを意味します。つまり、実行中のCPUにはハードウェア浮動小数点ユニットがないということです。
    • 両方の条件が真の場合、プログラムは互換性のない環境で実行されていると判断されます。
    • runtime·printf(...): ユーザーに対して、CPUに浮動小数点ハードウェアがないため、現在のバイナリは実行できないこと、そしてGOARM=5で再コンパイルする必要があることを伝えるエラーメッセージを出力します。
    • runtime·exit(1): プログラムをエラーコード1で終了させます。これにより、不正な命令の実行によるクラッシュを防ぎ、クリーンな終了を保証します。

これらの変更により、GoランタイムはARM環境での実行時に、必要なハードウェア機能が利用可能かどうかをインテリジェントにチェックし、互換性の問題を早期に検出してユーザーに適切なガイダンスを提供できるようになりました。

関連リンク

参考にした情報源リンク

  • ARM Architecture Reference Manuals: ARMの公式ドキュメントは、各アーキテクチャバージョン(ARMv5, ARMv6, ARMv7)の機能セット、特に浮動小数点ユニット(VFP, NEON)に関する詳細な情報を提供しています。
  • Linux Kernel Documentation on Auxiliary Vector: Linuxカーネルのドキュメントは、AT_HWCAPやその他のAuxiliary Vectorエントリの構造と意味について説明しています。
  • Go Compiler and Runtime Documentation: Go言語の公式ドキュメントやソースコードは、GOARM環境変数の挙動やランタイムの初期化プロセスに関する情報を提供しています。
  • Stack Overflow / Go Forum Discussions: 過去のGoコミュニティでの議論やStack Overflowの質問は、GOARMとARM浮動小数点に関する一般的な問題と解決策を理解するのに役立ちます。
  • Dave Cheney's Blog/Articles: コミットの作者であるDave Cheney氏のブログや記事は、Go言語の内部動作やARMアーキテクチャに関する深い洞察を提供している場合があります。
  • Go Source Code: 実際のGoのソースコード(特にsrc/pkg/runtimeディレクトリ)は、ランタイムの動作を理解するための最も正確な情報源です。
  • Go Issue Tracker: GoのIssueトラッカーは、過去に報告されたバグや機能要求、それらに対する議論と解決策の履歴を追跡するのに役立ちます。