[インデックス 13034] ファイルの概要
このコミットは、Go言語のツールチェイン(具体的にはcmd/cgo
、cmd/cc
、cmd/ld
)において、動的リンカのパスを自動的に検出する機能を追加するものです。これにより、新しいLinuxディストリビューション(特にUbuntu ARMのマルチアーキテクチャ環境)やDebian GNU/kFreeBSDのような、従来のハードコードされたパスとは異なる場所に動的リンカが配置されているシステムでのGoプログラムのビルドと実行を可能にします。
コミット
commit dac4c3eee949ccc395bde808832ab7b2bba370da
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sat May 5 01:54:16 2012 +0800
cmd/cgo, cmd/cc, cmd/ld: detect dynamic linker automatically
Some newer Linux distributions (Ubuntu ARM at least) use a new multiarch
directory organization, where dynamic linker is no longer in the hardcoded
path in our linker.
For example, Ubuntu 12.04 ARM hardfloat places its dynamic linker at
/lib/arm-linux-gnueabihf/ld-linux.so.3
Ref: http://lackof.org/taggart/hacking/multiarch/
Also, to support Debian GNU/kFreeBSD as a FreeBSD variant, we need this capability, so it's part of issue 3533.
This CL add a new pragma (#pragma dynlinker "path") to cc.
R=iant, rsc
CC=golang-dev
https://golang.org/cl/6086043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dac4c3eee949ccc395bde808832ab7b2bba370da
元コミット内容
cmd/cgo, cmd/cc, cmd/ld: detect dynamic linker automatically
Some newer Linux distributions (Ubuntu ARM at least) use a new multiarch
directory organization, where dynamic linker is no longer in the hardcoded
path in our linker.
For example, Ubuntu 12.04 ARM hardfloat places its dynamic linker at
/lib/arm-linux-gnueabihf/ld-linux.so.3
Ref: http://lackof.org/taggart/hacking/multiarch/
Also, to support Debian GNU/kFreeBSD as a FreeBSD variant, we need this capability, so it's part of issue 3533.
This CL add a new pragma (#pragma dynlinker "path") to cc.
変更の背景
この変更の主な背景には、以下の2つの主要な問題がありました。
-
Ubuntu ARMのマルチアーキテクチャ環境への対応:
- 当時のGoリンカは、動的リンカのパスを
/lib/ld-linux.so.2
のような特定の場所にハードコードしていました。 - しかし、Ubuntu 12.04 ARM hardfloatなどの新しいLinuxディストリビューションでは、マルチアーキテクチャ(Multiarch)という新しいディレクトリ構成が導入されていました。
- マルチアーキテクチャは、異なるCPUアーキテクチャ(例:
armhf
、arm64
)向けのライブラリやバイナリを単一のファイルシステム上に共存させるための機能です。これにより、例えばUbuntu 12.04 ARM hardfloatでは、動的リンカが/lib/arm-linux-gnueabihf/ld-linux.so.3
のような、アーキテクチャ固有のパスに配置されるようになりました。 - Goのツールチェインがこの新しいパスを認識できないため、Goでビルドされた動的リンクされたプログラムが正しく実行できない問題が発生していました。
- 当時のGoリンカは、動的リンカのパスを
-
Debian GNU/kFreeBSDのサポート:
- Debian GNU/kFreeBSDは、GNUユーザーランド(glibcを含む)をFreeBSDカーネル上で動作させることを目的としたDebianの派生版です。
- この環境でも、動的リンカのパスが従来のLinuxシステムとは異なる可能性があり、Goがこの環境をFreeBSDのバリアントとしてサポートするためには、動的リンカの自動検出機能が必要とされていました。
- これは、GoのIssue 3533(
cmd/cgo: GNU/kFreeBSD support
)の一部として議論されていました。
これらの問題に対処するため、Goのツールチェインが動的リンカのパスを柔軟に検出できるよう、新しいメカニズムの導入が不可欠となりました。
前提知識の解説
動的リンカ (Dynamic Linker / Program Interpreter)
動的リンカ(またはプログラムインタプリタ)は、LinuxやUnix系システムにおいて、動的にリンクされた実行可能ファイルが起動される際に、その実行に必要な共有ライブラリ(例: libc.so
)をメモリにロードし、シンボル解決を行うプログラムです。実行可能ファイルのヘッダ(ELF形式の場合、PT_INTERP
セグメント)に、使用すべき動的リンカのパスが記述されています。
例えば、一般的なLinuxシステムでは/lib/ld-linux.so.2
が動的リンカとして指定されています。プログラムが実行されると、カーネルはこのパスに指定された動的リンカをロードし、その動的リンカが実際のプログラムと必要な共有ライブラリをロードして実行を開始します。
マルチアーキテクチャ (Multiarch)
マルチアーキテクチャは、単一のシステム上で異なるCPUアーキテクチャ(例: x86_64
とi386
、またはarmhf
とarm64
)向けのバイナリとライブラリを共存させるためのDebian/Ubuntuの機能です。これにより、例えば64ビットシステム上で32ビットアプリケーションを実行したり、クロスコンパイル環境を構築したりすることが容易になります。
マルチアーキテクチャ環境では、ライブラリのパスがアーキテクチャごとに分離されます。例えば、armhf
アーキテクチャのライブラリは/usr/lib/arm-linux-gnueabihf/
のようなパスに配置され、動的リンカも同様にアーキテクチャ固有のパスに存在します。
PT_INTERP
セグメント
ELF (Executable and Linkable Format) 形式の実行可能ファイルには、プログラムヘッダテーブルが含まれています。このテーブルには、プログラムのロード方法や実行に必要な情報が記述されています。その中の一つにPT_INTERP
セグメントがあります。
PT_INTERP
セグメントは、プログラムインタプリタ(動的リンカ)のパスを格納するために使用されます。カーネルは、実行可能ファイルをロードする際にこのセグメントを読み取り、指定されたパスの動的リンカをロードして実行を開始します。このコミットの目的は、このPT_INTERP
に設定されるべき動的リンカのパスを、環境に応じて適切に決定することにあります。
cgo
、cc
、ld
cgo
: GoプログラムからC言語のコードを呼び出すためのツールです。Cgoは、CコードをGoコードとリンクするために、Cコンパイラ(cc
)とリンカ(ld
)を内部的に利用します。cc
(C Compiler): C言語のソースコードをコンパイルするツールです。Goのツールチェインでは、CgoがCコードをコンパイルする際に使用されます。ld
(Linker): コンパイルされたオブジェクトファイルやライブラリを結合して、実行可能ファイルや共有ライブラリを生成するツールです。Goのリンカは、GoのオブジェクトファイルとCgoによって生成されたCのオブジェクトファイルをリンクします。
技術的詳細
このコミットは、動的リンカの自動検出を実現するために、主に以下の技術的変更を導入しています。
-
#pragma dynlinker "path"
の導入:- Cコンパイラ(
cmd/cc
)に新しいプリプロセッサディレクティブ#pragma dynlinker "path"
が追加されました。 - このプラグマは、Cgoが生成するCコード内で使用され、特定の動的リンカのパスをGoのリンカに伝える役割を果たします。
src/cmd/cc/cc.h
にpragdynlinker
関数の宣言とdynlinker
変数の定義が追加され、src/cmd/cc/dpchk.c
にpragdynlinker
の実装が追加されました。この実装は、プラグマで指定されたパスをdynlinker
変数に格納します。src/cmd/cc/macbody
では、macprag
関数がdynlinker
プラグマを認識し、pragdynlinker
関数を呼び出すように変更されました。
- Cコンパイラ(
-
cgo
によるELFファイルの.interp
セクションの読み取り:src/cmd/cgo/out.go
が変更され、Cgoがオブジェクトファイル(.o
ファイル)を処理する際に、ELF形式のオブジェクトファイルの.interp
セクションを読み取るようになりました。.interp
セクションには、そのオブジェクトファイルが依存する動的リンカのパスが記述されています。- Cgoは、この
.interp
セクションから動的リンカのパスを抽出し、それを#pragma dynlinker "path"
形式で標準出力に出力します。この出力は、後続のGoコンパイラやリンカによって処理されます。
-
Goリンカ(
cmd/ld
)でのdynlinker
情報の処理:- Goリンカ(
src/cmd/ld/go.c
)が、Cgoから渡される#pragma dynlinker
情報を解析し、動的リンカのパスを決定するようになりました。 loaddynlinker
という新しい静的関数が追加され、パッケージデータ内のdynlinker
セクションを読み込み、interpreter
変数に動的リンカのパスを設定します。- リンカは、コマンドラインオプションで
-I
(インタプリタパスのオーバーライド)が指定されていない限り、この#pragma dynlinker
で指定されたパスを優先的に使用します。 - 複数の
#pragma dynlinker
が異なるパスを指定している場合、リンカは競合を検出し、エラーを報告します。
- Goリンカ(
これらの変更により、Goのツールチェインは、ビルド時にCgoが依存する共有ライブラリの動的リンカパスを自動的に検出し、その情報をGoリンカに渡し、最終的に生成される実行可能ファイルのPT_INTERP
セグメントに正しい動的リンカのパスを設定できるようになります。これにより、マルチアーキテクチャ環境やDebian GNU/kFreeBSDのような特殊な環境でも、Goの動的リンクされたプログラムが正しく動作するようになります。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルは以下の通りです。
src/cmd/5c/swt.c
src/cmd/5l/obj.c
src/cmd/6c/swt.c
src/cmd/6l/obj.c
src/cmd/8c/swt.c
src/cmd/8l/obj.c
src/cmd/cc/cc.h
src/cmd/cc/dpchk.c
src/cmd/cc/lexbody
src/cmd/cc/macbody
src/cmd/cgo/out.go
src/cmd/ld/go.c
これらのファイルは、Goのコンパイラ(5c
, 6c
, 8c
はそれぞれPlan 9 CコンパイラのARM, AMD64, x86版)、リンカ(5l
, 6l
, 8l
はそれぞれのアーキテクチャ版)、Cコンパイラ(cc
)、Cgoツール(cgo
)、およびGoリンカ(ld
)に関連するものです。
コアとなるコードの解説
src/cmd/5c/swt.c
, src/cmd/6c/swt.c
, src/cmd/8c/swt.c
(Cコンパイラのバックエンド)
これらのファイルは、GoのCコンパイラ(cmd/cc
)のバックエンド部分で、生成されるアセンブリコードやメタデータを出力する役割を担っています。
変更点としては、outcode
関数内で、dynexport
セクションと同様に、dynlinker
セクションが追加され、dynlinker
変数がnil
でない場合にその値(動的リンカのパス)がファイルに出力されるようになりました。これは、Goリンカが後で読み取るためのメタデータとして機能します。
// src/cmd/5c/swt.c (同様の変更が6c/swt.c, 8c/swt.cにも適用)
Bprint(&outbuf, "\n$$ // dynlinker\n");
if(dynlinker != nil) {
Bprint(&outbuf, "dynlinker %s\n", dynlinker);
}
src/cmd/5l/obj.c
, src/cmd/6l/obj.c
, src/cmd/8l/obj.c
(Goリンカのアーキテクチャ固有部分)
これらのファイルは、Goリンカのアーキテクチャ固有の初期化やコマンドライン引数処理を行う部分です。
変更点としては、コマンドラインオプション-I
が指定された場合に、debug['I']
フラグをセットする行が追加されました。このフラグは、リンカが#pragma dynlinker
で検出されたパスよりもコマンドラインで指定されたパスを優先すべきかどうかを判断するために使用されます。
// src/cmd/5l/obj.c (同様の変更が6l/obj.c, 8l/obj.cにも適用)
case 'I':
debug['I'] = 1; // denote cmdline interpreter override
interpreter = EARGF(usage());
break;
src/cmd/cc/cc.h
(Cコンパイラのヘッダ)
#pragma dynlinker
をサポートするための宣言が追加されました。
// src/cmd/cc/cc.h
void pragdynlinker(void);
EXTERN char *dynlinker; // 動的リンカのパスを保持する変数
src/cmd/cc/dpchk.c
(Cコンパイラのプラグマ処理)
#pragma dynlinker
の具体的な処理が実装されました。getquoted()
関数を使って、プラグマに続く引用符で囲まれた文字列(動的リンカのパス)を抽出し、dynlinker
変数に設定します。
// src/cmd/cc/dpchk.c
void
pragdynlinker(void)
{
dynlinker = getquoted();
if(dynlinker == nil)
goto err;
goto out;
err:
yyerror("usage: #pragma dynlinker \"path\"");
out:
while(getnsc() != '\n')
;
}
src/cmd/cc/macbody
(Cコンパイラのマクロ処理)
Cコンパイラが#pragma
ディレクティブを解析する際に、dynlinker
というキーワードを認識し、対応するpragdynlinker
関数を呼び出すように変更されました。
// src/cmd/cc/macbody
if(s && strcmp(s->name, "dynlinker") == 0) {
pragdynlinker();
return;
}
src/cmd/cgo/out.go
(Cgoツール)
Cgoが生成するCコードに、動的リンカのパスを埋め込むための重要な変更が加えられました。
Cgoは、入力として与えられたオブジェクトファイル(obj
)がELF形式である場合、その.interp
セクションを読み取ります。.interp
セクションには、そのELFファイルが依存する動的リンカのパスが格納されています。Cgoはこのパスを抽出し、#pragma dynlinker "path"
という形式で標準出力に書き出します。この出力は、Goコンパイラによって処理され、最終的にGoリンカに渡されます。
// src/cmd/cgo/out.go
if f, err := elf.Open(obj); err == nil {
if sec := f.Section(".interp"); sec != nil {
if data, err := sec.Data(); err == nil && len(data) > 1 {
// skip trailing \0 in data
fmt.Fprintf(stdout, "#pragma dynlinker %q\n", string(data[:len(data)-1]))
}
}
// ...
}
src/cmd/ld/go.c
(Goリンカの主要部分)
Goリンカが、Cgoから渡されたdynlinker
情報を処理するロジックが追加されました。
loaddynlinker
という新しい静的関数が追加されました。この関数は、Cgoが生成したメタデータ内のdynlinker
セクションを解析し、動的リンカのパスを抽出します。- 抽出されたパスは、リンカの内部変数
interpreter
に設定されます。 - リンカは、コマンドラインで
-I
オプションが指定されていない限り、この#pragma dynlinker
で検出されたパスを優先します。 - 複数の
dynlinker
パスが検出された場合、競合をチェックし、異なるパスが指定されていればエラーを報告します。 ldpkg
関数内で、dynlinker
セクションを読み込むための処理が追加されました。
// src/cmd/ld/go.c
static void loaddynlinker(char*, char*, char*, int); // 新しい関数の宣言
// ldpkg関数内でのdynlinkerセクションの読み込み
p0 = strstr(p1, "\n$$ // dynlinker");
if(p0 != nil) {
// ... セクションの開始と終了を特定 ...
loaddynlinker(filename, pkg, p0 + 1, p1 - (p0+1));
}
// loaddynlinker関数の実装
static void
loaddynlinker(char *file, char *pkg, char *p, int n)
{
// ... dynlinker行を解析 ...
if(!debug['I']) { // コマンドラインでオーバーライドされていない場合
if(interpreter != nil && strcmp(interpreter, dynlinker) != 0) {
// 競合検出
fprintf(2, "%s: conflict dynlinker: %s and %s\n", argv0, interpreter, dynlinker);
nerrors++;
return;
}
free(interpreter);
interpreter = strdup(dynlinker); // interpreter変数を設定
}
// ...
}
これらの変更により、Goのビルドシステムは、Cgoを介してCコードをリンクする際に、そのCコードが依存する動的リンカのパスを自動的に検出し、最終的なGoの実行可能ファイルに正しい動的リンカのパスを埋め込むことができるようになりました。これにより、Goプログラムが様々なLinuxディストリビューションやアーキテクチャでより堅牢に動作するようになります。
関連リンク
- Go Change-list: https://golang.org/cl/6086043
- Multiarchに関する参照記事: http://lackof.org/taggart/hacking/multiarch/
- Go Issue 3533: https://github.com/golang/go/issues/3533
参考にした情報源リンク
- Ubuntu ARM multiarch dynamic linker: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGH6lrfeUpX0Xo-MHS-7P40ox_ZKUDrTJaCgz0wnrLIV_HPFJJ2ffJGlsPRSG_H0LfGP7Rk19ELuDg83y3fWHbpbanjG1BW8_HyC6Jhe5hX8AOMRoxzwHNapDAguobmROGcaA_x2XP9fhQ9i11Uy3B_7xJzFiU3ZCluBn_ZMq0q4gbXFU2cZ90=
- Debian GNU/kFreeBSD dynamic linker: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEAM5kxP_rCcukyOWZof-4H4sY0J4cK4hCTq0tm9ynLp8BRH8X26CkaOfryVMryopS5ktQESr6gROIML2-4IbMjlvt_4VyxYEsEV78Y_-JZ9wPTulci3xVxPiJ__yIpbiOBZMZJSg==
- golang issue 3533: https://github.com/golang/go/issues/3533
- golang.org/cl/6086043: https://github.com/golang/go/commit/dac4c3eee949ccc395bde808832ab7b2bba370da (これはコミットページへのリンクと同じですが、検索結果として得られたため記載)
- Multiarchに関する参照記事の要約: http://lackof.org/taggart/hacking/multiarch/