[インデックス 13640] ファイルの概要
このコミットは、Go言語のCgo(GoとC/C++の相互運用機能)プログラムのビルドプロセスに重要な変更を導入します。具体的には、GCCでコンパイルされたオブジェクトファイルとGoのリンカ(cmd/go
)との連携を改善するため、中間的なリンキングステップを追加します。これにより、libgcc
、libmingwex
、libmingw32
といったGCCの標準ライブラリの静的リンクに関する問題が解決され、特にWindowsやARM環境でのCgoプログラムのビルドの信頼性が向上します。
コミット
- コミットハッシュ:
551d8b9ff5b64bded6a7dd284fb1790a2f78ead0
- 作者: Shenghou Ma minux.ma@gmail.com
- コミット日時: 2012年8月17日 金曜日 03:42:34 +0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/551d8b9ff5b64bded6a7dd284fb1790a2f78ead0
元コミット内容
cmd/go: new cgo build procedure
This CL adds a step to the build procedure for cgo programs. It uses 'ld -r'
to combine all gcc compiled object file and generate a relocatable object file
for our ld. Additionally, this linking step will combine some static linking
gcc library into the relocatable object file, so that we can use libgcc,
libmingwex and libmingw32 without problem.
Fixes #3261.
Fixes #1741.
Added a testcase for linking in libgcc.
TODO:
1. still need to fix the INDIRECT_SYMBOL_LOCAL problem on Darwin/386.
2. still need to enable the libgcc test on Linux/ARM, because 5l can't deal
with thumb libgcc.
Tested on Darwin/amd64, Darwin/386, FreeBSD/amd64, FreeBSD/386, Linux/amd64,
Linux/386, Linux/ARM, Windows/amd64, Windows/386
R=iant, rsc, bradfitz, coldredlemur
CC=golang-dev
https://golang.org/cl/5822049
変更の背景
Go言語のCgo機能は、GoプログラムからC言語のコードを呼び出すことを可能にしますが、そのビルドプロセスは複雑です。特に、Goのツールチェイン(コンパイラやリンカ)とGCC(GNU Compiler Collection)が生成するオブジェクトファイルやライブラリとの間で、リンキングに関する互換性の問題が発生することがありました。
このコミットの主な背景には、以下の具体的な問題がありました。
libgcc
のリンク問題 (Issue #3261): 特にARMアーキテクチャにおいて、Cgoプログラムがlibgcc
(GCCが生成するコードが依存するランタイムサポートライブラリ)を正しくリンクできない問題がありました。これは、Goのリンカがlibgcc
の特定の形式や、ARMのThumb命令セットでコンパイルされたライブラリを直接処理できないことに起因していました。- Windows環境でのライブラリリンク問題 (Issue #1741): Windows環境でMinGW(Minimalist GNU for Windows)を使用してCgoプログラムをビルドする際、
libmingwex
やlibmingw32
といったMinGW固有のライブラリが正しくリンクされない問題がありました。これにより、Windows上でのCgoプログラムのビルドが不安定になることがありました。
これらの問題は、GoのリンカがGCCが生成するオブジェクトファイルや特定の静的ライブラリの内部構造を完全に理解し、適切に結合することが困難であったために発生していました。このコミットは、これらのリンキングの課題を解決し、Cgoプログラムのクロスプラットフォームでのビルドの堅牢性を高めることを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下の技術的な概念を把握しておく必要があります。
- Cgo: Go言語の組み込み機能の一つで、GoプログラムからC言語の関数を呼び出したり、C言語のデータ構造を利用したりすることを可能にします。Cgoを使用すると、GoのコードとCのコードを同じプログラム内で混在させることができます。Goのビルドツールは、Cgoの指示に基づいてCコードをコンパイルし、Goのコードとリンクします。
- リンカ (Linker): コンパイラが生成したオブジェクトファイル(機械語コードとデータを含む中間ファイル)や、事前にコンパイルされたライブラリを結合し、最終的な実行可能ファイルや共有ライブラリを生成するプログラムです。リンカは、異なるオブジェクトファイル間で参照されるシンボル(関数名や変数名)を解決し、適切なメモリアドレスに配置する役割を担います。
ld -r
: GNU Binutilsに含まれるリンカld
のオプションの一つです。通常、ld
は複数のオブジェクトファイルやライブラリを結合して実行可能ファイルを生成しますが、-r
オプションを使用すると、複数の入力オブジェクトファイルを結合して、単一の再配置可能オブジェクトファイル (relocatable object file) を出力します。この中間オブジェクトファイルは、まだ最終的なメモリアドレスが決定されておらず、後続のリンカによってさらにリンクされることを前提としています。この機能は、複数の小さなオブジェクトファイルを一つにまとめ、後続のリンキングプロセスを簡素化するのに役立ちます。- 再配置可能オブジェクトファイル (Relocatable Object File): コンパイルされたコードとデータを含みますが、その中のシンボル(関数や変数)のアドレスはまだ最終的に決定されていません。これらのアドレスは、最終的な実行可能ファイルを生成する際に、リンカによって解決(再配置)されます。
- 静的リンク (Static Linking): ライブラリのコードを実行可能ファイルに直接組み込むリンク方式です。これにより、実行可能ファイルは自己完結型となり、実行時に外部ライブラリを必要としません。
libgcc
: GCC(GNU Compiler Collection)が生成するコードが依存するランタイムサポートライブラリです。特定のハードウェア命令に直接マッピングできないような低レベルな操作(例: 64ビット整数演算のエミュレーション、浮動小数点演算の補助、例外処理のサポートなど)を提供します。GCCでコンパイルされたC/C++コードは、暗黙的にlibgcc
にリンクされることがよくあります。libmingwex
およびlibmingw32
: これらは、Windows上でGCCを使用するための環境であるMinGW(Minimalist GNU for Windows)に含まれるライブラリです。libmingw32
はWindows APIへのラッパーやC標準ライブラリの一部を提供し、libmingwex
は追加の拡張機能や互換性レイヤーを提供します。Windows環境でCgoプログラムがGCCでコンパイルされたCコードを使用する場合、これらのライブラリへのリンクが必要になることがあります。- Issue #3261 (cmd/go: cgo linking fails with libgcc on arm): ARMアーキテクチャでCgoプログラムが
libgcc
をリンクする際に発生する問題。GoのリンカがARMの特定の命令セット(Thumb)でコンパイルされたlibgcc
を正しく処理できないことが原因でした。 - Issue #1741 (cmd/go: cgo linking fails with libmingwex on windows): Windows環境でCgoプログラムが
libmingwex
をリンクする際に発生する問題。MinGW環境でコンパイルされたCコードが依存するこれらのライブラリが、Goのリンカによって適切に解決されないことが原因でした。 INDIRECT_SYMBOL_LOCAL
on Darwin/386: Darwin(macOS)の32ビット環境で発生する可能性のあるシンボル解決に関する特定の問題。これは、リンカがローカルシンボルを間接的に参照する際に発生する複雑さに関連しています。- Thumb libgcc on Linux/ARM: ARMプロセッサには、コードサイズを削減するためのThumb命令セットがあります。
libgcc
がThumbモードでコンパイルされている場合、Goのリンカ(特に当時の5l
)がその形式を正しく扱えないことが問題でした。
技術的詳細
このコミットの核心は、GoのCgoビルドプロセスにおけるリンキングフェーズの変更にあります。以前は、GCCでコンパイルされた個々のオブジェクトファイルが直接Goのリンカに渡されていましたが、このアプローチでは、特にlibgcc
やMinGW関連のライブラリのような複雑な依存関係を持つ場合に問題が生じました。
新しいビルド手順は、src/cmd/go/build.go
のcgo
関数内で実装されています。主な変更点は以下の通りです。
-
中間リンキングステップの導入:
- GCCでコンパイルされたすべてのCオブジェクトファイル(
gccfiles
から生成される.o
ファイル)が、Goのリンカに直接渡される前に、一度GCCのリンカ(ld
)によって結合されます。 - この結合には
ld -r
オプションが使用されます。これにより、複数のGCCオブジェクトファイルと、必要に応じてlibgcc
、libmingwex
、libmingw32
といった静的ライブラリが結合され、単一の再配置可能オブジェクトファイル(例:_all.o
)が生成されます。 - この
_all.o
ファイルは、Goのリンカが処理しやすい形式になっています。Goのリンカは、この単一のオブジェクトファイルを扱うだけでよく、GCCが生成する個々のオブジェクトファイルの内部構造や、libgcc
などの複雑な依存関係を直接解決する必要がなくなります。
- GCCでコンパイルされたすべてのCオブジェクトファイル(
-
リンカフラグのフィルタリング:
- Cgoのビルド時に指定されるリンカフラグ(
cgoLDFLAGS
)から、-l
(ライブラリリンク)やDarwin環境での-framework
といったオプションがフィルタリングされ、bareLDFLAGS
という新しいリストが作成されます。 - これは、中間リンキングステップ(
ld -r
)で既にライブラリの結合が行われるため、Goの最終リンカにこれらのオプションを再度渡す必要がない、あるいは渡すべきではないためと考えられます。ld -r
がライブラリを静的に結合することで、最終的なGoのリンカは、結合された単一のオブジェクトファイル内のシンボルを解決するだけでよくなります。
- Cgoのビルド時に指定されるリンカフラグ(
-
静的ライブラリの明示的な追加:
staticLibs
というスライスが導入され、これには常に-lgcc
が含まれます。- Windows環境の場合、さらに
-lmingwex
と-lmingw32
が追加されます。 - これらのライブラリは、中間リンキングステップで
ld -r
に明示的に渡され、生成される_all.o
に静的に組み込まれます。これにより、Goのリンカがこれらのライブラリのシンボルを解決する必要がなくなり、リンキングエラーが回避されます。
-
出力オブジェクトファイルの構成変更:
- Goのリンカに渡される最終的なオブジェクトファイルのリスト(
outObj
)が変更されます。 - 以前は、GoのCgoコンパイラが生成するオブジェクトファイル(
importObj
)と、GCCが生成する個々のオブジェクトファイルが直接結合されていました。 - 変更後、
outObj
はimportObj
、GCCが生成した非オブジェクトファイル(nonGccObjs
)、そして新しく生成された単一の再配置可能オブジェクトファイル(_all.o
)の順で構成されます。これにより、Goのリンカは、GCC関連のすべてのシンボルが解決された_all.o
を処理するだけでよくなります。
- Goのリンカに渡される最終的なオブジェクトファイルのリスト(
これらの変更により、Cgoプログラムのビルドは、GCCが生成する複雑なオブジェクトファイルや特定のランタイムライブラリの依存関係をGoのリンカが直接扱う必要がなくなり、より堅牢で移植性の高いものになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/cmd/go/build.go
ファイルの cgo
関数に集中しています。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1558,6 +1558,24 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,\
// gcc
var linkobj []string
+
+ var bareLDFLAGS []string
+ // filter out -lsomelib, and -framework X if on Darwin
+ for i := 0; i < len(cgoLDFLAGS); i++ {
+ f := cgoLDFLAGS[i]
+ if !strings.HasPrefix(f, "-l") {
+ if goos == "darwin" && f == "-framework" { // skip the -framework X
+ i += 1
+ continue
+ }
+ bareLDFLAGS = append(bareLDFLAGS, f)
+ }
+ }
+ staticLibs := []string{"-lgcc"}
+ if goos == "windows" {
+ staticLibs = append(staticLibs, "-lmingwex", "-lmingw32")
+ }
+
for _, cfile := range cfiles {
ofile := obj + cfile[:len(cfile)-1] + "o"
if err := b.gcc(p, ofile, cgoCFLAGS, obj+cfile); err != nil {
@@ -1605,10 +1623,23 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,\
return nil, nil, err
}
+ ofile := obj + "_all.o"
+ var gccObjs, nonGccObjs []string
+ for _, f := range outObj {
+ if strings.HasSuffix(f, ".o") {
+ gccObjs = append(gccObjs, f)
+ } else {
+ nonGccObjs = append(nonGccObjs, f)
+ }
+ }
+ if err := b.gccld(p, ofile, stringList(bareLDFLAGS, "-Wl,-r", "-nostdlib", staticLibs), gccObjs); err != nil {
+ return nil, nil, err
+ }
+
// NOTE(rsc): The importObj is a 5c/6c/8c object and on Windows
// must be processed before the gcc-generated objects.
// Put it first. http://golang.org/issue/2601
- outObj = append([]string{importObj}, outObj...)\n
+ outObj = stringList(importObj, nonGccObjs, ofile)
return outGo, outObj, nil
}
また、misc/cgo/test/issue3261.go
が新規追加され、misc/cgo/test/cgo_test.go
にそのテストケースが追加されています。
misc/cgo/test/issue3261.go
の内容:
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgotest
/*
// libgcc on ARM might be compiled as thumb code, but our 5l
// can't handle that, so we have to disable this test on arm.
#ifdef __ARMEL__
#include <stdio.h>
int vabs(int x) {
puts("testLibgcc is disabled on ARM because 5l cannot handle thumb library.");
return (x < 0) ? -x : x;
}
#else
int __absvsi2(int); // dummy prototype for libgcc function
// we shouldn't name the function abs, as gcc might use
// the builtin one.
int vabs(int x) { return __absvsi2(x); }
#endif
*/
import "C"
import "testing"
func testLibgcc(t *testing.T) {
var table = []struct {
in, out C.int
}{
{0, 0},
{1, 1},
{-42, 42},
{1000300, 1000300},
{1 - 1<<31, 1<<31 - 1},
}
for _, v := range table {
if o := C.vabs(v.in); o != v.out {
t.Fatalf("abs(%d) got %d, should be %d", v.in, o, v.out)
return
}
}
}
コアとなるコードの解説
src/cmd/go/build.go
の cgo
関数は、Cgoプログラムのビルドロジックをカプセル化しています。この関数は、GoのCgoコンパイラが生成したGoファイルと、GCCがコンパイルしたCオブジェクトファイルを統合する役割を担います。
変更点について詳しく見ていきましょう。
-
bareLDFLAGS
の生成:cgoLDFLAGS
は、ユーザーがCgoビルド時に指定するリンカフラグのリストです。- このループでは、
cgoLDFLAGS
から-l
で始まるライブラリリンクオプション(例:-lm
)と、Darwin(macOS)環境での-framework
オプションをフィルタリングして除外します。 - これは、これらのライブラリが後続の
ld -r
ステップで静的に結合されるため、Goの最終リンカに再度渡す必要がないためです。これにより、Goのリンカが不要なライブラリ解決を試みることを防ぎ、リンキングプロセスを簡素化します。
-
staticLibs
の定義:staticLibs
は、中間リンキングステップで静的に結合されるべきライブラリのリストです。- 常に
"-lgcc"
が含まれます。これは、GCCでコンパイルされたコードがlibgcc
に依存することが多いためです。 goos == "windows"
の場合、"-lmingwex"
と"-lmingw32"
が追加されます。これは、Windows上のMinGW環境でCgoを使用する際に、これらのライブラリがCコードの実行に必要となるためです。
-
中間リンキングステップの実行 (
b.gccld
):ofile := obj + "_all.o"
: GCCでコンパイルされたすべてのオブジェクトファイルと静的ライブラリを結合した結果として生成される、単一の再配置可能オブジェクトファイルの名前を定義します。gccObjs
とnonGccObjs
への分割:outObj
(GCCが生成したすべての出力ファイル)を、.o
で終わる実際のオブジェクトファイル(gccObjs
)と、それ以外のファイル(nonGccObjs
)に分割します。if err := b.gccld(p, ofile, stringList(bareLDFLAGS, "-Wl,-r", "-nostdlib", staticLibs), gccObjs); err != nil { ... }
:- これが新しい中間リンキングステップの核心です。
b.gccld
は、GCCのリンカ(ld
)を呼び出すためのヘルパー関数です。 ofile
(_all.o
) が出力ファイルとして指定されます。stringList(...)
で渡される引数は、ld
に渡されるフラグです。bareLDFLAGS
: フィルタリングされたリンカフラグ。"-Wl,-r"
: リンカオプション-r
をld
に渡すためのGCCのリンカラッパーフラグ。これにより、複数の入力オブジェクトファイルを結合して単一の再配置可能オブジェクトファイルを出力します。"-nostdlib"
: 標準ライブラリを自動的にリンクしないように指示します。これにより、staticLibs
で明示的に指定されたライブラリのみがリンクされるようになります。staticLibs
:libgcc
やMinGW関連のライブラリがここに渡され、_all.o
に静的に組み込まれます。
gccObjs
: GCCでコンパイルされた個々のオブジェクトファイルが入力として渡されます。
- これが新しい中間リンキングステップの核心です。
- このステップにより、GCCが生成したすべてのCオブジェクトファイルと必要な静的ライブラリが、Goのリンカが扱いやすい単一の
_all.o
ファイルにまとめられます。
-
outObj
の再構築:outObj = stringList(importObj, nonGccObjs, ofile)
: Goの最終リンカに渡されるオブジェクトファイルのリストが再構築されます。importObj
: GoのCgoコンパイラが生成したGo側のオブジェクトファイル。nonGccObjs
: GCCが生成した非オブジェクトファイル(例:.s
ファイルなど)。ofile
(_all.o
): 新しく生成された、GCCオブジェクトと静的ライブラリを結合した単一の再配置可能オブジェクトファイル。
- この順序は重要で、特にWindowsでは
importObj
が最初に処理される必要があります(コミットメッセージのコメントにもあるように、http://golang.org/issue/2601
に関連)。
misc/cgo/test/issue3261.go
は、この変更が解決しようとしている問題、特にlibgcc
のリンク問題を検証するためのテストケースです。
- このファイルは、
__absvsi2
というlibgcc
の関数(整数絶対値を計算する内部関数)をCgo経由で呼び出すGoのテスト関数testLibgcc
を含んでいます。 - ARM環境では、
libgcc
がThumbコードでコンパイルされている場合、当時のGoのリンカ5l
がそれを扱えないため、テストが条件付きで無効化されています(#ifdef __ARMEL__
ブロック)。これは、このコミットが解決しきれていない既知の制限事項としてTODO
リストにも記載されています。 - このテストケースが成功することで、新しいビルド手順が
libgcc
のような外部Cライブラリのリンク問題を解決していることが確認できます。
全体として、このコミットは、GoのCgoビルドシステムに、GCCのリンカを中間ステップとして活用する賢明なアプローチを導入し、GoのリンカがC言語のエコシステムとの相互運用性をよりスムーズに行えるようにしています。
関連リンク
- Go Issue #3261: cmd/go: cgo linking fails with libgcc on arm
- Go Issue #1741: cmd/go: cgo linking fails with libmingwex on windows
- Go Change List (CL) 5822049: https://golang.org/cl/5822049
参考にした情報源リンク
- GNU Binutils
ld
man page:ld -r
オプションの動作について。- https://man7.org/linux/man-pages/man1/ld.1.html (またはお使いのOSの
man ld
コマンド)
- https://man7.org/linux/man-pages/man1/ld.1.html (またはお使いのOSの
- GCC Internals - libgcc:
libgcc
の役割と機能について。 - MinGW Project: MinGWと関連ライブラリについて。
- Go Wiki - Cgo: GoのCgo機能に関する公式ドキュメント。