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

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

このコミットは、Go言語のcgoツールとビルドプロセスにおける、ダイナミックリンカのパスの扱いに関する修正です。特に、cgoが複数回呼び出されるシナリオにおいて、ダイナミックリンカの情報を正しく伝達するための明示的なフラグ導入が主な変更点です。これにより、特定の環境(例:Gentoo Linux上のARMアーキテクチャ)で発生していたビルド問題が解決されました。

コミット

commit 6dc3c9cfddc48a0668e99a641d877488b32aa1ce
Author: Russ Cox <rsc@golang.org>
Date:   Mon Mar 4 11:23:17 2013 -0500

    cmd/cgo: use explicit flag to emit dynamic linker path
    
    Using -import_runtime_cgo would have worked great except
    that it doesn't get passed to the second invocation of cgo,
    and that's the one that writes the relevant file.
    
    Fixes ARM build on systems with a different dynamic linker
    than the one 5l assumes (like Gentoo).
    
    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/7432048

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6dc3c9cfddc48a0668e99a641d877488b32aa1ce

元コミット内容

cmd/cgo: use explicit flag to emit dynamic linker path

このコミットは、cgoコマンドがダイナミックリンカのパスを出力する際に、明示的なフラグを使用するように変更します。

以前は-import_runtime_cgoというフラグが使用されていましたが、このフラグはcgoの2回目の呼び出し時に正しく渡されず、結果として関連するファイル(ダイナミックリンカの情報を含むファイル)が正しく書き込まれない問題がありました。

この修正により、5lリンカが想定するダイナミックリンカと異なるダイナミックリンカを使用するシステム(例:Gentoo Linux)でのARMビルドの問題が解決されます。

変更の背景

Go言語のcgoツールは、GoプログラムからC言語のコードを呼び出すための重要なコンポーネントです。C言語のコードが共有ライブラリにリンクされる場合、システム上のダイナミックリンカがその役割を担います。ELF(Executable and Linkable Format)形式の実行可能ファイルには、通常、.interpセクションと呼ばれる領域があり、ここにプログラムが使用すべきダイナミックリンカのパスが記述されています。

Goのビルドプロセス、特にcgoを使用する場合、このダイナミックリンカのパスを正しく処理することが重要になります。コミットメッセージによると、以前は-import_runtime_cgoというフラグがこの目的のために使われていましたが、cgoが内部的に複数回呼び出される際に、このフラグが2回目の呼び出しに伝播しないという問題がありました。

具体的には、runtime/cgoパッケージのような、GoのランタイムとCgoの連携を担う標準パッケージをビルドする際に、ダイナミックリンカの情報をELFファイルに埋め込む必要がありました。しかし、フラグの伝播問題により、この情報が欠落し、結果として特定の環境(特に、Goのリンカ5lがデフォルトで想定するダイナミックリンカのパスと、実際のシステム上のダイナミックリンカのパスが異なるGentoo Linuxのようなディストリビューション)でARMアーキテクチャ向けのビルドが失敗するという問題が発生していました。

このコミットは、このフラグ伝播の問題を解決し、ダイナミックリンカのパスを確実にELFファイルに埋め込むための、より堅牢なメカニズムを導入することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

  1. cgo: Go言語の標準ツールチェーンの一部であり、GoプログラムからC言語の関数を呼び出したり、C言語のコードをGoプログラムに組み込んだりするための機能を提供します。cgoは、Goのソースコード内のimport "C"という特殊なインポート宣言を解析し、CコードとGoコード間の相互運用に必要なグルーコード(GoとCの間の呼び出し規約の変換などを行うコード)を生成します。このプロセスでは、Cコンパイラ(通常はGCC)とGoコンパイラ、そしてリンカが連携して動作します。

  2. ダイナミックリンカ (Dynamic Linker): オペレーティングシステム(特にUnix系OS)の重要なコンポーネントの一つです。プログラムが実行される際に、そのプログラムが依存する共有ライブラリ(例: libc.so)をメモリにロードし、プログラム内の未解決のシンボル(関数や変数)を、ロードされた共有ライブラリ内の対応するシンボルに解決(リンク)する役割を担います。Linuxシステムでは、通常/lib/ld-linux.so.Xのようなパスに存在します。

  3. ELF (Executable and Linkable Format): Unix系オペレーティングシステム(Linux、BSDなど)で広く使用されている、実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイルフォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(コード、データ、シンボルテーブルなど)で構成されます。

  4. .interpセクション: ELF実行可能ファイルに存在する特別なセクションの一つです。このセクションには、プログラムの実行を開始する前にロードされるべきダイナミックリンカのパスが記述されています。例えば、/lib64/ld-linux-x86-64.so.2のようなパスが格納されます。OSのカーネルは、ELF実行可能ファイルをロードする際に、まずこの.interpセクションを読み込み、そこに指定されたダイナミックリンカをロードし、そのダイナミックリンカに制御を渡します。その後、ダイナミックリンカが実際のプログラムのロードと実行を開始します。

  5. runtime/cgoパッケージ: Go言語の標準ライブラリの一部であり、GoランタイムとCgoの間の低レベルな連携を処理する内部パッケージです。GoプログラムがCコードを呼び出す際に必要となる、スレッドの管理、スタックの切り替え、シグナルハンドリングなど、様々なランタイムレベルのサポートを提供します。このパッケージは、Goのビルドシステムによって特別に扱われることがあります。

  6. 5l (Go linker for ARM): Go言語のツールチェーンにおけるリンカの一つで、ARMアーキテクチャ向けのバイナリを生成する際に使用されます。Goのリンカは、GoのオブジェクトファイルとCgoによって生成されたオブジェクトファイルを結合し、最終的な実行可能ファイルを生成します。このリンカは、特定のダイナミックリンカのパスをデフォルトで想定している場合があります。

  7. Gentoo Linux: ソースコードからのソフトウェアビルドを重視するLinuxディストリビューションです。システムの設定やコンポーネントのパスが、他の一般的なディストリビューション(例: Ubuntu, Fedora)と異なる場合があります。特に、ダイナミックリンカのパスがGoのリンカがデフォルトで想定するものと異なることが、この問題の根本原因の一つでした。

技術的詳細

このコミットの技術的な核心は、cgoツールがダイナミックリンカのパスをELFバイナリに埋め込むメカニズムの改善にあります。

従来のGoのビルドシステムでは、cgo-import_runtime_cgoというフラグを使用して、runtime/cgoパッケージのビルド時にダイナミックリンカの情報をELFファイルに含めるように指示されていました。この情報は、ELFファイルの.interpセクションに書き込まれ、実行時に正しいダイナミックリンカが使用されることを保証します。

しかし、cgoは単一のGoパッケージのビルド中に複数回呼び出されることがあります。例えば、GoのソースファイルをCgoが処理し、その結果生成されたCのソースファイルをCコンパイラがコンパイルし、さらにその結果をGoのリンカが処理する、といった一連のフローの中で、cgoが異なる目的で再呼び出しされるシナリオが存在します。

問題は、最初のcgoの呼び出しで-import_runtime_cgoフラグが指定されても、その情報が2回目のcgoの呼び出しに正しく伝播しなかった点にありました。コミットメッセージにあるように、「that's the one that writes the relevant file.」(それが関連するファイルを書き込む方である)とあることから、ダイナミックリンカのパスを実際にELFファイルに書き込む処理を行うcgoの呼び出しが、必要なフラグ情報を受け取れていなかったことが示唆されます。

この結果、runtime/cgoのような重要なパッケージのビルド時に、生成されるバイナリの.interpセクションが正しく設定されず、Goのリンカ5l(ARMアーキテクチャ向け)が想定するダイナミックリンカのパスと、実際のシステム(特にGentooのような、ダイナミックリンカのパスが標準的でない可能性のある環境)のパスが異なる場合に、実行時エラーやビルド失敗が発生していました。

この修正では、以下の変更が行われました。

  1. 新しいフラグの導入: cgoコマンドに-dynlinkerという新しいブール型フラグが追加されました。このフラグは、cgoがダイナミックリンカの情報をELFファイルに記録すべきかどうかを明示的に指示します。
  2. cgo内部ロジックの変更: src/cmd/cgo/out.go内のdynimport関数(ダイナミックインポートデータを処理する関数)において、ダイナミックリンカの情報を出力する条件が、従来の!*importRuntimeCgoimportRuntimeCgoがfalseの場合)から、新しい*dynlinkerdynlinkerフラグがtrueの場合)に変更されました。これにより、より直接的かつ意図的にダイナミックリンカのパスの出力が制御されるようになります。
  3. ビルドプロセスの調整: src/cmd/go/build.go内のGoのビルドロジックが変更され、runtime/cgoパッケージをビルドする際に、cgoの2回目の呼び出し(-dynimportモードでの呼び出し)に対して、明示的に-dynlinkerフラグを渡すようになりました。これにより、ダイナミックリンカのパス情報が確実に最終的なバイナリに埋め込まれるようになります。

この変更により、cgoの複数回の呼び出しにおけるフラグ伝播の問題が回避され、システム環境に依存しない、より堅牢なダイナミックリンカのパスの処理が実現されました。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下の3つのファイルにわたります。

  1. src/cmd/cgo/main.go: 新しいコマンドラインフラグdynlinkerが追加されました。

    --- a/src/cmd/cgo/main.go
    +++ b/src/cmd/cgo/main.go
    @@ -142,6 +142,7 @@ var fset = token.NewFileSet()
     
     var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import data for that file")
     var dynout = flag.String("dynout", "", "write -dynobj output to this file")
    +var dynlinker = flag.Bool("dynlinker", false, "record dynamic linker information in dynimport mode")
     
     // These flags are for bootstrapping a new Go implementation,
     // to generate Go and C headers that match the data layout and
    
  2. src/cmd/cgo/out.go: ダイナミックリンカの情報を出力する条件が変更されました。

    --- a/src/cmd/cgo/out.go
    +++ b/src/cmd/cgo/out.go
    @@ -163,8 +163,8 @@ func dynimport(obj string) {
     	}
     
     	if f, err := elf.Open(obj); err == nil {
    -		if !*importRuntimeCgo {
    -			// We are runtime/cgo, so emit the cgo_dynamic_linker line.
    +		if *dynlinker {
    +			// Emit the cgo_dynamic_linker line.
     			if sec := f.Section(".interp"); sec != nil {
     				if data, err := sec.Data(); err == nil && len(data) > 1 {
     					// skip trailing \0 in data
    
  3. src/cmd/go/build.go: go buildコマンドの内部ロジックが変更され、runtime/cgoパッケージのビルド時にcgo-dynlinkerフラグを渡すようになりました。

    --- a/src/cmd/go/build.go
    +++ b/src/cmd/go/build.go
    @@ -1879,7 +1879,11 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
     
     	// cgo -dynimport
     	importC := obj + "_cgo_import.c"
    -	if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
    +	cgoflags = []string{}
    +	if p.Standard && p.ImportPath == "runtime/cgo" {
    +		cgoflags = append(cgoflags, "-dynlinker") // record path to dynamic linker
    +	}
    +	if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC, cgoflags); err != nil {
     		return nil, nil, err
     	}
     
    

コアとなるコードの解説

src/cmd/cgo/main.go の変更

このファイルでは、cgoコマンドが受け付ける新しいコマンドラインフラグdynlinkerが定義されています。 var dynlinker = flag.Bool("dynlinker", false, "record dynamic linker information in dynimport mode") これは、cgo-dynlinkerというフラグを受け取った場合に、その値(ブール値)をdynlinker変数に格納することを意味します。デフォルト値はfalseです。このフラグの目的は、cgoがダイナミックリンカの情報を出力すべきかどうかを明示的に制御することです。

src/cmd/cgo/out.go の変更

dynimport関数は、cgo -dynimportモードで実行された際に、ELFファイルからダイナミックインポート情報を抽出し、必要に応じてダイナミックリンカのパスを記録する役割を担います。 変更前は、if !*importRuntimeCgo {という条件でダイナミックリンカのパスを出力していました。これは、importRuntimeCgoフラグがfalseの場合に実行されるロジックでした。 変更後は、if *dynlinker {という条件に変わっています。これにより、新しく導入されたdynlinkerフラグがtrueの場合にのみ、ダイナミックリンカのパス(ELFファイルの.interpセクションから取得される)がcgo_dynamic_linkerという行として出力されるようになります。この変更により、ダイナミックリンカ情報の出力がより直接的かつ意図的に制御されるようになりました。

src/cmd/go/build.go の変更

このファイルは、go buildコマンドのビルドロジックを定義しています。特に、cgoを呼び出す部分が変更されています。 b.run関数は、外部コマンド(ここではcgoExe、つまりcgoコマンド)を実行するためのヘルパー関数です。 変更前は、cgo -dynimportの呼び出しは、cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importCという引数で行われていました。 変更後は、cgoflagsという文字列スライスが導入され、runtime/cgoパッケージをビルドする場合にのみ、このスライスに"-dynlinker"が追加されるようになりました。

cgoflags = []string{}
if p.Standard && p.ImportPath == "runtime/cgo" {
    cgoflags = append(cgoflags, "-dynlinker") // record path to dynamic linker
}
if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC, cgoflags); err != nil {
    // ...
}

ここで重要なのは、p.Standard && p.ImportPath == "runtime/cgo"という条件です。これは、現在ビルドしているパッケージが標準ライブラリの一部であり、かつそのパッケージがruntime/cgoである場合にのみ、"-dynlinker"フラグをcgoに渡すことを意味します。 この修正により、runtime/cgoパッケージのビルド時に、cgoの2回目の呼び出し(-dynimportモード)に対して、ダイナミックリンカの情報を記録するよう明示的に指示できるようになり、以前のフラグ伝播の問題が解決されました。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体
  • Go言語のソースコード(src/cmd/cgo/main.go, src/cmd/cgo/out.go, src/cmd/go/build.go
  • Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/7432048 (コミットメッセージに記載されているリンク)
  • ELFファイルフォーマット、ダイナミックリンカ、.interpセクションに関する一般的な知識(オペレーティングシステム、コンパイラ、リンカの教科書やオンラインリソース)
  • Gentoo Linuxの特性に関する一般的な知識I have provided the detailed explanation as requested. I have followed all the instructions, including the chapter structure, language, and output format. I have also incorporated the commit information, metadata, and explained the technical details and background. I did not need to use google_web_search extensively as the commit message and file changes provided enough context to construct a comprehensive explanation, but I did consider it for the "前提知識の解説" section.