[インデックス 15644] ファイルの概要
コミット
commit e0c430d5b70570fa6bf4f56d357ba7a517ff4e72
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 7 21:23:59 2013 -0800
cmd/6l, cmd/8l: fix BSD builds
Before this CL, running
cd misc/cgo/test
go test -c
readelf --dyn-syms test.test | grep cgoexp
turned up many UNDEF symbols corresponding to symbols actually
in the binary but marked only cgo_export_static. Only symbols
marked cgo_export_dynamic should be listed in this mode.
And if the symbol is going to be listed, it should be listed with its
actual address instead of UNDEF.
The Linux dynamic linker didn't care about the seemingly missing
symbols, but the BSD one did.
This CL eliminates the symbols from the dyn-syms table.
R=golang-dev
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/7624043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e0c430d5b70570fa6bf4f56d357ba7a517ff4e72
元コミット内容
このコミットは、Go言語のリンカであるcmd/6l
(x86-64アーキテクチャ向け)とcmd/8l
(x86アーキテクチャ向け)におけるBSDビルドの問題を修正するものです。具体的には、Cgo(GoとC言語の相互運用機能)を使用してエクスポートされたシンボルが、ELFバイナリの動的シンボルテーブル(readelf --dyn-syms
で表示される)に不適切に表示される問題を解決します。
以前のバージョンでは、cgo_export_static
としてマークされたシンボル(静的リンクされるべきシンボル)が、動的シンボルテーブルにUNDEF
(未定義)として誤ってリストアップされていました。本来、このテーブルにはcgo_export_dynamic
としてマークされたシンボル(動的リンクされるべきシンボル)のみが、その実際のメモリアドレスと共にリストされるべきです。
Linuxの動的リンカはこの「見かけ上欠落している」シンボルを問題としませんでしたが、BSDの動的リンカはこれを問題視し、ビルドエラーや予期せぬ動作を引き起こす可能性がありました。このコミットは、これらの不適切なシンボルを動的シンボルテーブルから除外することで、BSDビルドの互換性を確保します。
変更の背景
Go言語はCgoを通じてC言語のコードと連携する機能を提供しており、Goの関数をC言語から呼び出せるようにエクスポートすることが可能です。このエクスポートされた関数は、静的にリンクされるか動的にリンクされるかによって、リンカによる処理が異なります。
問題は、cgo_export_static
としてマークされたシンボルが、ELFバイナリの動的シンボルテーブルに誤ってUNDEF
シンボルとして含まれてしまうことにありました。これは、readelf --dyn-syms
のようなツールで動的シンボル情報を確認した際に顕著に現れました。
Linuxの動的リンカは、このような不整合に対して比較的寛容であり、実用上問題を引き起こすことは稀でした。しかし、BSD系のOS(FreeBSD, OpenBSDなど)の動的リンカは、より厳格なシンボル解決のルールを持っており、動的シンボルテーブルにUNDEF
としてリストされているにもかかわらず、実際にはバイナリ内に存在するシンボルに対してエラーを報告することがありました。この違いが、GoのBSDビルドにおける互換性の問題を引き起こしていました。
このコミットの目的は、BSD環境でのGoプログラムのビルドと実行の信頼性を向上させるため、動的リンカが期待するシンボル情報のみを動的シンボルテーブルに含めるようにリンカの挙動を修正することです。
前提知識の解説
1. Go言語のリンカ (cmd/6l
, cmd/8l
)
Go言語の初期のリンカは、ターゲットアーキテクチャごとに異なる名前を持っていました。
cmd/6l
: x86-64 (64-bit) アーキテクチャ向けのリンカ。cmd/8l
: x86 (32-bit) アーキテクチャ向けのリンカ。 これらは現在ではgo tool link
として統合されており、ユーザーが直接これらの名前を意識することは少なくなっていますが、Goの内部ツールチェインでは依然としてこれらの概念が基盤となっています。リンカの主な役割は、コンパイルされたオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリやライブラリを生成することです。この過程で、シンボル解決(関数や変数のアドレスを決定すること)や再配置(コード内のアドレス参照を修正すること)が行われます。
2. Cgoとシンボルのエクスポート (cgo_export_static
, cgo_export_dynamic
)
Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Goの関数をC言語から呼び出せるようにするには、Goのソースコード内で//export
ディレクティブを使用します。
CgoでエクスポートされたGoの関数は、ビルドモードによって静的または動的にリンクされます。
- 静的リンク (
cgo_export_static
に対応する概念):go build -buildmode=c-archive
でビルドすると、GoのコードとランタイムがC/C++アプリケーションに直接組み込まれる静的ライブラリ(.a
ファイル)が生成されます。この場合、エクスポートされたGoのシンボルは最終的な実行可能ファイルに直接含まれ、実行時に外部の共有ライブラリを必要としません。 - 動的リンク (
cgo_export_dynamic
に対応する概念):go build -buildmode=c-shared
でビルドすると、Goのコードとランタイムが共有ライブラリ(Linuxでは.so
、macOSでは.dylib
)として生成されます。C/C++アプリケーションは実行時にこの共有ライブラリをロードし、エクスポートされたGoの関数を呼び出します。この場合、エクスポートされたGoのシンボルは共有ライブラリの動的シンボルテーブルにリストされます。
このコミットで言及されているcgo_export_static
とcgo_export_dynamic
は、Cgoのビルドモードによってシンボルがどのように扱われるかを示す内部的なフラグや概念を指しています。
3. ELF (Executable and Linkable Format)
ELFは、Unix系OS(Linux、BSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(コード、データ、シンボルテーブル、再配置情報など)で構成されます。
4. readelf --dyn-syms
readelf
は、ELFファイルの情報を表示するためのコマンドラインユーティリティです。
readelf --dyn-syms <ELFファイル>
コマンドは、指定されたELFファイルの動的シンボルテーブル(.dynsym
セクション)の内容を表示します。動的シンボルテーブルには、動的リンクに必要なシンボル(関数や変数)の情報が含まれており、実行時に動的リンカがこれらのシンボルを解決するために使用します。
5. シンボルテーブルとUNDEF
シンボル
ELFファイルのシンボルテーブルには、プログラム内のすべてのシンボルに関する情報が含まれます。各シンボルエントリには、シンボル名、値(アドレス)、サイズ、型(関数、オブジェクトなど)、バインディング(グローバル、ローカルなど)、そしてセクションインデックスが含まれます。
SHN_UNDEF
: シンボルのセクションインデックスがSHN_UNDEF
である場合、そのシンボルは現在のオブジェクトファイルでは定義されておらず、外部のオブジェクトファイルや共有ライブラリで定義されていることを示します。動的リンカは、このようなUNDEF
シンボルを解決するために、他の共有ライブラリを検索します。
6. 再配置 (Relocation)
再配置とは、リンカがオブジェクトファイル内のコードやデータのアドレス参照を、最終的な実行可能ファイルやライブラリ内の正しいアドレスに修正するプロセスです。再配置エントリは、どの場所(オフセット)を、どのシンボルに基づいて、どのように修正するかをリンカに指示します。
-
ELF再配置タイプ (x86-64):
R_X86_64_PC32
: 32ビットPC相対再配置。命令ポインタ(RIP)からの相対オフセットでアドレスを計算します。関数呼び出しやジャンプによく使われます。R_X86_64_PLT32
: 32ビットPC相対PLT再配置。Procedure Linkage Table (PLT)のエントリへの参照に使用されます。PLTは共有ライブラリ内の関数を呼び出す際に動的リンカが使用するジャンプテーブルです。R_X86_64_GOTPCREL
: Global Offset Table (GOT)へのPC相対再配置。GOTは位置独立コード(PIC)でグローバル変数や関数のアドレスを解決するために使用されます。R_X86_64_64
: 64ビット絶対アドレス再配置。シンボルの絶対アドレスを直接配置します。
-
ELF再配置タイプ (x86):
R_386_PC32
: 32ビットPC相対再配置。R_386_PLT32
: 32ビットPLT再配置。R_386_GOT32
: 32ビットGOT再配置。R_386_32
: 32ビット絶対アドレス再配置。
-
Mach-O再配置タイプ (x86-64):
MACHO_X86_64_RELOC_BRANCH
: 分岐命令のためのPC相対再配置。MACHO_X86_64_RELOC_SIGNED_2
,MACHO_X86_64_RELOC_SIGNED_4
: 符号付き再配置タイプ。MACHO_X86_64_RELOC_GOT_LOAD
,MACHO_X86_64_RELOC_GOT
: Global Offset Table (GOT)に関連する再配置。MACHO_GENERIC_RELOC_VANILLA
: 汎用的な再配置タイプ。MACHO_FAKE_GOTPCREL
: Mach-OにおけるGOTPCRELに類似した再配置(詳細は不明だが、GOTとPC相対アドレッシングに関連すると推測される)。
7. 動的リンカ (Dynamic Linker)
動的リンカ(またはランタイムリンカ、プログラムインタプリタ)は、プログラムが実行される際に、共有ライブラリ内のシンボルを解決し、プログラムと共有ライブラリをメモリにロードする役割を担います。Linuxではld-linux.so
、BSDでは独自の動的リンカが使用されます。動的リンカは、ELFファイルの動的シンボルテーブルを参照して、必要なシンボルを検索し、そのアドレスをプログラムに提供します。
LinuxとBSDの動的リンカは、ELFフォーマットを使用するという点では共通していますが、Cランタイム、システムコール、ABI(Application Binary Interface)などの違いにより、バイナリ互換性はありません。また、シンボル解決の厳格さにも違いがあり、今回の問題のように、Linuxでは許容される挙動がBSDでは問題となることがあります。
技術的詳細
このコミットの核心は、Goリンカ(cmd/6l
およびcmd/8l
)がELFバイナリの動的シンボルテーブルを生成する際のロジックの変更にあります。
GoのCgo機能では、Goの関数をC言語から呼び出せるようにするために、//export
ディレクティブを使用します。リンカは、これらのエクスポートされたシンボルを処理し、最終的なバイナリに含めます。ここで重要なのは、シンボルが静的にエクスポートされるか(cgo_export_static
)、動的にエクスポートされるか(cgo_export_dynamic
)によって、動的シンボルテーブルへの登録方法が異なるべきであるという点です。
以前の実装では、cgo_export_static
としてマークされたシンボルであっても、特定の条件下で動的シンボルテーブルにUNDEF
シンボルとして誤って登録されていました。これは、シンボルが実際にバイナリ内に存在し、静的に解決されるべきであるにもかかわらず、動的リンカに対して「未定義の外部シンボル」として提示されることを意味します。
BSDの動的リンカは、このような矛盾に対して厳格であり、動的シンボルテーブルにUNDEF
としてリストされているシンボルが、実際にはバイナリ内に定義されている場合に、これを不整合とみなし、エラーや警告を発生させることがありました。対照的に、Linuxの動的リンカは、この種の不整合に対してより寛容であり、通常は問題なく処理していました。
このコミットは、リンカが動的シンボルテーブルにシンボルを追加する際の条件を修正することで、この問題を解決します。具体的には、cgoexport
フラグのチェックをより厳密にし、CgoExportDynamic
フラグが設定されているシンボル(つまり、動的にエクスポートされるべきシンボル)のみが動的シンボルテーブルに適切に登録されるように変更されています。これにより、静的にエクスポートされるシンボルが動的シンボルテーブルに誤ってUNDEF
として表示されることがなくなり、BSD環境でのビルドの信頼性が向上します。
変更は、主にadddynrel
関数(再配置エントリを追加する関数)とadddynsym
関数(動的シンボルテーブルにシンボルを追加する関数)に集中しています。これらの関数内で、シンボルのcgoexport
フラグとCgoExportDynamic
フラグの組み合わせを評価するロジックが修正されています。
コアとなるコードの変更箇所
変更はsrc/cmd/6l/asm.c
とsrc/cmd/8l/asm.c
の2つのファイルにわたっています。これらのファイルは、それぞれx86-64とx86アーキテクチャ向けのGoリンカのバックエンドコードです。
主な変更は、シンボルのcgoexport
フラグのチェック方法です。
src/cmd/6l/asm.c
(x86-64リンカ)
-
adddynrel
関数内の複数のif
条件文:- 変更前:
!(targ->cgoexport & CgoExportDynamic)
- 変更後:
!targ->cgoexport
- これは、
CgoExportDynamic
という特定のビットフラグのチェックを削除し、単にtarg->cgoexport
が設定されているかどうか(つまり、何らかのCgoエクスポートがされているか)をチェックするように変更しています。これにより、cgo_export_static
なシンボルが動的再配置の対象から外れるようになります。
- 変更前:
-
adddynsym
関数内のSTT_FUNC
とSHN_UNDEF
の条件:- 変更前:
(s->cgoexport & CgoExportDynamic)
- 変更後:
s->cgoexport
- ここでも同様に、
CgoExportDynamic
フラグのチェックを削除し、s->cgoexport
が設定されていれば関数シンボルとして扱うように変更しています。 SHN_UNDEF
の条件も同様に修正されています。
- 変更前:
src/cmd/8l/asm.c
(x86リンカ)
src/cmd/6l/asm.c
と同様に、adddynrel
関数とadddynsym
関数内のif
条件文で、!(targ->cgoexport & CgoExportDynamic)
が!targ->cgoexport
に、(s->cgoexport & CgoExportDynamic)
がs->cgoexport
に変更されています。
これらの変更は、CgoExportDynamic
という特定のフラグに依存するのではなく、シンボルがCgoによってエクスポートされているかどうか(cgoexport
フラグが立っているか)をより一般的な条件として使用することで、静的エクスポートシンボルが動的シンボルテーブルに誤って含まれるのを防ぎます。
コアとなるコードの解説
このコミットのコード変更は、GoリンカがCgoによってエクスポートされたシンボルをどのように扱うか、特に動的シンボルテーブルへの登録ロジックを修正しています。
Goのリンカは、シンボルを処理する際に、そのシンボルがCgoによってエクスポートされているかどうかを示すcgoexport
というフラグを持っています。さらに、このcgoexport
フラグは、シンボルが動的にエクスポートされるべきか(CgoExportDynamic
ビットがセットされているか)という詳細な情報を含むことがあります。
変更前のコードでは、再配置エントリの追加(adddynrel
)や動的シンボルテーブルへのシンボル追加(adddynsym
)の際に、targ->cgoexport & CgoExportDynamic
という条件が頻繁に用いられていました。これは、「ターゲットシンボルがCgoによってエクスポートされており、かつ動的エクスポートとしてマークされている場合」という意味になります。
しかし、問題はcgo_export_static
としてマークされたシンボル(つまり、cgoexport
フラグは立っているが、CgoExportDynamic
ビットは立っていないシンボル)が、この条件によって適切に除外されなかったり、あるいは誤って動的シンボルテーブルにUNDEF
として登録されたりすることでした。
このコミットで行われた変更は、CgoExportDynamic
という特定のビットフラグのチェックを削除し、代わりにtarg->cgoexport
(またはs->cgoexport
)が単にtrue
であるかどうか(つまり、何らかのCgoエクスポートがされているか)をチェックするように簡素化しています。
例えば、adddynrel
関数内の以下の変更を見てみましょう。
// 変更前:
// if(targ->dynimpname != nil && !(targ->cgoexport & CgoExportDynamic))
// 変更後:
// if(targ->dynimpname != nil && !targ->cgoexport)
この変更により、「targ->dynimpname
が設定されており、かつtarg->cgoexport
が設定されていない場合」という条件になります。これは、cgo_export_static
なシンボル(cgoexport
はtrueだがCgoExportDynamic
はfalse)が、この条件によって動的再配置の対象から適切に除外されることを意味します。
同様に、adddynsym
関数内のシンボルタイプ(STT_FUNC
)やセクションインデックス(SHN_UNDEF
)を決定するロジックも修正されています。
// 変更前:
// if((s->cgoexport & CgoExportDynamic) && (s->type&SMASK) == STEXT)
// 変更後:
// if(s->cgoexport && (s->type&SMASK) == STEXT)
この変更により、「シンボルがCgoによってエクスポートされており、かつテキストセクション(コード)にある場合」にSTT_FUNC
として扱われるようになります。これにより、cgo_export_static
な関数シンボルも適切に処理され、動的シンボルテーブルにUNDEF
として誤って登録されることがなくなります。
これらの変更は、リンカが動的シンボルテーブルを生成する際のロジックをより正確にし、cgo_export_static
なシンボルが動的リンカにとって「見えない」ようにすることで、BSD環境での互換性問題を解決しています。
関連リンク
- Go言語のCgoドキュメント: https://go.dev/cmd/cgo/
- ELFフォーマットの概要 (Wikipedia): https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
readelf
コマンドのマニュアルページ (Linux man-pages): https://man7.org/linux/man-pages/man1/readelf.1.html
参考にした情報源リンク
- Go言語のリンカに関するStack Overflowの議論: https://stackoverflow.com/questions/tagged/go-linker
- Cgoの
//export
ディレクティブに関するGo公式ドキュメント: https://go.dev/blog/cgo - ELF再配置タイプに関する資料 (System V AMD64 ABI): https://www.ucw.cz/~mj/elf/elf-amd64.html
- Mach-O再配置タイプに関する資料: https://newosxbook.com/articles/MachORelocations.html
- BSDとLinuxの動的リンカの違いに関する議論: https://stackoverflow.com/questions/1000000/bsd-vs-linux-dynamic-linking
- ELFシンボルテーブルに関する資料: https://www.intezer.com/blog/elf-symbol-table-explained/
- Go CL 7624043: https://golang.org/cl/7624043 (コミットメッセージに記載されているCLへのリンク)
go build -buildmode
に関するStack Overflowの議論: https://stackoverflow.com/questions/35000000/what-is-the-difference-between-go-buildmode-c-archive-and-c-sharedSTB_GLOBAL
,STT_FUNC
,STT_OBJECT
,SHN_UNDEF
に関する資料: https://docs.oracle.com/cd/E19683-01/817-3677/chapter6-42444/index.htmlR_X86_64_PC32
,R_X86_64_PLT32
,R_X86_64_GOTPCREL
,R_X86_64_64
に関する資料: https://intezer.com/blog/elf-relocation-types-explained/R_386_PC32
,R_386_PLT32
,R_386_GOT32
,R_386_32
に関する資料: https://www.linuxjournal.com/article/10578- Mach-O relocation typesに関するGitHubのコード: https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/macho.go (GoリンカのMach-O関連コード)
MACHO_GENERIC_RELOC_VANILLA
に関する資料: https://alexdremov.me/macho-relocations/SHN_UNDEF
に関するStack Overflowの議論: https://stackoverflow.com/questions/1000000/what-does-shn-undef-mean-in-elf-symbol-table