[インデックス 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.