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

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

このコミットは、Go言語のリンカであるcmd/5l(ARMアーキテクチャ用)とcmd/ld(汎用リンカ)に、動的リンクライブラリのサポートを追加するものです。特にLinux/ARM環境における動的リンクの実現に焦点を当てており、cgo(C言語との相互運用)サポートの一部として導入されました。

コミット

commit 452a9e452b8a88de95b343e13107782aa26c1ed2
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri May 4 18:14:26 2012 +0800

    cmd/5l, cmd/ld: dynamic linking library support
            Part 1 of CL 5601044 (cgo: Linux/ARM support)
            Limitation: doesn't support thumb library yet.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/5991065

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

https://github.com/golang/go/commit/452a9e452b8a88de95b343e13107782aa26c1ed2

元コミット内容

このコミットは、Go言語のリンカ(cmd/5lcmd/ld)に動的リンクライブラリのサポートを導入するものです。特にLinux/ARMプラットフォームでのcgo(C言語との相互運用)サポートの一環として、共有ライブラリへのリンクを可能にします。ただし、当時の制限としてThumb命令セットを使用するライブラリはサポートされていませんでした。

変更の背景

Go言語は、その設計思想として静的リンクを強く推奨し、デフォルトで実行ファイルを完全に自己完結型にビルドします。これにより、デプロイの容易さや依存関係の問題の回避といったメリットがあります。しかし、C言語で書かれた既存のライブラリ(特にシステムライブラリやGPUドライバなど)を利用するcgoのようなシナリオでは、動的リンクが必要となる場合があります。

このコミットは、GoのリンカがELF(Executable and Linkable Format)形式の共有ライブラリを適切に処理し、外部の動的ライブラリへの参照を解決できるようにするための基盤を構築するものです。特にARMアーキテクチャは組み込みシステムやモバイルデバイスで広く利用されており、これらの環境でGoと既存のCライブラリを連携させるためには動的リンクサポートが不可欠でした。

コミットメッセージにある「Part 1 of CL 5601044 (cgo: Linux/ARM support)」は、この変更がLinux/ARMにおけるcgoサポートという大きな目標の一部であることを示しています。つまり、GoプログラムがCの共有ライブラリを呼び出せるようにするための重要なステップでした。

前提知識の解説

1. リンカ (Linker)

リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能ファイルやライブラリを生成するプログラムです。リンカの主な役割は、シンボル(関数名や変数名)の参照を解決し、各コードセクションやデータセクションをメモリ上の適切なアドレスに配置することです。

2. 動的リンク (Dynamic Linking)

動的リンクは、プログラムの実行時に必要なライブラリをロードするリンク方式です。

  • メリット:
    • 実行ファイルのサイズが小さくなる(ライブラリが別途存在するため)。
    • 複数のプログラムで同じライブラリを共有できるため、メモリ使用量が削減される。
    • ライブラリの更新が容易(プログラムを再コンパイルせずにライブラリだけを更新できる)。
  • デメリット:
    • 実行時にライブラリが見つからないとプログラムが起動しない(DLL Hellなど)。
    • 起動時にライブラリのロードと解決のオーバーヘッドがある。

3. ELF (Executable and Linkable Format)

ELFは、Unix系システム(Linuxを含む)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクションで構成されます。動的リンクに関連する主要なセクションには以下があります。

4. PLT (Procedure Linkage Table) と GOT (Global Offset Table)

動的リンクにおいて、共有ライブラリ内の関数呼び出しやグローバル変数へのアクセスを解決するために使用される重要なメカニズムです。

  • GOT (Global Offset Table): グローバル変数や共有ライブラリ内の関数のアドレスを格納するテーブルです。プログラムのロード時に動的リンカによってこれらのアドレスが埋められます。
  • PLT (Procedure Linkage Table): 共有ライブラリ内の関数を呼び出す際に使用される、小さなコードの断片です。PLTエントリは、GOTエントリを介して実際の関数のアドレスにジャンプします。初回呼び出し時には動的リンカを介してアドレス解決が行われ、以降はGOTにキャッシュされたアドレスが直接使用されます。これにより、遅延バインディング(Lazy Binding)が実現され、プログラムの起動時間を短縮できます。

5. ELF Relocations (再配置)

ELFファイル内のシンボル参照を、最終的なメモリ上のアドレスに解決するプロセスです。動的リンクでは、プログラムのロード時に動的リンカが再配置エントリを読み取り、共有ライブラリ内のシンボルへの参照を更新します。

  • R_ARM_PLT32: ARMアーキテクチャにおけるPLTへの相対アドレス再配置。
  • R_ARM_GOT32: ARMアーキテクチャにおけるGOTへの相対アドレス再配置。
  • R_ARM_CALL: ARMアーキテクチャにおける関数呼び出しの再配置。
  • R_ARM_V4BX: ARMv4T以降のプロセッサで、古いARMv4以前のコードとの互換性を提供するための再配置。通常は無視されるか、NOP(何もしない)として扱われます。

6. OABI (Old Application Binary Interface) と EABI (Embedded Application Binary Interface)

ARMアーキテクチャにおけるABI(Application Binary Interface)の規格です。ABIは、バイナリレベルでのプログラムとOS、またはプログラムとライブラリ間のインターフェースを定義します。

  • OABI: 古いABI。
  • EABI: 新しいABIで、より効率的なコード生成や浮動小数点演算のサポートが改善されています。Linux/ARM環境ではEABIが主流となっています。このコミットで/lib/ld-linux.so.2から/lib/ld-linux.so.3への変更が見られるのは、OABIからEABIへの移行を示唆しています。/lib/ld-linux.so.2は通常OABI、/lib/ld-linux.so.3はEABIの動的リンカを指します。

7. GNU Versioning (.gnu.version, .gnu.version_r セクション)

ELFファイルにおけるシンボルバージョニングのためのセクションです。

  • .gnu.version: 各シンボルがどのバージョンに属するかを示す情報を含みます。
  • .gnu.version_r: 外部ライブラリが提供するシンボルのバージョン要件を記述します。これにより、複数のバージョンのライブラリが存在する場合でも、プログラムが正しいバージョンのシンボルを使用できるようになります。

技術的詳細

このコミットは、GoリンカがLinux/ARM環境で動的リンクをサポートするために、ELFファイルの構造と再配置処理に大幅な変更を加えています。

  1. 新しい再配置タイプ(D_定数)の導入: src/cmd/5l/5.out.hに、ARM特有の動的リンクに関連する新しい再配置タイプが追加されました。これらは、PLT/GOTへの参照や関数呼び出しの解決に使用されます。

    • D_GOTOFF: GOTオフセット
    • D_PLT0, D_PLT1, D_PLT2: PLTエントリの生成に関連する内部的な再配置タイプ
    • D_PLT32: PLTへの32ビット相対ジャンプ
    • D_CALL: 関数呼び出し
  2. 動的リンカパスの更新: src/cmd/5l/asm.cで、Linuxの動的リンカのパスが/lib/ld-linux.so.2から/lib/ld-linux.so.3に変更されました。これは、ARM LinuxにおけるABIがOABIからEABIへ移行したことに対応するためです。

  3. 動的シンボル、PLT、GOTの管理機能の追加/強化: src/cmd/5l/asm.cに、adddynrel, adddynsym, addpltsym, addgotsym, addgotsyminternalといった関数が追加または大幅に修正されました。

    • adddynrel: 動的再配置エントリを追加します。ELFオブジェクトファイルからの再配置(R_ARM_PLT32, R_ARM_GOT32, R_ARM_CALLなど)を処理し、Goのリンカ内部の再配置タイプ(D_PLT32, D_GOTOFF, D_CALLなど)に変換します。
    • adddynsym: 動的シンボルテーブル(.dynsym)にシンボルを追加します。これは、外部から参照されるシンボルや外部ライブラリからインポートされるシンボルを管理するために必要です。
    • addpltsym: PLTエントリと関連するGOTエントリを生成し、シンボルをPLTに登録します。これにより、共有ライブラリ内の関数を呼び出す際の遅延バインディングが可能になります。
    • addgotsym, addgotsyminternal: GOTエントリを生成し、シンボルをGOTに登録します。これは、グローバル変数や関数のアドレスを間接的に参照するために使用されます。
  4. ELFセクションの追加と設定: src/cmd/5l/asm.cdoelf関数において、動的リンクに必要な新しいELFセクション(.gnu.version, .gnu.version_r)が追加され、既存のセクション(.plt, .got.plt, .got, .dynsym, .dynstr, .rel, .rel.plt)の設定が更新されました。

    • .pltセクションは、ARM ELFの慣例に従い、.gotセクションの前に配置されるように変更されました。
    • .dynsymセクションのサイズ計算が修正されました。
  5. 再配置処理の拡張 (archreloc): src/cmd/5l/asm.carchreloc関数が拡張され、新しいARM固有の再配置タイプ(D_CONST, D_GOTOFF, D_PLT0, D_PLT1, D_PLT2, D_PLT32, D_CALL)を処理できるようになりました。これにより、リンカはこれらの再配置タイプに基づいて、適切なアドレス計算と命令のパッチ適用を行います。

  6. TLS (Thread-Local Storage) オフセットの追加: src/cmd/5l/l.htlsoffset変数が追加され、src/cmd/5l/obj.cで初期化されています。これは、Goのランタイムがスレッドローカルなデータにアクセスするために必要なオフセットであり、cgoとの連携において重要です。

  7. シンボル解決とセクション配置の調整: src/cmd/5l/span.csrc/cmd/5l/pass.csrc/cmd/ld/ldelf.cなど、リンカのコア部分でシンボルの解決ロジックやセクションの配置ロジックが調整されました。特に、外部関数やELFセクションシンボルの扱い、およびbinutilsが生成するARMの特殊なマッピングシンボル($a, $d)の無視などが含まれます。

これらの変更により、GoのリンカはELF形式の共有ライブラリを適切に扱い、Linux/ARM環境で動的リンクされた実行ファイルを生成できるようになりました。

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

src/cmd/5l/5.out.h

  • 新しい再配置タイプ(D_GOTOFF, D_PLT0, D_PLT1, D_PLT2, D_PLT32, D_CALL)の定義を追加。

src/cmd/5l/asm.c

  • linuxdynldパスを/lib/ld-linux.so.2から/lib/ld-linux.so.3に変更。
  • ElfStrGnuVersion, ElfStrGnuVersionRの定義を追加。
  • adddynrel関数を大幅に修正し、ARM ELFの再配置タイプを処理するロジックを追加。
  • elfsetupplt関数を実装し、PLTの初期エントリ(遅延バインディングのためのコード)を生成。
  • archreloc関数を拡張し、新しいD_再配置タイプに基づいて値の計算を行うロジックを追加。
  • addpltsym, addgotsyminternal, addgotsym関数を新規追加または大幅に修正し、PLT/GOTエントリの生成と動的シンボルの管理を実装。
  • adddynsym関数を修正し、動的シンボルテーブルへのシンボル追加ロジックを実装。
  • doelf関数で、動的リンク関連のELFセクション(.dynsym, .dynstr, .rel, .rel.plt, .gnu.version, .gnu.version_r)の初期化と設定を強化。特に、.pltセクションが.gotセクションの前に配置されるように変更。

src/cmd/5l/l.h

  • tlsoffset変数の宣言を追加。

src/cmd/5l/obj.c

  • Hlinux(ARM ELF)の場合にdebug['d']を0(動的リンクを有効化)に設定。
  • tlsoffset-8に初期化。
  • addexport()の呼び出しを追加。

src/cmd/5l/span.c

  • symaddr関数を簡素化し、シンボルが到達可能であるかどうかのチェックを追加。
  • 外部関数やELFセクションシンボルの扱い、およびテキストセクションの配置ロジックを調整。

src/cmd/ld/data.c

  • dynrelocsym関数で、r->sym != Sの条件を追加し、NULLシンボルに対する動的再配置を回避。

src/cmd/ld/elf.h

  • ARM固有のELF再配置タイプ(R_ARM_CALL, R_ARM_V4BX)の定義を追加。

src/cmd/ld/ldelf.c

  • ldelf関数で、絶対再配置(シンボルIDが0の場合)の処理を追加。
  • readsym関数で、binutilsが生成するARMの特殊なマッピングシンボル($a, $d)を無視するロジックを追加。
  • reltype関数で、新しいARM固有の再配置タイプを認識するように拡張。

コアとなるコードの解説

このコミットの核心は、GoリンカがELF形式の動的リンクを適切に処理するための、ARMアーキテクチャに特化した再配置とセクション管理のロジックの実装です。

src/cmd/5l/asm.cにおけるadddynrelarchrelocの連携

adddynrel関数は、入力されたELFオブジェクトファイル内の再配置エントリをGoリンカ内部の再配置タイプに変換し、動的リンクに必要な追加処理(PLT/GOTエントリの生成など)をトリガーします。例えば、R_ARM_PLT32のような外部関数呼び出しの再配置は、addpltsymを呼び出してPLTエントリを確保し、Goリンカ内部のD_PLT32タイプに変換されます。

// src/cmd/5l/asm.c (抜粋)
void
adddynrel(Sym *s, Reloc *r)
{
    Sym *targ, *rel;

    targ = r->sym;
    cursym = s;

    switch(r->type) {
    // ... (他の再配置タイプ)
    case 256 + R_ARM_PLT32: // ELFのR_ARM_PLT32再配置を処理
        r->type = D_PLT32; // Goリンカ内部のD_PLT32に変換
        if(targ->dynimpname != nil && !targ->dynexport) {
            addpltsym(targ); // PLTエントリを生成
            r->sym = lookup(".plt", 0); // 再配置対象を.pltセクションに設定
            r->add = braddoff(r->add, targ->plt / 4); // PLT内のオフセットを計算
        }
        return;
    // ... (他のARM固有の再配置タイプ)
    }
    // ... (Go自身のオブジェクトファイルからの参照処理)
}

その後、リンカの最終的なコード生成段階でarchreloc関数が呼び出され、これらのGoリンカ内部の再配置タイプに基づいて、実際の機械語命令がパッチされます。

// src/cmd/5l/asm.c (抜粋)
int
archreloc(Reloc *r, Sym *s, vlong *val)
{
    switch(r->type) {
    // ... (D_CONST, D_GOTOFFなど)
    case D_PLT0: // add ip, pc, #0xXX00000
        // ... ARM命令のバイト列を計算し、*valに設定
        return 0;
    case D_PLT1: // add ip, ip, #0xYY000
        // ... ARM命令のバイト列を計算し、*valに設定
        return 0;
    case D_PLT2: // ldr pc, [ip, #0xZZZ]!
        // ... ARM命令のバイト列を計算し、*valに設定
        return 0;
    case D_PLT32: // bl XXXXXX or b YYYYYY in R_ARM_PLT32
        // ... ARMのBL命令(分岐リンク)のオフセットを計算し、*valに設定
        return 0;
    case D_CALL: // bl XXXXXX
        // ... ARMのBL命令のオフセットを計算し、*valに設定
        return 0;
    }
    return -1;
}

D_PLT0, D_PLT1, D_PLT2は、ARMのPLTエントリを構成する3つの命令(PC相対アドレス計算、GOTエントリへのジャンプ)に対応しており、elfsetuppltaddpltsymによってこれらの再配置が生成され、archrelocで具体的な命令が埋め込まれます。

addpltsymelfsetuppltによるPLT/GOTの構築

addpltsym関数は、外部の動的シンボル(共有ライブラリからインポートされる関数など)が参照された際に呼び出されます。この関数は、そのシンボルに対応するPLTエントリとGOTエントリを確保し、必要な再配置エントリを生成します。

// src/cmd/5l/asm.c (抜粋)
static void
addpltsym(Sym *s)
{
    Sym *plt, *got, *rel;

    if(s->plt >= 0) // 既にPLTエントリがある場合はスキップ
        return;

    adddynsym(s); // 動的シンボルテーブルにシンボルを追加

    if(iself) {
        plt = lookup(".plt", 0);
        got = lookup(".got.plt", 0);
        rel = lookup(".rel", 0);
        if(plt->size == 0)
            elfsetupplt(); // PLTの初期エントリをセットアップ

        // .got.plt エントリの確保
        s->got = got->size;
        adduint32(got, 0); // 初期値は0

        // .plt エントリの生成 (3つの命令に対応する再配置)
        s->plt = plt->size;
        addpltreloc(plt, got, s, D_PLT0);
        addpltreloc(plt, got, s, D_PLT1);
        addpltreloc(plt, got, s, D_PLT2);

        // .rel.plt (JUMP_SLOT) 再配置エントリの追加
        addaddrplus(rel, got, s->got);
        adduint32(rel, ELF32_R_INFO(s->dynid, R_ARM_JUMP_SLOT));
    } else {
        diag("addpltsym: unsupported binary format");
    }
}

elfsetuppltは、PLTの最初の特別なエントリを生成します。このエントリは、動的リンカを呼び出して実際の関数アドレスを解決し、GOTに書き込む役割を担います。

// src/cmd/5l/asm.c (抜粋)
static void
elfsetupplt(void)
{
    Sym *plt, *got;

    plt = lookup(".plt", 0);
    got = lookup(".got.plt", 0);
    if(plt->size == 0) {
        // PLTの初期コード (ARMアセンブリ命令)
        // str lr, [sp, #-4]!
        adduint32(plt, 0xe52de004);
        // ldr lr, [pc, #4]
        adduint32(plt, 0xe59fe004);
        // add lr, pc, lr
        adduint32(plt, 0xe08fe00e);
        // ldr pc, [lr, #8]!
        adduint32(plt, 0xe5bef008);
        // .word &GLOBAL_OFFSET_TABLE[0] - . (GOTのベースアドレスへのPC相対オフセット)
        addpcrelplus(plt, got, 4);

        // 最初の.pltエントリに対応する3つの.got.pltエントリ
        adduint32(got, 0);
        adduint32(got, 0);
        adduint32(got, 0);
    }
}

これらの関数群が連携することで、GoのリンカはELFの動的リンクメカニズム(PLT/GOT、動的シンボルテーブル、再配置テーブル)を適切に構築し、Goプログラムが外部の共有ライブラリと連携できるようにします。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/cmd/5lsrc/cmd/ldディレクトリ)
  • ELF Specification (Tool Interface Standard (TIS) Portable Formats Specification, Version 1.1)
  • ARM Architecture Reference Manual
  • Linux man pages (特にld.so, elf)
  • Stack Overflowや技術ブログの解説記事 (PLT/GOT, ELF relocations, ARM ABIに関するもの)
  • GoのChange List (CL) 5601044 および 5991065 (Goのコードレビューシステム)