[インデックス 19403] ファイルの概要
このコミットは、Go言語のリンカ(liblink
)におけるフィールドトラッキングの挙動を修正するものです。具体的には、USEFIELD
命令がリンカに到達しなくなった問題に対応し、代わりにR_USEFIELD
という新しいタイプのリロケーションを導入することで、参照ピン留め(pinning references)のメカニズムを維持しています。
コミット
commit d9c9665f1dcd09b0e315c9437d192fed795587a5
Author: Russ Cox <rsc@golang.org>
Date: Tue May 20 00:30:58 2014 -0400
liblink: fix field tracking
The USEFIELD instructions no longer make it to the linker,
so we have to do something else to pin the references
they were pinning. Emit a 0-length relocation of type R_USEFIELD.
Fixes #7486.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews, r
https://golang.org/cl/95530043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d9c9665f1dcd09b0e315c9437d192fed795587a5
元コミット内容
このコミットの目的は、liblink
(Goリンカの内部コンポーネント)におけるフィールドトラッキングの不具合を修正することです。以前はUSEFIELD
命令がリンカによって処理されていましたが、何らかの変更によりこれらの命令がリンカに到達しなくなりました。このため、USEFIELD
命令が担っていた参照のピン留め(特定のフィールドがリンカによって削除されないようにマークすること)の機能が失われました。
このコミットでは、この問題を解決するために、R_USEFIELD
という新しいタイプのリロケーションを導入しています。USEFIELD
命令がリンカに到達しない代わりに、コンパイラが0
バイト長のリロケーションとしてR_USEFIELD
を発行するように変更されました。これにより、リンカは引き続きフィールドの参照を適切に追跡し、必要なフィールドが最終的なバイナリに含まれることを保証します。
コミットメッセージにあるFixes #7486
は、Goプロジェクトの内部イシュートラッカーにおける問題番号を指しており、公開されているGitHubのイシューとは異なります。
変更の背景
Go言語のツールチェインには、構造体のフィールド使用状況を追跡する実験的な機能が存在します。これは主にデバッグや最適化の目的で、どのフィールドが実際にバイナリ内で参照されているかをリンカが把握するために利用されます。この機能は、GOEXPERIMENT=fieldtrack
環境変数を設定してビルドすることで有効になります。
このフィールドトラッキング機能を実現するために、Goコンパイラは特定のフィールドの使用を示すUSEFIELD
という内部的な命令を生成していました。リンカはこれらのUSEFIELD
命令を解釈し、関連するフィールドがバイナリから削除されないように(例えば、デッドコード削除の対象とならないように)「ピン留め」していました。
しかし、このコミットが作成される以前のGoツールチェインの変更により、USEFIELD
命令がリンカの処理パイプラインに到達しなくなりました。その結果、リンカはフィールドの使用状況を正確に把握できなくなり、フィールドトラッキング機能が正しく動作しなくなる、あるいは意図しない最適化によって必要なフィールドが削除されてしまう可能性が生じました。このコミットは、この内部的な変更によって生じたフィールドトラッキングの機能不全を修正するために導入されました。
前提知識の解説
Go言語のリンカ (liblink
)
Go言語のビルドプロセスにおいて、リンカ(cmd/link
)は非常に重要な役割を担っています。コンパイラ(cmd/compile
)がGoのソースコードをオブジェクトファイル(.o
ファイル)に変換した後、リンカはこれらのオブジェクトファイルと、標準ライブラリなどの依存関係を結合して、実行可能なバイナリを生成します。リンカの主な機能には以下のようなものがあります。
- シンボル解決: 異なるオブジェクトファイル間で定義された関数や変数の参照を解決します。
- リロケーション: コード内のアドレス依存の参照(例えば、グローバル変数へのアクセスや関数呼び出し)を、最終的なメモリレイアウトに基づいて修正します。
- デッドコード削除: 実行されないコードや参照されないデータを最終的なバイナリから削除し、ファイルサイズを削減します。
liblink
は、これらのリンカの機能を提供する内部ライブラリであり、Goツールチェインの低レベルな部分を構成しています。
リロケーション (Relocation)
リロケーションとは、コンパイル時に決定できなかったアドレスを、リンカが最終的な実行可能ファイルを作成する際に解決するプロセスです。オブジェクトファイル内のコードやデータには、他のセクションや外部ライブラリのシンボルへの参照が含まれることがありますが、これらのシンボルが最終的にメモリ上のどこに配置されるかは、リンク時まで分かりません。
リロケーションエントリは、リンカに対して「この場所の値を、指定されたシンボルの最終アドレスに基づいて修正しなさい」という指示を与えます。例えば、関数呼び出し命令のオペランドや、グローバル変数へのポインタなどがリロケーションの対象となります。
Goリンカには様々な種類のリロケーションタイプ(R_CALL
, R_DATA
, R_PCREL
など)があり、それぞれ異なる目的と処理方法を持っています。このコミットで導入されたR_USEFIELD
もその一つです。
フィールドトラッキング (Field Tracking)
Goのフィールドトラッキングは、構造体の特定のフィールドがプログラム内で実際に使用されているかどうかをリンカが追跡できるようにする実験的な機能です。これは、主にGoの内部開発やプロファイリング、あるいは特定の最適化のために利用されます。
この機能は、GOEXPERIMENT=fieldtrack
環境変数を設定してGoプログラムをビルドする際に有効になります。有効な場合、コンパイラは構造体のフィールドが参照される箇所に、リンカが認識できる特別なマーク(以前はUSEFIELD
命令、このコミット以降はR_USEFIELD
リロケーション)を埋め込みます。これにより、リンカはたとえ直接的なコードパスから参照されていなくても、そのフィールドが「使用されている」と判断し、デッドコード削除の対象から外すことができます。
例えば、rsc.io/tmp/fieldtrack
のような実験的なパッケージは、このフィールドトラッキングのデータを利用して、実行中のバイナリからどのフィールドが追跡され、どのように参照されているかといった情報を取得する機能を提供します。
技術的詳細
このコミットの技術的な核心は、USEFIELD
命令がリンカに到達しなくなったという内部的な変更に対応し、その機能をR_USEFIELD
リロケーションに置き換える点にあります。
-
R_USEFIELD
リロケーションタイプの追加:include/link.h
ファイルに、新しいリロケーションタイプR_USEFIELD
が追加されました。これは、リンカが認識すべきリロケーションの種類を定義する列挙型enum
の一部です。これにより、リンカはR_USEFIELD
という新しい指示を処理できるようになります。enum { R_PLT0, R_PLT1, R_PLT2, R_USEFIELD, // <-- 新しく追加されたリロケーションタイプ };
-
AUSEFIELD
命令の処理変更:src/liblink/asm6.c
とsrc/liblink/asm8.c
は、それぞれ386アーキテクチャとAMD64アーキテクチャ向けのGoアセンブラのバックエンドコードです。これらのファイル内のasmins
関数(アセンブリ命令を処理する関数)が変更されました。変更前は、コンパイラが生成した
AUSEFIELD
(アセンブリレベルでのUSEFIELD
命令)がリンカに直接渡され、リンカがその命令を処理することでフィールドの参照をピン留めしていました。変更後、
asmins
関数はAUSEFIELD
命令を検出すると、その命令自体を処理するのではなく、addrel
関数を呼び出して新しいリロケーションエントリを作成します。この新しいリロケーションは以下の特性を持ちます。r->off = 0;
: リロケーションのオフセット(適用される場所)は0
です。これは、このリロケーションが特定のコードやデータのバイト列を修正するものではなく、単にシンボル参照をマークするためのものであることを示唆しています。r->siz = 0;
: リロケーションのサイズも0
です。これもオフセットと同様に、実際のバイナリデータに影響を与えるものではなく、メタデータ的な役割を持つことを意味します。r->sym = p->from.sym;
: リロケーションが関連付けられるシンボルは、AUSEFIELD
命令のfrom
フィールドから取得されます。これが、ピン留めしたい対象のフィールドシンボルです。r->type = R_USEFIELD;
: リロケーションのタイプは、新しく定義されたR_USEFIELD
です。
この変更により、
AUSEFIELD
命令はリンカの命令処理パイプラインから事実上削除され、その役割はR_USEFIELD
リロケーションに引き継がれました。リンカは、この0
バイト長のリロケーションを検出することで、関連するシンボル(フィールド)が使用されていることを認識し、デッドコード削除の対象から外すなどの適切な処理を行うことができます。
このアプローチは、リンカの内部構造の変更に柔軟に対応しつつ、既存のフィールドトラッキング機能のセマンティクスを維持するためのものです。
コアとなるコードの変更箇所
include/link.h
--- a/include/link.h
+++ b/include/link.h
@@ -243,6 +243,7 @@ enum
R_PLT0,
R_PLT1,
R_PLT2,
+ R_USEFIELD, // 新しいリロケーションタイプが追加
};
// Auto.type
src/liblink/asm6.c
--- a/src/liblink/asm6.c
+++ b/src/liblink/asm6.c
@@ -3443,6 +3443,15 @@ asmins(Link *ctxt, Prog *p)
ctxt->andptr = ctxt->and;
ctxt->asmode = p->mode;
+ if(p->as == AUSEFIELD) { // AUSEFIELD命令を検出
+ r = addrel(ctxt->cursym); // 新しいリロケーションを作成
+ r->off = 0; // オフセットは0
+ r->siz = 0; // サイズは0
+ r->sym = p->from.sym; // 関連するシンボルを設定
+ r->type = R_USEFIELD; // タイプはR_USEFIELD
+ return; // 命令の処理を終了
+ }
+
if(ctxt->headtype == Hnacl) {
if(p->as == AREP) {
ctxt->rep++;
src/liblink/asm8.c
--- a/src/liblink/asm8.c
+++ b/src/liblink/asm8.c
@@ -2744,7 +2744,18 @@ static uchar naclret[] = {
static void
asmins(Link *ctxt, Prog *p)
{
+ Reloc *r; // Reloc構造体へのポインタを宣言
+
ctxt->andptr = ctxt->and;
+
+ if(p->as == AUSEFIELD) { // AUSEFIELD命令を検出
+ r = addrel(ctxt->cursym); // 新しいリロケーションを作成
+ r->off = 0; // オフセットは0
+ r->sym = p->from.sym; // 関連するシンボルを設定
+ r->type = R_USEFIELD; // タイプはR_USEFIELD
+ r->siz = 0; // サイズは0
+ return; // 命令の処理を終了
+ }
if(ctxt->headtype == Hnacl) {
switch(p->as) {
コアとなるコードの解説
このコミットの主要な変更は、Goリンカの内部でアセンブリ命令を処理するasmins
関数に集中しています。
-
include/link.h
: このヘッダーファイルは、リンカが使用する定数やデータ構造を定義しています。R_USEFIELD
がenum
に追加されたことで、リンカはこれを有効なリロケーションタイプとして認識し、内部で処理できるようになります。これは、リンカの「語彙」に新しい単語が追加されたようなものです。 -
src/liblink/asm6.c
およびsrc/liblink/asm8.c
: これらのファイルは、Goのアセンブラが生成する中間表現(Prog
構造体で表される)をリンカが処理する部分です。asmins
関数は、個々のアセンブリ命令(Prog *p
)を検査し、それに応じてリンカの内部状態を更新したり、最終的なバイナリコードを生成したりします。追加された
if(p->as == AUSEFIELD)
ブロックがこのコミットの核心です。p->as == AUSEFIELD
: これは、現在の命令がAUSEFIELD
タイプであるかどうかをチェックしています。AUSEFIELD
は、コンパイラが特定のフィールドが使用されていることをリンカに伝えるために生成する内部的なアセンブリ命令です。r = addrel(ctxt->cursym);
:addrel
関数は、現在のシンボル(ctxt->cursym
)に関連付けられた新しいリロケーションエントリを作成します。リロケーションは、リンカが最終的なバイナリを生成する際に、特定のアドレスを修正するための指示です。r->off = 0;
およびr->siz = 0;
: これらの行は、作成されたリロケーションのオフセットとサイズを0
に設定しています。通常のリロケーションは、コードやデータの特定の位置(オフセット)にある特定サイズ(サイズ)の値を修正しますが、R_USEFIELD
の場合は、実際のバイナリデータを変更する目的はありません。これは、単にリンカに対して「このシンボルは使用されている」というメタデータ的な情報を提供するためのものです。r->sym = p->from.sym;
:p->from.sym
は、AUSEFIELD
命令が参照しているシンボル(つまり、追跡対象のフィールド)です。この行は、リロケーションがどのシンボルに関連付けられているかをリンカに伝えます。r->type = R_USEFIELD;
: これが最も重要な部分で、リロケーションのタイプを新しく定義されたR_USEFIELD
に設定しています。リンカはこのタイプを見て、これがフィールドトラッキングに関連するリロケーションであることを認識します。return;
:AUSEFIELD
命令がリロケーションとして処理された後、それ以上のアセンブリ命令としての処理は不要なため、関数を終了します。
この変更により、AUSEFIELD
命令はもはやリンカによって直接実行される命令ではなくなり、その存在はR_USEFIELD
という0
バイト長のリロケーションとしてリンカに伝えられるようになりました。これにより、リンカの内部的な変更にもかかわらず、フィールドトラッキングのセマンティクスが維持され、必要なフィールドがバイナリに適切に含まれることが保証されます。
関連リンク
- Go言語のリンカに関する公式ドキュメント(一般的な情報): https://go.dev/doc/articles/go-toolchain
- Go言語の内部的なリロケーションタイプに関する議論(より深い理解のため): Goソースコード内の
src/cmd/link/internal/ld/sym.go
やsrc/cmd/internal/obj/link.go
などを参照すると、様々なリロケーションタイプとその処理に関する詳細が見つかります。
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/link
およびsrc/cmd/internal/obj
ディレクトリ) - Go言語のリンカに関する非公式な解説記事やブログポスト(Goのリンカは内部的な詳細が多く、公式ドキュメントだけでは全てを把握しにくい場合があります)
- Go言語の実験的なフィールドトラッキング機能に関する情報:
- https://go.dev/src/cmd/go/internal/modcmd/edit.go (GOEXPERIMENT=fieldtrackの言及)
- https://go.dev/src/rsc.io/tmp/fieldtrack/fieldtrack.go (実験的なfieldtrackパッケージのコード)
- Go言語のIssueトラッカー(内部的なIssue #7486の文脈理解のため)