[インデックス 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つの問題を解決するために導入されました。
-
到達不可能な動的インポートシンボルがELFシンボルテーブルに誤って出力される問題: Goのリンカ(
cmd/ld
)は、最終的な実行可能ファイルや共有ライブラリを生成する際に、シンボルテーブルを作成します。このシンボルテーブルには、プログラムが使用する関数や変数などの情報が含まれます。動的インポートシンボル(SDYNIMPORT
)は、実行時に外部の共有ライブラリからロードされるシンボルを指します。 問題は、これらの動的インポートシンボルが、実際にはプログラムから到達不可能(つまり、コード内で参照されていない)であるにもかかわらず、ELFシンボルテーブルに誤って含まれてしまうことでした。これは、シンボルテーブルの肥大化や、場合によってはリンカの誤動作を引き起こす可能性がありました。特に、特定のOS(後述のDragonfly BSDなど)では、この問題がビルドエラーに直結していました。 -
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.c
とsrc/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
フラグのチェックを追加したことです。
-
src/cmd/ld/data.c
における変更:dynrelocsym
関数は、動的再配置(dynamic relocation)を処理する際に呼び出されます。動的再配置とは、実行時に外部ライブラリのシンボルアドレスを解決するプロセスです。この関数内で、リロケーションのターゲットとなるシンボル(r->sym
)がSDYNIMPORT
タイプである場合、そのシンボルがreachable
であるかどうかが確認されます。if(!targ->reachable)
およびif(!r->sym->reachable)
のチェックが追加されたことで、リンカは、本来到達不可能であるはずの動的インポートシンボルが、何らかの理由で動的再配置の対象として現れた場合に、それを「内部的な不整合」として診断できるようになりました。これは、リンカの内部状態の健全性を保つための防御的なチェックであり、デバッグや問題特定に役立ちます。 -
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変数の動的インポートが正しく行われ、その値が期待通りに読み取れることを確認することで、リンカの修正が意図した通りに動作していることを保証します。
関連リンク
- Go CL (Code Review) へのリンク: https://golang.org/cl/64340043
- 関連するGo Issue:
- Issue 7318: https://github.com/golang/go/issues/7318
- Issue 7367: https://github.com/golang/go/issues/7367
参考にした情報源リンク
- ELF (Executable and Linkable Format) - Wikipedia: https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
- シンボルテーブル - Wikipedia: https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB
- Thread-local storage - Wikipedia: https://en.wikipedia.org/wiki/Thread-local_storage
- Go cmd/ld source code (Go source code on GitHub): https://github.com/golang/go/tree/master/src/cmd/ld
- Go Cgo documentation: https://go.dev/blog/cgo