[インデックス 12129] ファイルの概要
このコミットは、Go言語のリンカ(ld
)におけるデータセクションの取り扱いに関する修正です。具体的には、NOPTR
(ポインタを含まない)セクションの初期分類をSNOPTRBSS
(ポインタを含まないBSSセクション)に限定し、その後のdodata
処理で必要に応じてSNOPTRDATA
(ポインタを含まないデータセクション)に変換するように変更しています。これにより、特にARMアーキテクチャでのビルド問題を解決することを目指しています。
コミット
commit 433e47dc030c19402af1f62f78789ff2ccbb66ae
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 21 23:04:38 2012 -0500
ld: only set SNOPTRBSS during load
dodata will convert to SNOPTRDATA if appropriate.
Should fix arm build (hope springs eternal).
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/5687074
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/433e47dc030c19402af1f62f78789ff2ccbb66ae
元コミット内容
リンカがNOPTR
(ポインタを含まない)セクションをロードする際に、常にSNOPTRBSS
として初期設定するように変更します。dodata
関数が、必要に応じてSNOPTRDATA
に変換する役割を担います。この変更は、ARMアーキテクチャでのビルド問題を修正することを目的としています。
変更の背景
このコミットの背景には、Go言語のリンカが特定のデータセクション、特にポインタを含まない(NOPTR
)セクションをどのように扱うかという問題がありました。以前の実装では、NOPTR
セクションが初期段階でSNOPTRDATA
またはSNOPTRBSS
のいずれかに分類されていました。SNOPTRDATA
は初期化されたポインタなしデータ、SNOPTRBSS
は初期化されていないポインタなしデータ(ゼロ初期化される)を指します。
問題は、この初期分類が特定のアーキテクチャ、特にARMにおいて正しく機能しない場合があったことです。リンカがセクションの最終的な性質(初期化されているか否か)を完全に把握する前に、誤った型を割り当ててしまう可能性がありました。これにより、ARMビルドでリンケージエラーや不正な実行時動作が発生していたと考えられます。
このコミットは、初期段階での分類をより安全なSNOPTRBSS
に統一し、その後のリンカの処理フェーズであるdodata
関数に、セクションが実際に初期化されているかどうかを判断させ、必要に応じてSNOPTRDATA
に昇格させるというアプローチを取ることで、この問題を解決しようとしています。これにより、リンカのデータセクション処理の堅牢性が向上し、特にARMのような特定のアーキテクチャでの互換性が改善されることが期待されました。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
-
リンカ (ld): リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するシステムプログラムです。リンカの主な役割は、異なるオブジェクトファイルに分散しているコードやデータを集め、シンボル(変数名や関数名)の参照を解決し、最終的なメモリレイアウトを決定することです。Go言語のツールチェインでは、
5l
(ARM),6l
(x86-64),8l
(x86) など、各アーキテクチャに対応したリンカが存在します。 -
データセクション (Data Sections): 実行可能ファイルは、通常、異なる種類のデータを格納するために複数のセクションに分割されます。主要なセクションには以下のようなものがあります。
.text
: 実行可能な機械語コードが格納されます。.data
: 初期化されたグローバル変数や静的変数が格納されます。プログラムの開始時に値が設定されます。.bss
(Block Started by Symbol): 初期化されていないグローバル変数や静的変数が格納されます。プログラムの開始時にゼロで初期化されることが保証されます。.data
セクションとは異なり、ファイルサイズを削減するために、実行可能ファイル内には実際のデータは含まれず、サイズ情報のみが記録されます。.rodata
(Read-Only Data): 読み取り専用のデータ(文字列リテラル、定数など)が格納されます。
-
Go言語におけるデータセクションの型 (
SNOPTRBSS
,SNOPTRDATA
,SRODATA
など): Go言語のランタイムとリンカは、ガベージコレクション(GC)の効率化のために、メモリ上のデータがポインタを含むかどうかを厳密に区別します。これにより、GCがヒープをスキャンする際に、ポインタではないデータ領域をスキップし、パフォーマンスを向上させることができます。SRODATA
: Read-Only Data (読み取り専用データ)。通常、ポインタを含まない。NOPTR
: No Pointer (ポインタを含まない) の略。Goのリンカが内部的に使用するフラグで、このセクション内のデータがポインタを含まないことを示します。SNOPTRBSS
: Static No Pointer BSS (静的ポインタなしBSS)。初期化されていない、ポインタを含まないデータが格納されるセクション。プログラム開始時にゼロ初期化されます。GCはこれらの領域をスキャンする必要がありません。SNOPTRDATA
: Static No Pointer Data (静的ポインタなしデータ)。初期化された、ポインタを含まないデータが格納されるセクション。GCはこれらの領域をスキャンする必要がありません。SBSS
: Static BSS (静的BSS)。初期化されていない、ポインタを含む可能性のあるデータが格納されるセクション。SDATA
: Static Data (静的データ)。初期化された、ポインタを含む可能性のあるデータが格納されるセクション。
-
dodata
関数: Goリンカの内部関数の一つで、データセクションの最終的な処理と配置を担当します。この関数は、シンボルやセクションの属性を分析し、最終的なメモリレイアウトを決定する過程で、セクションの型を調整する役割を担うことがあります。このコミットでは、NOPTR
セクションの最終的な型(SNOPTRBSS
かSNOPTRDATA
か)をdodata
に委ねることで、より正確な分類を可能にしています。
これらの概念を理解することで、リンカがどのようにメモリを管理し、Goのガベージコレクションと連携しているか、そしてこのコミットがそのプロセスをどのように改善しているかを把握できます。
技術的詳細
このコミットの技術的な核心は、Goリンカがシンボル(変数など)をメモリセクションに割り当てる際のロジックの変更にあります。特に、ポインタを含まないことが保証されているデータ(NOPTR
フラグが設定されているデータ)の初期分類方法が修正されています。
以前のリンカでは、NOPTR
フラグを持つシンボルを処理する際に、そのシンボルが既に何らかのデータを持っている(s->np > 0
、つまりサイズが0より大きい)かどうかをチェックし、それに基づいてSNOPTRDATA
またはSNOPTRBSS
のいずれかに直接分類していました。
s->np > 0
の場合:SNOPTRDATA
(初期化されたポインタなしデータ)s->np == 0
の場合:SNOPTRBSS
(初期化されていないポインタなしBSS)
このロジックは、リンカがシンボルをロードする初期段階で行われていました。しかし、この初期段階では、シンボルが最終的に初期化されるかどうか、あるいはそのデータがどこから来るのか(例えば、別のオブジェクトファイルからの参照など)を完全に把握できていない場合があります。特に、BSSセクションは通常ゼロ初期化されるため、明示的な初期値を持たないことが多いですが、リンカの処理の後半でデータが割り当てられる可能性もゼロではありません。
このコミットでは、この初期分類のロジックを簡素化し、NOPTR
フラグを持つシンボルは常にSNOPTRBSS
として初期設定されるように変更しました。
- else if(p->reg & NOPTR) {
- if(s->np > 0)
- s->type = SNOPTRDATA;
- else
- s->type = SNOPTRBSS;
- }
+ else if(p->reg & NOPTR)
+ s->type = SNOPTRBSS;
この変更のポイントは、SNOPTRDATA
への変換を**dodata
関数に委ねる**という点です。dodata
関数はリンカの後半フェーズで実行され、すべてのオブジェクトファイルがロードされ、シンボル解決がある程度進んだ段階で、データセクションの最終的な配置と属性の決定を行います。この段階では、リンカはシンボルが実際に初期値を持っているかどうか、つまりSNOPTRDATA
として扱うべきかSNOPTRBSS
として扱うべきかをより正確に判断できます。
具体的には、dodata
関数は、SNOPTRBSS
としてマークされたシンボルであっても、実際に初期化データが存在する場合(例えば、別のオブジェクトファイルでそのシンボルが初期化されている場合)には、その型をSNOPTRDATA
に昇格させるロジックを持っています。
このアプローチにより、リンカは初期段階での推測を避け、より情報が揃った段階で正確なセクション分類を行うことができるようになります。これにより、特にARMのような特定のアーキテクチャで発生していた、データセクションの誤った分類に起因するビルド問題やランタイムエラーが解消されることが期待されます。
また、src/cmd/8l/obj.c
の変更では、シンボルの再定義チェックロジックも更新されています。以前はSNOPTRDATA
をチェックしていましたが、この変更によりSNOPTRBSS
もチェック対象に含めることで、新しい分類ロジックと整合性を取っています。
- if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {
+ if(s->type != SBSS && s->type != SNOPTRBSS && !s->dupok) {
そして、8l
(x86リンカ)のADATA
ケースでも、NOPTR
セクションの初期型をSNOPTRBSS
に設定するように統一されています。
- else if(p->from.scale & NOPTR)
- s->type = SNOPTRDATA;
+ else if(p->from.scale & NOPTR)
+ s->type = SNOPTRBSS;
これらの変更は、リンカのデータセクション処理パイプライン全体の一貫性と正確性を向上させるものです。
コアとなるコードの変更箇所
このコミットは、Goリンカの主要な部分であるsrc/cmd/5l/obj.c
(ARM), src/cmd/6l/obj.c
(x86-64), src/cmd/8l/obj.c
(x86) の3つのファイルに影響を与えています。
src/cmd/5l/obj.c
(ARM リンカ)
--- a/src/cmd/5l/obj.c
+++ b/src/cmd/5l/obj.c
@@ -551,12 +551,8 @@ loop:
s->dupok = 1;
if(p->reg & RODATA)
s->type = SRODATA;
- else if(p->reg & NOPTR) {
- if(s->np > 0)
- s->type = SNOPTRDATA;
- else
- s->type = SNOPTRBSS;
- }
+ else if(p->reg & NOPTR)
+ s->type = SNOPTRBSS;
break;
case ADATA:
src/cmd/6l/obj.c
(x86-64 リンカ)
--- a/src/cmd/6l/obj.c
+++ b/src/cmd/6l/obj.c
@@ -567,12 +567,8 @@ loop:
s->dupok = 1;
if(p->from.scale & RODATA)
s->type = SRODATA;
- else if(p->from.scale & NOPTR) {
- if(s->np > 0)
- s->type = SNOPTRDATA;
- else
- s->type = SNOPTRBSS;
- }
+ else if(p->from.scale & NOPTR)
+ s->type = SNOPTRBSS;
goto loop;
case ADATA:
src/cmd/8l/obj.c
(x86 リンカ)
--- a/src/cmd/8l/obj.c
+++ b/src/cmd/8l/obj.c
@@ -563,7 +563,7 @@ loop:
s->type = SBSS;
s->size = 0;
}
- if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {
+ if(s->type != SBSS && s->type != SNOPTRBSS && !s->dupok) {
diag("%s: redefinition: %s in %s",
pn, s->name, TNAME);
s->type = SBSS;
@@ -576,7 +576,7 @@ loop:
if(p->from.scale & RODATA)
s->type = SRODATA;
else if(p->from.scale & NOPTR)
- s->type = SNOPTRDATA;
+ s->type = SNOPTRBSS;
goto loop;
case ADATA:
コアとなるコードの解説
上記の変更箇所は、Goリンカがオブジェクトファイル内のシンボルを処理し、それらを適切なメモリセクションタイプに割り当てるロジックの一部です。
src/cmd/5l/obj.c
および src/cmd/6l/obj.c
の変更
これらのファイルでは、シンボル(s
)のタイプを決定するloop
内のcase ASYMDEF:
または類似のセクションで変更が行われています。
変更前:
else if(p->reg & NOPTR) {
if(s->np > 0)
s->type = SNOPTRDATA;
else
s->type = SNOPTRBSS;
}
このコードは、シンボルがNOPTR
(ポインタを含まない)としてマークされている場合、そのシンボルが既にデータを持っているか(s->np > 0
)をチェックしていました。
- もしデータがあれば、
SNOPTRDATA
(初期化されたポインタなしデータ)として分類。 - データがなければ、
SNOPTRBSS
(初期化されていないポインタなしBSS)として分類。
変更後:
else if(p->reg & NOPTR)
s->type = SNOPTRBSS;
変更後は、NOPTR
としてマークされたシンボルは、常に初期段階でSNOPTRBSS
として分類されるようになりました。s->np > 0
のチェックと、それに基づくSNOPTRDATA
への直接的な分類が削除されています。
これは、シンボルが実際に初期化データを持つかどうかを、リンカのより後のフェーズ(特にdodata
関数)で判断させるという方針転換を示しています。dodata
関数は、すべての入力が処理された後で、シンボルの最終的な状態に基づいてSNOPTRBSS
をSNOPTRDATA
に昇格させる責任を負います。これにより、初期段階での誤った推測を避け、より正確なセクション分類が可能になります。
src/cmd/8l/obj.c
の変更
このファイルでは、2つの異なる箇所で変更が行われています。
-
再定義チェックの修正:
変更前:
if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {
この行は、シンボルが再定義されていないかをチェックするロジックの一部です。以前は、シンボルタイプが
SBSS
でもSNOPTRDATA
でもなく、かつ重複が許可されていない場合に再定義エラーを報告していました。変更後:
if(s->type != SBSS && s->type != SNOPTRBSS && !s->dupok) {
変更後は、
SNOPTRDATA
の代わりにSNOPTRBSS
をチェックするようになりました。これは、上記の変更によってNOPTR
シンボルが初期段階でSNOPTRBSS
として分類されるようになったため、再定義チェックも新しい分類ロジックと整合性を取るように更新されたことを意味します。これにより、SNOPTRBSS
として初期分類されたシンボルが、後でSNOPTRDATA
に昇格する前に誤って再定義と判断されることを防ぎます。 -
ADATA
ケースでの初期分類の統一:変更前:
else if(p->from.scale & NOPTR) s->type = SNOPTRDATA;
このコードは、
ADATA
(データ定義)命令を処理する際に、NOPTR
フラグを持つシンボルを直接SNOPTRDATA
として分類していました。変更後:
else if(p->from.scale & NOPTR) s->type = SNOPTRBSS;
ここでも、
5l
や6l
と同様に、NOPTR
シンボルの初期分類をSNOPTRBSS
に統一しています。これにより、すべてのリンカでNOPTR
シンボルの初期処理が一貫したものとなり、dodata
関数が最終的な型決定を行うという新しい方針が徹底されます。
これらの変更は全体として、Goリンカのデータセクション処理をより堅牢で予測可能なものにし、特に初期化されていないデータと初期化されたデータの区別を、リンカのより後の段階で正確に行うことで、特定のアーキテクチャでの問題を解決することを目的としています。
関連リンク
- Go issue tracker (関連する可能性のあるissue):
- https://github.com/golang/go/issues?q=is%3Aissue+linker+arm+bss+data (一般的なリンカとARM関連のissue検索)
- Goのリンカに関するドキュメント(公式ドキュメントや設計ドキュメントがあれば):
- Goのリンカは非常に低レベルな部分であり、詳細な公開ドキュメントは少ないですが、Goのソースコード自体が最も正確なドキュメントとなります。
参考にした情報源リンク
- Go言語のソースコード:
src/cmd/5l/obj.c
src/cmd/6l/obj.c
src/cmd/8l/obj.c
src/cmd/link/internal/ld/data.go
(dodata関数が定義されている可能性のあるファイル)
- Go言語のリンカに関する議論やメーリングリスト:
- https://groups.google.com/g/golang-dev (Go開発者メーリングリスト)
- https://golang.org/cl/5687074 (このコミットのCode Reviewリンク)
- 一般的なリンカの概念:
- BSSセクション、DATAセクションに関する一般的なコンピュータサイエンスの知識。
- https://en.wikipedia.org/wiki/.bss
- https://en.wikipedia.org/wiki/Data_segment
- Goのガベージコレクションとメモリレイアウト:
- GoのGCがポインタの有無をどのように利用するかに関する情報源。
- https://go.dev/doc/gc-guide (GoのGCガイド)
- https://go.dev/blog/go1.5gc (Go 1.5 GCの解説)
- Goのリンカの内部構造に関するブログ記事や解説:
- Goのリンカの内部構造を詳細に解説している非公式のブログ記事や技術記事も参考になる場合がありますが、公式ドキュメントやソースコードが最も信頼性が高いです。
- (具体的なURLは検索結果によるため、ここでは一般的なカテゴリのみ記載)