[インデックス 18851] ファイルの概要
このコミットは、Go言語のコンパイラツールチェーンの一つであるgccgoにおいて、リンク時に誤ったライブラリが使用される問題を修正するものです。具体的には、src/cmd/go/build.go
ファイル内のgccgoToolchain
のld
(リンカ)処理に関する変更が含まれています。
コミット
commit 12474d01c4c68f3ca4a7cbc5a3d20419cbccaba8
Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
Date: Wed Mar 12 23:05:54 2014 -0400
cmd/go: use correct libraries during gccgo link
Under some circumstances, gccgoToolchain's ld can pass the path of
build outputs that have been deleted to the link command.
Fixes #7303.
LGTM=rsc
R=golang-codereviews, dave, michael.hudson, rsc
CC=golang-codereviews
https://golang.org/cl/61970044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/12474d01c4c68f3ca4a7cbc5a3d20419cbccaba8
元コミット内容
cmd/go: use correct libraries during gccgo link
このコミットは、gccgoリンカが、ビルド出力のうち既に削除されたファイルのパスをリンクコマンドに渡してしまうことがあるという問題に対処しています。
変更の背景
Go言語のビルドシステムにおいて、gccgo
ツールチェーンを使用する際に、リンカが正しくないライブラリパスを参照してしまうという問題が発生していました。具体的には、ビルドプロセス中に生成され、その後削除される一時的なビルド成果物のパスが、リンカに渡されてしまう状況がありました。これにより、リンクエラーが発生したり、予期せぬ動作を引き起こす可能性がありました。
この問題は、特にinstall
アクションとbuild
アクションの順序に関連していました。install
アクションはビルド成果物を所定の場所に配置した後、元の一時ファイルを削除することがあります。しかし、リンカがこれらの削除されたファイルのパスをまだ参照していると、問題が生じます。
このコミットは、この問題を解決し、gccgo
ツールチェーンが常に正しい、存在するビルド成果物を参照するようにすることで、ビルドの安定性と信頼性を向上させることを目的としています。
前提知識の解説
- Go言語のビルドシステム (
cmd/go
):go build
やgo install
などのコマンドを提供するGo言語の公式ツールです。ソースコードのコンパイル、リンク、パッケージ管理など、Goプロジェクトのビルドプロセス全体を管理します。 - gccgo: Go言語のフロントエンドを持つGCC(GNU Compiler Collection)ベースのコンパイラです。標準のGoコンパイラ(gc)とは異なり、GCCの最適化やバックエンドを利用できる点が特徴です。
- リンカ (
ld
): コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成するツールです。リンカは、プログラムが必要とするすべてのコードとデータを正しいアドレスに配置し、参照を解決します。 - ビルド成果物 (build outputs): コンパイルやリンクの過程で生成される中間ファイルや最終的な実行可能ファイル、ライブラリなどを指します。
Package
(Go言語のパッケージ): Go言語におけるコードの組織化の単位です。関連するGoソースファイルがまとめられ、インポートによって他のパッケージから利用されます。action
(ビルドアクション): Goのビルドシステム内部で、特定のパッケージに対するコンパイル、リンク、インストールなどの操作を表す概念です。allactions
: ビルドプロセス全体で実行されるすべてのアクションのリストです。これには、依存関係にあるパッケージのビルドアクションも含まれます。a.target
: 各action
が生成する最終的な出力ファイルのパスです。a.p
: 各action
が関連付けられているPackage
オブジェクトです。a.p.Standard
: そのパッケージがGoの標準ライブラリの一部であるかどうかを示すフラグです。afiles
: リンカに渡されるアーカイブファイル(ライブラリ)のパスのリストです。apackagesSeen
: 既に処理されたパッケージを追跡するためのマップです。これにより、同じパッケージが複数回リンカに渡されるのを防ぎます。
技術的詳細
このコミットの核心は、gccgoToolchain
のld
関数における、リンカに渡すアーカイブファイル(.a
ファイル)の収集ロジックの変更です。
以前の実装では、allactions
リストを単純にイテレートし、a.target
をafiles
に追加していました。しかし、この方法では、install
アクションがbuild
アクションの出力ファイルを削除する前に、その削除されるファイルのパスがafiles
に追加されてしまう可能性がありました。
新しいロジックでは、以下の点が改善されています。
apackagesSeen
の導入:afilesSeen
からapackagesSeen
に名称が変更され、より明確にパッケージ単位での追跡を行うようになりました。allactions
の逆順イテレーション:allactions
リストを末尾から先頭に向かって(逆順に)イテレートするように変更されました。これは非常に重要な変更点です。- Goのビルドシステムでは、依存関係の解決とアクションのスケジューリングが行われます。通常、
install
アクションは、その元となるbuild
アクションよりも後に実行されるか、あるいはbuild
アクションの成果物を利用して最終的な配置を行うため、allactions
リスト内ではinstall
アクションがbuild
アクションよりも「後方」に位置する傾向があります(依存関係の解決順序による)。 - リストを逆順にイテレートすることで、
install
アクションがbuild
アクションよりも先に処理される可能性が高まります。これにより、install
アクションが生成する最終的なa.target
(インストール先のファイル)が、build
アクションが生成する一時的なa.target
(削除される可能性のあるファイル)よりも優先的にafiles
に追加されるようになります。
- Goのビルドシステムでは、依存関係の解決とアクションのスケジューリングが行われます。通常、
- 条件付きの
a.target
追加:!a.p.Standard
:標準ライブラリではないパッケージのみを対象とします。!apackagesSeen[a.p]
:そのパッケージがまだ処理されていない場合にのみ、a.target
をafiles
に追加します。これにより、各パッケージの最終的なビルド成果物(通常はinstall
アクションの出力)が一度だけリンカに渡されることが保証されます。
この変更により、リンカは常に存在する正しいビルド成果物のパスを受け取るようになり、削除されたファイルのパスを参照することによるリンクエラーが回避されます。
コアとなるコードの変更箇所
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1843,7 +1843,7 @@ func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles\
func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
// gccgo needs explicit linking with all package dependencies,
// and all LDFLAGS from cgo dependencies.
- afilesSeen := make(map[*Package]bool)
+ apackagesSeen := make(map[*Package]bool)
afiles := []string{}
sfiles := []string{}
ldflags := b.gccArchArgs()
@@ -1851,14 +1851,23 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
usesCgo := false
cxx := false
objc := false
++
+ // Prefer the output of an install action to the output of a build action,
+ // because the install action will delete the output of the build action.
+ // Iterate over the list backward (reverse dependency order) so that we
+ // always see the install before the build.
+ for i := len(allactions) - 1; i >= 0; i-- {
+ a := allactions[i]
+ if !a.p.Standard {
+ if a.p != nil && !apackagesSeen[a.p] {
+ apackagesSeen[a.p] = true
+ afiles = append(afiles, a.target)
+ }
+ }
+ }
+
for _, a := range allactions {
if a.p != nil {
- if !a.p.Standard {
- if !afilesSeen[a.p] || a.objpkg != a.target {
- afilesSeen[a.p] = true
- afiles = append(afiles, a.target)
- }
- }
cgoldflags = append(cgoldflags, a.p.CgoLDFLAGS...)
if len(a.p.CgoFiles) > 0 {
usesCgo = true
コアとなるコードの解説
変更の主要な部分は、func (tools gccgoToolchain) ld(...)
関数内にあります。
-
afilesSeen
からapackagesSeen
への変更:afilesSeen := make(map[*Package]bool)
がapackagesSeen := make(map[*Package]bool)
に変更されました。これは、マップの目的が、ファイルではなくパッケージの処理状況を追跡することであることをより明確に示しています。 -
allactions
の逆順イテレーションの追加:// Prefer the output of an install action to the output of a build action, // because the install action will delete the output of the build action. // Iterate over the list backward (reverse dependency order) so that we // always see the install before the build. for i := len(allactions) - 1; i >= 0; i-- { a := allactions[i] if !a.p.Standard { if a.p != nil && !apackagesSeen[a.p] { apackagesSeen[a.p] = true afiles = append(afiles, a.target) } } }
この新しいループが追加されました。
for i := len(allactions) - 1; i >= 0; i--
:allactions
スライスを逆順にループします。これにより、install
アクション(通常はリストの後方にある)が、それに対応するbuild
アクション(通常はリストの前方にある)よりも先に処理される可能性が高まります。if !a.p.Standard
:標準ライブラリのパッケージは対象外とします。if a.p != nil && !apackagesSeen[a.p]
:アクションが有効なパッケージに関連付けられており、かつそのパッケージがまだ処理されていない場合にのみ、以下の処理を行います。apackagesSeen[a.p] = true
:このパッケージが処理済みであることをマークします。afiles = append(afiles, a.target)
:このアクションのターゲットファイル(ビルド成果物)を、リンカに渡すファイルリストafiles
に追加します。
-
元のファイル収集ロジックの削除:
- for _, a := range allactions { - if a.p != nil { - if !a.p.Standard { - if !afilesSeen[a.p] || a.objpkg != a.target { - afilesSeen[a.p] = true - afiles = append(afiles, a.target) - } - }
以前の
afiles
収集のためのループが削除されました。これにより、新しい逆順イテレーションのロジックが唯一のファイル収集メカニズムとなります。
この変更により、gccgo
リンカは、ビルド成果物が一時的に存在し、その後削除されるようなシナリオにおいても、常に正しい最終的なライブラリパスを参照するようになります。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- GCCGoに関する情報: https://gcc.gnu.org/onlinedocs/gccgo/
- GoのIssue Tracker: https://github.com/golang/go/issues (ただし、このコミットが修正したIssue #7303は、検索結果で示された別のIssue #7303とは異なる文脈のようです。)
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージにある
https://golang.org/cl/61970044
はこのGerritの変更リストへのリンクです。)
参考にした情報源リンク
- Go言語のソースコード (
src/cmd/go/build.go
) - コミットメッセージと差分情報
- Go言語のビルドシステムに関する一般的な知識
- GCCおよびリンカの動作に関する一般的な知識