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

[インデックス 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のような特定のアーキテクチャでの互換性が改善されることが期待されました。

前提知識の解説

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

  1. リンカ (ld): リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するシステムプログラムです。リンカの主な役割は、異なるオブジェクトファイルに分散しているコードやデータを集め、シンボル(変数名や関数名)の参照を解決し、最終的なメモリレイアウトを決定することです。Go言語のツールチェインでは、5l (ARM), 6l (x86-64), 8l (x86) など、各アーキテクチャに対応したリンカが存在します。

  2. データセクション (Data Sections): 実行可能ファイルは、通常、異なる種類のデータを格納するために複数のセクションに分割されます。主要なセクションには以下のようなものがあります。

    • .text: 実行可能な機械語コードが格納されます。
    • .data: 初期化されたグローバル変数や静的変数が格納されます。プログラムの開始時に値が設定されます。
    • .bss (Block Started by Symbol): 初期化されていないグローバル変数や静的変数が格納されます。プログラムの開始時にゼロで初期化されることが保証されます。.dataセクションとは異なり、ファイルサイズを削減するために、実行可能ファイル内には実際のデータは含まれず、サイズ情報のみが記録されます。
    • .rodata (Read-Only Data): 読み取り専用のデータ(文字列リテラル、定数など)が格納されます。
  3. 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 (静的データ)。初期化された、ポインタを含む可能性のあるデータが格納されるセクション。
  4. dodata関数: Goリンカの内部関数の一つで、データセクションの最終的な処理と配置を担当します。この関数は、シンボルやセクションの属性を分析し、最終的なメモリレイアウトを決定する過程で、セクションの型を調整する役割を担うことがあります。このコミットでは、NOPTRセクションの最終的な型(SNOPTRBSSSNOPTRDATAか)を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関数は、すべての入力が処理された後で、シンボルの最終的な状態に基づいてSNOPTRBSSSNOPTRDATAに昇格させる責任を負います。これにより、初期段階での誤った推測を避け、より正確なセクション分類が可能になります。

src/cmd/8l/obj.c の変更

このファイルでは、2つの異なる箇所で変更が行われています。

  1. 再定義チェックの修正:

    変更前:

    	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に昇格する前に誤って再定義と判断されることを防ぎます。

  2. ADATAケースでの初期分類の統一:

    変更前:

    	else if(p->from.scale & NOPTR)
    		s->type = SNOPTRDATA;
    

    このコードは、ADATA(データ定義)命令を処理する際に、NOPTRフラグを持つシンボルを直接SNOPTRDATAとして分類していました。

    変更後:

    	else if(p->from.scale & NOPTR)
    		s->type = SNOPTRBSS;
    

    ここでも、5l6lと同様に、NOPTRシンボルの初期分類をSNOPTRBSSに統一しています。これにより、すべてのリンカでNOPTRシンボルの初期処理が一貫したものとなり、dodata関数が最終的な型決定を行うという新しい方針が徹底されます。

これらの変更は全体として、Goリンカのデータセクション処理をより堅牢で予測可能なものにし、特に初期化されていないデータと初期化されたデータの区別を、リンカのより後の段階で正確に行うことで、特定のアーキテクチャでの問題を解決することを目的としています。

関連リンク

  • Go issue tracker (関連する可能性のある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言語のリンカに関する議論やメーリングリスト:
  • 一般的なリンカの概念:
  • Goのガベージコレクションとメモリレイアウト:
  • Goのリンカの内部構造に関するブログ記事や解説:
    • Goのリンカの内部構造を詳細に解説している非公式のブログ記事や技術記事も参考になる場合がありますが、公式ドキュメントやソースコードが最も信頼性が高いです。
    • (具体的なURLは検索結果によるため、ここでは一般的なカテゴリのみ記載)