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

[インデックス 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を使用している場合には、システムにインストールされている外部リンカ(例: gccclangが提供するld)を呼び出してC言語のオブジェクトファイルをリンクすることもあります。
  • -rdynamicリンカオプション: このオプションは、リンカに対して、生成される実行ファイルの動的シンボルテーブルに、すべてのグローバルシンボル(特に非静的関数やグローバル変数)を含めるように指示します。これにより、dlopen()dlsym()のような動的ローディングAPIを通じて、実行時にこれらのシンボルにアクセスできるようになります。また、gdbperfのようなデバッグ・プロファイリングツールが、実行中のプロセス内のシンボル情報をより詳細に取得できるようになります。主に共有ライブラリやプラグインをロードするアプリケーション、あるいは高度なデバッグが必要な場合に利用されます。
  • -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のビルドプロセスにおける外部リンカの挙動と、特定のリンカオプションの相互作用に起因する問題の回避策にあります。

  1. hostlink関数の役割: src/cmd/ld/lib.c内のhostlink関数は、Goのリンカが最終的な実行ファイルを生成するために外部リンカを呼び出す際に使用されます。この関数は、外部リンカに渡すコマンドライン引数を構築します。
  2. clangの挙動: clangは、-staticオプションが指定されている場合でも、-rdynamicオプションを外部リンカに渡すという特異な挙動を持っていました。これは、clangが内部的に-rdynamicを常に含めるように設計されているか、あるいは特定のビルド設定でそのように動作するためと考えられます。
  3. GNU ldのエラー: GNU ldは、-static-rdynamicが同時に指定された場合に、論理的な矛盾を検出してエラーを発生させます。静的リンクされたバイナリは動的シンボル解決を必要としないため、-rdynamicは無意味であり、GNU ldはこの矛盾を許容しません。
  4. Goリンカによる回避策: このコミットは、Goのリンカが外部リンカに引数を渡す前に、この矛盾を検出して修正するロジックを追加します。具体的には、外部リンカに渡される引数リストを走査し、もし-staticオプションが存在し、かつ生成される実行ファイルがELF形式(iselfが真)である場合に、引数リストから-rdynamicを削除します。
  5. -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を渡したい場合はそれが可能であるように設計されています。

この修正により、clangGNU 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";
			}
		}

このコードの動作は以下の通りです。

  1. 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オプションが外部リンカの引数リストに見つかった場合」という条件を意味します。
  2. 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指定を尊重する柔軟性も確保しています。

関連リンク

参考にした情報源リンク