[インデックス 15629] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) におけるホストリンキング(外部リンカとの連携)のサポートを、特に linux/amd64
環境向けに導入するものです。Cgo(GoとC言語の相互運用機能)を使用する際に、Goのツールチェインが生成したオブジェクトファイルを、システムの標準的なCリンカ(例: GCC)と連携させて最終的な実行ファイルを生成する機能の基盤を構築しています。これにより、Cgoがより複雑なCライブラリやシステムライブラリと連携できるようになります。
コミット
commit 60f783d92bc07fa7ca78e8efccbbc841d9f9cbcb
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 7 09:19:02 2013 -0500
cmd/ld: host linking support for linux/amd64
Still to do: non-linux and non-amd64.
It may work on other ELF-based amd64 systems too, but untested.
"go test -ldflags -hostobj $GOROOT/misc/cgo/test" passes.
Much may yet change, but this seems a reasonable checkpoint.
R=iant
CC=golang-dev
https://golang.org/cl/7369057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/60f783d92bc07fa7ca78e8efccbbc841d9f9cbcb
元コミット内容
このコミットは、Goリンカ (cmd/ld
) に linux/amd64
環境でのホストリンキング(外部リンカとの連携)のサポートを追加します。これは、Goのツールチェインが生成したオブジェクトファイルを、システムのCリンカ(例: GCC)と連携させて最終的な実行ファイルを生成するためのものです。まだ non-linux
や non-amd64
環境への対応は残っていますが、ELFベースの amd64
システムでは動作する可能性があります。go test -ldflags -hostobj $GOROOT/misc/cgo/test
がパスすることを確認しており、現時点での合理的なチェックポイントとされています。
変更の背景
Go言語は、当初から独自のツールチェイン(コンパイラ、アセンブラ、リンカなど)を持つことを目指していました。これにより、クロスコンパイルが容易になり、依存関係の管理がシンプルになるという利点がありました。しかし、Cgo(GoとC言語の相互運用)を使用する場合、GoのコードがCのライブラリやシステムコールと連携する必要があります。特に、動的リンクライブラリ(DLLや共有オブジェクト)や、C言語のランタイムライブラリ(libcなど)に依存するCコードをGoプログラムに組み込む場合、Goのリンカだけでは対応が難しいケースが生じます。
このコミット以前は、Goのリンカは主に「内部リンキング」と呼ばれる方式で動作していました。これは、Goのツールチェインがすべてのリンク処理を完結させる方式です。しかし、Cgoがより高度な機能(例: Cの動的ライブラリのエクスポート/インポート、特定のシステムライブラリへのリンク)を必要とするにつれて、Goのリンカが外部のCリンカ(通常はGCC)と連携する「外部リンキング」または「ホストリンキング」の必要性が高まりました。
この変更の主な目的は、Cgoプログラムが外部のCライブラリとよりシームレスに連携できるようにすること、特に動的リンクを必要とするシナリオに対応することです。これにより、Goプログラムが既存のC/C++エコシステムとより深く統合できるようになります。
前提知識の解説
1. Go言語のリンカ (cmd/ld
, 5l
, 6l
, 8l
)
Go言語のビルドシステムは、go build
コマンドを通じて、ソースコードをコンパイルし、最終的な実行ファイルを生成します。このプロセスにおいて、リンカは非常に重要な役割を担います。
cmd/ld
: Goのメインリンカです。これは、アーキテクチャ固有のリンカ(5l
for ARM,6l
for amd64,8l
for 386など)を呼び出す共通のフロントエンドとして機能します。5l
,6l
,8l
: それぞれARM、amd64、386アーキテクチャ向けのGoリンカです。これらはGoのオブジェクトファイル(.a
や.o
)を結合し、実行可能ファイルやライブラリを生成します。Goのリンカは、C言語のリンカとは異なり、Go独自のシンボル解決やメモリレイアウトの管理を行います。
2. Cgo
Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、Goの強力な並行処理とC言語の既存のライブラリやシステムAPIへのアクセスを組み合わせることができます。Cgoは、GoのビルドプロセスにCコンパイラ(通常はGCC)を統合し、CコードをコンパイルしてGoのオブジェクトファイルとリンクします。
3. 内部リンキングと外部リンキング
- 内部リンキング (Internal Linking): Goのツールチェイン(コンパイラ、リンカ)が、すべてのGoコードとCgoでコンパイルされたCコードを結合し、単一の実行ファイルを生成する方式です。この場合、外部のCリンカは使用されません。GoのリンカがCのオブジェクトファイルも直接処理します。
- 外部リンキング (External Linking): GoのツールチェインがGoのオブジェクトファイルを生成した後、最終的なリンクステップをシステムのCリンカ(例: GCC)に委ねる方式です。Cgoが外部のCライブラリに依存する場合や、特定のリンカフラグが必要な場合にこの方式が選択されます。このコミットは、この外部リンキングのサポートを強化するものです。
4. 実行ファイルフォーマット (ELF, Mach-O, PE)
オペレーティングシステムは、実行可能ファイルや共有ライブラリを特定のフォーマットで管理します。
- ELF (Executable and Linkable Format): LinuxやUnix系システムで広く使用されている実行可能ファイル、共有ライブラリ、オブジェクトファイルの標準フォーマットです。
- Mach-O: macOS (旧OS X) やiOSで使用されている実行可能ファイル、共有ライブラリ、オブジェクトファイルのフォーマットです。
- PE (Portable Executable): Windowsで使用されている実行可能ファイル、DLL、オブジェクトファイルのフォーマットです。
このコミットでは、特にELFフォーマット(linux/amd64
)に焦点を当てていますが、他のフォーマットへの対応も将来的に見据えています。
5. リロケーション (Relocations)
リロケーションとは、コンパイル時にアドレスが確定できないシンボル(変数や関数)への参照を、リンク時に正しいメモリアドレスに修正するプロセスです。オブジェクトファイルには、リロケーションエントリが含まれており、リンカはこれらを参照して最終的な実行ファイルのアドレスを解決します。
R_ARM_PLT32
,R_X86_64_PC32
,R_386_PLT32
など: これらは、特定のアーキテクチャとリロケーションタイプを示す定数です。例えば、PLT32
は Procedure Linkage Table (PLT) を介した関数呼び出しのリロケーションを示し、PC32
はプログラムカウンタ相対アドレスのリロケーションを示します。
6. GOT (Global Offset Table) と PLT (Procedure Linkage Table)
動的リンクされたプログラムでは、共有ライブラリ内の関数やデータへのアクセスは、GOTとPLTという特別なテーブルを介して行われます。
- GOT (Global Offset Table): グローバル変数や関数へのオフセットを格納するテーブルです。プログラムが共有ライブラリ内のデータにアクセスする際に使用されます。
- PLT (Procedure Linkage Table): 共有ライブラリ内の関数を呼び出す際に使用されるテーブルです。PLTエントリは、GOTエントリを介して実際の関数アドレスにジャンプします。これにより、共有ライブラリのロードアドレスが実行時に決定されても、プログラムは正しく関数を呼び出すことができます。
7. 動的エクスポートと静的エクスポート (#pragma cgo_export_static
, #pragma cgo_export_dynamic
)
Cgoでは、Goの関数をC言語から呼び出せるように「エクスポート」することができます。
#pragma cgo_export_static
: Goの関数を静的にエクスポートします。これは、Goのリンカが生成する最終的なバイナリに、その関数が直接含まれることを意味します。#pragma cgo_export_dynamic
: Goの関数を動的にエクスポートします。これは、その関数が共有ライブラリの一部としてエクスポートされ、実行時に動的にロードされることを意味します。
このコミットでは、Sym
構造体の dynexport
フィールドが cgoexport
に変更され、静的エクスポートと動的エクスポートの両方をビットマスクで管理できるようになっています。
8. メモリ管理関数 (realloc
, strdup
, erealloc
, estrdup
)
C言語のプログラムでは、メモリ管理が重要です。
realloc
: 既に割り当てられたメモリブロックのサイズを変更します。strdup
: 文字列を複製し、新しいメモリを割り当てます。erealloc
,estrdup
: このコミットで導入された、realloc
やstrdup
のラッパー関数です。これらの関数は、メモリ割り当てが失敗した場合にエラーを診断し、プログラムを終了させることで、より堅牢なエラーハンドリングを提供します。
技術的詳細
このコミットの主要な技術的変更点は、Goリンカが外部のCリンカと連携するためのメカニズムを導入したことです。
-
Sym
構造体の変更:src/cmd/5l/l.h
,src/cmd/6l/l.h
,src/cmd/8l/l.h
において、シンボル構造体Sym
のuchar dynexport;
フィールドがuchar cgoexport;
に変更されました。src/cmd/ld/lib.h
に新しい列挙型CgoExportDynamic = 1<<0
とCgoExportStatic = 1<<1
が追加され、cgoexport
がビットマスクとして機能するようになりました。これにより、シンボルが動的にエクスポートされるか、静的にエクスポートされるか、あるいはその両方であるかを柔軟に表現できるようになりました。
-
リンキングモードの導入:
src/cmd/ld/lib.h
にLinkAuto
,LinkInternal
,LinkExternal
という新しい列挙型が導入され、リンカの動作モードを制御するlinkmode
変数が追加されました。LinkAuto
は自動検出モードで、Cgoの使用状況に基づいてLinkInternal
またはLinkExternal
に切り替わります。src/cmd/5l/obj.c
,src/cmd/6l/obj.c
,src/cmd/8l/obj.c
のmain
関数で、linkmode
の初期値がLinkInternal
に設定されています。src/cmd/ld/lib.c
のloadlib
関数内で、LinkAuto
モードの場合にCgoの使用状況に応じてLinkExternal
に切り替えるロジックが追加されました。
-
ホストリンキングのプロセス (
hostlink
関数):src/cmd/ld/lib.c
にhostlink
関数が追加されました。この関数は、linkmode
がLinkExternal
の場合に呼び出されます。hostlink
は、Goリンカが生成したオブジェクトファイルと、Cgoが生成したCのオブジェクトファイルを、システムのCリンカ(通常はGCC)に渡して最終的な実行ファイルを生成します。- 具体的には、一時ディレクトリを作成し、GoのオブジェクトファイルとCのオブジェクトファイルをその一時ディレクトリにコピーします。
- その後、
gcc
コマンドをexec
し、-o
で出力ファイル名を指定し、-rdynamic
フラグ(動的シンボルテーブルにすべてのグローバルシンボルを含める)を付けて、すべての一時オブジェクトファイルをリンクします。 -ggdb
フラグも追加され、デバッグ情報が生成されるようになっています。- リンクが完了すると、一時ディレクトリは
atexit
ハンドラによってクリーンアップされます。
-
オブジェクトファイル読み込みの変更 (
ldobj
,ldhostobj
):src/cmd/ld/lib.c
のldobj
関数のシグネチャが変更され、file
引数が追加されました。これは、オブジェクトファイルがアーカイブ(.a
)内に含まれている場合に、元のアーカイブファイル名を追跡するために使用されます。ldobj
関数内で、ELF、Mach-O、PE形式のオブジェクトファイルを検出した場合に、直接ldelf
,ldmacho
,ldpe
を呼び出すのではなく、新しく導入されたldhostobj
関数を呼び出すようになりました。ldhostobj
は、オブジェクトファイルの情報をHostobj
構造体に格納し、後でhostobjs
関数によってまとめて処理されるようにキューに入れます。これにより、外部リンキングが必要なオブジェクトファイルが効率的に収集されます。
-
メモリ割り当て関数の堅牢化:
src/cmd/ld/lib.c
にestrdup
とerealloc
関数が追加されました。これらは、それぞれstrdup
とrealloc
のラッパーであり、メモリ割り当てが失敗した場合にエラーメッセージを出力してプログラムを終了させることで、より安全なメモリ管理を提供します。既存のコードベース全体でstrdup
やrealloc
の呼び出しがこれらの新しい関数に置き換えられています。
-
Cgoコールバックとメイン関数のエクスポート:
src/pkg/runtime/cgo/callbacks.c
では、crosscall2
関数が#pragma cgo_export_static crosscall2
と#pragma cgo_export_dynamic crosscall2
の両方でエクスポートされるようになりました。これは、共有ライブラリからのコールバックをサポートするために、リンキングモードに関わらずこのシンボルが必要であることを示しています。src/pkg/runtime/cgocall.c
では、外部リンキングを使用するCgoプログラムのために、Goのmain
関数が#pragma cgo_export_static main
としてエクスポートされるようになりました。これにより、libcがCランタイムの起動を処理し、GoプログラムをCのmain
関数として呼び出すことができるようになります。
これらの変更により、Goのリンカは、Cgoが外部のCライブラリと連携する際の複雑なリンキング要件に対応できるようになり、GoとCの相互運用性が大幅に向上しました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。
src/cmd/{5l,6l,8l}/asm.c
:adddynrel
関数内で、シンボルの動的エクスポートを判定する条件がtarg->dynexport
から(targ->cgoexport & CgoExportDynamic)
に変更されました。adddynsym
関数内で、シンボルの動的エクスポートを判定する条件が同様にs->dynexport
から(s->cgoexport & CgoExportDynamic)
に変更されました。
src/cmd/{5l,6l,8l}/l.h
:Sym
構造体のuchar dynexport;
フィールドがuchar cgoexport;
に変更されました。
src/cmd/{5l,6l,8l}/obj.c
:main
関数内でlinkmode = LinkInternal;
が追加され、リンキングモードの初期設定が行われています。hostlink()
関数の呼び出しが追加され、外部リンキングの処理が実行されるようになりました。
src/cmd/ld/data.c
:realloc
の呼び出しがerealloc
に置き換えられ、メモリ割り当てエラーのハンドリングが強化されました。dynrelocsym
関数内で、動的エクスポートの判定条件がr->sym->dynexport
から(r->sym->cgoexport & CgoExportDynamic)
に変更されました。
src/cmd/ld/dwarf.c
:realloc
の呼び出しがerealloc
に、strdup
の呼び出しがestrdup
に置き換えられました。
src/cmd/ld/go.c
:strdup
の呼び出しがestrdup
に、realloc
の呼び出しがerealloc
に置き換えられました。loadcgo
関数内で、cgo_export_static
とcgo_export_dynamic
の処理が追加され、s->dynexport = 1;
がs->cgoexport |= CgoExportStatic;
またはs->cgoexport |= CgoExportDynamic;
に変更されました。dynexp
グローバル変数がSym** dynexp;
として宣言され、エクスポートされるシンボルを管理するようになりました。
src/cmd/ld/ldelf.c
,src/cmd/ld/ldmacho.c
:s->dynexport
の判定が!(s->cgoexport & CgoExportDynamic)
に変更されました。
src/cmd/ld/lib.c
:Lflag
関数内でrealloc
がerealloc
に変更されました。addlibpath
関数内でrealloc
がerealloc
に変更されました。loadlib
関数内で、linkmode
の自動切り替えロジックと、dynexp
リストのフィルタリング、hostobjs()
およびhostlinksetup()
の呼び出しが追加されました。objfile
関数とldobj
関数のシグネチャが変更され、file
引数が追加されました。ldobj
関数内で、ELF, Mach-O, PE形式のオブジェクトファイルをldhostobj
を介して処理するように変更されました。dowrite
関数がhostlink
の内部に移動されました。Hostobj
構造体、ldhostobj
,hostobjs
,hostlinksetup
,hostlink
関数が新しく追加されました。これらは外部リンキングの主要なロジックを実装しています。estrdup
とerealloc
関数が追加されました。
src/cmd/ld/lib.h
:EXTERN Sym** dynexp;
が追加されました。LinkAuto
,LinkInternal
,LinkExternal
の列挙型とEXTERN int linkmode;
が追加されました。CgoExportDynamic
,CgoExportStatic
の列挙型が追加されました。ldobj
関数のシグネチャが更新されました。hostobjs
,hostlink
,estrdup
,erealloc
関数のプロトタイプが追加されました。
src/cmd/ld/macho.c
:realloc
がerealloc
に置き換えられました。
src/cmd/ld/pe.c
:initdynimport
およびinitdynexport
関数内で、動的エクスポートの判定条件がs->dynexport
から(s->cgoexport & CgoExportDynamic)
に変更されました。
src/cmd/ld/symtab.c
:putelfsym
関数内で、linkmode == LinkExternal
かつ!(x->cgoexport&CgoExportStatic)
の場合にシンボルをSTB_LOCAL
としてマークするロジックが追加されました。これにより、外部リンキング時に不要なシンボルが動的シンボルテーブルにエクスポートされるのを防ぎます。
src/pkg/runtime/cgo/callbacks.c
:crosscall2
関数に#pragma cgo_export_static crosscall2
と#pragma cgo_export_dynamic crosscall2
の両方が追加されました。
src/pkg/runtime/cgocall.c
:main
関数に#pragma cgo_export_static main
が追加されました。
コアとなるコードの解説
Sym
構造体と cgoexport
ビットマスク
以前は uchar dynexport;
という単一のフラグでシンボルの動的エクスポートを管理していましたが、このコミットでは uchar cgoexport;
に変更され、CgoExportDynamic
と CgoExportStatic
というビットマスクが導入されました。
// src/cmd/ld/lib.h
enum
{
CgoExportDynamic = 1<<0,
CgoExportStatic = 1<<1,
};
これにより、シンボルが動的にエクスポートされるか(共有ライブラリ用)、静的にエクスポートされるか(静的リンクされた実行ファイル用)、あるいはその両方であるかをより細かく制御できるようになりました。例えば、crosscall2
のように、どちらのリンキングモードでもエクスポートが必要なシンボルに対しては、両方のフラグを立てることができます。
リンキングモード (linkmode
) の制御
linkmode
変数は、Goリンカがどのように動作するかを決定します。
// src/cmd/ld/lib.h
enum
{
LinkAuto = 0,
LinkInternal,
LinkExternal,
};
EXTERN int linkmode;
LinkAuto
: デフォルトのモードです。Cgoが使用されているかどうか、およびCgoが外部リンキングを必要とするかどうかをGoリンカが自動的に判断します。LinkInternal
: Goリンカがすべてのリンク処理を内部で完結させます。外部のCリンカは使用されません。LinkExternal
: GoリンカはGoのオブジェクトファイルを生成した後、最終的なリンクステップを外部のCリンカ(GCCなど)に委ねます。
src/cmd/ld/lib.c
の loadlib
関数内で、LinkAuto
の場合にCgoの使用状況に基づいて LinkExternal
に切り替えるロジックがあります。
// src/cmd/ld/lib.c (抜粋)
// If we got this far in automatic mode, there were no
// cgo uses that suggest we need external mode.
// Switch to internal.
if(linkmode == LinkAuto)
linkmode = LinkInternal;
// Now that we know the link mode, trim the dynexp list.
x = CgoExportDynamic;
if(linkmode == LinkExternal)
x = CgoExportStatic;
w = 0;
for(i=0; i<ndynexp; i++)
if(dynexp[i]->cgoexport & x)
dynexp[w++] = dynexp[i];
ndynexp = w;
// In internal link mode, read the host object files.
if(linkmode == LinkInternal)
hostobjs();
else
hostlinksetup();
このコードは、linkmode
が LinkAuto
のままであれば LinkInternal
に設定し、その後、現在の linkmode
に応じて dynexp
リスト(エクスポートされるシンボルのリスト)をフィルタリングします。LinkInternal
モードでは hostobjs()
を呼び出してホストオブジェクトファイルを読み込み、LinkExternal
モードでは hostlinksetup()
を呼び出して外部リンキングの準備を行います。
ホストリンキングの実行 (hostlink
関数)
hostlink
関数は、LinkExternal
モードの際に最終的なリンク処理を実行します。
// src/cmd/ld/lib.c (抜粋)
void
hostlink(void)
{
char *p, **argv;
int i, w, n, argc, len;
Hostobj *h;
Biobuf *f;
static char buf[64<<10];
if(linkmode != LinkExternal)
return;
argv = malloc((10+nhostobj+nldflag)*sizeof argv[0]);
argc = 0;
argv[argc++] = "gcc"; // GCCを呼び出す
if(!debug['s'])
argv[argc++] = "-ggdb"; // デバッグ情報を生成
argv[argc++] = "-o";
argv[argc++] = outfile; // 出力ファイル名
argv[argc++] = "-rdynamic"; // 動的シンボルテーブルにすべてのグローバルシンボルを含める
// 一時ディレクトリにコピーされたホストオブジェクトファイルを追加
for(i=0; i<nhostobj; i++) {
h = &hostobj[i];
// ... ファイルコピーロジック ...
argv[argc++] = p; // 一時ファイルパスを引数に追加
}
argv[argc++] = smprint("%s/go.o", tmpdir); // Goリンカが生成したオブジェクトファイル
for(i=0; i<nldflag; i++)
argv[argc++] = ldflag[i]; // 追加のリンカフラグ
argv[argc] = nil;
// ... ログ出力 ...
if(runcmd(argv) < 0) { // GCCを実行
diag("%s: running %s failed: %r", argv0, argv[0]);
errorexit();
}
}
この関数は、Goリンカが生成したGoのオブジェクトファイル (go.o
) と、Cgoが使用する外部のCオブジェクトファイル(hostobj
リストに格納されているもの)をすべて集め、それらを gcc
コマンドの引数として渡して実行します。これにより、GoとCのコードが外部のCリンカによって最終的にリンクされ、実行可能ファイルが生成されます。
安全なメモリ割り当て関数 (estrdup
, erealloc
)
このコミットでは、メモリ割り当ての失敗に対する堅牢性を高めるために、strdup
と realloc
のラッパー関数が導入されました。
// src/cmd/ld/lib.c
char*
estrdup(char *p)
{
p = strdup(p);
if(p == nil) {
cursym = S;
diag("out of memory");
errorexit();
}
return p;
}
void*
erealloc(void *p, long n)
{
p = realloc(p, n);
if(p == nil) {
cursym = S;
diag("out of memory");
errorexit();
}
return p;
}
これらの関数は、メモリ割り当てが失敗した場合(nil
を返す場合)に、エラーメッセージを出力してプログラムを終了させます。これにより、メモリ不足による未定義動作やクラッシュを防ぎ、デバッグを容易にします。既存のコードベース全体でこれらの新しい関数が使用されるように変更されています。
関連リンク
- https://github.com/golang/go/commit/60f783d92bc07fa7ca78e8efccbbc841d9f9cbcb
- https://golang.org/cl/7369057 (Gerrit Code Review)
参考にした情報源リンク
- Go言語公式ドキュメント (Cgo, リンカに関する情報)
- ELF (Executable and Linkable Format) 仕様
- Mach-O (Mach Object) ファイルフォーマットに関するドキュメント
- PE (Portable Executable) フォーマットに関するドキュメント
- GCC (GNU Compiler Collection) のリンカオプションに関するドキュメント
- 動的リンク、GOT、PLTに関する一般的なコンピュータサイエンスの資料