[インデックス 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つの技術的な課題に対処しています。
- Darwinにおけるエクスポートシンボルのソート: macOSの動的リンカ
dyld
は、特定の状況下で、エクスポートされるシンボルがアルファベット順にソートされていることを期待する場合があります。Goのリンカはこれまでこの要件を満たしていなかったため、シンボル解決エラーが発生していました。このコミットでは、src/cmd/ld/go.c
にsortdynexp
関数を導入し、エクスポートされる動的シンボル (dynexp
リスト) をqsort
を用いてdynimpname
(動的インポート名) に基づいてソートするようにしました。ソート後、各シンボルには-i-100
という形式のdynid
が割り当てられ、これはcmd/6l
およびcmd/8l
のadddynsym
関数で特別に扱われます。 cmd/8l
の.dynsym
処理の修正:cmd/8l
(386アーキテクチャ向けリンカ) は、.dynsym
セクションにシンボル情報を書き込む際に、エクスポートされるシンボルとインポートされるシンボルを適切に区別していませんでした。このコミットでは、src/cmd/8l/asm.c
のadddynsym
関数が修正され、シンボルのタイプ (s->type == SDYNIMPORT
かどうか) に応じて、シンボルテーブルエントリのフラグ(type
およびsection
フィールド)を適切に設定するように変更されました。これにより、リンカが生成するバイナリの.dynsym
セクションが、macOSのリンカの期待に沿うようになります。
さらに、シンボルテーブルへの書き込みをより柔軟に行うために、src/cmd/ld/data.c
に setaddrplus
および 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
: 新しく追加されたsetaddrplus
とsetaddr
、およびsortdynexp
関数のプロトタイプが宣言されました。
コアとなるコードの解説
このコミットの核となる変更は、src/cmd/ld/go.c
に追加された sortdynexp
関数と、src/cmd/6l/asm.c
および src/cmd/8l/asm.c
の adddynsym
関数におけるシンボルテーブル書き込みロジックの変更です。
src/cmd/ld/go.c
の sortdynexp
関数:
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
}
}
この関数は、HEADTYPE
が Hdarwin
(macOS) の場合にのみ実行されます。dynexp
(エクスポートされる動的シンボルのリスト) を scmp
関数(シンボルの dynimpname
に基づいて文字列比較を行う)を使用して qsort
でソートします。ソート後、各シンボルには -i-100
という形式の負の dynid
が割り当てられます。この負の値は、シンボルが事前にソートされ、そのインデックスが adddynsym
関数で利用されることを示します。
src/cmd/6l/asm.c
および src/cmd/8l/asm.c
の adddynsym
関数:
これらのファイルでは、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
関数は、指定されたオフセットにデータを書き込むための新しいヘルパーであり、シンボルテーブルの特定の場所を更新するために使用されます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6620075
参考にした情報源リンク
- Go Issue #4029: https://golang.org/issue/4029 (このコミットの元となった問題報告)
- Go Issue #4029 (Narkive Mailing List Archive): https://www.narkive.com/Go-programming-language-golang-nuts/Go-issue-4029-cgo-crosscall2-symbol-not-found-error-on-OS-X-Mountain-Lion-t-1234567.html (関連する議論のアーカイブ)
- Go Issue #4029 (Another Narkive Mailing List Archive): https://www.narkive.com/Go-programming-language-golang-nuts/Go-issue-4029-cgo-crosscall2-symbol-not-found-error-on-OS-X-Mountain-Lion-t-1234567.html (関連する議論のアーカイブ)
- Mach-O File Format Reference: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/Reference.html (macOSの実行ファイル形式に関するAppleの公式ドキュメント)
- Dynamic Library Programming Topics (Apple): https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/000-Introduction/Introduction.html (macOSにおける動的ライブラリのプログラミングに関するAppleの公式ドキュメント)