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

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

このコミットは、Go言語のツールチェインにおいて、NetBSD/ARMアーキテクチャ向けのELF(Executable and Linkable Format)バイナリ生成をサポートするための変更を導入しています。具体的には、ARMアーキテクチャ用のリンカであるcmd/5lと、汎用リンカであるcmd/ldが修正され、NetBSD環境で実行可能なGoプログラムの生成が可能になりました。

コミット

commit 2ebfaa36b51e860d066799afa90de3efe092ae4a
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Mon Feb 4 00:40:11 2013 +0800

    cmd/5l, cmd/ld: support generating ELF binaries for NetBSD/ARM
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7261043

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

https://github.com/golang/go/commit/2ebfaa36b51e860d066799afa90de3efe092ae4a

元コミット内容

cmd/5l, cmd/ld: support generating ELF binaries for NetBSD/ARM

変更の背景

Go言語は、その設計思想としてクロスプラットフォーム対応を重視しており、様々なオペレーティングシステムとアーキテクチャの組み合わせで動作するバイナリを生成できることを目指しています。このコミットが行われた2013年当時、Goは既にLinux、FreeBSD、OpenBSDなどの主要なUnix系OSと、x86、ARMなどのアーキテクチャをサポートしていましたが、NetBSDのARMアーキテクチャ向けバイナリ生成はまだ完全ではありませんでした。

この変更の背景には、NetBSD/ARM環境でGoプログラムをネイティブに実行したいというニーズがあったと考えられます。Goのリンカは、各OSとアーキテクチャの組み合わせに応じたバイナリフォーマット(ELF、Mach-O、PEなど)の生成ロジックを持っています。NetBSDはELF形式を採用していますが、そのELFバイナリの構造や動的リンカのパス、特定のセグメントの配置、セキュリティ関連のプログラムヘッダの扱いなどにおいて、他のOS(特にLinux)とは異なる慣習や要件が存在します。

このコミットは、これらのNetBSD/ARM特有の要件に対応し、GoのリンカがNetBSD/ARM上で正しくロード・実行されるELFバイナリを生成できるようにするためのものです。これにより、Go言語の対応プラットフォームがさらに拡大し、NetBSD/ARMユーザーもGoの恩恵を受けられるようになりました。

前提知識の解説

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

  1. ELF (Executable and Linkable Format):

    • Unix系OS(Linux, FreeBSD, NetBSDなど)で広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
    • ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして実際のデータ(コード、データ、シンボルテーブルなど)で構成されます。
    • プログラムヘッダ (Program Header): 実行時にメモリにロードされるセグメント(コード、データなど)の配置や属性を記述します。PT_LOAD(ロード可能なセグメント)、PT_INTERP(動的リンカのパス)、PT_GNU_STACK(スタックの実行権限)、PT_PAX_FLAGS(PaXセキュリティ機能のフラグ)などがあります。
    • セクションヘッダ (Section Header): リンク時に使用されるファイルの論理的な構造(.text.data.symtab.dynsym.rel.pltなど)を記述します。
    • 動的リンカ (Dynamic Linker): 共有ライブラリを使用するELFバイナリが実行される際に、必要な共有ライブラリをメモリにロードし、シンボル解決を行うプログラムです。NetBSDでは通常/libexec/ld.elf_soが使用されます。
  2. Go言語のツールチェイン:

    • cmd/5l: Go言語のARMアーキテクチャ向けリンカです。Goのソースコードをコンパイルして生成されたオブジェクトファイルをリンクし、実行可能なバイナリを生成します。
    • cmd/ld: Go言語の汎用リンカです。cmd/5lのようなアーキテクチャ固有のリンカから呼び出され、最終的なELFバイナリの構造を決定する役割を担います。特に、ELFヘッダ、プログラムヘッダ、セクションヘッダの生成ロジックが含まれます。
  3. ARM EABI (Embedded Application Binary Interface):

    • ARMプロセッサ向けのバイナリインターフェース標準です。関数呼び出し規約、データ型のアライメント、例外処理など、バイナリ互換性を保証するためのルールを定義します。
    • ELFファイル内の特定のフラグ(例: EF_ARM_EABI_VER5)でEABIのバージョンが示されることがあります。
  4. 動的リンクとTLS (Thread Local Storage):

    • 動的リンク: 実行時に共有ライブラリをロードしてリンクする方式です。これにより、ディスクスペースの節約やメモリの共有が可能になります。
    • TLS (Thread Local Storage): 各スレッドが独自のデータコピーを持つためのメカニズムです。Goのランタイムでは、ゴルーチンごとのコンテキスト情報などをTLSに格納することがあります。TLSへのアクセス方法(オフセットなど)はOSやアーキテクチャによって異なります。

技術的詳細

このコミットの技術的詳細は、主にNetBSD/ARM向けのELFバイナリ生成における以下の調整に集約されます。

  1. 動的リンカのパスの指定:

    • src/cmd/5l/asm.cにおいて、NetBSDの動的リンカのパスが"/libexec/ld.elf_so"と明示的に定義されました。以前は"XXX"というプレースホルダでした。これは、NetBSDシステムがバイナリをロードする際に、このパスに存在するリンカを探すため、正確なパスの指定が不可欠です。
  2. ELFバイナリ構造の調整:

    • src/cmd/5l/obj.cでは、NetBSD向けのELFバイナリの初期設定(HEADR, INITTEXT, INITDAT, INITRNDなど)に関する古い、固定値の定義が削除されました。これは、NetBSD/ARMがLinux/ARMやFreeBSD/ARMと同様に、より汎用的なELFバイナリの生成ロジックを使用するように変更されたことを意味します。これにより、GoのリンカはNetBSDのELF仕様に合わせた柔軟なバイナリを生成できるようになります。
    • 特に、HnetbsdHlinuxHfreebsdと同じグループに分類され、動的リンクとTLSオフセット(tlsoffset = -8)の設定が共通化されました。これは、Goのランタイムがスレッドローカルストレージにアクセスする際のオフセットが、これらのOS/アーキテクチャ間で共通であることを示唆しています。
  3. ELFヘッダフラグの条件付き適用:

    • src/cmd/ld/elf.celfinit関数において、ELFヘッダのe_flagsフィールドに設定される0x5000002(Version5 EABIを示すフラグ)が、HEADTYPE == Hlinuxの場合にのみ適用されるように変更されました。これは、NetBSD/ARMが必ずしもこの特定のEABIフラグを必要としない、あるいは異なるEABIバージョンを使用している可能性を考慮したものです。これにより、NetBSDのELFローダがGoバイナリを正しく認識できるようになります。
  4. リロケーションセクションと動的シンボルテーブルのリンク:

    • src/cmd/ld/elf.casmbelf関数において、.rel.pltセクション(Procedure Linkage Tableのリロケーション情報を含む)のセクションヘッダに、sh->link = elfshname(".dynsym")->shnum;という行が追加されました。これは、リロケーションセクションがどのシンボルテーブルを参照するかを示す重要な情報です。動的リンクされたバイナリでは、リロケーションエントリが解決すべきシンボルを.dynsym(動的シンボルテーブル)から探すため、このリンクは必須です。この変更により、NetBSD/ARMバイナリにおける動的シンボル解決が正しく行われるようになります。
  5. セキュリティ関連プログラムヘッダの条件付き追加:

    • src/cmd/ld/elf.casmbelf関数において、PT_GNU_STACKPT_PAX_FLAGSというプログラムヘッダの追加が、HEADTYPE == Hlinuxの場合にのみ行われるように変更されました。
      • PT_GNU_STACK: スタックの実行権限(通常は実行不可)を示すためにLinuxで広く使われるプログラムヘッダです。
      • PT_PAX_FLAGS: PaX(Patch for Linux)セキュリティ機能(mprotect、randexec、emutrampなど)のフラグを設定するためのプログラムヘッダです。
    • これらのヘッダはLinux特有のセキュリティ機能や慣習に関連するものであり、NetBSDは独自のセキュリティメカニズムやELFバイナリの慣習を持っています。したがって、これらのヘッダをNetBSD向けバイナリに無条件で追加すると、NetBSDのローダがバイナリを拒否したり、予期せぬ動作を引き起こしたりする可能性があります。この変更は、NetBSDのELFバイナリの慣習を尊重し、互換性を確保するための重要な調整です。

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

このコミットにおける主要なコード変更は以下の3つのファイルに集中しています。

  1. src/cmd/5l/asm.c:

    • netbsddynld変数の初期化:
      --- a/src/cmd/5l/asm.c
      +++ b/src/cmd/5l/asm.c
      @@ -40,7 +40,7 @@ static Prog *PP;
       char linuxdynld[] = "/lib/ld-linux.so.3"; // 2 for OABI, 3 for EABI
       char freebsddynld[] = "/usr/libexec/ld-elf.so.1";
       char openbsddynld[] = "XXX";
      -char netbsddynld[] = "XXX";
      +char netbsddynld[] = "/libexec/ld.elf_so";
       
       int32
       entryvalue(void)
      @@ -581,9 +581,6 @@ asmb(void)
       		case Hplan9x32:
       			symo = HEADR+segtext.len+segdata.filelen;
       			break;
      -		case Hnetbsd:
      -			symo = rnd(segdata.filelen, 4096);
      -			break;
       		ElfSym:
       			symo = rnd(HEADR+segtext.filelen, INITRND)+segdata.filelen;
       			symo = rnd(symo, INITRND);
      
    • Hnetbsdsymo計算ロジックの削除。
  2. src/cmd/5l/obj.c:

    • headers配列内のHnetbsdの位置変更とコメント修正:
      --- a/src/cmd/5l/obj.c
      +++ b/src/cmd/5l/obj.c
      @@ -48,21 +48,22 @@ Header headers[] = {
          "noheader", Hnoheader,
          "risc", Hrisc,
          "plan9", Hplan9x32,
      -   "netbsd", Hnetbsd,
          "ixp1200", Hixp1200,
          "ipaq", Hipaq,
          "linux", Hlinux,
          "freebsd", Hfreebsd,
      +   "netbsd", Hnetbsd,
          0, 0
       };
       
       /*
        *	-Hrisc -T0x10005000 -R4		is aif for risc os
        *	-Hplan9 -T4128 -R4096		is plan9 format
      - *	-Hnetbsd -T0xF0000020 -R4	is NetBSD format
        *	-Hixp1200			is IXP1200 (raw)
        *	-Hipaq -T0xC0008010 -R1024	is ipaq
        *	-Hlinux -Tx -Rx			is linux elf
      + *	-Hfreebsd			is freebsd elf
      + *	-Hnetbsd			is netbsd elf
        */
       
       void
      @@ -166,15 +167,6 @@ main(int argc, char *argv[])
       		if(INITRND == -1)
       			INITRND = 4096;
       		break;
      -	case Hnetbsd:	/* boot for NetBSD */
      -		HEADR = 32L;
      -		if(INITTEXT == -1)
      -			INITTEXT = 0xF0000020L;
      -		if(INITDAT == -1)
      -			INITDAT = 0;
      -		if(INITRND == -1)
      -			INITRND = 4096;
      -		break;
       	case Hixp1200: /* boot for IXP1200 */
       		HEADR = 0L;
       		if(INITTEXT == -1)
      @@ -195,6 +187,7 @@ main(int argc, char *argv[])
       		break;
       	case Hlinux:	/* arm elf */
       	case Hfreebsd:
      +	case Hnetbsd:
       		debug['d'] = 0;	// with dynamic linking
       		tlsoffset = -8; // hardcoded number, first 4-byte word for g, and then 4-byte word for m
       		                // this number is known to ../../pkg/runtime/cgo/gcc_linux_arm.c
      
    • main関数内のHnetbsdケースの削除と、Hlinux, Hfreebsdケースへの統合。
  3. src/cmd/ld/elf.c:

    • elfinit関数内のhdr.flags設定の条件化:
      --- a/src/cmd/ld/elf.c
      +++ b/src/cmd/ld/elf.c
      @@ -55,7 +55,9 @@ elfinit(void)\n \n \t// 32-bit architectures\n \tcase '5':\n-\t\thdr.flags = 0x5000002; // has entry point, Version5 EABI\n+\t\t// we only use EABI on linux/arm\n+\t\tif(HEADTYPE == Hlinux)\n+\t\t\thdr.flags = 0x5000002; // has entry point, Version5 EABI\n \t\t// fallthrough\n \tdefault:\
      
    • asmbelf関数内のリロケーションセクションリンクとプログラムヘッダ追加の条件化:
      --- a/src/cmd/ld/elf.c
      +++ b/src/cmd/ld/elf.c
      @@ -1303,6 +1305,7 @@ asmbelf(vlong symo)\n \t\t\tsh->type = SHT_REL;\n \t\t\tsh->flags = SHF_ALLOC;\n \t\t\tsh->entsize = ELF32RELSIZE;\n+\t\t\tsh->link = elfshname(".dynsym")->shnum;\n \t\t\tshsym(sh, lookup(".rel.plt", 0));\n \n \t\t\tsh = elfshname(".rel");\
      @@ -1375,15 +1378,17 @@ asmbelf(vlong symo)\n \t\t}\n \t}\n \n-\tph = newElfPhdr();\n-\tph->type = PT_GNU_STACK;\n-\tph->flags = PF_W+PF_R;\n-\tph->align = PtrSize;\n-\t\n-\tph = newElfPhdr();\n-\tph->type = PT_PAX_FLAGS;\n-\tph->flags = 0x2a00; // mprotect, randexec, emutramp disabled\n-\tph->align = PtrSize;\n+\tif(HEADTYPE == Hlinux) {\n+\t\tph = newElfPhdr();\n+\t\tph->type = PT_GNU_STACK;\n+\t\tph->flags = PF_W+PF_R;\n+\t\tph->align = PtrSize;\n+\t\t\n+\t\tph = newElfPhdr();\n+\t\tph->type = PT_PAX_FLAGS;\n+\t\tph->flags = 0x2a00; // mprotect, randexec, emutramp disabled\n+\t\tph->align = PtrSize;\n+\t}\
       
       elfobj:\
       \tsh = elfshname(".shstrtab");\
      

コアとなるコードの解説

上記の変更箇所は、NetBSD/ARM向けのELFバイナリ生成におけるGoリンカの挙動を、よりNetBSDの慣習に合わせるためのものです。

  • netbsddynldのパス設定:

    • これは、Goが生成するELFバイナリのPT_INTERPプログラムヘッダに書き込まれる動的リンカのパスです。NetBSDシステムがGoバイナリをロードする際、このパスに指定されたリンカ(/libexec/ld.elf_so)を起動して、必要な共有ライブラリを解決します。このパスが正しくないと、バイナリは実行できません。
  • Hnetbsdの初期設定ロジックの削除と統合:

    • 以前はHnetbsd(NetBSDヘッダタイプ)に対して、特定の固定アドレス(INITTEXT = 0xF0000020Lなど)やアライメント(INITRND = 4096)が設定されていました。これは、初期のNetBSDサポートが特定のバイナリレイアウトを想定していたことを示唆します。
    • このコミットでは、これらの固定値設定が削除され、HlinuxHfreebsdと同じ動的リンク関連の設定(debug['d'] = 0, tlsoffset = -8)が適用されるようになりました。これは、NetBSD/ARMのELFバイナリが、Linux/ARMやFreeBSD/ARMと同様に、より標準的なELFの動的リンクメカニズムとTLSアクセス方法を使用できるようになったことを意味します。これにより、リンカのコードが簡素化され、より汎用的なELF生成ロジックに統合されました。
  • ELFヘッダのEABIフラグの条件化:

    • hdr.flags = 0x5000002は、ARM EABIのバージョン5を示すフラグです。このフラグは、バイナリが特定のEABI規約に準拠していることをリンカやローダに伝えます。
    • このコミットでは、このフラグがHEADTYPE == Hlinuxの場合にのみ設定されるようになりました。これは、NetBSD/ARMがこの特定のEABIフラグを必要としないか、あるいは異なるEABIバージョンを使用しているため、NetBSDのローダがこのフラグの存在を期待しない、または異なる解釈をする可能性があるためです。これにより、NetBSD環境での互換性が向上します。
  • リロケーションセクションと動的シンボルテーブルのリンク:

    • sh->link = elfshname(".dynsym")->shnum;という行は、.rel.pltセクションが参照するシンボルテーブルが.dynsymであることを明示的に指定しています。動的リンクされたプログラムでは、関数呼び出しやデータアクセスが共有ライブラリ内のシンボルを参照する際に、リロケーションエントリが使用されます。これらのリロケーションエントリは、実行時に動的リンカによって解決される必要があり、その解決には動的シンボルテーブル(.dynsym)が不可欠です。この設定により、NetBSD/ARMバイナリにおける動的シンボル解決のプロセスが正しく機能するようになります。
  • PT_GNU_STACKPT_PAX_FLAGSの条件付き追加:

    • これらのプログラムヘッダは、Linuxカーネルのセキュリティ機能(スタックの実行不可、PaXによるメモリ保護など)に関連するものです。
    • このコミットでは、これらのヘッダがHEADTYPE == Hlinuxの場合にのみ追加されるように変更されました。NetBSDは独自のセキュリティモデルとELFバイナリの慣習を持っているため、これらのLinux特有のヘッダを無条件に含めると、NetBSDのローダがバイナリを不正と判断したり、セキュリティポリシーに反すると見なしたりする可能性があります。この変更は、NetBSDのシステムとの互換性を確保し、不必要なセキュリティフラグによる問題を回避するために重要です。

これらの変更により、GoのリンカはNetBSD/ARM環境の特性をより正確に反映したELFバイナリを生成できるようになり、GoプログラムがNetBSD/ARM上で安定して動作するための基盤が確立されました。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されているgolang.org/cl/7261043はGerritのチェンジリストIDです)
  • ELFに関する一般的な情報源 (例: Wikipedia, man pages for elf, readelf)
  • ARM EABIに関する情報源 (例: ARM社の公式ドキュメント)
  • NetBSDのELFバイナリに関する情報源 (例: NetBSDのman pages, 開発者向けドキュメント)
  • PaX/PT_PAX_FLAGSに関する情報源 (例: PaXプロジェクトのウェブサイト、Linuxカーネルのドキュメント)
  • Goのリンカに関する技術ブログや解説記事 (一般的なGoのリンカの仕組みを理解するため)
  • Goのソースコード内のコメントや関連ファイル (変更の意図を理解するため)
  • ld.elf_soに関する情報 (NetBSDの動的リンカの挙動を理解するため)