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

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

このコミットは、Go言語のランタイムがLinux/ARMシステム上で動作する際に、古いOABI (Old Application Binary Interface) システムを検出し、互換性がない場合に早期にエラーメッセージを出力して終了するように修正するものです。これにより、GoプログラムがEABI (Embedded Application Binary Interface) を必要とする環境で、OABIシステム上で誤って実行されることを防ぎ、ユーザーに適切な診断情報を提供します。

コミット

commit bb40196ebf50d461b4c9bef7b5124b04dc4cb73f
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Feb 9 16:18:21 2012 -0500

    runtime: Linux/ARM: exit early on OABI systems, and give an error message
    Fixes #2533.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5654045

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

https://github.com/golang/go/commit/bb40196ebf50d461b4c9bef7b5124b04dc4cb73f

元コミット内容

このコミットは、GoランタイムがLinux上のARMアーキテクチャで実行される際に、OABI (Old Application Binary Interface) システムで早期に終了し、エラーメッセージを表示するように変更を加えるものです。これは、GoプログラムがEABI (Embedded Application Binary Interface) を前提としているため、OABI環境では正しく動作しない問題を解決することを目的としています。具体的には、GoのIssue #2533を修正します。

変更の背景

Go言語は、その設計上、特定のシステムコール規約やABI (Application Binary Interface) に依存しています。ARMアーキテクチャにおけるLinux環境では、OABI (Old Application Binary Interface) とEABI (Embedded Application Binary Interface) という2つの主要なABIが存在します。

  • OABI (Old Application Binary Interface): これはARMの初期のABIであり、特に浮動小数点演算の扱いにおいて非効率な部分がありました。ハードウェアFPU (Floating Point Unit) がないシステムでは、FPU命令がカーネルレベルでエミュレートされ、これが非常に遅いコンテキストスイッチを引き起こしていました。
  • EABI (Embedded Application Binary Interface): OABIの非効率性を改善するために設計された新しいABIです。EABIでは、FPU命令のエミュレーションをユーザー空間で行うことが可能になり、大幅な高速化が実現されました。Go言語は、このEABIを前提として設計されており、特にシステムコールやスタックフレームの管理においてEABIの規約に準拠しています。

GoプログラムがOABIシステム上で実行されると、ABIの不一致により、予期せぬ動作、クラッシュ、またはパフォーマンスの問題が発生する可能性がありました。このコミット以前は、GoランタイムはOABIシステム上で実行された際に、明確なエラーメッセージを出力せずにクラッシュするか、あるいは単に動作しないという問題がありました。

この変更の背景には、GoプログラムがEABI環境でのみ動作することを明確にし、OABIシステム上で実行された場合にユーザーに分かりやすい診断情報を提供することで、デバッグや互換性の問題を軽減するという目的があります。Issue #2533は、この互換性の問題と、それに対する明確なエラーハンドリングの必要性を提起していました。

前提知識の解説

ARMアーキテクチャにおけるABI (Application Binary Interface)

ABIは、オペレーティングシステムとアプリケーション、またはアプリケーションの異なるモジュール間で、どのようにデータがやり取りされ、関数が呼び出されるかといった低レベルの規約を定義します。ARMアーキテクチャでは、特にLinux環境において、OABIとEABIという2つの主要なABIが存在します。

  • OABI (Old Application Binary Interface):
    • ARMの初期のABI。
    • 浮動小数点演算の処理が非効率的で、ハードウェアFPUがないシステムではカーネルによるエミュレーションが必要となり、パフォーマンスが低下する原因となっていました。
    • システムコール規約もEABIとは異なります。
  • EABI (Embedded Application Binary Interface):
    • OABIの改善版として登場。
    • 浮動小数点演算の効率が向上し、ユーザー空間でのエミュレーションが可能になりました。
    • より効率的なスタックフレームの管理や、システムコール規約の改善が含まれます。
    • Go言語はEABIに準拠しています。

ARMアセンブリとSWI (Software Interrupt) 命令

ARMアセンブリは、ARMプロセッサの命令セットを記述するための低レベル言語です。SWI (Software Interrupt) 命令は、ユーザーモードのプログラムがオペレーティングシステムに対して特権操作(システムコール)を要求するためのメカニズムです。

  • SWI命令が実行されると、プロセッサは特権モード(通常はSupervisorモード)に切り替わり、特定のメモリアドレス(SWI例外ベクタ)にジャンプします。
  • このアドレスには、システムコールを処理するための例外ハンドラが配置されており、要求されたサービスを実行します。
  • システムコールの番号や引数は、通常、特定のレジスタ(例: ARM EABIではR7にシステムコール番号、R0-R3に引数)に格納されて渡されます。

SIGILL (Illegal Instruction) シグナル

SIGILLは「不正命令」を意味するシグナルです。プロセスが不正な、特権的な、または形式が正しくない機械語命令を実行しようとしたときに、カーネルによってプロセスに送信されます。

  • 発生原因:
    • プログラムのバグ(コードがデータ領域を上書きするなど)。
    • CPUがサポートしていない命令の実行(例: AVX2命令をサポートしないCPUでAVX2命令を含むコードを実行)。
    • 実行ファイルやライブラリの破損。
  • デフォルトの動作: SIGILLを受け取ったプロセスは、デフォルトで終了し、デバッグのためにコアダンプが生成されることがあります。
  • シグナルハンドリング: SIGILLのシグナルハンドラを設定することは可能ですが、不正命令が発生した場所に戻ることはできません。

Linux ARMシステムコール規約

LinuxにおけるARMのシステムコール規約は、使用されるABIによって異なります。

  • ARM 32-bit (EABI):
    • システムコールは通常swi #0命令で開始されます。
    • システムコール番号はレジスタR7に渡されます。
    • 最初の4つの引数はレジスタR0からR3に渡されます。それ以上の引数はスタックに渡されます。
    • 戻り値はレジスタR0に格納されます。
  • ARM 32-bit (OABI):
    • OABIでは、システムコール番号はswi NRのようにSWI命令の即値として渡されることがありました。これはEABIとは異なる点です。

このコミットでは、EABIシステムコールを意図的に実行し、OABIシステムでSIGILLが発生することを利用してABIを検出しています。

技術的詳細

このコミットの主要な目的は、GoランタイムがLinux/ARMシステム上で起動する際に、そのシステムがOABIであるかEABIであるかを検出し、OABIであれば早期にエラーメッセージを出力して終了することです。この検出は、EABI固有のシステムコールを試行し、その結果としてSIGILLシグナルが発生するかどうかを監視するという巧妙な方法で行われます。

具体的な検出ロジックは以下のステップで構成されます。

  1. SIGILLハンドラのセットアップ:

    • まず、SIGILLシグナル(シグナル番号4)が発生した際に呼び出されるシグナルハンドラbad_abi<>を設定します。
    • sigactionシステムコール(システムコール番号174)を使用して、このハンドラを登録します。sigactionは、シグナルに対するアクション(ハンドラ関数、フラグ、シグナルマスクなど)を設定するためのシステムコールです。
    • このsigactionシステムコール自体は、OABIとEABIの両方で互換性のある方法で呼び出す必要があります。コミットでは、oabi_syscall<>というヘルパー関数を介して呼び出されています。これは、Thumbモードに切り替えてSWI命令を実行することで、OABI環境でも動作するように設計されています。
  2. EABIシステムコールの試行:

    • SIGILLハンドラが設定された後、EABI固有のシステムコールを意図的に実行します。
    • ここではsys_getpid(システムコール番号20)が選択されています。これは、EABIのシステムコール規約に従ってレジスタR7にシステムコール番号をセットし、SWI $0命令を実行します。
    • もしシステムがEABIであれば、このシステムコールは正常に実行され、_rt0_armラベルにジャンプしてGoランタイムの初期化が続行されます。
    • もしシステムがOABIであれば、EABIのシステムコール規約に従って発行されたSWI $0命令は、OABIカーネルにとっては不正な命令と解釈され、SIGILLシグナルが発生します。
  3. SIGILLハンドラでの処理:

    • OABIシステムでSIGILLが発生すると、事前に設定されたbad_abi<>ハンドラが呼び出されます。
    • このハンドラ内では、標準エラー出力(stderr)に「This program can only be run on EABI kernels」というエラーメッセージを出力します。
    • メッセージ出力後、sys_exitシステムコール(システムコール番号1)を呼び出し、終了コード1でプログラムを終了させます。これにより、GoプログラムはOABIシステム上で実行を継続することなく、ユーザーに明確なエラーメッセージを提示して終了します。

このアプローチの鍵は、sigactionシステムコールの呼び出しと、エラーメッセージの出力および終了処理にoabi_syscall<>というヘルパー関数を使用している点です。これは、OABI環境でも確実に動作するように、Thumbモードへの切り替えと特定のSWI命令の組み合わせを利用して、OABI互換のシステムコール呼び出しを実現しています。これにより、ABI検出の初期段階で、Goランタイムがまだ完全に初期化されていない状態でも、安全にシステムコールを実行し、エラーハンドリングを行うことが可能になっています。

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

変更はsrc/pkg/runtime/rt0_linux_arm.sファイルに集中しています。

--- a/src/pkg/runtime/rt0_linux_arm.s
+++ b/src/pkg/runtime/rt0_linux_arm.s
@@ -2,5 +2,59 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.\n
-TEXT _rt0_arm_linux(SB),7,$0
+TEXT _rt0_arm_linux(SB),7,$-4
+	// We first need to detect the kernel ABI, and warn the user
+	// if the system only supports OABI
+	// The strategy here is to call some EABI syscall to see if
+	// SIGILL is received.
+	// To catch SIGILL, we have to first setup sigaction, this is
+	// a chicken-and-egg problem, because we can't do syscall if
+	// we don't know the kernel ABI... Oh, not really, we can do
+	// syscall in Thumb mode.
+
+	// set up sa_handler
+	MOVW	$bad_abi<>(SB), R0 // sa_handler
+	MOVW	$0, R1 // sa_flags
+	MOVW	$0, R2 // sa_restorer
+	MOVW	$0, R3 // sa_mask
+	MOVM.DB.W [R0-R3], (R13)
+	MOVW	$4, R0 // SIGILL
+	MOVW	R13, R1 // sa
+	MOVW	$0, R2 // old_sa
+	MOVW	$8, R3 // c
+	MOVW	$174, R7 // sys_sigaction
+	BL	oabi_syscall<>(SB)
+	ADD 	$16, R13
+	// do an EABI syscall
+	MOVW	$20, R7 // sys_getpid
+	SWI 	$0 // this will trigger SIGILL on OABI systems
+
 	B	_rt0_arm(SB)
+\n
+TEXT bad_abi<>(SB),7,$-4
+	// give diagnosis and exit
+	MOVW	$2, R0 // stderr
+	MOVW	$bad_abi_msg(SB), R1 // data
+	MOVW	$45, R2 // len
+	MOVW	$4, R7 // sys_write
+	BL	oabi_syscall<>(SB)
+	MOVW	$1, R0
+	MOVW	$1, R7 // sys_exit
+	BL	oabi_syscall<>(SB)
+	B  	0(PC)
+\n
+DATA bad_abi_msg+0x00(SB)/8, $"This pro"\n
+DATA bad_abi_msg+0x08(SB)/8, $"gram can"\n
+DATA bad_abi_msg+0x10(SB)/8, $" only be"\n
+DATA bad_abi_msg+0x18(SB)/8, $" run on "\n
+DATA bad_abi_msg+0x20(SB)/8, $"EABI ker"\n
+DATA bad_abi_msg+0x28(SB)/4, $"nels"\n
+DATA bad_abi_msg+0x2c(SB)/1, $0xa\n
+GLOBL bad_abi_msg(SB), $45
+\n
+TEXT oabi_syscall<>(SB),7,$-4
+	ADD $1, PC, R4
+	WORD $0xe12fff14 //BX	(R4) // enter thumb mode
+	// TODO(minux): only supports little-endian CPUs
+	WORD $0x4770df01 // swi $1; bx lr

コアとなるコードの解説

_rt0_arm_linux 関数内の変更

このセクションは、Goランタイムの初期エントリポイントである_rt0_arm_linux関数の先頭に追加されたABI検出ロジックです。

TEXT _rt0_arm_linux(SB),7,$-4
	// We first need to detect the kernel ABI, and warn the user
	// if the system only supports OABI
	// The strategy here is to call some EABI syscall to see if
	// SIGILL is received.
	// To catch SIGILL, we have to first setup sigaction, this is
	// a chicken-and-egg problem, because we can't do syscall if
	// we don't know the kernel ABI... Oh, not really, we can do
	// syscall in Thumb mode.

	// set up sa_handler
	MOVW	$bad_abi<>(SB), R0 // sa_handler
	MOVW	$0, R1 // sa_flags
	MOVW	$0, R2 // sa_restorer
	MOVW	$0, R3 // sa_mask
	MOVM.DB.W [R0-R3], (R13)
	MOVW	$4, R0 // SIGILL
	MOVW	R13, R1 // sa
	MOVW	$0, R2 // old_sa
	MOVW	$8, R3 // c
	MOVW	$174, R7 // sys_sigaction
	BL	oabi_syscall<>(SB)
	ADD 	$16, R13
	// do an EABI syscall
	MOVW	$20, R7 // sys_getpid
	SWI 	$0 // this will trigger SIGILL on OABI systems

	B	_rt0_arm(SB)
  • TEXT _rt0_arm_linux(SB),7,$-4: _rt0_arm_linux関数の定義。スタックフレームサイズが変更されています。
  • コメント: カーネルABIを検出し、OABIシステムであればユーザーに警告を出す目的を説明しています。SIGILLを捕捉するためにsigactionを設定する必要があるが、ABIが不明な状態ではシステムコールが難しいという「鶏と卵」の問題に触れ、Thumbモードでのシステムコールが解決策であることを示唆しています。
  • MOVW $bad_abi<>(SB), R0 // sa_handler: bad_abi関数のアドレスをR0レジスタにロードします。これはSIGILLシグナルハンドラとして使用されます。
  • MOVW $0, R1 // sa_flags, MOVW $0, R2 // sa_restorer, MOVW $0, R3 // sa_mask: sigaction構造体の他のフィールド(フラグ、レストアラー、シグナルマスク)をゼロに設定します。
  • MOVM.DB.W [R0-R3], (R13): R0からR3までのレジスタの内容をスタックポインタR13が指すアドレスにストアします。これにより、sigaction構造体がスタック上に構築されます。
  • MOVW $4, R0 // SIGILL: SIGILLシグナル(シグナル番号4)をR0にロードします。これはsigactionシステムコールの最初の引数です。
  • MOVW R13, R1 // sa: スタック上のsigaction構造体のアドレスをR1にロードします。これはsigactionシステムコールの2番目の引数です。
  • MOVW $0, R2 // old_sa: 古いsigaction構造体を格納する場所をR2にロードします(ここではNULL)。
  • MOVW $8, R3 // c: これはsigactionシステムコールの引数の一部ですが、具体的な意味は文脈に依存します。Linuxのsigactionシステムコールは、sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)という形式で、cは通常使用されません。これは、Goランタイムが内部的に使用する特定の規約か、あるいは古いカーネルバージョンとの互換性のためのものかもしれません。
  • MOVW $174, R7 // sys_sigaction: sys_sigactionシステムコール番号(174)をR7にロードします。
  • BL oabi_syscall<>(SB): oabi_syscall関数を呼び出します。この関数は、OABI互換の方法でシステムコールを実行します。
  • ADD $16, R13: スタックポインタR13を16バイト分進めます。これは、sigaction構造体(4ワード = 16バイト)がスタックからポップされたことを意味します。
  • MOVW $20, R7 // sys_getpid: sys_getpidシステムコール番号(20)をR7にロードします。
  • SWI $0 // this will trigger SIGILL on OABI systems: EABIのシステムコール規約に従ってSWI $0命令を実行します。OABIシステムでは、この命令は不正な命令と解釈され、SIGILLシグナルが発生します。
  • B _rt0_arm(SB): もしSIGILLが発生しなければ(つまりEABIシステムであれば)、Goランタイムの通常の初期化ルーチンである_rt0_armにジャンプします。

bad_abi 関数

この関数は、OABIシステムでEABIシステムコールを試行した結果SIGILLシグナルが発生した場合に呼び出されるシグナルハンドラです。

TEXT bad_abi<>(SB),7,$-4
	// give diagnosis and exit
	MOVW	$2, R0 // stderr
	MOVW	$bad_abi_msg(SB), R1 // data
	MOVW	$45, R2 // len
	MOVW	$4, R7 // sys_write
	BL	oabi_syscall<>(SB)
	MOVW	$1, R0
	MOVW	$1, R7 // sys_exit
	BL	oabi_syscall<>(SB)
	B  	0(PC)
  • TEXT bad_abi<>(SB),7,$-4: bad_abi関数の定義。
  • MOVW $2, R0 // stderr: 標準エラー出力のファイルディスクリプタ(2)をR0にロードします。
  • MOVW $bad_abi_msg(SB), R1 // data: エラーメッセージ文字列bad_abi_msgのアドレスをR1にロードします。
  • MOVW $45, R2 // len: メッセージの長さ(45バイト)をR2にロードします。
  • MOVW $4, R7 // sys_write: sys_writeシステムコール番号(4)をR7にロードします。
  • BL oabi_syscall<>(SB): oabi_syscall関数を呼び出し、エラーメッセージを標準エラー出力に書き込みます。
  • MOVW $1, R0: 終了コード1をR0にロードします。
  • MOVW $1, R7 // sys_exit: sys_exitシステムコール番号(1)をR7にロードします。
  • BL oabi_syscall<>(SB): oabi_syscall関数を呼び出し、プログラムを終了させます。
  • B 0(PC): ここには到達しませんが、関数の末尾に置かれる一般的なアセンブリの慣習です。

bad_abi_msg データ

これは、OABIシステムでエラーが発生した場合に表示されるメッセージのデータ定義です。

DATA bad_abi_msg+0x00(SB)/8, $"This pro"
DATA bad_abi_msg+0x08(SB)/8, $"gram can"
DATA bad_abi_msg+0x10(SB)/8, $" only be"
DATA bad_abi_msg+0x18(SB)/8, $" run on "
DATA bad_abi_msg+0x20(SB)/8, $"EABI ker"
DATA bad_abi_msg+0x28(SB)/4, $"nels"
DATA bad_abi_msg+0x2c(SB)/1, $0xa
GLOBL bad_abi_msg(SB), $45
  • DATA ...: 文字列「This program can only be run on EABI kernels」をバイト単位で定義しています。
  • $0xa: 改行文字(LF)を表します。
  • GLOBL bad_abi_msg(SB), $45: bad_abi_msgをグローバルシンボルとして宣言し、そのサイズが45バイトであることを示します。

oabi_syscall 関数

この関数は、OABI環境でも確実に動作するように設計されたシステムコールラッパーです。

TEXT oabi_syscall<>(SB),7,$-4
	ADD $1, PC, R4
	WORD $0xe12fff14 //BX	(R4) // enter thumb mode
	// TODO(minux): only supports little-endian CPUs
	WORD $0x4770df01 // swi $1; bx lr
  • TEXT oabi_syscall<>(SB),7,$-4: oabi_syscall関数の定義。
  • ADD $1, PC, R4: プログラムカウンタ(PC)に1を加算した値をR4にロードします。これは、次の命令のアドレス(Thumbモードの命令)を計算するためです。
  • WORD $0xe12fff14 //BX (R4) // enter thumb mode: これはARM命令のバイナリ表現です。BX R4命令に相当し、R4レジスタの値に基づいてプロセッサをThumbモードに切り替えてジャンプします。Thumbモードは、ARM命令セットのサブセットであり、よりコンパクトなコードを生成できます。OABI環境でのシステムコールは、Thumbモードで実行されることで互換性が確保される場合があります。
  • WORD $0x4770df01 // swi $1; bx lr: これもARM命令のバイナリ表現です。
    • swi $1: ソフトウェア割り込み命令です。OABIでは、SWI命令の即値がシステムコール番号として解釈されることがあります。ここでは$1が使用されていますが、これは一般的なシステムコール番号ではなく、OABI環境での特定の動作を期待している可能性があります。
    • bx lr: lr(リンクレジスタ)に格納されているアドレスにジャンプし、呼び出し元に戻ります。

このoabi_syscall関数は、Goランタイムがまだ完全に初期化されていない非常に早い段階で、OABI環境でも安全にシステムコールを実行するための低レベルなトリックを使用しています。

関連リンク

  • Go Issue #2533: https://github.com/golang/go/issues/2533 (ただし、検索結果によると、このIssue番号はGoのVS Code拡張機能の別の問題に関連している可能性があり、元のコミットが参照しているIssueとは異なる可能性があります。Goの古いIssueトラッカーのリンクが失われている可能性も考慮されます。)
  • Go Code Review (CL): https://golang.org/cl/5654045

参考にした情報源リンク