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

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

このコミットは、Go言語のランタイムおよびツールチェインにGoogle Native Client (NaCl) のサポートを統合するための最終的なマージです。具体的には、Go 1.3リリースに向けてNaCl環境でGoプログラムをコンパイル・実行できるように、リンカ(cmd/ldcmd/5lcmd/6lcmd/8l)とアセンブラ(liblink/asm6.cliblink/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固有の変更を導入しています。主な技術的変更点は以下の通りです。

  1. Hnacl定数の導入:

    • include/link.hHnaclという新しい定数が追加され、リンカが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固有の処理が分岐されるようになります。
  2. ポインタサイズ (PtrSize) とレジスタサイズ (RegSize) の動的な設定:

    • src/cmd/6l/l.hからPtrSizeIntSizeの定義が削除され、EXTERN宣言に変わっています。これは、これらの値がコンパイル時に固定されるのではなく、リンカの初期化時に動的に設定されるようになったことを示します。
    • src/cmd/6l/obj.clinkarchinit関数で、GOARCHが"amd64p32"(NaClのamd64版)の場合にlinkamd64p32アーキテクチャを使用するように設定され、PtrSizeRegSizethelinkarch(現在のリンカアーキテクチャ)から取得されるようになります。これにより、NaClの32ビットポインタモード(amd64p32)がサポートされます。
    • include/link.hLinkArch構造体にregsizeフィールドが追加されています。
  3. NaCl固有の初期化と設定:

    • src/cmd/6l/obj.csrc/cmd/8l/obj.carchinit関数にHnaclケースが追加されています。
      • elfinit()が呼び出され、ELF形式のバイナリ生成が有効になります。
      • debug['w']++によりDWARFデバッグ情報の生成が無効化されます。NaCl環境ではデバッグが困難であるため、不要なオーバーヘッドを避ける目的があります。
      • HEADR (ヘッダサイズ), INITTEXT (テキストセグメントの開始アドレス), INITDAT (データセグメントの開始アドレス), INITRND (セグメントのアライメント) など、メモリレイアウトに関するNaCl固有の値が設定されます。特にfuncalign = 32は、NaClの命令アライメント要件(32バイト境界)を満たすための重要な設定です。
  4. 命令パディングとアライメント:

    • NaClは、セキュリティと検証のために、コードが特定のバイト境界(通常32バイト)にアライメントされていることを要求します。このコミットでは、アセンブラ(src/liblink/asm6.c, src/liblink/asm8.c)にnaclpad関数が追加され、命令の間にNOP(No Operation)命令を挿入してアライメントを調整するロジックが導入されています。
    • 特に、ACALL命令やAREPALOCKなどのプレフィックス命令の後に、32バイト境界を跨がないようにパディングが挿入されます。
    • src/cmd/ld/data.ctextaddress関数で、テキストセグメントのアライメントにfuncalignが使用されるようになり、NaClの32バイトアライメント要件がリンカレベルでも適用されます。
  5. NaCl固有の命令変換とレジスタ操作:

    • src/liblink/asm6.csrc/liblink/asm8.casmins関数に、Hnaclの場合の特殊な処理が追加されています。
      • ARET(リターン)命令がNaCl固有のシーケンス(naclret)に置き換えられます。これは、NaClのサンドボックス内で安全にリターンするために、スタックポインタやベースポインタを調整し、R15レジスタ(NaClのベースレジスタ)を基準としたジャンプを行うためのものです。
      • ACALL(コール)やAJMP(ジャンプ)命令も、ターゲットアドレスを32バイト境界にアライメントし、R15レジスタを基準としたアドレス計算を行うための命令が追加されます。
      • ASCAS*, ASTOS*, AMOVS*などの文字列操作命令や、AINT(割り込み)命令もNaCl固有の安全な実装に置き換えられます。
      • REPLOCKプレフィックス命令は、Goのアセンブラでは独立した命令として扱われますが、NaClでは後続の命令のプレフィックスとして扱われるため、asmins関数内で適切に処理されます。
      • D_SP(スタックポインタ)やD_BP(ベースポインタ)へのアクセスに対して、naclspfixnaclbpfixというNaCl固有の命令シーケンスが追加され、R15レジスタを基準としたアドレス変換が行われます。
  6. スタックチェックとgポインタの扱い:

    • src/liblink/obj6.caddstacksplit関数や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と異なるためと考えられます。
  7. 型情報のデコードの変更:

    • 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: リンカの共通ライブラリヘッダ。funcalignEXTERN宣言追加。
  • 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(ベースポインタ)へのアクセスに対して、naclspfixnaclbpfixという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の厳格なサンドボックス環境で正しく動作するために、命令レベル、メモリレイアウトレベル、およびランタイムの挙動レベルで多岐にわたる調整が行われたことを示しています。

関連リンク

参考にした情報源リンク