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

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

このコミットは、Goリンカ(cmd/ld)におけるELFシンボルテーブルの生成に関する問題を修正し、特に到達不可能な動的インポートシンボルが誤って含まれることを防ぎます。また、Dragonfly BSDでのビルド問題を解決し、Cgoテストケースを追加しています。

変更されたファイルは以下の通りです。

  • misc/cgo/testso/cgoso_c.c: Cgoテスト用のCソースファイル
  • misc/cgo/testso/cgoso_unix.go: Cgoテスト用のGoソースファイル(新規追加)
  • src/cmd/ld/data.c: Goリンカのデータ処理関連のソースファイル
  • src/cmd/ld/symtab.c: Goリンカのシンボルテーブル処理関連のソースファイル

コミット

commit d4a9bbef51d6b631fa799cc5560294f465273f47
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Feb 23 16:20:40 2014 -0500

    cmd/ld: don't emit unreachable dynimport symbols in ELF symtab.
    Fix build for Dragonfly BSD.
    Fixes #7318.
    Fixes #7367.
    
    LGTM=jsing, iant
    R=jsing, iant, mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/64340043

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

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

元コミット内容

cmd/ld: don't emit unreachable dynimport symbols in ELF symtab.
Fix build for Dragonfly BSD.
Fixes #7318.
Fixes #7367.

LGTM=jsing, iant
R=jsing, iant, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/64340043

変更の背景

このコミットは、主に以下の2つの問題を解決するために導入されました。

  1. 到達不可能な動的インポートシンボルがELFシンボルテーブルに誤って出力される問題: Goのリンカ(cmd/ld)は、最終的な実行可能ファイルや共有ライブラリを生成する際に、シンボルテーブルを作成します。このシンボルテーブルには、プログラムが使用する関数や変数などの情報が含まれます。動的インポートシンボル(SDYNIMPORT)は、実行時に外部の共有ライブラリからロードされるシンボルを指します。 問題は、これらの動的インポートシンボルが、実際にはプログラムから到達不可能(つまり、コード内で参照されていない)であるにもかかわらず、ELFシンボルテーブルに誤って含まれてしまうことでした。これは、シンボルテーブルの肥大化や、場合によってはリンカの誤動作を引き起こす可能性がありました。特に、特定のOS(後述のDragonfly BSDなど)では、この問題がビルドエラーに直結していました。

  2. Dragonfly BSDでのビルド問題: 上記の問題が、Dragonfly BSDという特定のオペレーティングシステム環境でGoプログラムをビルドする際に顕在化し、ビルドが失敗する原因となっていました。これは、Dragonfly BSDのリンカやシステムライブラリの挙動が、到達不可能な動的インポートシンボルの存在に対してより厳格であったためと考えられます。

このコミットは、これらの問題を解決し、Goのリンカがより正確なELFシンボルテーブルを生成し、特にDragonfly BSDを含む様々な環境でのビルドの堅牢性を向上させることを目的としています。関連するIssueは #7318 と #7367 です。

前提知識の解説

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

  • ELF (Executable and Linkable Format): Unix系OS(Linux, BSDなど)で実行可能ファイル、オブジェクトファイル、共有ライブラリの標準的なフォーマットです。ELFファイルは、プログラムコード、データ、シンボルテーブル、セクションヘッダなどの情報を含んでいます。

  • シンボルテーブル (Symbol Table): ELFファイルの一部であり、プログラム内で定義または参照されるシンボル(関数名、変数名など)とそのアドレスや型などの情報が格納されています。リンカはシンボルテーブルを使用して、異なるオブジェクトファイル間の参照を解決します。

  • 動的インポートシンボル (Dynamic Import Symbol / SDYNIMPORT): プログラムが実行時に外部の共有ライブラリ(例: .soファイルや.dllファイル)からロードする関数や変数を指します。これらのシンボルは、コンパイル時にはその実体が確定せず、実行時に動的リンカによって解決されます。Goのリンカでは、SDYNIMPORTというタイプでこれらのシンボルを識別します。

  • cmd/ld (Goリンカ): Go言語のビルドツールチェーンの一部であり、Goのソースコードから生成されたオブジェクトファイルや、Cgoを通じてリンクされるC/C++のオブジェクトファイルを結合して、最終的な実行可能ファイルや共有ライブラリを生成する役割を担います。リンカは、シンボル解決、再配置、セクションの結合など、様々な複雑な処理を行います。

  • 到達可能性 (Reachability): プログラム解析における概念で、あるコードパスやデータが、プログラムのエントリポイントから実行可能であるか、または参照可能であるかを示します。リンカの文脈では、シンボルがプログラムの実行フローから実際に使用されるかどうかを指します。到達不可能なシンボルは、通常、最終的なバイナリには含まれるべきではありません。

  • Cgo: GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、GoとCのコードを混在させることができます。このコミットでは、Cgoのテストケースが追加されており、Cgoが生成する共有ライブラリのシンボル解決に関連する問題が背景にあることが示唆されます。

  • TLS (Thread Local Storage): スレッドごとに独立したデータを保持するための記憶域です。各スレッドは、同じグローバル変数名を参照しても、それぞれ異なるTLS領域に格納された自身の値にアクセスします。C言語では__threadキーワードなどで実現されます。このコミットのテストコードで__thread int tlsvarが使用されていることから、CgoとTLSの相互作用が問題の一因であった可能性が考えられます。

技術的詳細

このコミットの核心は、GoリンカがELFシンボルテーブルを生成する際に、SDYNIMPORTタイプのシンボルが「到達可能(reachable)」であるかどうかを厳密にチェックするようになった点です。

以前のリンカの挙動では、SDYNIMPORTタイプのシンボルは、たとえそれがプログラムのどの部分からも参照されていなくても、無条件にELFシンボルテーブルに含められてしまう可能性がありました。これは、リンカがシンボルの到達可能性を判断するロジックが、動的インポートシンボルに対して不完全であったためです。

この修正では、src/cmd/ld/data.csrc/cmd/ld/symtab.cの2つのファイルに、s->reachableというフラグのチェックが追加されました。sはリンカが処理するシンボル(LSym構造体)を表し、reachableフラグはそのシンボルがプログラムの実行フローから到達可能であるかを示します。

具体的には、dynrelocsym関数(動的再配置シンボルを処理する関数)とasmelfsym関数(ELFシンボルテーブルをアセンブルする関数)において、SDYNIMPORTタイプのシンボルを処理する際に、そのシンボルがreachableであるかどうかの確認が追加されました。もしSDYNIMPORTシンボルが到達不可能であるにもかかわらず処理されようとした場合、リンカは内部的な不整合として診断メッセージを出力するようになりました。

この変更により、リンカは不要な動的インポートシンボルをELFシンボルテーブルから除外できるようになり、結果として生成されるバイナリのシンボルテーブルがより正確かつ効率的になります。これは、特にDragonfly BSDのような特定の環境で、リンカが不要なシンボルを検出してエラーを出す問題を解決する上で重要でした。

また、misc/cgo/testso/cgoso_unix.goという新しいテストファイルが追加されました。このテストは、Cgoを使用してCの共有ライブラリからTLS(Thread Local Storage)変数をインポートし、その値が正しく読み取れることを確認します。これは、TLS変数の動的インポートとシンボル解決が正しく機能していることを検証するためのものであり、以前の問題がTLS関連のシンボル解決に起因していた可能性を示唆しています。

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

misc/cgo/testso/cgoso_c.c

 #else
 extern void goCallback(void);
 void setCallback(void *f) { (void)f; }
+__thread int tlsvar = 12345;
 #endif

__thread int tlsvar = 12345; が追加され、Cgoテストで使用されるスレッドローカル変数tlsvarが定義されました。

misc/cgo/testso/cgoso_unix.go (新規ファイル)

// Copyright 2014 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.

// +build darwin dragonfly freebsd linux netbsd

package cgosotest

/*
extern int __thread tlsvar;
int *getTLS() { return &tlsvar; }
*/
import "C"

func init() {
	if v := *C.getTLS(); v != 12345 {
		println("got", v)
		panic("BAD TLS value")
	}
}

このファイルは、Cgoを使用してCの共有ライブラリからtlsvarをインポートし、その値が期待通り12345であることを検証するテストです。+buildタグにより、Unix系OSでのみビルドされます。

src/cmd/ld/data.c

@@ -312,6 +312,8 @@ dynrelocsym(LSym *s)
 		for(r=s->r; r<s->r+s->nr; r++) {
 			targ = r->sym;
+			if(!targ->reachable)
+				diag("internal inconsistency: dynamic symbol %s is not reachable.", targ->name);
 			if(r->sym->plt == -2 && r->sym->got != -2) { // make dynimport JMP table for PE object files.
 				targ->plt = rel->size;
 				r->sym = rel;
@@ -340,8 +342,11 @@ dynrelocsym(LSym *s)
 	}
 
 	for(r=s->r; r<s->r+s->nr; r++) {
-		if(r->sym != S && r->sym->type == SDYNIMPORT || r->type >= 256)
+		if(r->sym != S && r->sym->type == SDYNIMPORT || r->type >= 256) {
+			if(!r->sym->reachable)
+				diag("internal inconsistency: dynamic symbol %s is not reachable.", r->sym->name);
 			adddynrel(s, r);
+		}
 	}
 }

dynrelocsym関数内で、動的インポートシンボル(SDYNIMPORT)を処理する際に、そのシンボルがreachableであるかどうかのチェックが追加されました。到達不可能な場合は診断メッセージが出力されます。

src/cmd/ld/symtab.c

@@ -197,7 +197,7 @@ asmelfsym(void)
 	genasmsym(putelfsym);
 	
 	for(s=ctxt->allsym; s!=S; s=s->allsym) {
-		if(s->type != SHOSTOBJ && s->type != SDYNIMPORT)
+		if(s->type != SHOSTOBJ && !(s->type == SDYNIMPORT && s->reachable))
 			continue;
 		if(s->type == SDYNIMPORT)
 			name = s->extname;

asmelfsym関数内で、ELFシンボルテーブルにシンボルを含める条件が変更されました。以前はSDYNIMPORTタイプのシンボルは無条件で含まれる可能性がありましたが、この変更により、SDYNIMPORTシンボルはreachableである場合にのみ含まれるようになりました。

コアとなるコードの解説

このコミットの主要な変更は、Goリンカがシンボルテーブルを構築する際のロジックにreachableフラグのチェックを追加したことです。

  1. src/cmd/ld/data.cにおける変更: dynrelocsym関数は、動的再配置(dynamic relocation)を処理する際に呼び出されます。動的再配置とは、実行時に外部ライブラリのシンボルアドレスを解決するプロセスです。この関数内で、リロケーションのターゲットとなるシンボル(r->sym)がSDYNIMPORTタイプである場合、そのシンボルがreachableであるかどうかが確認されます。 if(!targ->reachable) および if(!r->sym->reachable) のチェックが追加されたことで、リンカは、本来到達不可能であるはずの動的インポートシンボルが、何らかの理由で動的再配置の対象として現れた場合に、それを「内部的な不整合」として診断できるようになりました。これは、リンカの内部状態の健全性を保つための防御的なチェックであり、デバッグや問題特定に役立ちます。

  2. src/cmd/ld/symtab.cにおける変更: asmelfsym関数は、最終的なELFシンボルテーブルをアセンブルする役割を担います。この関数内のループは、リンカが認識している全てのシンボル(ctxt->allsym)を走査し、ELFシンボルテーブルに含めるべきシンボルを選別します。 変更前の条件 if(s->type != SHOSTOBJ && s->type != SDYNIMPORT) は、「SHOSTOBJタイプでもSDYNIMPORTタイプでもないシンボルはスキップする」という意味でした。つまり、SDYNIMPORTシンボルは常に処理対象となっていました。 変更後の条件 if(s->type != SHOSTOBJ && !(s->type == SDYNIMPORT && s->reachable)) は、「SHOSTOBJタイプではない、かつ (SDYNIMPORTタイプでかつreachableである) という条件がであるシンボルはスキップする」という意味になります。 これを言い換えると、ELFシンボルテーブルに含められるのは、以下のいずれかの条件を満たすシンボルです。

    • SHOSTOBJタイプではない、かつ、SDYNIMPORTタイプではないシンボル。
    • SDYNIMPORTタイプであり、かつ reachableであるシンボル。 この変更により、SDYNIMPORTタイプのシンボルであっても、それがプログラムから到達不可能であれば、ELFシンボルテーブルには含まれなくなりました。これにより、不要なシンボルがバイナリに埋め込まれることを防ぎ、特にDragonfly BSDのような環境でのビルドエラーを解消しました。

misc/cgo/testso/cgoso_unix.goの追加は、この修正がCgoとTLSの組み合わせで正しく機能することを検証するためのものです。TLS変数の動的インポートが正しく行われ、その値が期待通りに読み取れることを確認することで、リンカの修正が意図した通りに動作していることを保証します。

関連リンク

参考にした情報源リンク