[インデックス 18688] ファイルの概要
このコミットは、Go言語のランタイムおよびツールチェインにGoogle Native Client (NaCl) のサポートを統合するための最終的なマージです。具体的には、Go 1.3リリースに向けてNaCl環境でGoプログラムをコンパイル・実行できるように、リンカ(cmd/ld
、cmd/5l
、cmd/6l
、cmd/8l
)とアセンブラ(liblink/asm6.c
、liblink/asm8.c
)にNaCl固有の変更が加えられています。これにより、GoプログラムがNaClのサンドボックス環境の制約内で正しく動作するためのアライメント、メモリ管理、命令変換などが調整されています。
コミット
commit d9c6ae6ae8359429a249b074b07baf5cbc894d7f
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 27 20:37:00 2014 -0500
all: final merge of NaCl tree
This CL replays the following one CL from the rsc-go13nacl repo.
This is the last replay CL: after this CL the main repo will have
everything the rsc-go13nacl repo did. Changes made to the main
repo after the rsc-go13nacl repo branched off probably mean that
NaCl doesn't actually work after this CL, but all the code is now moved
over and just needs to be redebugged.
---
cmd/6l, cmd/8l, cmd/ld: support for Native Client
See golang.org/s/go13nacl for design overview.
This CL is publicly visible but not CC'ed to golang-dev,
to avoid distracting from the preparation of the Go 1.2
release.
This CL and the others will be checked into my rsc-go13nacl
clone repo for now, and I will send CLs against the main
repo early in the Go 1.3 development.
R≡khr
https://golang.org/cl/15750044
---
LGTM=bradfitz, dave, iant
R=dave, bradfitz, iant
CC=golang-codereviews
https://golang.org/cl/69040044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d9c6ae6ae8359429a249b074b07baf5cbc894d7f
元コミット内容
cmd/6l, cmd/8l, cmd/ld: support for Native Client
See golang.org/s/go13nacl for design overview.
This CL is publicly visible but not CC'ed to golang-dev,
to avoid distracting from the preparation of the Go 1.2
release.
This CL and the others will be checked into my rsc-go13nacl
clone repo for now, and I will send CLs against the main
repo early in the Go 1.3 development.
R≡khr
https://golang.org/cl/15750044
変更の背景
このコミットは、Go言語がGoogle Native Client (NaCl) をサポートするための重要なステップです。Google Native Clientは、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術であり、ウェブアプリケーションにネイティブに近いパフォーマンスをもたらすことを目的としていました。
Go言語は、そのポータビリティとパフォーマンス特性から、NaCl環境での実行対象としても注目されていました。このコミットは、Go 1.3リリースに向けたNaClサポートの取り組みの一環として、Goのツールチェイン(特にリンカとアセンブラ)にNaCl固有の変更を統合するものです。
コミットメッセージにあるように、この変更はrsc-go13nacl
というRuss Cox氏の個人リポジトリで開発が進められていたNaCl関連の変更を、Goのメインリポジトリにマージする最終段階に当たります。Go 1.2のリリース準備と並行して進められていたため、golang-dev
メーリングリストへのCCは避けられていました。
しかし、Google Native Client自体はその後WebAssemblyにその役割を譲り、Go言語もGo 1.13リリース以降はNaClのサポートを終了しています。このコミットは、Goがウェブプラットフォームへの展開を模索していた時期の歴史的な一歩を示しています。
前提知識の解説
Google Native Client (NaCl)
Google Native Client (NaCl) は、Googleが開発したオープンソースのサンドボックス技術です。ウェブブラウザ内でC/C++などのネイティブコードを安全かつ高性能に実行することを可能にしました。主な目的は、ウェブアプリケーションがネイティブアプリケーションに近いパフォーマンスと機能を提供できるようにすることでした。
NaClは、以下の主要な特徴を持っていました。
- サンドボックス化: 実行されるネイティブコードは厳重なサンドボックス内で動作し、ホストシステムへの不正なアクセスを防ぎます。
- アーキテクチャ非依存: 当初はx86アーキテクチャに特化していましたが、後にPortable Native Client (PNaCl) として、中間表現(LLVM IRに似たもの)を導入し、異なるCPUアーキテクチャ(x86, ARM, MIPSなど)に対応しました。
- 検証: NaClモジュールは、実行前にコードの安全性を検証するプロセスを経ます。これにより、不正な命令やメモリ破壊を引き起こすコードが実行されるのを防ぎます。
Go言語がNaClをサポートするということは、Goで書かれたプログラムがNaClモジュールとしてコンパイルされ、ウェブブラウザ内で実行できるようになることを意味します。
Go言語のリンカとアセンブラ
Go言語のビルドプロセスにおいて、リンカ(cmd/ld
)とアセンブラ(cmd/5l
, cmd/6l
, cmd/8l
など、数字はアーキテクチャを示す: 5はARM, 6はamd64, 8は386)は重要な役割を担っています。
- アセンブラ (
cmd/5l
,cmd/6l
,cmd/8l
): Goのコンパイラが生成した中間コードや、Goの組み込みアセンブリコードを、特定のCPUアーキテクチャの機械語に変換します。 - リンカ (
cmd/ld
): コンパイルされたオブジェクトファイル(機械語コード)を結合し、実行可能なバイナリを生成します。この際、ライブラリのリンク、シンボルの解決、メモリレイアウトの決定、実行ファイルフォーマット(ELF, Mach-O, PEなど)の生成などを行います。
NaCl環境でGoプログラムを実行するためには、これらのツールがNaClのサンドボックスの制約(例えば、特定の命令の使用制限、メモリのアライメント要件、システムコールインターフェースの違いなど)を理解し、それに準拠したコードを生成する必要があります。
技術的詳細
このコミットは、GoのリンカとアセンブラにNaCl固有の変更を導入しています。主な技術的変更点は以下の通りです。
-
Hnacl
定数の導入:include/link.h
にHnacl
という新しい定数が追加され、リンカがNaClターゲットを識別できるようになります。これは、Goのビルドシステムがサポートする様々なOS/アーキテクチャの組み合わせ(HEADTYPE
)の一つとしてNaClが追加されたことを意味します。src/cmd/5l/obj.c
,src/cmd/6l/asm.c
,src/cmd/6l/obj.c
,src/cmd/8l/asm.c
,src/cmd/8l/obj.c
,src/liblink/sym.c
など、リンカの様々な箇所でHnacl
が参照され、NaCl固有の処理が分岐されるようになります。
-
ポインタサイズ (
PtrSize
) とレジスタサイズ (RegSize
) の動的な設定:src/cmd/6l/l.h
からPtrSize
とIntSize
の定義が削除され、EXTERN
宣言に変わっています。これは、これらの値がコンパイル時に固定されるのではなく、リンカの初期化時に動的に設定されるようになったことを示します。src/cmd/6l/obj.c
のlinkarchinit
関数で、GOARCH
が"amd64p32"(NaClのamd64版)の場合にlinkamd64p32
アーキテクチャを使用するように設定され、PtrSize
とRegSize
がthelinkarch
(現在のリンカアーキテクチャ)から取得されるようになります。これにより、NaClの32ビットポインタモード(amd64p32)がサポートされます。include/link.h
のLinkArch
構造体にregsize
フィールドが追加されています。
-
NaCl固有の初期化と設定:
src/cmd/6l/obj.c
とsrc/cmd/8l/obj.c
のarchinit
関数にHnacl
ケースが追加されています。elfinit()
が呼び出され、ELF形式のバイナリ生成が有効になります。debug['w']++
によりDWARFデバッグ情報の生成が無効化されます。NaCl環境ではデバッグが困難であるため、不要なオーバーヘッドを避ける目的があります。HEADR
(ヘッダサイズ),INITTEXT
(テキストセグメントの開始アドレス),INITDAT
(データセグメントの開始アドレス),INITRND
(セグメントのアライメント) など、メモリレイアウトに関するNaCl固有の値が設定されます。特にfuncalign = 32
は、NaClの命令アライメント要件(32バイト境界)を満たすための重要な設定です。
-
命令パディングとアライメント:
- NaClは、セキュリティと検証のために、コードが特定のバイト境界(通常32バイト)にアライメントされていることを要求します。このコミットでは、アセンブラ(
src/liblink/asm6.c
,src/liblink/asm8.c
)にnaclpad
関数が追加され、命令の間にNOP(No Operation)命令を挿入してアライメントを調整するロジックが導入されています。 - 特に、
ACALL
命令やAREP
、ALOCK
などのプレフィックス命令の後に、32バイト境界を跨がないようにパディングが挿入されます。 src/cmd/ld/data.c
のtextaddress
関数で、テキストセグメントのアライメントにfuncalign
が使用されるようになり、NaClの32バイトアライメント要件がリンカレベルでも適用されます。
- NaClは、セキュリティと検証のために、コードが特定のバイト境界(通常32バイト)にアライメントされていることを要求します。このコミットでは、アセンブラ(
-
NaCl固有の命令変換とレジスタ操作:
src/liblink/asm6.c
とsrc/liblink/asm8.c
のasmins
関数に、Hnacl
の場合の特殊な処理が追加されています。ARET
(リターン)命令がNaCl固有のシーケンス(naclret
)に置き換えられます。これは、NaClのサンドボックス内で安全にリターンするために、スタックポインタやベースポインタを調整し、R15レジスタ(NaClのベースレジスタ)を基準としたジャンプを行うためのものです。ACALL
(コール)やAJMP
(ジャンプ)命令も、ターゲットアドレスを32バイト境界にアライメントし、R15レジスタを基準としたアドレス計算を行うための命令が追加されます。ASCAS*
,ASTOS*
,AMOVS*
などの文字列操作命令や、AINT
(割り込み)命令もNaCl固有の安全な実装に置き換えられます。REP
やLOCK
プレフィックス命令は、Goのアセンブラでは独立した命令として扱われますが、NaClでは後続の命令のプレフィックスとして扱われるため、asmins
関数内で適切に処理されます。D_SP
(スタックポインタ)やD_BP
(ベースポインタ)へのアクセスに対して、naclspfix
やnaclbpfix
というNaCl固有の命令シーケンスが追加され、R15レジスタを基準としたアドレス変換が行われます。
-
スタックチェックと
g
ポインタの扱い:src/liblink/obj6.c
のaddstacksplit
関数やload_g_cx
関数で、Hnacl
の場合のスタックチェックロジックとg
(goroutine構造体)ポインタのロード方法が変更されています。indir_cx
という新しいヘルパー関数が導入され、D_CX
レジスタ(Goのランタイムでg
ポインタを保持するために使われる)への間接参照が、NaCl環境ではD_R15
レジスタを基準としたアドレスに変換されるようになります。これは、NaClが特定のレジスタ(R15)をベースレジスタとして使用し、すべてのメモリ参照がそのレジスタからのオフセットとして表現されるという制約があるためです。ctxt->tlsoffset
がNaClでは0に設定されます(src/liblink/sym.c
)。これは、NaCl環境でのTLS(Thread Local Storage)の扱いが他のOSと異なるためと考えられます。
-
型情報のデコードの変更:
src/cmd/ld/decodesym.c
で、Goの型情報をデコードする際に使用されるCommonSize
が、固定値ではなくcommonsize()
関数呼び出しに置き換えられています。これにより、ポインタサイズ(PtrSize
)に依存する型情報のオフセット計算がより柔軟になります。src/pkg/runtime/type.h
のコメントがCommonSize
から../../cmd/ld/decodesym.c:/^commonsize
を参照するように変更され、リンカのデコードロジックとの同期が強調されています。
これらの変更は、Goの生成する機械語コードがNaClの厳格なセキュリティモデルと実行環境の制約に適合するように、低レベルな部分で細心の注意を払って調整されたことを示しています。
コアとなるコードの変更箇所
include/link.h
: リンカの共通ヘッダ。Hnacl
定数、Link
構造体へのNaCl関連フィールド追加、LinkArch
構造体へのregsize
フィールド追加。src/cmd/5l/obj.c
,src/cmd/6l/obj.c
,src/cmd/8l/obj.c
: 各アーキテクチャのリンカのオブジェクトファイル処理。Hnacl
固有の初期化ロジック追加。src/cmd/6l/l.h
,src/cmd/8l/l.h
: 各アーキテクチャのリンカのヘッダ。PtrSize
,IntSize
,RegSize
の定義変更。src/cmd/ld/data.c
: リンカのデータセクション処理。テキストセグメントのアライメント調整。src/cmd/ld/decodesym.c
: リンカのシンボルデコード処理。型情報デコードにおけるCommonSize
の扱い変更。src/cmd/ld/elf.c
: リンカのELFファイル生成処理。NaCl固有のELFヘッダ、プログラムヘッダ、セクションヘッダの設定。src/cmd/ld/lib.c
: リンカの共通ライブラリ処理。goarch
のチェックロジック変更、スタックチェック関連の調整。src/cmd/ld/lib.h
: リンカの共通ライブラリヘッダ。funcalign
のEXTERN
宣言追加。src/liblink/asm6.c
,src/liblink/asm8.c
: amd64および386アーキテクチャのアセンブラ。NaCl固有の命令パディング、命令変換、レジスタ操作ロジック追加。src/liblink/obj5.c
,src/liblink/obj6.c
,src/liblink/obj8.c
: 各アーキテクチャのリンカのオブジェクトファイル処理。LinkArch
構造体のregsize
初期化、progedit
関数でのNaCl固有のアドレス変換、スタック分割ロジックの調整。src/liblink/sym.c
: リンカのシンボル処理。Hnacl
の追加、goarch
チェックの厳格化、tlsoffset
のNaCl固有設定。src/pkg/runtime/type.h
,src/pkg/runtime/typekind.h
: Goランタイムの型定義。型情報のサイズに関するコメント変更。
コアとなるコードの解説
include/link.h
このファイルはリンカのデータ構造と定数を定義する中心的なヘッダです。
Hnacl
という新しい定数がenum
に追加され、GoのビルドターゲットとしてNative Clientが正式に認識されるようになりました。Link
構造体には、rep
,repn
,lock
といったフィールドが追加されています。これらは、アセンブラがREP
(繰り返し)、REPN
(繰り返し、ゼロフラグがセットされるまで)、LOCK
(アトミック操作)といったx86命令プレフィックスを処理する際に、その状態を管理するために使用されます。NaCl環境ではこれらのプレフィックスの扱いが特殊であるため、リンカがその状態を追跡する必要があります。LinkArch
構造体にはregsize
フィールドが追加されました。これは、アーキテクチャごとのレジスタサイズ(通常はポインタサイズと同じか、異なる場合もある)を定義するために使用され、特にNaClのamd64p32ターゲットで重要になります。
src/cmd/6l/obj.c
および src/cmd/8l/obj.c
これらのファイルは、それぞれamd64と386アーキテクチャのリンカの初期化とオブジェクトファイル処理を担当します。
linkarchinit()
関数が追加され、GOARCH
が"amd64p32"の場合にlinkamd64p32
アーキテクチャ(32ビットポインタのamd64)が選択されるようになりました。これにより、NaClの特定の実行環境に対応します。archinit()
関数内のHnacl
ケースでは、NaClバイナリの特性に合わせた初期設定が行われます。elfinit()
: ELF形式のバイナリを生成するための初期化。debug['w']++
: DWARFデバッグ情報の無効化。NaClのサンドボックス環境ではデバッグが困難なため、不要な情報を生成しないようにします。HEADR = 0x10000
,INITTEXT = 0x20000
,INITDAT = 0
,INITRND = 0x10000
: NaClバイナリのメモリレイアウトに関する特定のアドレスとアライメントが設定されます。特にfuncalign = 32
は、NaClの命令アライメント要件(32バイト境界)を満たすための重要な設定です。
src/liblink/asm6.c
および src/liblink/asm8.c
これらのファイルは、それぞれamd64と386アーキテクチャのGoのアセンブラのバックエンドです。Goのコンパイラが生成した中間表現を実際の機械語に変換する役割を担います。
naclpad
関数: この新しい関数は、NaClの32バイト命令アライメント要件を満たすために、コードにNOP命令を挿入する役割を担います。NaClは、セキュリティ上の理由から、すべての分岐ターゲットが32バイト境界にアライメントされていることを要求します。この関数は、命令のサイズと現在のオフセットに基づいて、必要なパディングバイト数を計算し、NOP命令で埋めます。asmins
関数: 個々のGoアセンブリ命令を機械語に変換する主要な関数です。Hnacl
の場合、ARET
(リターン)、ACALL
(コール)、AJMP
(ジャンプ)などの命令がNaCl固有の命令シーケンスに置き換えられます。これは、NaClのサンドボックス内で安全に制御を移譲し、スタックを管理するために必要です。例えば、naclret
はスタックポインタとベースポインタを調整し、R15レジスタ(NaClのベースレジスタ)を基準としたジャンプを行います。AREP
,AREPN
,ALOCK
といったx86命令プレフィックスは、Goのアセンブラでは独立した命令として扱われますが、NaClでは後続の命令のプレフィックスとして扱われるため、asmins
関数内で適切に処理され、ctxt->rep
,ctxt->repn
,ctxt->lock
フラグがセットされます。D_SP
(スタックポインタ)やD_BP
(ベースポインタ)へのアクセスに対して、naclspfix
やnaclbpfix
というNaCl固有の命令シーケンスが追加されます。これらは、R15レジスタを基準としたアドレス変換を行い、NaClのメモリモデルに適合させます。nacltrunc
関数は、レジスタの値を32ビットに切り詰めるための命令を生成します。これは、NaClのamd64p32環境でポインタが32ビットに制限されるためです。
src/liblink/obj6.c
amd64アーキテクチャのリンカのオブジェクトファイル処理です。
progedit
関数: プログラムの命令を編集する関数です。Hnacl
の場合、nacladdr
関数を呼び出して、命令のオペランドのアドレスをNaCl固有の形式に変換します。nacladdr
関数: この新しい関数は、NaClのメモリモデルに合わせてアドレスを変換します。特に、D_GS
(スレッドローカルストレージ)への参照をD_BP
(ベースポインタ)またはD_R15
(NaClのベースレジスタ)を基準としたアドレスに変換します。これは、NaClが特定のレジスタをベースレジスタとして使用し、すべてのメモリ参照がそのレジスタからのオフセットとして表現されるという制約があるためです。addstacksplit
関数: Goのスタック拡張チェックを挿入する関数です。Hnacl
の場合、スタックサイズのアライメントチェックが追加され、g->panicwrap
の計算でctxt->arch->regsize
が使用されるようになります。indir_cx
関数:g
ポインタ(goroutine構造体へのポインタ)をロードする際に使用されるD_CX
レジスタへの間接参照を、NaCl環境ではD_R15
レジスタを基準としたアドレスに変換します。stacksplit
関数: スタック分割ロジックを実装する関数です。Hnacl
の場合、CMPQ
,LEAQ
,MOVQ
,SUBQ
といった命令が、それぞれACMPL
,ALEAL
,AMOVL
,ASUBL
といった32ビット版の命令に置き換えられます。これは、NaClのamd64p32環境でポインタが32ビットに制限されるためです。
これらの変更は、Goの生成するバイナリがNaClの厳格なサンドボックス環境で正しく動作するために、命令レベル、メモリレイアウトレベル、およびランタイムの挙動レベルで多岐にわたる調整が行われたことを示しています。
関連リンク
- https://github.com/golang/go/commit/d9c6ae6ae8359429a249b074b07baf5cbc894d7f
- golang.org/s/go13nacl (Go 1.3 Native Clientの設計概要に関するドキュメントへのリンク、現在はアクセスできない可能性があります)
- https://golang.org/cl/15750044 (元のCLへのリンク、現在はアクセスできない可能性があります)
- https://golang.org/cl/69040044 (このコミットのCLへのリンク、現在はアクセスできない可能性があります)
参考にした情報源リンク
- Google Native Client - Wikipedia
- Go 1.3 Native Client Support - Reddit
- Go 1.13 Release Notes - Deprecation of Native Client
- Optimizing Assembly - Agner Fog (NOP命令の最適化に関する一般的な情報源として参照)
- Go's linker and its internals (Go 1.3リンカに関する一般的な情報源として参照)