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

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

このコミットは、Go言語のリンカ (cmd/ld) および特定のアーキテクチャ向けアセンブラ/リンカ (cmd/6l for amd64, cmd/8l for 386) における、Darwin (macOS) 環境での動的シンボル(特にエクスポートされるシンボル)の取り扱いを改善するものです。主な変更点は、Darwin上でエクスポートされる動的シンボルをソートするようにしたこと、および cmd/8l.dynsym セクションでエクスポートされたシンボルとインポートされたシンボルを適切に区別するように修正したことです。これにより、GoプログラムがCgoを介して動的にロードされるライブラリ(特にmacOSのフレームワークなど)と連携する際の互換性が向上しました。

コミット

  • コミットハッシュ: fa563ae82e2d1038656be21c52831e7ad42108a8
  • 作者: Shenghou Ma minux.ma@gmail.com
  • コミット日時: 2012年10月10日 水曜日 00:55:48 +0800

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

https://github.com/golang/go/commit/fa563ae82e2d1038656be21c52831e7ad42108a8

元コミット内容

cmd/ld, cmd/6l, cmd/8l: sort exported dynamic symbols for Darwin
Also corrected cmd/8l's .dynsym handling (differentiate between exported symbols and imported symbols)

        Fixes #4029.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6620075

変更の背景

このコミットは、Go言語のIssue #4029を修正するために行われました。Issue #4029は、特にmacOS Mountain Lion環境において、Cgoを使用してビルドされたGoプログラムが、動的にロードされるライブラリ(例えば、Objective-Cのフレームワークなど)から特定のシンボル(例: _crosscall2)を見つけられないという問題でした。

macOSのリンカ(dyld)は、動的ライブラリ内のエクスポートされたシンボルを特定の順序で期待することがあります。Goのリンカがこれらのシンボルをソートせずに配置していたため、macOSのシステムリンカが期待する順序と異なり、シンボル解決に失敗するケースが発生していました。

また、cmd/8l (386アーキテクチャ向けリンカ) において、.dynsym (ダイナミックシンボルテーブル) の扱いが不正確で、エクスポートされるシンボルとインポートされるシンボルを適切に区別できていなかったことも問題の一因でした。これらの問題が複合的に作用し、Cgoを利用したmacOSアプリケーションのビルドと実行に支障をきたしていました。

前提知識の解説

  • 動的シンボル (Dynamic Symbols): プログラムが実行時に他のライブラリからロードされる関数や変数などのシンボルを指します。これらは、コンパイル時にはアドレスが確定せず、実行時に動的リンカによって解決されます。
  • リンカ (Linker): コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成するツールです。Go言語では、cmd/ld が主要なリンカであり、cmd/6l (amd64) や cmd/8l (386) はそれぞれ特定アーキテクチャ向けのアセンブラとリンカの役割を兼ねています。
  • Cgo: GoプログラムからC言語のコードを呼び出すためのGoの機能です。Cgoを使用すると、GoとCの間の相互運用が可能になりますが、その過程で動的リンキングの複雑さが増します。
  • Darwin (macOS): AppleのオペレーティングシステムであるmacOSの基盤となるUNIX系OSです。macOSの実行ファイル形式はMach-Oであり、動的リンキングの挙動は他のUNIX系システム(ELF形式)とは異なる場合があります。特に、シンボルの解決順序や命名規則(Cシンボルに _ プレフィックスが付くなど)に独自の要件があります。
  • dlopen / dlsym: UNIX系システムで動的ライブラリをロードし、その中のシンボルを検索するためのC標準ライブラリ関数です。Cgoを介してGoプログラムがこれらの関数を使用する場合、Goのリンカが生成するバイナリがこれらの動的リンキングの要件を満たしている必要があります。
  • .dynsym セクション: 実行ファイルや共有ライブラリに含まれる、動的リンキングに必要なシンボル情報が格納されるセクションです。ここには、外部からインポートされるシンボルと、外部にエクスポートされるシンボルの両方が含まれます。

技術的詳細

このコミットは、主に以下の2つの技術的な課題に対処しています。

  1. Darwinにおけるエクスポートシンボルのソート: macOSの動的リンカ dyld は、特定の状況下で、エクスポートされるシンボルがアルファベット順にソートされていることを期待する場合があります。Goのリンカはこれまでこの要件を満たしていなかったため、シンボル解決エラーが発生していました。このコミットでは、src/cmd/ld/go.csortdynexp 関数を導入し、エクスポートされる動的シンボル (dynexp リスト) を qsort を用いて dynimpname (動的インポート名) に基づいてソートするようにしました。ソート後、各シンボルには -i-100 という形式の dynid が割り当てられ、これは cmd/6l および cmd/8ladddynsym 関数で特別に扱われます。
  2. cmd/8l.dynsym 処理の修正: cmd/8l (386アーキテクチャ向けリンカ) は、.dynsym セクションにシンボル情報を書き込む際に、エクスポートされるシンボルとインポートされるシンボルを適切に区別していませんでした。このコミットでは、src/cmd/8l/asm.cadddynsym 関数が修正され、シンボルのタイプ (s->type == SDYNIMPORT かどうか) に応じて、シンボルテーブルエントリのフラグ(type および section フィールド)を適切に設定するように変更されました。これにより、リンカが生成するバイナリの .dynsym セクションが、macOSのリンカの期待に沿うようになります。

さらに、シンボルテーブルへの書き込みをより柔軟に行うために、src/cmd/ld/data.csetaddrplus および setaddr といった新しいヘルパー関数が追加されました。これらは、特定のオフセットにアドレスを書き込む機能を提供し、既存の adduintXX 系関数が常に末尾に追加するのとは異なり、事前に確保された領域にデータを書き込むことを可能にします。これは、ソートされたシンボルに対して dynid を事前に割り当て、その dynid に対応するオフセットにシンボル情報を書き込む際に利用されます。

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

このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。

  • misc/cgo/test/cgo_test.go: 新しいテストケース Test4029 が追加されました。
  • misc/cgo/test/issue4029.go: Issue #4029を再現し、修正を検証するための新しいCgoテストファイルが追加されました。このテストは、dlopen(nil, RTLD_NOW) を使用して現在のプロセスを動的に開き、dlsym でGoからエクスポートされたシンボルを検索することで、動的シンボル解決の挙動を検証します。
  • src/cmd/6l/asm.c: adddynsym 関数が修正され、Darwin向けにエクスポートされるシンボルの dynid が負の値(事前にソートされたことを示す)の場合に、シンボルテーブルの特定のオフセットにデータを書き込むように変更されました。setuint32, setuint8, setuint16, setuint64, setaddr といった新しい set 系関数が使用されています。
  • src/cmd/8l/asm.c: src/cmd/6l/asm.c と同様に、adddynsym 関数が修正され、エクスポートされるシンボルとインポートされるシンボルを区別し、適切なシンボルタイプとセクション情報を設定するように変更されました。こちらも set 系関数が導入されています。
  • src/cmd/ld/data.c: setaddrplus および setaddr 関数が追加されました。これらは、指定されたオフセットにアドレス(シンボルへの参照)を書き込むための汎用的なヘルパー関数です。
  • src/cmd/ld/go.c: sortdynexp 関数が追加されました。この関数は、Darwin向けにエクスポートされる動的シンボルをソートし、それらに対応する dynid を事前に割り当てます。
  • src/cmd/ld/lib.c: loadlib 関数内で sortdynexp() が呼び出されるようになりました。これにより、リンキング処理の早い段階で動的シンボルがソートされます。
  • src/cmd/ld/lib.h: 新しく追加された setaddrplussetaddr、および sortdynexp 関数のプロトタイプが宣言されました。

コアとなるコードの解説

このコミットの核となる変更は、src/cmd/ld/go.c に追加された sortdynexp 関数と、src/cmd/6l/asm.c および src/cmd/8l/asm.cadddynsym 関数におけるシンボルテーブル書き込みロジックの変更です。

src/cmd/ld/go.csortdynexp 関数:

static int
scmp(const void *p1, const void *p2)
{
    Sym *s1, *s2;

    s1 = *(Sym**)p1;
    s2 = *(Sym**)p2;
    return strcmp(s1->dynimpname, s2->dynimpname);
}

void
sortdynexp(void)
{
    int i;

    // On Mac OS X Mountain Lion, we must sort exported symbols
    // So we sort them here and pre-allocate dynid for them
    // See http://golang.org/issue/4029
    if(HEADTYPE != Hdarwin)
        return;
    qsort(dynexp, ndynexp, sizeof dynexp[0], scmp);
    for(i=0; i<ndynexp; i++) {
        dynexp[i]->dynid = -i-100; // also known to [68]l/asm.c:^adddynsym
    }
}

この関数は、HEADTYPEHdarwin (macOS) の場合にのみ実行されます。dynexp (エクスポートされる動的シンボルのリスト) を scmp 関数(シンボルの dynimpname に基づいて文字列比較を行う)を使用して qsort でソートします。ソート後、各シンボルには -i-100 という形式の負の dynid が割り当てられます。この負の値は、シンボルが事前にソートされ、そのインデックスが adddynsym 関数で利用されることを示します。

src/cmd/6l/asm.c および src/cmd/8l/asm.cadddynsym 関数:

これらのファイルでは、adddynsym 関数が大幅に修正されています。特に重要なのは、s->dynid <= -100 の条件分岐です。

// src/cmd/6l/asm.c (抜粋)
// ...
    if(s->dynid <= -100) { // pre-allocated, see cmd/ld/go.c:^sortdynexp()
        s->dynid = -s->dynid-100;
        off = s->dynid*16; // 6l uses 16 bytes per symbol entry
    } else {
        off = d->size;
        s->dynid = off/16;
    }
    // ...
    setuint32(d, off, str->size); // Write string table offset at specific offset
    off += 4;
    // ...
    if(s->type == SDYNIMPORT) {
        setuint8(d, off, 0x01); // type - N_EXT - external symbol
        off++;
        setuint8(d, off, 0); // section
        off++;
    } else {
        setuint8(d, off, 0x0f); // type - N_EXT - external symbol (for exported)
        off++;
        switch(s->type) { // Set section based on symbol type
        // ...
        }
        off++;
    }
    setuint16(d, off, 0); // desc
    off += 2;
    if(s->type == SDYNIMPORT)
        setuint64(d, off, 0); // value for imported
    else
        setaddr(d, off, s); // value for exported (address of symbol)
    off += 8;
// ...

このコードは、dynid が負の値の場合(つまり、sortdynexp によって事前にソートされたシンボルである場合)、その dynid を使用してシンボルテーブル内の正確なオフセット (off) を計算し、そこにシンボル情報を書き込みます。これにより、ソートされた順序が維持されます。

また、s->type == SDYNIMPORT (インポートされる動的シンボル) かどうかによって、シンボルエントリの type および section フィールドが適切に設定されるようになりました。これにより、cmd/8l がエクスポートシンボルとインポートシンボルを正しく区別できるようになります。

setuintXX および setaddr 関数は、指定されたオフセットにデータを書き込むための新しいヘルパーであり、シンボルテーブルの特定の場所を更新するために使用されます。

関連リンク

参考にした情報源リンク