[インデックス 19709] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/5c
、cmd/5g
)、リンカ(cmd/5l
)、およびリンカライブラリ(liblink
)に、Google Native Client (NaCl) のARMアーキテクチャサポートを追加するものです。これにより、GoプログラムをNaCl環境のARMプロセッサ上で実行できるようになります。
コミット
commit 783bcba84d253227950fa005bf8214a6350d1104
Author: Shenghou Ma <minux@golang.org>
Date: Thu Jul 10 15:14:37 2014 -0400
cmd/5c, cmd/5g, cmd/5l, liblink: nacl/arm support
LGTM=dave, rsc
R=rsc, iant, dave
CC=golang-codereviews
https://golang.org/cl/108360043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/783bcba84d253227950fa005bf8214a6350d1104
元コミット内容
cmd/5c
, cmd/5g
, cmd/5l
, liblink
に Native Client (NaCl) の ARM サポートを追加します。
変更の背景
この変更の背景には、Go言語の実行環境を多様なプラットフォームに拡大するというGoプロジェクトの継続的な取り組みがあります。特に、Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術であり、Goプログラムをウェブアプリケーションの一部として利用可能にするための重要なターゲットプラットフォームでした。
当初、GoはNaClのx86アーキテクチャ(32-bitおよび64-bit)をサポートしていましたが、ARMベースのデバイス(例えばChromebooksやAndroidデバイス)の普及に伴い、NaCl環境でのARMサポートの需要が高まりました。このコミットは、GoコンパイラとリンカがARMアーキテクチャ向けのNaClバイナリを生成できるようにすることで、このギャップを埋めることを目的としています。
NaCl環境は、セキュリティ上の理由から、通常のネイティブコードとは異なる厳格な制約(例えば、特定のシステムコールへのアクセス制限、コードの整列要件、間接ジャンプの制限など)を課します。これらの制約に対応するためには、コンパイラとリンカレベルでの特別な処理が必要となります。このコミットは、GoのツールチェーンがこれらのNaCl/ARM固有の制約を適切に処理し、安全かつ効率的なコードを生成するための基盤を構築します。
前提知識の解説
Google Native Client (NaCl)
Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。主な目的は、ウェブアプリケーションのパフォーマンスを向上させつつ、セキュリティを維持することです。NaClは、以下の特徴を持ちます。
- サンドボックス化: 実行されるコードは厳重に隔離された環境で動作し、ホストシステムへの不正なアクセスを防ぎます。
- 検証: 実行前にバイナリコードがセキュリティポリシーに準拠しているか検証されます。これには、特定の命令の使用制限や、コードの整列(バンドル化)要件などが含まれます。
- アーキテクチャ独立性: Portable Native Client (PNaCl) を使用することで、異なるCPUアーキテクチャ(x86, ARMなど)間でバイナリの移植性を実現します。ただし、このコミットはPNaClではなく、特定のアーキテクチャ(ARM)向けのNaClバイナリ生成に関するものです。
Goコンパイラとリンカの役割
Go言語のビルドプロセスでは、以下の主要なツールが関与します。
cmd/5c
(Go ARMコンパイラ): GoソースコードをARMアセンブリコードにコンパイルします。5c
の5
はARMアーキテクチャを指します(歴史的な経緯による)。cmd/5g
(Go ARMコンパイラ): GoソースコードをARMアセンブリコードにコンパイルします。5c
と5g
はGoの初期のコンパイラ設計の名残で、それぞれC言語で書かれたフロントエンドとGo言語で書かれたバックエンドを指すことがありますが、文脈によっては両方ともARMコンパイラを指します。このコミットでは、両者がARMコード生成に関連する変更を受けています。cmd/5l
(Go ARMリンカ): コンパイルされたオブジェクトファイルとライブラリを結合し、実行可能なバイナリを生成します。この際、ターゲットプラットフォーム(この場合はNaCl/ARM)固有のフォーマットや制約(例:ELFフォーマットの調整、セクションのアドレス配置)を考慮します。liblink
(リンカライブラリ): リンカの共通ロジックやアーキテクチャ固有のアセンブリ生成ルーチンを提供するライブラリです。cmd/5l
はこのライブラリを利用して、最終的なバイナリを生成します。
ARMアーキテクチャと命令セット
ARMは、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)アーキテクチャです。NaCl環境では、ARM命令セットのサブセットが許可され、特定の命令(例:システムコール命令)は制限されます。また、NaClのサンドボックスモデルでは、コードの実行が特定の「バンドル」に整列されている必要があります。これは、間接ジャンプのターゲットを制限し、コードの検証を容易にするためです。
技術的詳細
このコミットは、GoツールチェーンがNaCl/ARM環境の特殊な要件を満たすように、複数のコンポーネントにわたる変更を導入しています。
-
NaCl固有のコード生成の調整:
src/cmd/5c/peep.c
およびsrc/cmd/5g/peep.c
: 命令の最適化(peephole optimization)を行う部分で、nacl
フラグが導入されています。これにより、NaCl環境では特定のメモリアドレッシングモード(D_SHIFT
など)が制限されるため、それらの使用を避けるようになります。src/cmd/5c/reg.c
およびsrc/cmd/5g/reg.c
: レジスタ割り当てのロジックが変更され、mod/div
ランタイムルーチンがレジスタR12
を破壊する可能性があるため、R12
が使用済みとしてマークされるようになります。これはNaCl環境でのレジスタ使用規約に合わせた調整と考えられます。src/cmd/5c/swt.c
およびsrc/cmd/5g/cgen.c
,src/cmd/5g/ggen.c
:nacl
フラグに基づいて、特定の最適化(例:スイッチ文の直接ジャンプテーブル生成、MOVW
命令の最適化、duffzero
やduffcopy
のような最適化されたメモリ操作ルーチンの使用)を無効化しています。これは、NaClのコード検証器がこれらの最適化によって生成される特定の命令パターンを許可しないため、またはコードの整列要件に違反する可能性があるためです。
-
リンカのNaCl/ARMサポート:
src/cmd/5l/5.out.h
: 新しい命令タイプADATABUNDLE
とADATABUNDLEEND
が追加されています。これらはNaClのコードバンドル整列要件に対応するためのものです。src/cmd/5l/asm.c
およびsrc/cmd/5l/obj.c
: リンカがELF形式のNaClバイナリを生成する際に、Hnacl
(Host Native Client)タイプを認識し、NaCl固有のヘッダサイズ(HEADR
)、関数整列(funcalign
)、テキストセクションとデータセクションの初期アドレス(INITTEXT
,INITDAT
)を設定するようになります。これは、NaClバイナリが特定のメモリレイアウトと整列要件を持つためです。src/liblink/asm5.c
: このファイルが最も広範な変更を受けており、NaCl/ARM固有のアセンブリ生成ロジックが実装されています。asmoutnacl
関数が導入され、NaCl環境での命令アセンブルを担当します。この関数は、命令のサイズを計算し、必要に応じてパディングを追加してコードを16バイト境界に整列させます。AUNDEF
(未定義命令)やAPLD
(プリロード命令)のような特定の命令が、NaCl環境では異なる命令(例:NACL_INSTR_ARM_ABORT_NOW
)に置き換えられます。AB
(無条件分岐)やABL
(関数呼び出し)命令が、NaClの制約(間接ジャンプの制限)に対応するために、レジスタを介した分岐に変換される場合があります。特に、runtime.deferreturn
のような特定の関数呼び出しでは、コードの整列を保証するためにPC(プログラムカウンタ)の調整が行われます。MOVW.W x(R13), PC
のようなスタックポインタ(R13)からのロードとPCへの書き込みを伴う命令(関数からのリターンによく使われる)が、NaClの制約に合わせて複数の命令に展開されます。これは、PCへの直接書き込みが制限されるため、R12レジスタを介した間接ジャンプに変換されます。- 非常に大きなフレームサイズを持つ関数プロローグ(
MOVW.W R14,-100004(R13)
のような命令)が、ADD
命令とMOVW
命令の2つに分割されます。これは、単一の命令で表現できない大きなオフセットを扱うためです。 MOVW Rx, X(Ry)
のようなロード/ストア命令で、レジスタRy
がR9
やR13
でない場合に、アドレス計算のためにR11
レジスタを一時的に使用するよう命令が分割されることがあります。これは、NaCl環境でのレジスタ使用の制約や、特定のメモリアドレッシングモードの制限に対応するためです。ADATABUNDLE
とADATABUNDLEEND
命令が、リテラルプール(定数データが配置される領域)の整列を保証するために使用されます。ADATABUNDLE
は新しいデータバンドルの開始を示し、ADATABUNDLEEND
はパディングを追加して次の命令を16バイト境界に整列させます。
src/liblink/sym.c
: TLS(Thread Local Storage)オフセットの初期化にARMアーキテクチャ('5'
)のケースが追加されています。
これらの変更は、Goのコンパイラとリンカが、NaClの厳格なサンドボックスモデルとARMアーキテクチャの特性の両方に対応できるようにするためのものです。特に、コードの整列、間接ジャンプの制限、特定の命令の使用制限といったNaCl固有の要件を満たすための複雑なアセンブリ生成ロジックがliblink/asm5.c
に集約されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主にsrc/liblink/asm5.c
に集中しています。特に、asmoutnacl
関数の追加と、span5
関数におけるNaCl固有の命令アセンブルロジックの導入が重要です。
src/liblink/asm5.c
の変更点
-
asmoutnacl
関数の追加: この関数は、NaCl環境向けにARM命令をアセンブルする主要なロジックを含んでいます。命令のサイズ計算、16バイト境界へのパディング、特定の命令のNaCl互換な代替命令への変換(例:AUNDEF
->NACL_INSTR_ARM_ABORT_NOW
)、分岐命令の変換(レジスタを介した分岐)、スタックポインタ操作を伴うリターン命令の展開、大きなオフセットを持つロード/ストア命令の分割などを行います。static int asmoutnacl(Link *ctxt, int32 origPC, Prog *p, Optab *o, int32 *out) { // ... (詳細な実装は上記コミット内容を参照) }
-
span5
関数におけるasmoutnacl
の呼び出し:span5
関数は、Goのリンカにおけるコードレイアウトと命令のサイズ計算を行う主要な関数です。この関数内で、ctxt->headtype == Hnacl
(ターゲットがNaClの場合)の条件分岐が追加され、通常のoplook
の代わりにasmoutnacl
を呼び出して命令のサイズを計算し、必要に応じてPC(プログラムカウンタ)を調整するようになります。これにより、NaClのコード整列要件が満たされます。void span5(Link *ctxt, LSym *cursym) { // ... for(op = p, p = p->link; p != nil || ctxt->blitrl != nil; op = p, p = p->link) { // ... if(ctxt->headtype != Hnacl) { m = o->size; } else { m = asmoutnacl(ctxt, c, p, o, nil); // サイズ計算とPC調整 c = p->pc; // asmoutnaclがPCを変更する可能性を考慮 o = oplook(ctxt, p); // asmoutnaclがpを変更する可能性を考慮 } // ... } // ... }
-
リテラルプール(Literal Pool)の管理:
checkpool
およびaddpool
関数が変更され、NaCl環境でのリテラルプールの整列(16バイト境界)を保証するために、ADATABUNDLE
およびADATABUNDLEEND
命令が挿入されるようになります。// src/liblink/asm5.c static int checkpool(Link *ctxt, Prog *p, int sz) { // ... if(ctxt->headtype == Hnacl && pool.size % 16 != 0) { // ... ADATABUNDLEEND を挿入して整列 } // ... } // src/liblink/asm5.c void addpool(Link *ctxt, Prog *p, Addr *a) { // ... if(ctxt->headtype == Hnacl && pool.size%16 == 0) { // ... ADATABUNDLE を挿入して新しいデータバンドルを開始 } // ... }
その他の主要な変更箇所
src/cmd/5c/peep.c
,src/cmd/5g/peep.c
:nacl
フラグに基づくメモリアドレッシングモードの制限。src/cmd/5c/reg.c
,src/cmd/5g/reg.c
:mod/div
ルーチンによるR12
レジスタの破壊を考慮したレジスタ割り当て。src/cmd/5l/5.out.h
:ADATABUNDLE
,ADATABUNDLEEND
命令の定義追加。src/cmd/5l/obj.c
:Hnacl
ターゲットに対するリンカの初期設定(HEADR
,funcalign
,INITTEXT
,INITDAT
,INITRND
)。
コアとなるコードの解説
asmoutnacl
関数
asmoutnacl
関数は、NaCl/ARMバイナリ生成の心臓部です。NaClのサンドボックスは、セキュリティと検証の目的で、実行されるコードに非常に厳格な制約を課します。特に、ARMアーキテクチャでは、命令が特定の「バンドル」に整列されている必要があり、間接ジャンプのターゲットも制限されます。
この関数は、Goコンパイラが生成した抽象的な命令(Prog
構造体で表現される)を受け取り、それを実際のARMマシンコードに変換する際に、以下のNaCl固有の処理を行います。
- 命令の整列とパディング: NaClでは、命令は16バイト境界に整列された「バンドル」内に配置される必要があります。
asmoutnacl
は、命令のPC(プログラムカウンタ)を調整し、必要に応じてNOP(No Operation)命令や特別なパディング命令を挿入して、次の命令が正しい境界から始まるようにします。これは、p->pc = (p->pc+15) & ~15;
のようなコードで実現されます。 - 特殊命令の変換:
AUNDEF
(未定義命令) は、NaCl環境ではNACL_INSTR_ARM_ABORT_NOW
(UDF #0xEDE0) という特別な命令に変換されます。これは、未定義の動作が発生した場合に安全にプログラムを終了させるためのNaCl固有のメカニズムです。APLD
(プリロード命令) は、NaClでは許可されないため、MOVW R1, R1
(NOPに相当) に変換されます。
- 分岐命令の処理:
AB
(無条件分岐) やABL
(関数呼び出し) のような分岐命令は、NaClの制約により、直接的なPC相対分岐が制限される場合があります。asmoutnacl
は、これらの命令をレジスタを介した間接分岐(例:BIC $0xc000000f, Rx
とBX Rx
の組み合わせ)に変換することがあります。これは、NaClのコード検証器が間接ジャンプのターゲットをより厳密にチェックできるようにするためです。- 特に、
runtime.deferreturn
のようなGoランタイムの重要な関数への呼び出しでは、コードの整列を保証するために、呼び出し命令のPCが厳密に調整されます。
- スタックポインタ操作とリターン命令の展開:
Goの関数からのリターンでは、スタックポインタ(ARMのR13)からのロードとPCへの書き込み(
MOVW.W x(R13), PC
)がよく使われます。NaClではPCへの直接書き込みが制限されるため、この命令は複数の命令に展開されます。具体的には、ロードされた値が一時的にR12レジスタに格納され、その後R12を介して間接的にPCにジャンプします。これにより、NaClのセキュリティモデルに準拠しつつ、Goのリターンメカニズムをエミュレートします。 - 大きなオフセットを持つロード/ストア命令の分割:
ARM命令は、ロード/ストア命令のオフセット値に制限があります。Goコンパイラが非常に大きなスタックフレームを持つ関数を生成する場合、スタックポインタからのオフセットが命令の許容範囲を超えることがあります。
asmoutnacl
は、このような命令をADD
命令とMOVW
命令の2つに分割することで対応します。例えば、MOVW.W R14,-100004(R13)
はADD $-100004, R13
とMOVW R14, 0(R13)
に変換されます。 - レジスタ使用の調整:
特定のロード/ストア命令で、アドレス計算に使用されるレジスタが
R9
やR13
でない場合、R11
レジスタを一時的に使用してアドレスを計算し、その後ロード/ストアを実行するように命令が分割されることがあります。これは、NaCl環境でのレジスタ使用の制約や、特定のメモリアドレッシングモードの制限に対応するためです。
リテラルプールとデータバンドル
NaClでは、コードとデータが混在するセクション(特にリテラルプール)も16バイト境界に整列された「データバンドル」として扱われる必要があります。ADATABUNDLE
とADATABUNDLEEND
命令は、この目的のために導入されました。
ADATABUNDLE
: 新しいデータバンドルの開始を示し、その後のデータが16バイト境界から始まるようにします。ADATABUNDLEEND
: データバンドルの終端を示し、必要に応じてパディングを挿入して次のデータまたはコードが16バイト境界に整列するようにします。
これらの命令は、リンカがリテラルプールを配置する際に、NaClの整列要件を満たすように自動的に挿入されます。これにより、GoプログラムがNaCl環境で正しくロードされ、検証されることが保証されます。
関連リンク
- Google Native Client (NaCl) 公式サイト (現在は非推奨): https://developer.chrome.com/native-client
- Go言語のIssueトラッカーでの関連する議論 (CLへのリンク): https://golang.org/cl/108360043
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/5c
,src/cmd/5g
,src/cmd/5l
,src/liblink
ディレクトリ) - Google Native Client のドキュメント (特にARMアーキテクチャとサンドボックスの制約に関する情報)
- ARMアーキテクチャリファレンスマニュアル (命令セットとアドレッシングモードの理解のため)
- Go言語のコンパイラとリンカの内部構造に関する一般的な知識
- Go言語のコードレビューシステム (Gerrit) のCL (Change-list) ページ
- Go言語のメーリングリストやフォーラムでの関連する議論 (もしあれば)
- https://developer.chrome.com/docs/native-client/nacl-and-pnacl/
- https://go.dev/doc/install/source#go1.4 (Go 1.4 リリースノート、NaClサポートに関する言及がある可能性)
- https://go.dev/doc/go1.3 (Go 1.3 リリースノート、このコミットがGo 1.3または1.4の期間に属するため)