[インデックス 18385] ファイルの概要
このコミットは、Go言語のリンカ(cmd/ld
)におけるバグ修正に関するものです。具体的には、外部リンクモード(external link mode
)で"runtime/cgo"
という文字列定数が誤って扱われる問題に対処しています。この問題は、コンパイラとリンカが文字列データの配置に関して異なる仮定を持っていたために発生していました。
コミット
commit 1683dab725f66320d3d9d500b33233a11930f596
Author: Ian Lance Taylor <iant@golang.org>
Date: Thu Jan 30 09:25:47 2014 -0800
cmd/ld: fix bug with "runtime/cgo" in external link mode
In external link mode the linker explicitly adds the string
constant "runtime/cgo". It adds the string constant using the
same symbol name as the compiler, but a different format. The
compiler assumes that the string data immediately follows the
string header, but the linker puts the two in different
sections. The result is bad string data when the compiler
sees "runtime/cgo" used as a string constant.
The compiler assumption is in datastring in [568]g/gobj.c.
The linker layout is in addstrdata in ld/data.c. The compiler
assumption is valid for string literals. The linker is not
creating a string literal, so its assumption is also valid.
There are a few ways to avoid this problem. This patch fixes
it by only doing the fake import of runtime/cgo if necessary,
and by only creating the string symbol if necessary.
Fixes #7234.
LGTM=dvyukov
R=golang-codereviews, dvyukov, bradfitz
CC=golang-codereviews
https://golang.org/cl/58410043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1683dab725f66320d3d9d500b33233a11930f596
元コミット内容
Go言語のリンカ(cmd/ld
)において、外部リンクモードでビルドする際に発生する"runtime/cgo"
文字列定数の誤った処理を修正します。
外部リンクモードでは、リンカは明示的に"runtime/cgo"
という文字列定数を追加します。この際、リンカはコンパイラと同じシンボル名を使用しますが、そのフォーマットが異なります。コンパイラは文字列データが文字列ヘッダの直後に続くと仮定しますが、リンカはこれらを異なるセクションに配置します。この不一致により、コンパイラが"runtime/cgo"
を文字列定数として参照した際に、不正な文字列データが読み込まれるという問題が発生していました。
コンパイラの仮定は[568]g/gobj.c
のdatastring
関数にあり、リンカのレイアウトはld/data.c
のaddstrdata
関数にあります。コンパイラの仮定は文字列リテラルに対しては有効であり、リンカは文字列リテラルを作成しているわけではないため、リンカの仮定もまた有効です。
この問題にはいくつかの回避策がありますが、このパッチでは、runtime/cgo
の擬似インポートが必要な場合にのみ行い、文字列シンボルが必要な場合にのみ作成するようにすることで修正しています。
この修正は、Issue #7234を解決します。
変更の背景
GoプログラムがCGO(C言語との相互運用機能)を使用する場合、または外部リンカ(gcc
など)を使用してビルドされる場合、Goランタイムは特定の初期化処理を行う必要があります。この初期化の一部として、Goリンカは"runtime/cgo"
という文字列定数を生成し、プログラムに含めることがあります。これは、ランタイムがCGO関連の初期化を行うべきかどうかを判断するための内部的なメカニズムとして機能します。
問題は、GoコンパイラとGoリンカが、メモリ上での文字列定数の表現方法について異なる仮定を持っていたことに起因します。
- コンパイラの仮定: コンパイラは、文字列の「ヘッダ」(文字列の長さやデータへのポインタを含む構造体)の直後に、実際の文字列データが連続して配置されることを期待します。これは、Goの内部的な文字列リテラルの表現方法に合致しています。
- リンカの動作: 外部リンクモードでは、Goリンカは
"runtime/cgo"
という文字列を、コンパイラが生成する通常の文字列リテラルとは異なる方法で、つまり文字列ヘッダと文字列データを異なるメモリセクションに配置して生成していました。
この不一致により、コンパイラが生成したコードがリンカによって配置された"runtime/cgo"
文字列定数を読み込もうとすると、文字列ヘッダが指すアドレスが実際の文字列データの開始位置と異なり、結果として不正なデータが読み込まれてしまうというバグが発生していました。
このバグは、特に外部リンカを使用するビルド環境で顕在化し、GoプログラムのCGO関連機能の動作に影響を与える可能性がありました。Issue #7234は、この具体的な問題点を報告しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムとメモリ管理に関する基本的な知識が必要です。
-
Goのビルドプロセス:
- Goのソースコードは、まずGoコンパイラ(
cmd/compile
)によってオブジェクトファイルにコンパイルされます。 - 次に、これらのオブジェクトファイルはGoリンカ(
cmd/ld
)によって結合され、実行可能ファイルが生成されます。 - Goは独自のコンパイラとリンカを持っていますが、CGOを使用する場合や特定のビルド設定では、システムにインストールされている外部リンカ(例:
gcc
)を使用することがあります。これを**外部リンクモード(external link mode
)**と呼びます。
- Goのソースコードは、まずGoコンパイラ(
-
文字列定数とメモリ表現:
- Goにおいて、文字列は不変なバイトシーケンスです。内部的には、文字列は「文字列ヘッダ」と「文字列データ」の2つの部分で構成されます。
- 文字列ヘッダ: 文字列の長さと、実際の文字列データが格納されているメモリ上のアドレス(ポインタ)を含む構造体です。
- 文字列データ: 文字列を構成する実際のバイト列です。
- 通常、Goコンパイラが生成する文字列リテラル(例:
"hello"
)の場合、文字列ヘッダと文字列データはメモリ上で連続して配置されることが期待されます。これにより、ヘッダのポインタからオフセットを計算することで、データに効率的にアクセスできます。
-
シンボルとセクション:
- シンボル: プログラム内の関数、変数、定数などの名前付きエンティティを識別するためのものです。リンカはシンボル名を使って、異なるオブジェクトファイルに分散しているコードやデータを結合します。
- セクション: 実行可能ファイル内のメモリ領域の論理的な区分です。例えば、
.text
セクションには実行可能なコードが、.data
セクションには初期化されたデータが、.rodata
セクションには読み取り専用データ(文字列定数など)が格納されます。リンカは、これらのセクションにシンボルを配置します。
-
runtime/cgo
パッケージ:- Goの標準ライブラリの一部であり、C言語のコードをGoプログラムから呼び出すための機能を提供します。CGOを使用するプログラムでは、このパッケージがインポートされ、GoランタイムがCGO関連の初期化を行うためのフックを提供します。
-
Issue #7234:
- GoプロジェクトのIssueトラッカーで報告された特定のバグを指します。このコミットは、このIssueで報告された問題の解決を目的としています。
技術的詳細
このバグの核心は、GoコンパイラとGoリンカが文字列定数のメモリレイアウトに関して異なる、しかしそれぞれが「正しい」と見なす仮定を持っていた点にあります。
-
コンパイラの
datastring
関数([568]g/gobj.c
):- Goコンパイラは、Goソースコード内の文字列リテラルを処理する際に、文字列ヘッダとそれに続く文字列データをメモリ上に連続して配置することを前提としています。これは、Goの内部的な文字列表現の最適化であり、文字列ヘッダのポインタから直接オフセットを計算することで、文字列データにアクセスできるような設計になっています。
- この
datastring
関数は、このような連続したメモリレイアウトを生成することを担当しています。
-
リンカの
addstrdata
関数(ld/data.c
):- Goリンカは、特に外部リンクモードにおいて、特定の内部的な目的のために
"runtime/cgo"
のような文字列定数を生成して実行可能ファイルに含めることがあります。 addstrdata
関数は、リンカが文字列データを追加する際に使用されます。しかし、この関数はコンパイラが生成するような厳密な「文字列リテラル」の形式ではなく、文字列ヘッダと文字列データを異なるメモリセクションに配置する可能性のある、より一般的なデータ追加メカニズムとして機能していました。例えば、文字列ヘッダは.data
セクションに、実際の文字列データは.rodata
セクションに配置されるといった状況が考えられます。
- Goリンカは、特に外部リンクモードにおいて、特定の内部的な目的のために
問題の発生メカニズム:
- 外部リンクモードでビルドされるGoプログラムにおいて、Goリンカは
"runtime/cgo"
という文字列定数をプログラムに含める必要がありました。 - リンカは、この文字列を
addstrdata
関数を使って追加しました。この際、リンカはコンパイラが使用するシンボル名(例:go.string."runtime/cgo"
)と同じものを使用しました。 - しかし、リンカは文字列ヘッダと文字列データを異なるメモリセクションに配置しました。
- Goコンパイラによって生成されたコード(またはGoランタイムのコード)が、このリンカによって追加された
"runtime/cgo"
文字列定数を参照しようとしました。 - コンパイラ側のコードは、
datastring
関数が生成するような、文字列ヘッダの直後に文字列データが続くという仮定に基づいていました。 - 結果として、文字列ヘッダのポインタが指すアドレスは、リンカが配置した実際の文字列データの開始位置とは異なり、コンパイラ側のコードは誤ったメモリ領域を読み込み、不正な文字列データ(またはクラッシュ)を引き起こしました。
修正アプローチ:
このコミットは、リンカが"runtime/cgo"
文字列定数を追加する際のロジックを変更することで、この問題を解決しています。具体的には、以下の2つの条件を導入しています。
runtime/cgo
の擬似インポートが必要な場合のみ処理を行う: リンカがruntime/cgo
パッケージの擬似インポート(loadinternal("runtime/cgo")
)を行うのは、外部リンクモードであり、かつまだruntime/cgo
がインポートされていない場合に限定されます。これにより、不要な処理を削減します。- 文字列シンボルが必要な場合のみ作成する:
go.string."runtime/cgo"
という文字列シンボルをaddstrdata
関数で追加する前に、そのシンボルが既に存在しないかを確認します(linkrlookup(ctxt, cgostrsym, 0) == nil
)。これにより、リンカが既に存在する文字列シンボルを上書きしたり、重複して作成したりするのを防ぎます。
この変更により、リンカは"runtime/cgo"
文字列定数を、コンパイラの仮定と矛盾しない方法で、かつ必要な場合にのみ生成するようになります。これにより、文字列データの破損が回避され、外部リンクモードでのCGOプログラムの安定性が向上します。
コアとなるコードの変更箇所
このコミットでは、主にsrc/cmd/ld/lib.c
ファイルが変更されています。また、このバグを再現し、修正を検証するための新しいテストファイルmisc/cgo/test/issue7234_test.go
が追加されています。
src/cmd/ld/lib.c
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -164,6 +164,7 @@ loadlib(void)
{
int i, w, x;
LSym *s, *gmsym;
+ char* cgostrsym;
if(flag_shared) {
s = linklookup(ctxt, "runtime.islibrary", 0);
@@ -176,7 +177,15 @@ loadlib(void)
loadinternal("math");
if(flag_race)
loadinternal("runtime/race");
- if(linkmode == LinkExternal) {
+
+ for(i=0; i<ctxt->libraryp; i++) {
+ if(debug['v'] > 1)
+ Bprint(&bso, "%5.2f autolib: %s (from %s)\n", cputime(), ctxt->library[i].file, ctxt->library[i].objref);
+ iscgo |= strcmp(ctxt->library[i].pkg, "runtime/cgo") == 0;
+ objfile(ctxt->library[i].file, ctxt->library[i].pkg);
+ }
+
+ if(linkmode == LinkExternal && !iscgo) {
// This indicates a user requested -linkmode=external.
// The startup code uses an import of runtime/cgo to decide
// whether to initialize the TLS. So give it one. This could
@@ -184,7 +193,6 @@ loadlib(void)
loadinternal("runtime/cgo");
// Pretend that we really imported the package.
- // This will do no harm if we did in fact import it.
s = linklookup(ctxt, "go.importpath.runtime/cgo.", 0);
s->type = SDATA;
s->dupok = 1;
@@ -192,16 +200,11 @@ loadlib(void)
// Provided by the code that imports the package.
// Since we are simulating the import, we have to provide this string.
- addstrdata("go.string.\"runtime/cgo\"", "runtime/cgo");
+ cgostrsym = "go.string.\"runtime/cgo\"";
+ if(linkrlookup(ctxt, cgostrsym, 0) == nil)
+ addstrdata(cgostrsym, "runtime/cgo");
}
-- for(i=0; i<ctxt->libraryp; i++) {
-- if(debug['v'] > 1)
-- Bprint(&bso, "%5.2f autolib: %s (from %s)\n", cputime(), ctxt->library[i].file, ctxt->library[i].objref);
-- iscgo |= strcmp(ctxt->library[i].pkg, "runtime/cgo") == 0;
-- objfile(ctxt->library[i].file, ctxt->library[i].pkg);
-- }
--
if(linkmode == LinkAuto) {
if(iscgo && externalobj)
linkmode = LinkExternal;
misc/cgo/test/issue7234_test.go
--- /dev/null
+++ b/misc/cgo/test/issue7234_test.go
@@ -0,0 +1,21 @@
+// 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.
+
+package cgotest
+
+import "testing"
+
+// This test actually doesn't have anything to do with cgo. It is a
+// test of http://golang.org/issue/7234, a compiler/linker bug in
+// handling string constants when using -linkmode=external. The test
+// is in this directory because we routinely test -linkmode=external
+// here.
+
+var v7234 = [...]string{"runtime/cgo"}
+
+func TestIssue7234(t *testing.T) {
+ if v7234[0] != "runtime/cgo" {
+ t.Errorf("bad string constant %q", v7234[0])
+ }
+}
コアとなるコードの解説
src/cmd/ld/lib.c
の変更点
-
iscgo
変数の初期化とfor
ループの移動:- 変更前は、
loadlib
関数の後半でctxt->libraryp
をループしてiscgo
フラグ(runtime/cgo
がインポートされているかを示す)を設定し、objfile
を呼び出していました。 - 変更後は、このループが
loadlib
関数のより早い段階、特にlinkmode == LinkExternal
のチェックの直前に移動されました。これにより、iscgo
フラグがLinkExternal
モードのロジックに入る前に正しく設定されるようになります。 - この移動は、リンカが
runtime/cgo
の擬似インポートを行うべきかどうかを判断する際に、既にruntime/cgo
がインポートされているかどうかを正確に把握するために重要です。
- 変更前は、
-
linkmode == LinkExternal
条件の変更:- 変更前:
if(linkmode == LinkExternal)
- 変更後:
if(linkmode == LinkExternal && !iscgo)
- この変更により、リンカが
runtime/cgo
の擬似インポート(loadinternal("runtime/cgo")
)を行うのは、外部リンクモードであり、かつまだruntime/cgo
がインポートされていない場合のみに限定されます。これにより、不要な擬似インポートやそれに伴う文字列定数の生成が回避されます。
- 変更前:
-
go.string."runtime/cgo"
シンボル生成の条件付き化:- 変更前:
addstrdata("go.string.\"runtime/cgo\"", "runtime/cgo");
- 変更後:
cgostrsym = "go.string.\"runtime/cgo\""; if(linkrlookup(ctxt, cgostrsym, 0) == nil) addstrdata(cgostrsym, "runtime/cgo");
- この変更がバグ修正の核心です。リンカが
"runtime/cgo"
という文字列定数に対応するシンボル(go.string."runtime/cgo"
) を追加する前に、linkrlookup
関数を使ってそのシンボルが既に存在しないかを確認するようになりました。 linkrlookup
は、指定されたシンボル名を持つシンボルをリンカのシンボルテーブルから検索します。linkrlookup
がnil
を返す(つまり、シンボルが存在しない)場合にのみ、addstrdata
関数が呼び出され、新しい文字列シンボルが追加されます。- これにより、リンカが既に存在する文字列シンボルを誤って上書きしたり、重複して作成したりするのを防ぎます。この「必要な場合にのみ作成する」というアプローチが、コンパイラとリンカの仮定の不一致による文字列データ破損の問題を解決します。
- 変更前:
misc/cgo/test/issue7234_test.go
の追加
- このファイルは、Issue #7234で報告されたバグを再現し、修正が正しく機能するかを検証するための新しいテストケースです。
- テストはCGOとは直接関係ありませんが、外部リンクモードでビルドされるGoプログラムにおける文字列定数の扱いをテストするために、CGOテストディレクトリに配置されています。
var v7234 = [...]string{"runtime/cgo"}
という配列を定義し、その最初の要素が期待通り"runtime/cgo"
であるかを検証しています。- もしバグが修正されていなければ、
v7234[0]
は不正な文字列データを含み、テストは失敗するはずです。このテストが成功することで、リンカが"runtime/cgo"
文字列定数を正しく処理していることが確認できます。
これらの変更により、Goリンカは外部リンクモードで"runtime/cgo"
文字列定数を扱う際に、コンパイラの期待するメモリレイアウトと矛盾しないように、かつ冗長な処理を避けるように動作するようになります。
関連リンク
- Go Issue #7234: https://golang.org/issue/7234
- Go CL 58410043: https://golang.org/cl/58410043
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/ld/
とsrc/cmd/compile/
ディレクトリ) - Go言語のIssueトラッカー
- Go言語のドキュメント(リンカ、CGOに関する部分)
- Go言語のコンパイラとリンカの内部構造に関する一般的な情報(Goのビルドプロセス、シンボル、セクションなど)