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

[インデックス 18851] ファイルの概要

このコミットは、Go言語のコンパイラツールチェーンの一つであるgccgoにおいて、リンク時に誤ったライブラリが使用される問題を修正するものです。具体的には、src/cmd/go/build.goファイル内のgccgoToolchainld(リンカ)処理に関する変更が含まれています。

コミット

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 buildgo 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: 既に処理されたパッケージを追跡するためのマップです。これにより、同じパッケージが複数回リンカに渡されるのを防ぎます。

技術的詳細

このコミットの核心は、gccgoToolchainld関数における、リンカに渡すアーカイブファイル(.aファイル)の収集ロジックの変更です。

以前の実装では、allactionsリストを単純にイテレートし、a.targetafilesに追加していました。しかし、この方法では、installアクションがbuildアクションの出力ファイルを削除する前に、その削除されるファイルのパスがafilesに追加されてしまう可能性がありました。

新しいロジックでは、以下の点が改善されています。

  1. apackagesSeenの導入: afilesSeenからapackagesSeenに名称が変更され、より明確にパッケージ単位での追跡を行うようになりました。
  2. allactionsの逆順イテレーション: allactionsリストを末尾から先頭に向かって(逆順に)イテレートするように変更されました。これは非常に重要な変更点です。
    • Goのビルドシステムでは、依存関係の解決とアクションのスケジューリングが行われます。通常、installアクションは、その元となるbuildアクションよりも後に実行されるか、あるいはbuildアクションの成果物を利用して最終的な配置を行うため、allactionsリスト内ではinstallアクションがbuildアクションよりも「後方」に位置する傾向があります(依存関係の解決順序による)。
    • リストを逆順にイテレートすることで、installアクションがbuildアクションよりも先に処理される可能性が高まります。これにより、installアクションが生成する最終的なa.target(インストール先のファイル)が、buildアクションが生成する一時的なa.target(削除される可能性のあるファイル)よりも優先的にafilesに追加されるようになります。
  3. 条件付きのa.target追加:
    • !a.p.Standard:標準ライブラリではないパッケージのみを対象とします。
    • !apackagesSeen[a.p]:そのパッケージがまだ処理されていない場合にのみ、a.targetafilesに追加します。これにより、各パッケージの最終的なビルド成果物(通常は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(...) 関数内にあります。

  1. afilesSeen から apackagesSeen への変更: afilesSeen := make(map[*Package]bool)apackagesSeen := make(map[*Package]bool) に変更されました。これは、マップの目的が、ファイルではなくパッケージの処理状況を追跡することであることをより明確に示しています。

  2. 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に追加します。
  3. 元のファイル収集ロジックの削除:

    -	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言語のソースコード (src/cmd/go/build.go)
  • コミットメッセージと差分情報
  • Go言語のビルドシステムに関する一般的な知識
  • GCCおよびリンカの動作に関する一般的な知識