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

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

このコミットは、Go言語のリンカ (cmd/ld) における ldelf.c ファイルの変更に関するものです。具体的には、Linux/ARM環境でのビルド問題を修正するために、ldelf() 関数内にシンボルチェックのガードを追加しています。

コミット

commit e0b0f62d96b37dd78811159d6ddd9819f374d603
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 23 11:36:24 2012 +0800

    cmd/ld: fix Linux/ARM build
            CL 5823055 removed a line introduced in Linux/ARM cgo support.
            Because readsym() now returns nil for "$a", "$d" mapping symbols,
            no matter the settings of `needSym', we still have to guard against
            them in ldelf().
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/6220073

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

https://github.com/golang/go/commit/e0b0f62d96b37dd78811159d6ddd9819f374d603

元コミット内容

このコミットは、Go言語のリンカ (cmd/ld) におけるLinux/ARM環境でのビルド問題を修正するものです。以前の変更である CL 5823055 によって、Linux/ARMのcgoサポートで導入された特定の行が削除されました。その結果、readsym() 関数が "$a" や "$d" といったマッピングシンボルに対して nil を返すようになり、needSym の設定に関わらず、ldelf() 関数内でこれらのシンボルに対するガードが必要になったため、そのガードを追加する変更です。

変更の背景

この変更の背景には、Go言語のリンカがELF(Executable and Linkable Format)形式のバイナリを処理する際の特定の挙動と、ARMアーキテクチャにおけるシンボルの扱いの複雑さがあります。

Go言語のリンカ (cmd/ld) は、Goプログラムをコンパイルして実行可能なバイナリを生成する際に重要な役割を担います。特に、C言語のコードとGo言語のコードを連携させるcgoを使用する場合、リンカは両方の言語のシンボルを適切に解決し、リンクする必要があります。

問題の発端は、以前の変更である CL 5823055 にあります。この変更は「cmd/ld, cmd/6l, cmd/8l: fix hidden/local symbol import for ELF systems」と題されており、ELFシステムにおける隠しシンボルやローカルシンボルのインポートに関する問題を修正することを目的としていました。この CL 5823055 の導入により、Linux/ARM cgoサポートで以前導入されていた特定のコード行が削除されました。

この行の削除が、readsym() 関数の挙動に影響を与えました。readsym() はシンボルテーブルからシンボルを読み取る関数ですが、CL 5823055 以降、ARMアーキテクチャ特有の "$a" や "$d" といったマッピングシンボルに対して nil を返すようになりました。これらのシンボルは、ARMのThumbモードとARMモード間の切り替えや、データとコードの区別など、特定のアーキテクチャ的特性を扱うために使用されることがあります。

readsym()nil を返すようになったにもかかわらず、ldelf() 関数(ELFファイルをロードして解析するリンカの主要な部分)では、これらのシンボルに対する適切なチェックが不足していました。その結果、リンカが予期せぬ nil シンボルを処理しようとして、Linux/ARM環境でのビルドが失敗する問題が発生しました。

このコミットは、このビルド問題を解決するために、ldelf() 関数内に明示的なガード(チェック)を追加し、readsym()nil を返す可能性のある "$a" や "$d" マッピングシンボルを適切にスキップするようにすることで、リンカの堅牢性を向上させています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  • Go言語のリンカ (cmd/ld): Go言語のコンパイラツールチェーンの一部であり、Goのソースコードから実行可能なバイナリを生成する際に、オブジェクトファイルやライブラリを結合(リンク)する役割を担います。cmd/ld は、Goプログラムの実行に必要なすべてのコードとデータを一つのファイルにまとめ上げます。
  • ELF (Executable and Linkable Format): Unix系システム(Linuxを含む)で広く使用されている、実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプの標準ファイル形式です。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして実際のデータ(コード、データ、シンボルテーブルなど)で構成されます。
  • ARMアーキテクチャ: スマートフォン、タブレット、組み込みシステムなどで広く利用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。ARMプロセッサは、その電力効率の高さから、モバイルデバイスで特に普及しています。
  • cgo: Go言語の機能の一つで、C言語のコードをGoプログラムから呼び出したり、GoのコードをC言語から呼び出したりすることを可能にします。cgoを使用すると、既存のCライブラリをGoプロジェクトで再利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。cgoを使用する際には、GoとCのリンケージ規約の違いをリンカが適切に処理する必要があります。
  • シンボル: プログラム内の関数、変数、ラベルなどの名前付きエンティティを指します。リンカはシンボルを使用して、異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
  • シンボルテーブル: ELFファイル内に含まれるデータ構造で、プログラム内のシンボルとそのアドレス、型、サイズなどの情報が格納されています。リンカはシンボルテーブルを参照して、シンボル解決を行います。
  • readsym(): リンカの内部関数で、ELFファイルのシンボルテーブルからシンボル情報を読み取る役割を担います。この関数は、特定のシンボルを検索したり、シンボルテーブル全体を走査したりするために使用されます。
  • ldelf(): リンカの内部関数で、ELFファイルをロードし、その構造を解析する主要な部分です。シンボルテーブルの読み込み、セクションの処理、再配置エントリの適用など、ELFファイルのリンクに必要な多くの処理を行います。
  • "$a" および "$d" マッピングシンボル: ARMアーキテクチャ特有のシンボルで、主にコードとデータの区別、またはARM命令セットとThumb命令セット間の切り替えを示すために使用されます。
    • $a シンボルは、続くコードがARM命令セットで記述されていることを示します。
    • $d シンボルは、続くデータがデータセクションであることを示します。 これらは、リンカがコードとデータを正しく配置し、プロセッサが適切なモードで実行できるようにするために重要です。

技術的詳細

このコミットの技術的詳細は、GoリンカのELF処理におけるシンボル解決のロジックに深く関わっています。

CL 5823055 の変更により、readsym() 関数が特定の条件下で、特にARMアーキテクチャの "$a" や "$d" といったマッピングシンボルに対して nil を返すようになりました。これは、readsym() が「不要なシンボルをスキップする」という内部ロジックを持つためです。しかし、ldelf() 関数内では、readsym()nil を返す可能性があるという前提が十分に考慮されていませんでした。

ldelf() 関数は、ELFファイルのシンボルテーブルを走査し、各シンボルを処理します。通常、readsym() が有効なシンボル情報を返すことを期待して処理を進めます。しかし、readsym()nil を返した場合、ldelf() はその nil シンボルを処理しようとし、これが問題を引き起こしました。

このコミットは、ldelf() 関数内のシンボル処理ループに、以下のガードを追加することでこの問題を解決しています。

		// even when we pass needSym == 1 to readsym, it might still return nil to skip some unwanted symbols
		if(sym.sym == S)
			continue;

ここで、sym.sym == S は、読み取られたシンボルが nil であるか、またはリンカがスキップすべき特定の種類のシンボルである場合に真となります。S は、Goリンカの内部でシンボルが有効でないことを示すために使用される定数であると推測されます。この条件が真の場合、continue ステートメントによって現在のシンボルの処理をスキップし、次のシンボルへとループを進めます。

この変更により、readsym() が "$a" や "$d" のようなマッピングシンボルに対して nil を返した場合でも、ldelf() はそれらを安全に無視し、ビルドプロセスが中断されることなく続行できるようになります。これは、リンカがELFファイルを正しく解析し、ARM環境でのcgoを含むGoプログラムのビルドを成功させるために不可欠な修正です。

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

変更は src/cmd/ld/ldelf.c ファイルにあります。

--- a/src/cmd/ld/ldelf.c
+++ b/src/cmd/ld/ldelf.c
@@ -575,6 +575,9 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)
 		}
 		if(sym.shndx >= obj->nsect || sym.shndx == 0)
 			continue;
+		// even when we pass needSym == 1 to readsym, it might still return nil to skip some unwanted symbols
+		if(sym.sym == S)
+			continue;
 		sect = obj->sect+sym.shndx;
 		if(sect->sym == nil) {
 			diag("%s: sym#%d: ignoring %s in section %d (type %d)", pn, i, sym.name, sym.shndx, sym.type);

コアとなるコードの解説

追加されたコードは以下の3行です。

		// even when we pass needSym == 1 to readsym, it might still return nil to skip some unwanted symbols
		if(sym.sym == S)
			continue;

このコードブロックは、ldelf() 関数内のシンボル処理ループの中に挿入されています。

  1. // even when we pass needSym == 1 to readsym, it might still return nil to skip some unwanted symbols これはコメントであり、このコードが追加された理由を説明しています。readsym() 関数に needSym == 1 を渡した場合でも、readsym() が一部の不要なシンボルをスキップするために nil を返す可能性があることを示しています。これは、readsym() の内部ロジックが、特定のシンボル(この場合はARMの "$a", "$d" マッピングシンボル)を意図的に無視するように設計されていることを示唆しています。
  2. if(sym.sym == S) これは条件文です。sym は現在処理中のシンボルを表す構造体(またはそれに類するもの)であり、sym.sym はそのシンボルの種類や状態を示すフィールドであると推測されます。S は、Goリンカの内部で「スキップすべきシンボル」や「無効なシンボル」を示すために使用される定数であると考えられます。したがって、この条件は「もし現在のシンボルがスキップすべきシンボルであるならば」という意味になります。
  3. continue; これはループ制御文です。if 文の条件が真(つまり、シンボルがスキップすべきものである)の場合、この continue ステートメントが実行され、現在のループの残りの処理をスキップして、次のシンボルへと処理を移します。

この変更により、readsym()nil を返すような特定のARMマッピングシンボルが ldelf() によって安全に無視されるようになり、リンカがこれらのシンボルを不適切に処理しようとしてクラッシュするのを防ぎます。結果として、Linux/ARM環境でのGoプログラムのビルドが正常に完了するようになります。

関連リンク

参考にした情報源リンク