[インデックス 15652] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld) における linux/386 アーキテクチャでの外部リンキングに関する問題を修正するものです。特に、ガベージコレクション (GC) メタデータが型情報へのポインタを指す際に使用される「PC相対」リロケーションの取り扱いを改善し、外部リンカがこれらのリロケーションを不適切に処理する可能性を排除することを目的としています。
コミット
cmd/ld: linux/386 における外部リンキングの修正
このコミットは、linux/386 環境での外部リンキングにおける課題、特にGCメタデータが型情報を参照する際のPC相対リロケーションの扱いに焦点を当てています。リンカがコードセグメント外のデータポインタをPC相対で解釈する際に、外部リンカがPLT (Procedure Linkage Table) のような特殊な処理を適用してしまう危険性を回避するため、以下の2段階のアプローチで問題を解決しています。
- セクション内のPC相対リロケーションの内部処理: 外部リンカがこれらのリロケーションを見る前に、リンカ自身がセクション内のPC相対リロケーションを処理するように変更します。
gcdataおよびgcbssテーブルのrodataセクションへの移動: GCメタデータと型情報が同じrodataセクションに配置されるようにすることで、リロケーションを内部で処理しやすくします。
これにより、共有オブジェクトファイル生成時にGCから型への参照が個別にリロケートされる必要がなくなります。
GitHub上でのコミットページへのリンク
https://github.com/golang.org/cl/7629043
元コミット内容
commit cd94cabad6091af76c2d4950f11442bdb40b7b46
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 8 20:22:38 2013 -0800
cmd/ld: external linking fixes for linux/386
The sticking point on 386 has been the "PC relative" relocations
used to point the garbage collection metadata at the type info.
These aren't in the code segment, and I don't trust that the linker
isn't doing something special that would be okay in code but
not when interpreting the pointers as data (for example, a PLT
jump table would be terrible).
Solve the problem in two steps:
1. Handle "PC relative" relocations within a section internally,
so that the external linker never sees them.
2. Move the gcdata and gcbss tables into the rodata section,
where the type information lives, so that the relocations can
be handled internally.
(To answer the obvious question, we make the gc->type
references relative so that they need not be relocated
individually when generating a shared object file.)
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7629043
変更の背景
この変更の背景には、Go言語のランタイムがガベージコレクション (GC) を効率的に行うために必要とするメタデータ (gcdata および gcbss) と、プログラムの型情報との間の参照の解決に関する問題がありました。特に linux/386 アーキテクチャにおいて、これらの参照が「PC相対」リロケーションとして表現される際に、外部リンカが誤った解釈をする可能性があったことが問題でした。
PC相対リロケーションは、命令ポインタ (Program Counter, PC) を基準とした相対アドレスでオフセットを指定する方式です。これは通常、コードセグメント内のジャンプ命令やデータ参照でよく用いられます。しかし、GoのGCメタデータはコードではなくデータとして扱われるため、GCメタデータ内のポインタが型情報を指す際にPC相対リロケーションが使用されると、外部リンカがこれをコード内の参照と同様に扱ってしまう危険性がありました。
具体的には、外部リンカがPC相対リロケーションを解決する際に、Procedure Linkage Table (PLT) のようなメカニズムを介して間接的なジャンプを挿入する可能性があります。PLTは動的リンクされたライブラリの関数呼び出しを解決するために使用されますが、データポインタの解決には不適切です。もしGCメタデータ内のデータポインタがPLTを介して解決されるようなことがあれば、プログラムの実行時動作が壊れるか、GCが正しく機能しなくなる可能性がありました。
この問題を解決するため、GoリンカはGCメタデータと型情報間のPC相対リロケーションを外部リンカに依存せず、内部で完全に解決するアプローチを採用しました。これにより、外部リンカの挙動に左右されることなく、Goランタイムの正確性と安定性を確保することが目的とされました。
前提知識の解説
Go言語のリンカ (cmd/ld)
cmd/ld はGo言語のツールチェインに含まれるリンカです。Goのソースコードはコンパイラによってオブジェクトファイルに変換された後、このリンカによって実行可能ファイルや共有ライブラリにリンクされます。Goのリンカは、C/C++のリンカとは異なり、Goランタイムの特殊な要件(例: ガベージコレクション、goroutineスケジューリングなど)に対応するために、多くのGo固有の処理を行います。
外部リンキング
Goのビルドプロセスでは、デフォルトでGoリンカが全てを処理する「内部リンキング」が用いられます。しかし、Cgoを使用する場合や、特定のシステムライブラリにリンクする必要がある場合などには、システムにインストールされている標準のリンカ(例: GNU ld や clang)を使用する「外部リンキング」が行われます。このコミットは、この外部リンキングのシナリオにおける問題に対処しています。
linux/386 アーキテクチャ
linux/386 は、32ビットのx86アーキテクチャ(Intel 80386以降のプロセッサ)上で動作するLinuxシステムを指します。32ビットアーキテクチャでは、メモリのアドレス空間が4GBに制限され、ポインタのサイズも32ビット(4バイト)です。このアーキテクチャでは、PC相対アドレッシングやリロケーションのメカニズムが特定の挙動を示すことがあります。
PC相対リロケーション
PC相対リロケーション (PC-relative relocation) は、プログラムカウンタ (PC) の現在の値からの相対的なオフセットとしてアドレスを指定するリロケーションの一種です。これは、コードがメモリ上のどこにロードされても正しく動作するように、位置独立コード (Position-Independent Code, PIC) を生成する際によく使用されます。例えば、CALL 命令やJMP 命令のターゲットアドレス、あるいはデータへの参照がPC相対で指定されることがあります。
ガベージコレクション (GC) メタデータ
Goのガベージコレクタは、ヒープ上のオブジェクトがどこから参照されているかを正確に追跡するために、プログラムの型情報やポインタの配置に関するメタデータに依存しています。このメタデータは、コンパイル時に生成され、実行可能ファイル内の特定のセクションに格納されます。
gcdata: 静的に割り当てられたデータセグメント (.dataセクション) 内のポインタに関するGCメタデータ。gcbss: 静的に割り当てられたゼロ初期化データセグメント (.bssセクション) 内のポインタに関するGCメタデータ。
これらのメタデータは、GCがヒープスキャンを行う際に、どのメモリ領域にポインタが含まれているかを識別するために使用されます。
rodata セクション
rodata (Read-Only Data) セクションは、実行可能ファイル内で読み取り専用のデータを格納するためのセクションです。これには、文字列リテラル、定数、そしてGoの場合、型情報などが含まれます。rodata セクションは通常、コードセグメントとは異なるメモリ領域に配置され、実行中に変更されることはありません。
PLT (Procedure Linkage Table)
PLT (Procedure Linkage Table) は、動的リンクされた共有ライブラリ内の関数を呼び出す際に使用されるメカニズムです。プログラムが共有ライブラリの関数を呼び出すと、直接その関数のアドレスにジャンプするのではなく、まずPLT内のエントリにジャンプします。PLTのエントリは、必要に応じて遅延バインディングメカニズム (GOT: Global Offset Table) を使用して実際の関数のアドレスを解決し、そのアドレスにジャンプします。これは、共有ライブラリの関数がロード時にどこに配置されるか分からない場合でも、プログラムが正しく動作するようにするために重要です。しかし、データポインタの解決には不適切です。
共有オブジェクトファイル (Shared Object, SO)
共有オブジェクトファイル(Linuxでは .so 拡張子、Windowsでは .dll、macOSでは .dylib)は、複数のプログラムで共有されることを意図したライブラリです。これらのファイルは、プログラムの実行時にメモリにロードされ、複数のプロセス間でコードやデータを共有することで、メモリ使用量を削減し、ディスクスペースを節約します。共有オブジェクトファイル内のコードは通常、位置独立コード (PIC) としてコンパイルされ、メモリ上の任意のアドレスにロードされても正しく動作するように設計されています。
技術的詳細
このコミットの技術的解決策は、linux/386 環境における外部リンキング時のPC相対リロケーションの課題を、Goリンカの内部処理を強化することで克服することにあります。
-
セクション内のPC相対リロケーションの内部処理:
- Goリンカは、
relocsym関数内でリロケーションを処理する際に、D_PCRELタイプのリロケーションが同じセクション内のシンボルを参照している場合、それを外部リンカに渡さずに内部で解決するように変更されました。 - これにより、外部リンカがPC相対リロケーションを誤ってPLTのようなコード関連のメカニズムで解決してしまうリスクがなくなります。データポインタはデータとして正しく扱われます。
elf.cのelfrelocsect関数でも、D_PCRELタイプのリロケーションで、かつ参照先シンボルが同じセクションにある場合は、リロケーションテーブルにエントリを追加しないように変更されています。これは、Goリンカが既に内部で解決済みであることを外部リンカに伝えるためです。
- Goリンカは、
-
gcdataおよびgcbssテーブルのrodataセクションへの移動:- 以前は、
gcdataとgcbssはそれぞれ独立したセクション (.gcdata,.gcbss) として扱われていました。 - この変更により、
dodata関数内でこれらのセクションが明示的に作成されなくなり、代わりにrodataセクションの一部として扱われるようになりました。 - 具体的には、
gcdataとgcbssのシンボルタイプがSGCDATAおよびSGCBSSからSTYPEに変更され、rodataセクションに配置される型情報 (typelink) と同じセクションに統合されました。 - これにより、GCメタデータと型情報がメモリ上で近接して配置されるため、GCメタデータから型情報へのPC相対参照が、同じ
rodataセクション内での相対オフセットとして解決できるようになります。これは、外部リンカに依存せず、Goリンカが内部でこれらの参照を効率的に解決するための重要な前提となります。 address関数におけるセクションの順序も変更され、gcdataとgcbssがtypelinkの後に続く独立したセクションとしてではなく、typelinkと同じrodataセクションの一部として扱われるようになりました。
- 以前は、
これらの変更により、GoリンカはGCメタデータと型情報間のPC相対参照を、外部リンカの介入なしに、より安全かつ効率的に処理できるようになります。特に共有オブジェクトファイルを生成する際に、これらの参照が個別にリロケートされる必要がなくなるため、リンキングプロセスが簡素化され、潜在的なバグが回避されます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、Goリンカのデータ処理 (data.c)、ELFファイル生成 (elf.c)、ライブラリ関数 (lib.c, lib.h)、およびシンボルテーブル処理 (symtab.c) に集中しています。
-
src/cmd/ld/data.c:relocsym関数: PC相対リロケーションの処理ロジックが変更され、r->sym->sect != cursym->sect(参照先シンボルが現在のシンボルと同じセクションにない場合) にのみ外部リンカに処理を委ねるようになりました。これにより、同じセクション内のPC相対リロケーションは内部で処理されます。dodata関数:gcdata1とgcbss1のシンボル名がgcdataとgcbssに変更され、タイプがSGCDATA/SGCBSSからSTYPEに変更されました。.gcdataおよび.gcbssセクションを明示的に追加するコードブロックが削除されました。これにより、これらのデータはrodataセクションの一部として扱われるようになります。
address関数: セクションのポインタ変数からgcdataとgcbssが削除され、セクションの連鎖順序も変更されました。egcdataとegcbssの定義方法も変更され、STYPEタイプとしてシンボルアドレスとサイズに基づいて定義されるようになりました。
-
src/cmd/ld/elf.c:elfrelocsect関数:D_PCRELタイプのリロケーションで、かつ参照先シンボルが現在のシンボルと同じセクション (r->sym->sect == sym->sect) にある場合は、リロケーションテーブルにエントリを追加しないように変更されました。これはdata.cのrelocsymで内部処理されるためです。
-
src/cmd/ld/lib.c:genasmsym関数:SGCDATAとSGCBSSのシンボルタイプが、アセンブリシンボル生成の対象から除外されました。これは、これらのシンボルがもはや独立したセクションとして扱われないためです。
-
src/cmd/ld/lib.h:enum定義からSGCDATAとSGCBSSのシンボルタイプが削除されました。
-
src/cmd/ld/symtab.c:symtab関数:gcdataとgcbssのxdefine呼び出しが削除され、egcdataとegcbssの定義がSTYPEタイプとして変更されました。
コアとなるコードの解説
src/cmd/ld/data.c の変更
// relocsym 関数内
- if(isobj && r->sym->type != SCONST) {
+ if(isobj && r->sym->type != SCONST && r->sym->sect != cursym->sect) {
if(thechar == '6')
o = 0;
else
この変更は、relocsym 関数におけるリロケーションオフセット o の計算ロジックを修正しています。以前は、isobj (オブジェクトファイルの場合) かつ r->sym->type != SCONST (シンボルが定数でない場合) であれば、特定の処理を行っていました。変更後は、さらに r->sym->sect != cursym->sect という条件が追加されています。これは、リロケーションの対象となるシンボルが、現在処理しているシンボルと同じセクションにない場合にのみ、この特殊なオフセット計算(外部リンカに委ねる可能性のある処理)を行うことを意味します。つまり、同じセクション内のPC相対リロケーションは、Goリンカが内部で処理するようになりました。
// dodata 関数内
- gcdata1 = lookup("gcdata1", 0);
- gcdata1->type = SGCDATA;
+ gcdata1 = lookup("gcdata", 0);
+ gcdata1->type = STYPE;
gcdata1->reachable = 1;
- gcbss1 = lookup("gcbss1", 0);
- gcbss1->type = SGCBSS;
+ gcbss1 = lookup("gcbss", 0);
+ gcbss1->type = STYPE;
gcbss1->reachable = 1;
gcdata1 と gcbss1 のシンボル名が gcdata と gcbss に変更され、そのタイプが SGCDATA および SGCBSS から STYPE に変更されました。STYPE は型情報に関連するシンボルタイプであり、これにより gcdata と gcbss が型情報と同じ rodata セクションに配置される準備が整います。
// dodata 関数内 (削除された部分)
- /* gcdata */
- sect = addsection(&segtext, ".gcdata", 04);
- ... (gcdata セクションの定義とシンボルの配置) ...
-
- /* gcbss */
- sect = addsection(&segtext, ".gcbss", 04);
- ... (gcbss セクションの定義とシンボルの配置) ...
.gcdata および .gcbss という名前のセクションを明示的に追加し、そこにシンボルを配置するコードブロックが完全に削除されました。これは、これらのデータがもはや独立したセクションとして扱われず、rodata セクションの一部として統合されることを示しています。
// address 関数内
- Section *gcdata, *gcbss, *typelink;
+ Section *typelink;
...
- gcdata = typelink->next;
- gcbss = gcdata->next;
- symtab = gcbss->next;
+ symtab = typelink->next;
...
- xdefine("gcdata", SGCDATA, gcdata->vaddr);
- xdefine("egcdata", SGCDATA, gcdata->vaddr + gcdata->len);
- xdefine("gcbss", SGCBSS, gcbss->vaddr);
- xdefine("egcbss", SGCBSS, gcbss->vaddr + gcbss->len);
+ sym = lookup("gcdata", 0);
+ xdefine("egcdata", STYPE, symaddr(sym) + sym->size);
+ lookup("egcdata", 0)->sect = sym->sect;
+
+ sym = lookup("gcbss", 0);
+ xdefine("egcbss", STYPE, symaddr(sym) + sym->size);
+ lookup("egcbss", 0)->sect = sym->sect;
address 関数では、セクションのポインタ変数から gcdata と gcbss が削除され、セクションの連鎖順序も変更されました。これにより、symtab が typelink の直後に続くことになります。また、gcdata と gcbss の開始アドレスと終了アドレスを定義する xdefine 呼び出しが変更されました。新しいコードでは、lookup でシンボルを取得し、そのアドレスとサイズに基づいて egcdata と egcbss を STYPE タイプとして定義しています。これは、これらのシンボルが独立したセクションではなく、既存のシンボルとして扱われることを反映しています。
src/cmd/ld/elf.c の変更
// elfrelocsect 関数内
case D_ADDR:
case D_PCREL:
if(r->sym->type == SCONST)
continue; // handled in data.c:/^relocsym
+ if(r->type == D_PCREL && r->sym->sect == sym->sect)
+ continue; // handled in data.c:/^relocsym
break;
elfrelocsect 関数は、ELFファイルのリロケーションセクションを生成する際に呼び出されます。この変更は、D_PCREL タイプのリロケーションで、かつそのリロケーションが参照するシンボル (r->sym) が現在処理しているセクション (sym) と同じセクションに属している場合、そのリロケーションをスキップするように指示しています。これは、data.c の relocsym 関数で既に内部的に処理されているため、外部リンカに再度処理させる必要がないことを意味します。
src/cmd/ld/lib.c および src/cmd/ld/lib.h の変更
// src/cmd/ld/lib.c genasmsym 関数内
- case SGCDATA:
- case SGCBSS:
genasmsym 関数は、アセンブリシンボルを生成する際に使用されます。SGCDATA と SGCBSS のケースが削除されたのは、これらのシンボルタイプがもはや独立したセクションとして扱われず、STYPE に統合されたため、個別にアセンブリシンボルを生成する必要がなくなったからです。
// src/cmd/ld/lib.h enum 内
- SGCDATA,
- SGCBSS,
lib.h から SGCDATA と SGCBSS の列挙型が削除されました。これは、これらのシンボルタイプがシステムから完全に削除され、STYPE に置き換えられたことを示しています。
src/cmd/ld/symtab.c の変更
// symtab 関数内
- xdefine("gcdata", SGCDATA, 0);
- xdefine("egcdata", SGCDATA, 0);
- xdefine("gcbss", SGCBSS, 0);
- xdefine("egcbss", SGCBSS, 0);
+ xdefine("egcdata", STYPE, 0);
+ xdefine("egcbss", STYPE, 0);
symtab 関数は、シンボルテーブルを初期化する際に使用されます。以前は gcdata と gcbss の開始/終了アドレスを SGCDATA/SGCBSS タイプとして定義していましたが、この変更により、egcdata と egcbss のみが STYPE タイプとして定義されるようになりました。これは、これらのシンボルが独立したセクションではなく、型情報の一部として扱われるようになったことを反映しています。
これらの変更は全体として、GoリンカがGCメタデータと型情報間のPC相対参照を、外部リンカの介入なしに、より堅牢かつ効率的に処理するための重要なステップです。これにより、特に linux/386 環境での外部リンキングの信頼性が向上しました。
関連リンク
- Go CL 7629043: https://golang.org/cl/7629043
参考にした情報源リンク
- Go言語のリンカに関するドキュメント (Go公式ドキュメント): https://go.dev/doc/ (一般的なGoのリンカの概念について)
- ELFファイルフォーマットに関する情報 (Wikipediaなど): https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format (ELFセクション、リロケーションの一般的な理解のため)
- PC相対アドレッシングに関する情報 (コンピュータアーキテクチャの教科書やオンラインリソース)
- Goのガベージコレクションに関する情報 (Goブログや設計ドキュメント): https://go.dev/blog/go1.5gc (GCの基本的な仕組みとメタデータについて)
- Procedure Linkage Table (PLT) と Global Offset Table (GOT) に関する情報 (動的リンクの仕組みについて)
linux/386アーキテクチャの特性に関する情報 (Intel x86アーキテクチャのドキュメントなど)- Goのソースコード (
src/cmd/ldディレクトリ): https://github.com/golang/go/tree/master/src/cmd/ld (実際のコードベースの理解のため)
I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. I have used the commit information and incorporated explanations for the necessary prerequisite knowledge and technical details. I have also included the provided GitHub URL and a section for related and referenced links.