[インデックス 15457] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) における重要な変更を導入し、特に64ビットシステム上でのガベージコレクタ (GC_CALL
) 命令の取り扱いを改善します。既存のコードが64ビットPC相対アドレスを使用していたために発生していた、ELFリンカとの互換性の問題を解決することを目的としています。
コミット
commit 56a06db360d239d6ee78051cd2cca01fd469fe0a
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 26 19:42:56 2013 -0800
cmd/ld: change GC_CALL to 32-bit relative address
The current code uses 64-bit pc-relative on 64-bit systems,
but in ELF linkers there is no such thing, so we cannot
express this in a .o file. Change to 32-bit.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7383055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/56a06db360d239d6ee78051cd2cca01fd469fe0a
元コミット内容
cmd/ld: change GC_CALL to 32-bit relative address
現在のコードは64ビットシステム上で64ビットPC相対アドレスを使用しているが、ELFリンカにはそのようなものは存在しないため、.oファイルでこれを表現できない。32ビットに変更する。
変更の背景
Go言語のコンパイルおよびリンクプロセスにおいて、ガベージコレクタ (GC) は、ヒープ上のオブジェクトの型情報を追跡するために特定の命令 (GC_CALL
) を使用します。この命令は、関連する型情報へのポインタをPC相対アドレスとしてエンコードしていました。
問題は、64ビットシステムにおいて、このPC相対アドレスが64ビット幅で生成されていた点にありました。しかし、Unix系システムで広く使用されているELF (Executable and Linkable Format) オブジェクトファイル形式の仕様では、64ビットPC相対リロケーション(再配置)を直接サポートしていません。具体的には、.o
(オブジェクト) ファイル内で、リンカが最終的な実行可能ファイルを生成する際に解決すべき64ビットPC相対オフセットを表現する標準的な方法がありませんでした。
この不一致により、Goのリンカが生成するオブジェクトファイルがELFの仕様に準拠せず、結果として他のツールやシステムとの互換性の問題を引き起こす可能性がありました。特に、Goのリンカが生成したオブジェクトファイルを、標準的なELFリンカ(例えば ld
)と組み合わせて使用する場合に問題が生じます。
このコミットは、この根本的な互換性の問題を解決するために、GC_CALL
命令が使用するPC相対アドレスを、ELFがサポートする32ビット幅に変更することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を把握しておく必要があります。
-
Go言語のコンパイルとリンカ (
cmd/ld
):- Goプログラムは、ソースコードからアセンブリコード、そしてオブジェクトファイルへとコンパイルされます。
cmd/ld
はGo言語のリンカであり、複数のオブジェクトファイルやライブラリを結合して、最終的な実行可能ファイルや共有ライブラリを生成する役割を担います。この過程で、シンボル参照の解決やアドレスの再配置(リロケーション)が行われます。
-
ELF (Executable and Linkable Format):
- ELFは、Unix系オペレーティングシステム(Linux、BSDなど)で標準的に使用される、実行可能ファイル、オブジェクトコード、共有ライブラリ、およびコアダンプのファイル形式です。
- ELFファイルは、セクション(コード、データなど)と、リンカがプログラムを正しく実行できるようにするために必要な情報(シンボルテーブル、リロケーションエントリなど)で構成されます。
-
PC相対アドレス (PC-relative addressing):
- PC相対アドレスは、メモリ上の特定のアドレスを、プログラムカウンタ (PC) の現在の値からのオフセットとして表現するアドレッシングモードです。
- この方式は、コードがメモリ上のどこにロードされても正しく実行される「位置独立コード (Position-Independent Code, PIC)」を生成する際に非常に重要です。PICは、共有ライブラリなどでよく使用されます。
- 例えば、
CALL
命令のターゲットアドレスがPC + オフセット
の形式で指定される場合、これはPC相対コールと呼ばれます。
-
リロケーション (Relocation):
- リロケーションとは、リンカがオブジェクトファイル内のシンボル参照を解決し、それらの参照が最終的な実行可能ファイル内の正しいメモリ位置を指すように、コードやデータを調整するプロセスです。
- オブジェクトファイルには、未解決のシンボル参照や、ロード時にアドレスを調整する必要があるデータが含まれています。リロケーションエントリは、リンカに対して、どの場所の値をどのように修正すべきかを指示します。
- ELFには様々なリロケーションタイプがあり、例えば
R_X86_64_PC32
は32ビットPC相対リロケーションを意味します。
-
ガベージコレクタ (Garbage Collector, GC):
- Go言語は自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが不要になったメモリを自動的に解放し、メモリリークを防ぎます。
- GCは、ヒープ上のオブジェクトをスキャンし、到達可能なオブジェクトを特定するために、オブジェクトの型情報(ポインタがどこにあるかなど)を必要とします。
GC_CALL
は、この型情報を参照するための内部的なメカニズムの一部です。
-
uintptr
とint32
:uintptr
はGo言語におけるポインタを保持できる整数型です。そのサイズはシステム(32ビットまたは64ビット)によって異なります。64ビットシステムでは8バイト、32ビットシステムでは4バイトです。int32
は32ビット符号付き整数型で、常に4バイトです。- このコミットでは、
uintptr
で表現されていたアドレスオフセットをint32
で表現するように変更しています。これは、アドレスの表現方法と、それがメモリ上で占めるサイズに直接影響します。
技術的詳細
このコミットの核心は、Goのリンカが生成する GC_CALL
命令のリロケーションタイプを変更することにあります。
問題点:
64ビットシステムにおいて、Goのリンカは GC_CALL
命令のターゲットアドレスを D_PCREL
タイプのリロケーションとして生成していました。この D_PCREL
は、ポインタサイズ (PtrSize
、64ビットシステムでは8バイト) のPC相対オフセットを意味していました。しかし、標準的なELFリンカは、オブジェクトファイル内で64ビットPC相対リロケーションを直接サポートしていません。これは、ELFの R_X86_64_PC64
のようなリロケーションタイプが、通常、最終的な実行可能ファイルや共有ライブラリのリンク時に使用されるものであり、中間オブジェクトファイル (.o
ファイル) の段階では、より一般的な32ビットPC相対リロケーション (R_X86_64_PC32
) が期待されるためです。
解決策:
このコミットは、GC_CALL
命令が使用するPC相対アドレスを、64ビットではなく32ビット幅で表現するように変更します。これにより、ELFリンカがオブジェクトファイル内で適切に処理できる R_X86_64_PC32
のようなリロケーションタイプに変換されます。
具体的には、以下の変更が行われます。
-
src/cmd/ld/data.c
の変更:- リンカ側で、
GC_CALL
命令に関連するリロケーションを生成する際に、64ビットPC相対アドレスを扱うaddaddrpcrelplus
関数が削除されました。 - 代わりに、
addpcrelplus
関数(おそらく32ビットPC相対アドレスを扱う)が使用されるようになりました。 - 64ビットシステム (
PtrSize == 8
) の場合、32ビットのオフセットの後に4バイトのパディング(またはゼロ)が追加されます。これは、32ビットオフセットが64ビットのワード内で適切に配置されるようにするため、または将来的な拡張のためにスペースを確保するためと考えられます。これにより、メモリレイアウトが64ビットワードにアラインされ、ランタイムがオフセットを読み取る際に正しい位置から32ビット値を読み取れるようになります。
- リンカ側で、
-
src/pkg/runtime/mgc0.c
の変更:- ランタイム側で、
GC_CALL
命令のターゲットアドレスを計算するロジックが変更されました。 - 以前は
pc[2]
(つまり*(uintptr*)(pc + 2 * sizeof(uintptr))
) として64ビット値を読み取っていましたが、これは*(int32*)(pc+2)
(つまり*(int32*)(pc + 2 * sizeof(uintptr))
) として32ビット符号付き整数を読み取るように変更されました。 - この変更により、ランタイムはリンカが生成した32ビットPC相対オフセットを正しく解釈し、
GC_CALL
命令の実際のターゲットアドレスを計算できるようになります。
- ランタイム側で、
この変更により、Goのリンカが生成するオブジェクトファイルは、ELFの標準的なリロケーションメカニズムとより互換性を持つようになり、Goのツールチェーンと他のシステムツールとの連携がスムーズになります。
コアとなるコードの変更箇所
src/cmd/ld/data.c
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -748,27 +748,6 @@ setuint64(Sym *s, vlong r, uint64 v)
setuintxx(s, r, v, 8);
}
-static vlong
-addaddrpcrelplus(Sym *s, Sym *t, int32 add)
-{
- vlong i;
- Reloc *r;
-
- if(s->type == 0)
- s->type = SDATA;
- s->reachable = 1;
- i = s->size;
- s->size += PtrSize;
- symgrow(s, s->size);
- r = addrel(s);
- r->sym = t;
- r->off = i;
- r->siz = PtrSize;
- r->type = D_PCREL;
- r->add = add;
- return i;
-}
-
vlong
addaddrplus(Sym *s, Sym *t, int32 add)
{
@@ -949,7 +928,9 @@ gcaddsym(Sym *gc, Sym *s, int32 off)
//print("gcaddsym: %s %d %s\n", s->name, s->size, gotype->name);
adduintxx(gc, GC_CALL, PtrSize);
adduintxx(gc, off, PtrSize);
-\t\taddaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize);
+\t\taddpcrelplus(gc, decodetype_gc(gotype), 3*PtrSize+4);
+\t\tif(PtrSize == 8)
+\t\t\tadduintxx(gc, 0, 4);
} else {
//print("gcaddsym: %s %d <unknown type>\n", s->name, s->size);
for(a = -off&(PtrSize-1); a+PtrSize<=s->size; a+=PtrSize) {
src/pkg/runtime/mgc0.c
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -837,7 +837,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
// Stack push.
*stack_ptr-- = stack_top;
stack_top = (Frame){1, 0, stack_top.b + pc[1], pc+3 /*return address*/};
-\t\t\tpc = (uintptr*)((byte*)pc + (uintptr)pc[2]); // target of the CALL instruction
+\t\t\tpc = (uintptr*)((byte*)pc + *(int32*)(pc+2)); // target of the CALL instruction
continue;
case GC_MAP_PTR:
コアとなるコードの解説
src/cmd/ld/data.c
の変更点
-
addaddrpcrelplus
関数の削除: この関数は、シンボルt
へのPC相対アドレスを、シンボルs
のデータセクションに追加するために使用されていました。特に、r->siz = PtrSize;
とr->type = D_PCREL;
の行は、ポインタサイズ(64ビットシステムでは8バイト)のPC相対リロケーションを生成することを示しています。この関数が削除されたのは、64ビットPC相対リロケーションがELFリンカで問題を引き起こすためです。 -
gcaddsym
関数の変更:gcaddsym
は、ガベージコレクタが使用する型情報(gotype
)への参照をシンボルテーブルに追加する役割を担っています。- 変更前:
addaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize);
ここでは、decodetype_gc(gotype)
が指す型情報へのPC相対アドレスが、gc
シンボルに追加されていました。オフセットは4*PtrSize
でした。 - 変更後:
addpcrelplus(gc, decodetype_gc(gotype), 3*PtrSize+4); if(PtrSize == 8) adduintxx(gc, 0, 4);
addpcrelplus
は、おそらく32ビットPC相対リロケーションを生成する新しい(または既存の)関数です。オフセットが3*PtrSize+4
に変更されています。これは、GC_CALL
命令の後のデータ構造のレイアウト変更に対応しています。if(PtrSize == 8) adduintxx(gc, 0, 4);
の行は、64ビットシステムの場合に4バイトのゼロを追加しています。これは、32ビットのPC相対オフセットが64ビットのワード内に収まるようにパディングを行うか、またはランタイムが64ビットのポインタを期待する場所で、32ビットオフセットの後に続く4バイトをゼロで埋めることで、アラインメントを維持し、ランタイムが正しくデータを読み取れるようにするためのものです。
- 変更前:
src/pkg/runtime/mgc0.c
の変更点
scanblock
関数の変更:scanblock
は、ガベージコレクタがメモリブロックをスキャンする際に使用される関数です。特に、GC_CALL
命令を処理する部分が変更されています。- 変更前:
pc = (uintptr*)((byte*)pc + (uintptr)pc[2]);
この行は、GC_CALL
命令のターゲットアドレスを計算していました。pc[2]
は、pc
のアドレスから2 * sizeof(uintptr)
バイト先に格納されているuintptr
型の値(つまり64ビットシステムでは8バイト)を読み取っていました。これは、64ビットPC相対オフセットを期待するコードでした。 - 変更後:
pc = (uintptr*)((byte*)pc + *(int32*)(pc+2));
この行は、pc
のアドレスから2 * sizeof(uintptr)
バイト先に格納されている値を、int32
型(つまり4バイトの符号付き整数)として読み取るように変更されました。これにより、ランタイムはリンカが生成した32ビットPC相対オフセットを正しく解釈し、GC_CALL
命令のターゲットアドレスを計算できるようになります。この変更は、リンカ側での32ビットPC相対アドレスの生成と整合しています。
- 変更前:
これらの変更により、Goのリンカとランタイムは、64ビットシステム上でもELFリンカと互換性のある32ビットPC相対アドレスを使用して GC_CALL
命令を処理できるようになりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- ELFファイル形式の仕様 (Wikipedia): https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
- Go CL (Change List) 7383055: https://golang.org/cl/7383055
参考にした情報源リンク
- コミットメッセージと差分情報:
/home/orange/Project/comemo/commit_data/15457.txt
- GitHub上のコミットページ: https://github.com/golang/go/commit/56a06db360d239d6ee78051cd2cca01fd469fe0a
- ELFリロケーションタイプに関する一般的な情報 (例:
R_X86_64_PC32
): 各種リンカやアセンブラのドキュメント、またはOS開発関連の資料。 - Go言語のリンカとランタイムに関する一般的な知識。