[インデックス 19195] ファイルの概要
src/cmd/ld/lib.c
は、Go言語のリンカ(cmd/ld
)のソースコードの一部であり、主に外部リンカとの連携に関する処理を記述しています。Goのビルドシステムは、Goのコードをコンパイルした後、最終的な実行ファイルを生成するために、システムにインストールされている外部リンカ(例えばGCCやClangが提供するld
)を呼び出すことがあります。このファイルは、その外部リンカに渡す引数を準備し、リンカの挙動を制御する役割を担っています。
コミット
cmd/ld: don't pass -rdynamic to external linker if -static is used
Fixes #7800.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/87790051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/827aab07b80cd8ad26548a6fa234b7d038537d33
元コミット内容
このコミットは、Goのリンカ(cmd/ld
)が外部リンカを呼び出す際に、-static
オプションが使用されている場合には-rdynamic
オプションを渡さないようにする変更です。これは、特定のリンカの組み合わせ(特にClangとGNU ld)で発生するリンカエラーを修正することを目的としています。
変更の背景
この変更は、GoのIssue #7800で報告された問題に対応するものです。問題の核心は、clang
コンパイラが-static
オプションと-rdynamic
オプションを同時に外部リンカに渡す場合に、GNU ld
(GNU Linker)がエラーを発生させるというものでした。
通常、Goプログラムはデフォルトで静的にリンクされます。これは、Goランタイムや必要なライブラリがすべて単一の実行ファイルにバンドルされることを意味し、高い移植性を提供します。しかし、cgo
を使用してC言語のコードをGoプログラムに組み込む場合など、外部リンカが使用されることがあります。
-rdynamic
オプションは、実行時に動的リンカがシンボルを解決できるように、すべてのシンボルを動的シンボルテーブルに追加するようリンカに指示します。これはデバッグやプロファイリングの際に有用です。一方、-static
オプションは、すべてのライブラリを静的にリンクするようリンカに指示し、共有ライブラリへの依存をなくします。
これらのオプションは通常、排他的に使用されるべきものです。静的リンクされた実行ファイルは、実行時に動的シンボル解決を必要としないため、-rdynamic
は意味をなしません。しかし、clang
は、-static
が指定されていても-rdynamic
をリンカに渡してしまうという挙動を持っていました。この矛盾したオプションの組み合わせがGNU ld
でエラーを引き起こし、Goプログラムのビルドに失敗する原因となっていました。
このコミットは、Goのリンカがこの問題を検出し、-static
が使用されている場合には-rdynamic
を外部リンカの引数リストから削除することで、ビルドエラーを回避するようにします。
前提知識の解説
cmd/ld
: Go言語のビルドツールチェーンにおけるリンカです。Goのソースコードから最終的な実行ファイルを生成する役割を担います。Goのリンカは、Goのオブジェクトファイルをリンクするだけでなく、Cgoを使用している場合には、システムにインストールされている外部リンカ(例:gcc
やclang
が提供するld
)を呼び出してC言語のオブジェクトファイルをリンクすることもあります。-rdynamic
リンカオプション: このオプションは、リンカに対して、生成される実行ファイルの動的シンボルテーブルに、すべてのグローバルシンボル(特に非静的関数やグローバル変数)を含めるように指示します。これにより、dlopen()
やdlsym()
のような動的ローディングAPIを通じて、実行時にこれらのシンボルにアクセスできるようになります。また、gdb
やperf
のようなデバッグ・プロファイリングツールが、実行中のプロセス内のシンボル情報をより詳細に取得できるようになります。主に共有ライブラリやプラグインをロードするアプリケーション、あるいは高度なデバッグが必要な場合に利用されます。-static
リンカオプション: このオプションは、リンカに対して、可能な限りすべてのライブラリを静的にリンクするように指示します。静的リンクとは、必要なライブラリのコードを直接実行ファイルに組み込むことで、実行ファイルが外部の共有ライブラリに依存しないようにする方式です。これにより、実行ファイルは自己完結型となり、異なるシステム上での移植性が向上します。ただし、実行ファイルのサイズは大きくなり、ライブラリの更新があった場合には実行ファイルを再ビルドする必要があります。clang
: LLVMプロジェクトの一部であるC、C++、Objective-Cコンパイラです。モダンで高速なコンパイラとして知られ、GCCの代替として広く利用されています。GCC
(GNU Compiler Collection): GNUプロジェクトによって開発されている、様々なプログラミング言語に対応したコンパイラ群です。C、C++、Objective-C、Fortran、Ada、Goなどの言語をサポートしています。GNU ld
(GNU Linker): GNUプロジェクトによって開発されているリンカです。多くのUnix系システムでデフォルトのリンカとして使用されており、オブジェクトファイルとライブラリを結合して実行ファイルや共有ライブラリを生成します。iself
: このコミットのコードスニペットに登場する変数で、GoのリンカがELF(Executable and Linkable Format)形式の実行ファイルを生成しているかどうかを示すフラグです。ELFはLinuxや多くのUnix系システムで標準的に使用される実行ファイル形式です。
技術的詳細
このコミットの技術的詳細は、Goのビルドプロセスにおける外部リンカの挙動と、特定のリンカオプションの相互作用に起因する問題の回避策にあります。
hostlink
関数の役割:src/cmd/ld/lib.c
内のhostlink
関数は、Goのリンカが最終的な実行ファイルを生成するために外部リンカを呼び出す際に使用されます。この関数は、外部リンカに渡すコマンドライン引数を構築します。clang
の挙動:clang
は、-static
オプションが指定されている場合でも、-rdynamic
オプションを外部リンカに渡すという特異な挙動を持っていました。これは、clang
が内部的に-rdynamic
を常に含めるように設計されているか、あるいは特定のビルド設定でそのように動作するためと考えられます。GNU ld
のエラー:GNU ld
は、-static
と-rdynamic
が同時に指定された場合に、論理的な矛盾を検出してエラーを発生させます。静的リンクされたバイナリは動的シンボル解決を必要としないため、-rdynamic
は無意味であり、GNU ld
はこの矛盾を許容しません。- Goリンカによる回避策: このコミットは、Goのリンカが外部リンカに引数を渡す前に、この矛盾を検出して修正するロジックを追加します。具体的には、外部リンカに渡される引数リストを走査し、もし
-static
オプションが存在し、かつ生成される実行ファイルがELF形式(iself
が真)である場合に、引数リストから-rdynamic
を削除します。 -extldflags
との関連性: コミットメッセージには「We do it in this order, rather than only adding -rdynamic later, so that -extldflags can override -rdynamic without using -static.」とあります。これは、Goのビルドオプションである-ldflags
を通じて-extldflags
を指定することで、ユーザーが外部リンカに任意のフラグを渡せることを指しています。この修正は、-extldflags
で-rdynamic
を明示的に指定した場合でも、-static
が使われていなければ-rdynamic
が有効になるように、処理の順序を考慮していることを示唆しています。つまり、Goリンカが自動的に-rdynamic
を削除するのは、-static
が明示的に指定されている場合に限られ、ユーザーが意図的に-rdynamic
を渡したい場合はそれが可能であるように設計されています。
この修正により、clang
とGNU ld
の組み合わせでGoプログラムをビルドする際に発生していたリンカエラーが解消され、ビルドの信頼性が向上しました。
コアとなるコードの変更箇所
src/cmd/ld/lib.c
ファイルのhostlink
関数内に以下のコードが追加されました。
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -654,6 +654,20 @@ hostlink(void)\
if(*p == '\0')
break;
argv[argc++] = p;
+
+ // clang, unlike GCC, passes -rdynamic to the linker
+ // even when linking with -static, causing a linker
+ // error when using GNU ld. So take out -rdynamic if
+ // we added it. We do it in this order, rather than
+ // only adding -rdynamic later, so that -extldflags
+ // can override -rdynamic without using -static.
+ if(iself && strncmp(p, "-static", 7) == 0 && (p[7]==' ' || p[7]=='\0')) {
+ for(i=0; i<argc; i++) {
+ if(strcmp(argv[i], "-rdynamic") == 0)
+ argv[i] = "-static";
+ }
+ }
+
p = strchr(p + 1, ' ');
}
コアとなるコードの解説
追加されたコードブロックは、外部リンカに渡される引数リストを処理するループの中にあります。
if(iself && strncmp(p, "-static", 7) == 0 && (p[7]==' ' || p[7]=='\0')) {
for(i=0; i<argc; i++) {
if(strcmp(argv[i], "-rdynamic") == 0)
argv[i] = "-static";
}
}
このコードの動作は以下の通りです。
-
if(iself && strncmp(p, "-static", 7) == 0 && (p[7]==' ' || p[7]=='\0'))
:iself
: 現在ビルドしている実行ファイルがELF形式であるかどうかをチェックします。この問題は主にELF形式のシステム(Linuxなど)で発生するため、このチェックは重要です。strncmp(p, "-static", 7) == 0 && (p[7]==' ' || p[7]=='\0')
: 現在処理している引数p
が正確に-static
であるかをチェックします。strncmp
で最初の7文字が-static
と一致し、かつその後にスペースまたは文字列の終端が続くことを確認することで、例えば-static-libgcc
のような別のオプションと誤認しないようにしています。- この
if
文全体は、「もしELF形式のビルドで、かつ-static
オプションが外部リンカの引数リストに見つかった場合」という条件を意味します。
-
for(i=0; i<argc; i++) { if(strcmp(argv[i], "-rdynamic") == 0) argv[i] = "-static"; }
:- 上記の
if
条件が真であった場合、この内部ループが実行されます。 for(i=0; i<argc; i++)
: 外部リンカに渡すために既に収集されたすべての引数(argv
配列)を最初から最後まで走査します。if(strcmp(argv[i], "-rdynamic") == 0)
: 走査中の引数が-rdynamic
であるかをチェックします。argv[i] = "-static";
: もし-rdynamic
が見つかった場合、その引数を-static
に置き換えます。
- 上記の
このロジックにより、-static
オプションが指定されているにもかかわらず-rdynamic
が外部リンカに渡されてしまうという矛盾した状況を検出し、-rdynamic
を-static
に「変換」することで、リンカエラーを回避しています。コミットメッセージにあるように、-rdynamic
を単に削除するのではなく-static
に置き換えることで、引数リストの構造を維持しつつ、-extldflags
によるユーザーの明示的な-rdynamic
指定を尊重する柔軟性も確保しています。
関連リンク
- Go Issue #7800: https://code.google.com/p/go/issues/detail?id=7800 (現在はGitHubに移行済み)
- Go CL 87790051: https://golang.org/cl/87790051
参考にした情報源リンク
- Go
go build
command: https://go.dev/cmd/go/#hdr-Build_packages_and_dependencies - Go
ldflags
: https://go.dev/cmd/go/#hdr-Command_line_flags - GNU Linker (ld) man page (for
-rdynamic
and-static
): https://man7.org/linux/man-pages/man1/ld.1.html - Clang Command Guide: https://clang.llvm.org/docs/ClangCommandLineReference.html
- ELF (Executable and Linkable Format): https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- Static vs Dynamic Linking: https://en.wikipedia.org/wiki/Static_linking
- cgo: https://go.dev/blog/cgo
- Web search for "Go linker -rdynamic -static" (used for general understanding and confirmation of flag behaviors).