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

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

このコミットは、Go言語のARMアーキテクチャ向けリンカ (cmd/5l) における、未定義命令 (UNDEF) のアセンブル方法の変更に関するものです。具体的には、以前の BL $0 (自分自身への分岐命令) から、ARMアーキテクチャで未定義命令例外を確実に発生させる特定の命令コード 0xf7fabcfd を使用するように変更されています。これにより、6l (x86アーキテクチャ向けリンカ) との動作の一貫性が図られています。

コミット

commit ed6dce6f9d5bfb109b46a11f6843f8391abb8678
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Jun 11 02:02:42 2013 +0800

    cmd/5l: use guaranteed undefined instruction for UNDEF to match [68]l.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/10085050

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

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

元コミット内容

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -1515,11 +1515,9 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
 		// This is supposed to be something that stops execution.
 		// It's not supposed to be reached, ever, but if it is, we'd
 		// like to be able to tell how we got there.  Assemble as
-		//	BL $0
-		// TODO: Use addrel.
-		v = (0 - pc) - 8;
-		o1 = opbra(ABL, C_SCOND_NONE);
-		o1 |= (v >> 2) & 0xffffff;
+		// 0xf7fabcfd which is guranteed to raise undefined instruction
+		// exception.
+		o1 = 0xf7fabcfd;
 		break;
 	case 97:	/* CLZ Rm, Rd */
  		o1 = oprrr(p->as, p->scond);

変更の背景

この変更の背景には、Go言語のリンカにおける「未定義命令」の扱いの一貫性と堅牢性の向上が挙げられます。

Goのリンカは、プログラムの実行フローが到達してはならない、あるいは到達した場合に異常終了すべき箇所に、特定の命令を埋め込むことがあります。これは、デバッグを容易にし、予期せぬ実行パスを検出するために重要です。

以前の cmd/5l (ARMリンカ) では、このような「未定義」または「到達不能」なコードパスに対して BL $0 (自分自身への分岐命令) をアセンブルしていました。これは無限ループを引き起こす命令であり、実行が停止しないため、デバッガでスタックトレースを追うのが困難になる可能性がありました。また、他のアーキテクチャ(特にx86向けの 6l)では、より明確に未定義命令例外を発生させる命令を使用していたと考えられます。

このコミットの目的は、以下の点にあります。

  1. デバッグの容易性向上: BL $0 のような無限ループではなく、確実に未定義命令例外を発生させることで、プログラムが異常終了し、デバッガがその時点でのスタックトレースを正確に取得できるようになります。これにより、なぜその「到達不能な」コードパスに到達してしまったのかを特定しやすくなります。
  2. リンカの一貫性: cmd/5l の動作を cmd/6l (x86リンカ) や cmd/8l (amd64リンカ) と整合させることで、異なるアーキテクチャ間でのリンカの挙動に一貫性を持たせ、Goコンパイラツールチェーン全体の設計思想を統一します。
  3. 堅牢性の向上: 予期せぬコード実行に対するシステムの堅牢性を高めます。未定義命令例外は、単なる無限ループよりもシステムにとって明確なエラーシグナルとなります。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Go言語のツールチェーン:

    • cmd/5l: Go言語のARMアーキテクチャ向けリンカです。Goのソースコードはコンパイラによってアセンブリコードに変換され、その後リンカによって実行可能なバイナリに結合されます。5l はARMバイナリを生成する役割を担います。
    • cmd/6l / cmd/8l: それぞれx86およびamd64アーキテクチャ向けのGoリンカです。このコミットでは、5l の動作をこれらのリンカと一致させることが言及されています。
    • リンカの役割: リンカは、コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成します。この過程で、特定のシンボルやコードセクションを配置し、必要に応じてジャンプ命令のアドレスを解決します。
  2. ARMアーキテクチャとアセンブリ:

    • ARM命令セット: ARMプロセッサが理解する機械語命令の集合です。各命令は特定のビットパターンで表現されます。
    • BL (Branch with Link) 命令: ARMアセンブリにおける分岐命令の一つです。指定されたアドレスにジャンプし、同時に現在のプログラムカウンタ (PC) の次の命令のアドレスをリンクレジスタ (LR) に保存します。これにより、サブルーチン呼び出しからの戻りが可能になります。BL $0 は自分自身への分岐を意味し、無限ループを形成します。
    • 未定義命令 (Undefined Instruction): ARMプロセッサが認識できない、または実行が許可されていない命令コードのことです。プロセッサがこのような命令に遭遇すると、「未定義命令例外 (Undefined Instruction Exception)」を発生させます。これは通常、オペレーティングシステムやデバッガによって捕捉され、プログラムの異常終了やデバッグ処理に利用されます。
    • 例外ハンドリング: プロセッサが予期せぬイベント(割り込み、例外など)に遭遇した際に、通常のプログラム実行を中断し、特定のハンドラコードに制御を移すメカニズムです。未定義命令例外もこの一種です。
  3. 機械語とアセンブリ:

    • 機械語: コンピュータが直接実行できるバイナリ形式の命令です。
    • アセンブリ言語: 機械語命令を人間が読みやすいニーモニック(記号)で表現した低水準プログラミング言語です。リンカはアセンブリコードを機械語に変換する過程の一部を担います。

技術的詳細

このコミットは、src/cmd/5l/asm.c ファイル内の case 96 の処理を変更しています。この case 96 は、リンカが特定の「到達不能な」コードパス、すなわち実行されるべきではない箇所に遭遇した際に、そこに埋め込む命令を決定する部分です。

変更前は、以下のコードが使用されていました。

		//	BL $0
		// TODO: Use addrel.
		v = (0 - pc) - 8;
		o1 = opbra(ABL, C_SCOND_NONE);
		o1 |= (v >> 2) & 0xffffff;

これは、BL $0 というARMアセンブリ命令を生成しています。

  • BL は "Branch with Link" で、サブルーチン呼び出しに使われる分岐命令です。
  • $0 は相対アドレス0、つまり現在の命令の場所への分岐を意味します。
  • 結果として、この命令は自分自身に無限に分岐し続ける無限ループを形成します。

この無限ループは、プログラムがこの箇所に到達した場合にハングアップを引き起こしますが、デバッガから見ると単に実行が停止しない状態となり、なぜそこに到達したのか、どのようなスタックトレースで到達したのかを特定するのが困難でした。

変更後は、以下のコードに置き換えられました。

		// 0xf7fabcfd which is guranteed to raise undefined instruction
		// exception.
		o1 = 0xf7fabcfd;

ここで 0xf7fabcfd という16進数の値が直接 o1 に代入されています。o1 は最終的にアセンブルされる機械語命令のビットパターンを保持する変数です。

0xf7fabcfd は、ARMアーキテクチャにおいて「未定義命令例外」を確実に発生させるように設計された特定のビットパターンです。ARM命令セットには、将来の拡張のために予約されている命令空間や、特定の条件で未定義として扱われる命令があります。この値は、そのような未定義命令の一つとして機能します。

この変更により、プログラムがこのコードパスに到達すると、プロセッサは 0xf7fabcfd を命令として解釈しようとしますが、それが有効な命令ではないため、即座に未定義命令例外を発生させます。この例外はオペレーティングシステムによって捕捉され、通常はプログラムのクラッシュ(セグメンテーション違反など)として報告されます。このクラッシュは、デバッガにとって非常に有用な情報源となり、クラッシュ時の正確なプログラムカウンタとスタックトレースを提供するため、デバッグが格段に容易になります。

コメントにある // TODO: Use addrel. は、以前の BL $0 の実装が、相対アドレス計算 (addrel) を適切に利用していなかったことへの言及であり、このコミットで直接解決される問題ではありませんが、未定義命令の生成方法を変更することで、この TODO の文脈自体が不要になったとも解釈できます。

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

変更は src/cmd/5l/asm.c ファイルの case 96 ブロック内で行われています。

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -1515,11 +1515,9 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
 		// This is supposed to be something that stops execution.
 		// It's not supposed to be reached, ever, but if it is, we'd
 		// like to be able to tell how we got there.  Assemble as
-		//	BL $0
-		// TODO: Use addrel.
-		v = (0 - pc) - 8;
-		o1 = opbra(ABL, C_SCOND_NONE);
-		o1 |= (v >> 2) & 0xffffff;
+		// 0xf7fabcfd which is guranteed to raise undefined instruction
+		// exception.
+		o1 = 0xf7fabcfd;
 		break;
 	case 97:	/* CLZ Rm, Rd */
  		o1 = oprrr(p->as, p->scond);

具体的には、BL $0 を生成していた以下の5行が削除され、

		v = (0 - pc) - 8;
		o1 = opbra(ABL, C_SCOND_NONE);
		o1 |= (v >> 2) & 0xffffff;

代わりに、未定義命令コードを直接代入する1行が追加されています。

		o1 = 0xf7fabcfd;

コアとなるコードの解説

src/cmd/5l/asm.c は、Go言語のARMリンカ (5l) のアセンブリ生成ロジックの一部を実装しています。このファイルは、Goコンパイラが生成した中間表現(PCODEなど)を、最終的なARM機械語命令に変換する役割を担います。

変更された case 96 は、リンカが特定の「オペコード」または「命令タイプ」を処理する際の分岐の一つです。この case 96 が具体的にどのGoの内部表現に対応するかは、cmd/5l の他の部分やGoコンパイラの内部設計に依存しますが、コメントから「実行されるべきではない、到達不能なコード」をマークするためのものであることがわかります。

  • 変更前:

    • v = (0 - pc) - 8;:相対分岐オフセットを計算しています。pc は現在のプログラムカウンタ(命令のアドレス)です。0 - pc は現在の位置からの相対アドレスを計算し、-8 はARMの分岐命令のエンコーディングにおけるPC相対オフセットの計算方法(PC+8からの相対)を考慮したものです。
    • o1 = opbra(ABL, C_SCOND_NONE);ABLBL 命令のタイプを表す定数、C_SCOND_NONE は条件コードがないことを示します。これにより、BL 命令の基本構造が o1 に設定されます。
    • o1 |= (v >> 2) & 0xffffff;:計算されたオフセット v を右に2ビットシフトし(ARM命令のオフセットはワード単位であるため)、下位24ビットを抽出し、それを o1 にOR演算で結合しています。これにより、BL 命令の最終的な機械語コードが完成します。
  • 変更後:

    • o1 = 0xf7fabcfd;:これは非常に直接的な変更です。複雑なオフセット計算や命令エンコーディング関数 (opbra) の呼び出しを完全にスキップし、代わりに 0xf7fabcfd という固定の32ビット値を o1 に直接代入しています。この値は、ARMプロセッサが未定義命令として扱うことが保証されている特定のビットパターンです。

この変更は、リンカが「到達不能なコード」をマークする方法を、無限ループから明確な未定義命令例外へと切り替えることで、デバッグ時の情報量を大幅に向上させることを目的としています。これは、Goのツールチェーンがより堅牢でデバッグしやすいものになるための、細かではあるが重要な改善です。

関連リンク

参考にした情報源リンク

  • ARM Architecture Reference Manual (ARM命令セットと未定義命令に関する詳細情報)
  • Go言語のリンカ (cmd/5l, cmd/6l, cmd/8l) のソースコード
  • Go言語のコンパイラとリンカに関するドキュメントやブログ記事 (一般的なGoツールチェーンの理解のため)
  • Stack Overflow や技術フォーラムでのARM未定義命令に関する議論