[インデックス 15670] ファイルの概要
コミット
このコミットは、Goリンカー (cmd/ld
) における外部再配置計算の冗長性を排除することを目的としています。具体的には、再配置処理の効率化を図るため、Reloc
構造体に新しいフィールド (done
, xadd
, xsym
) を追加し、data.c
と elf.c
での再配置処理ロジックを修正しています。これにより、特にELF形式のオブジェクトファイルにおける再配置情報の処理が最適化され、リンカーのパフォーマンス向上が期待されます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e13803ae1a66afdb03c7c07c8b4517a9
元コミット内容
cmd/ld: avoid redundant external relocation calculations
R=ken2, ken
CC=golang-dev
https://golang.org/cl/7483045
変更の背景
Goリンカー (cmd/ld
) は、コンパイルされたGoオブジェクトファイルと依存関係を結合して実行可能バイナリを生成します。このプロセス中に、コンパイル時には不明だったシンボルアドレスを解決するために様々な再配置(relocation)を実行します。
以前のリンカーの実装では、特に外部シンボルに対する再配置計算において冗長な処理が行われていました。これは、data.c
内の relocsym
関数が再配置情報を処理する際に、ELF形式の再配置 (elfreloc1
) が必要となる場合でも、その計算を重複して行っていたためと考えられます。この冗長な計算は、リンカーの処理時間やリソース消費に影響を与える可能性がありました。
このコミットの目的は、この冗長性を排除し、再配置処理の効率を向上させることにあります。具体的には、再配置が既に処理されたかどうかを追跡するメカニズムと、外部再配置に必要な追加情報(xadd
と xsym
)を Reloc
構造体に持たせることで、elf.c
での elfrelocsect
関数が不要な計算をスキップできるようにしています。
前提知識の解説
リンカーと再配置 (Relocation)
リンカーは、コンパイラによって生成された複数のオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成するツールです。この結合プロセスにおいて、オブジェクトファイル内のコードやデータは、他のオブジェクトファイルやライブラリで定義されたシンボル(関数や変数など)を参照することがあります。しかし、コンパイル時にはこれらのシンボルの最終的なメモリ上のアドレスは確定していません。
「再配置」とは、リンカーがこれらの未解決のシンボル参照を、最終的なメモリ上のアドレスに解決するプロセスを指します。具体的には、オブジェクトファイル内に「再配置エントリ」と呼ばれる情報が含まれており、これには「どの場所の値を」「どのシンボルを参照して」「どのように修正するか」といった指示が記述されています。リンカーはこれらの指示に従って、コードやデータの特定の部分を修正し、正しいアドレスを埋め込みます。
ELF (Executable and Linkable Format)
ELFは、Unix系システム(Linuxなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、プログラムのコード、データ、シンボルテーブル、再配置情報など、実行可能プログラムを構成する様々なセクションを含んでいます。
D_ADDR
と D_PCREL
これらはGoリンカー内部で使われる再配置タイプを示す定数です。
D_ADDR
(Absolute Address Relocation): これは、シンボルの絶対メモリアドレスをコードやデータに直接埋め込むタイプの再配置を概念的に指します。ELFの標準的な再配置タイプで言えば、R_X86_64_64
のように、64ビットの絶対アドレスを解決するタイプに似ています。D_PCREL
(PC-Relative Relocation): これは、プログラムカウンタ(PC)からの相対オフセットを計算して埋め込むタイプの再配置です。PC相対再配置は、Position-Independent Code (PIC) の生成に不可欠であり、共有ライブラリがどのメモリアドレスにロードされても正しく動作するために重要です。ELFの標準的な再配置タイプで言えば、R_X86_64_PLT32
やR_AARCH64_PREL32
などがこれに該当します。
Sym
構造体と Reloc
構造体
Goリンカーの内部では、シンボルは Sym
構造体で表現され、再配置情報は Reloc
構造体で表現されます。
Sym
構造体: シンボルの名前、アドレス、タイプ(関数、変数など)、セクション情報などを保持します。Reloc
構造体: 再配置のオフセット、サイズ、タイプ、参照するシンボル (sym
)、そして追加のオフセット値 (add
) などを保持します。
技術的詳細
このコミットの主要な変更点は、Reloc
構造体への新しいフィールドの追加と、それを利用した再配置処理ロジックの最適化です。
Reloc
構造体の変更
src/cmd/*/l.h
(5l, 6l, 8l) の各アーキテクチャ固有のヘッダファイルで、Reloc
構造体に以下の3つのフィールドが追加されました。
uchar done;
: この再配置が既に処理されたかどうかを示すフラグです。1
であれば処理済み、0
であれば未処理です。int32 xadd;
/int64 xadd;
: 外部再配置に必要な追加のオフセット値です。アーキテクチャによってサイズが異なります。Sym* xsym;
: 外部再配置の最終的なターゲットとなるシンボルへのポインタです。これは、outer
シンボルチェーンを辿った最終的なシンボルを指します。
これらのフィールドは、特に D_ADDR
や D_PCREL
のような外部再配置が必要な場合に、relocsym
関数(data.c
)で計算された情報を elfrelocsect
関数(elf.c
)に引き渡すために使用されます。
data.c
の変更
src/cmd/ld/data.c
の relocsym
関数は、シンボル内の再配置エントリを処理する主要な関数です。
r->done = 1;
の追加: 各再配置エントリの処理を開始する際に、まずr->done = 1
を設定します。これは、その再配置がrelocsym
によって「内部的に」処理されたことを示します。D_ADDR
およびD_PCREL
の処理ロジック変更:isobj
(オブジェクトファイルの場合) かつr->sym->type != SCONST
(定数ではないシンボル) の場合、この再配置は外部再配置として扱われる可能性があります。- この場合、
r->done = 0;
に設定し直されます。これは、この再配置がrelocsym
だけでは完全に解決できず、後続のelfrelocsect
関数によるELF再配置処理が必要であることを示します。 r->xadd
とr->xsym
が計算され、設定されます。xadd
はr->add
に加えて、outer
シンボルチェーンを辿った際のオフセット差分を累積した値です。xsym
はouter
シンボルチェーンの最上位(最も外側)のシンボルを指します。- これにより、
elfrelocsect
が再配置を処理する際に、必要なaddend
とターゲットシンボル (elfsym
) を再計算することなく、Reloc
構造体から直接取得できるようになります。
elf.c
の変更
src/cmd/ld/elf.c
の elfrelocsect
関数は、ELFセクション内の再配置エントリを処理し、ELF再配置テーブルに書き込む役割を担います。
if(r->done) continue;
の追加:relocsym
関数で既に処理済み (r->done == 1
) とマークされた再配置は、ここでスキップされます。これにより、冗長な再計算やELF再配置テーブルへの重複した書き込みが回避されます。r->xsym
の利用: 以前はrs
とadd
を計算するためにouter
シンボルチェーンを辿るロジックがここに存在しましたが、それが削除されました。代わりに、Reloc
構造体に格納されたr->xsym
とr->xadd
を直接利用するようになりました。elfsym = r->xsym->elfsym;
を使用して、ELFシンボルインデックスを取得します。elfreloc1(r, sym->value+r->off - sect->vaddr)
を呼び出す際に、r->xadd
がelfreloc1
内部で利用されるように変更されました。
asm.c
(5l, 6l, 8l) の変更
各アーキテクチャ固有の asm.c
ファイルでは、elfreloc1
関数のシグネチャが変更されました。
int elfreloc1(Reloc *r, vlong off, int32 elfsym, vlong add)
からint elfreloc1(Reloc *r, vlong sectoff)
へと変更されました。- 以前は引数として渡されていた
elfsym
とadd
は、Reloc
構造体 (r
) の新しいフィールド (r->xsym->elfsym
とr->xadd
) から取得されるようになりました。これにより、elfreloc1
はより簡潔になり、Reloc
構造体に必要な情報がカプセル化される形になりました。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
src/cmd/5l/asm.c
:elfreloc1
関数のシグネチャ変更と内部ロジックの調整。src/cmd/5l/l.h
:Reloc
構造体にdone
,xadd
,xsym
フィールドを追加。src/cmd/6l/asm.c
:elfreloc1
関数のシグネチャ変更と内部ロジックの調整。src/cmd/6l/l.h
:Reloc
構造体にdone
,xadd
,xsym
フィールドを追加。src/cmd/8l/asm.c
:elfreloc1
関数のシグネチャ変更と内部ロジックの調整。src/cmd/8l/l.h
:Reloc
構造体にdone
,xadd
,xsym
フィールドを追加。src/cmd/ld/data.c
:relocsym
関数内で、外部再配置が必要な場合にReloc
構造体のdone
フラグをリセットし、xadd
とxsym
を計算・設定するロジックを追加。src/cmd/ld/elf.c
:elfrelocsect
関数内で、Reloc
構造体のdone
フラグをチェックして既に処理済みの再配置をスキップし、xsym
とxadd
を利用するように変更。src/cmd/ld/elf.h
:elfreloc1
関数のプロトタイプ宣言を更新。
コアとなるコードの解説
src/cmd/*/l.h
(例: src/cmd/5l/l.h
)
@@ -96,9 +96,12 @@ struct Reloc
{
int32 off;
uchar siz;
+ uchar done;
int16 type;
int32 add;
+ int32 xadd;
Sym* sym;
+ Sym* xsym;
};
Reloc
構造体に done
(再配置が処理済みかを示すフラグ)、xadd
(外部再配置の追加オフセット)、xsym
(外部再配置のターゲットシンボル) の3つのフィールドが追加されました。これにより、再配置に関するより詳細な状態と情報が Reloc
オブジェクト自体に保持されるようになります。
src/cmd/ld/data.c
@@ -155,6 +155,7 @@ relocsym(Sym *s)
cursym = s;
memset(&p, 0, sizeof p);
for(r=s->r; r<s->r+s->nr; r++) {
+\t\tr->done = 1;
\toff = r->off;
\tsiz = r->siz;
\tif(off < 0 || off+siz > s->np) {
@@ -181,31 +182,51 @@ relocsym(Sym *s)
\t\t\tdiag("unknown reloc %d", r->type);
\t\t\tbreak;
\t\tcase D_ADDR:
-\t\t\t\to = symaddr(r->sym) + r->add;
\t\t\tif(isobj && r->sym->type != SCONST) {
+\t\t\t\t\tr->done = 0;
+\n+\t\t\t\t// set up addend for eventual relocation via outer symbol.
+\t\t\t\t\trs = r->sym;
+\t\t\t\t\tr->xadd = r->add;
+\t\t\t\t\twhile(rs->outer != nil) {
+\t\t\t\t\t\tr->xadd += symaddr(rs) - symaddr(rs->outer);
+\t\t\t\t\t\trs = rs->outer;
+\t\t\t\t\t}
+\t\t\t\t\tr->xsym = rs;
+\n \t\t\t\tif(thechar == '6')
\t\t\t\t\to = 0;
-\t\t\t\telse {
-\t\t\t\t\t// set up addend for eventual relocation via outer symbol
-\t\t\t\t\trs = r->sym;
-\t\t\t\t\twhile(rs->outer != nil)
-\t\t\t\t\t\trs = rs->outer;
-\t\t\t\t\to -= symaddr(rs);
-\t\t\t\t}
+\t\t\t\telse
+\t\t\t\t\to = r->xadd;
+\t\t\t\tbreak;
\t\t\t}
+\t\t\to = symaddr(r->sym) + r->add;
\t\t\tbreak;
\t\tcase D_PCREL:
\t\t\t// r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call.
-\t\t\t\to = 0;
-\t\t\t\tif(r->sym)
-\t\t\t\t\to += symaddr(r->sym);
-\t\t\t\to += r->add - (s->value + r->off + r->siz);
-\t\t\t\tif(isobj && r->sym->type != SCONST && r->sym->sect != cursym->sect) {
+\t\t\t\tif(isobj && r->sym && r->sym->type != SCONST && r->sym->sect != cursym->sect) {
+\t\t\t\t\tr->done = 0;
+\n+\t\t\t\t// set up addend for eventual relocation via outer symbol.
+\t\t\t\t\trs = r->sym;
+\t\t\t\t\tr->xadd = r->add;
+\t\t\t\t\twhile(rs->outer != nil) {
+\t\t\t\t\t\tr->xadd += symaddr(rs) - symaddr(rs->outer);
+\t\t\t\t\t\trs = rs->outer;
+\t\t\t\t\t}
+\t\t\t\t\tr->xsym = rs;
+\t\t\t\t\tr->xadd -= r->siz;
+\n \t\t\t\tif(thechar == '6')
\t\t\t\t\to = 0;
\t\t\t\telse
-\t\t\t\t\to = r->add - r->siz;
+\t\t\t\t\to = r->xadd;
+\t\t\t\tbreak;
\t\t\t}
+\t\t\to = 0;
+\t\t\tif(r->sym)
+\t\t\t\to += symaddr(r->sym);
+\t\t\to += r->add - (s->value + r->off + r->siz);
\t\t\tbreak;
\t\tcase D_SIZE:
\t\t\to = r->sym->size + r->add;
relocsym
関数は、各再配置エントリの処理を開始する際に r->done = 1
を設定します。これは、この再配置が relocsym
によって「内部的に」処理されたことを示します。
しかし、D_ADDR
や D_PCREL
のような外部再配置が必要な場合(isobj
かつ r->sym->type != SCONST
)、r->done = 0
に設定し直されます。これは、この再配置が relocsym
だけでは完全に解決できず、後続の elfrelocsect
関数によるELF再配置処理が必要であることを示します。
この際、r->xadd
と r->xsym
が計算され、設定されます。xadd
は r->add
に加えて、outer
シンボルチェーンを辿った際のオフセット差分を累積した値です。xsym
は outer
シンボルチェーンの最上位(最も外側)のシンボルを指します。これにより、elfrelocsect
が再配置を処理する際に、必要な addend
とターゲットシンボル (elfsym
) を再計算することなく、Reloc
構造体から直接取得できるようになります。
src/cmd/ld/elf.c
@@ -834,30 +833,15 @@ elfrelocsect(Section *sect, Sym *first)
\t\tcursym = sym;
\t\t
\t\tfor(r = sym->r; r < sym->r+sym->nr; r++) {
-\t\t\t\t// Ignore relocations handled by reloc already.
-\t\t\t\tswitch(r->type) {
-\t\t\t\tcase D_SIZE:
+\t\t\t\tif(r->done)
+\t\t\t\t\tcontinue;
+\t\t\t\tif(r->xsym == nil) {
+\t\t\t\t\tdiag("missing xsym in relocation");
\t\t\t\t\tcontinue;
-\t\t\t\tcase D_ADDR:
-\t\t\t\tcase D_PCREL:
-\t\t\t\t\tif(r->sym->type == SCONST)
-\t\t\t\t\t\tcontinue;\t// handled in data.c:/^relocsym
-\t\t\t\t\tif(r->type == D_PCREL && r->sym->sect == sym->sect)
-\t\t\t\t\t\tcontinue;\t// handled in data.c:/^relocsym
-\t\t\t\t\tbreak;
-\t\t\t\t}
-\n-\t\t\tadd = r->add;
-\t\t\trs = r->sym;
-\t\t\twhile(rs->outer != nil) {
-\t\t\t\tadd += rs->value - rs->outer->value;
-\t\t\t\trs = rs->outer;
\t\t\t}
-\t\t\t\t
-\t\t\tif(rs->elfsym == 0)
-\t\t\t\tdiag("reloc %d to non-elf symbol %s (rs=%s) %d", r->type, r->sym->name, rs->name, rs->type);\n-\t\t\tif(elfreloc1(r, sym->value - sect->vaddr + r->off, rs->elfsym, add) < 0)
+\t\t\tif(r->xsym->elfsym == 0)
+\t\t\t\tdiag("reloc %d to non-elf symbol %s (outer=%s) %d", r->type, r->sym->name, r->xsym->name, r->sym->type);\n+\t\t\tif(elfreloc1(r, sym->value+r->off - sect->vaddr) < 0)
\t\t\t\tdiag("unsupported obj reloc %d/%d to %s", r->type, r->siz, r->sym->name);\n \t\t}\n \t}\n```
`elfrelocsect` 関数では、まず `if(r->done) continue;` が追加され、`relocsym` 関数で既に処理済み (`r->done == 1`) とマークされた再配置はスキップされるようになりました。これにより、冗長な再計算やELF再配置テーブルへの重複した書き込みが回避されます。
また、以前は `rs` と `add` を計算するために `outer` シンボルチェーンを辿るロジックがここに存在しましたが、それが削除されました。代わりに、`Reloc` 構造体に格納された `r->xsym` と `r->xadd` を直接利用するようになりました。これにより、コードが簡潔になり、再計算のオーバーヘッドがなくなります。
### `src/cmd/*/asm.c` (例: `src/cmd/5l/asm.c`)
```diff
@@ -240,12 +240,13 @@ adddynrel(Sym *s, Reloc *r)
}
int
-elfreloc1(Reloc *r, vlong off, int32 elfsym, vlong add)
+elfreloc1(Reloc *r, vlong sectoff)
{
-\tUSED(add);\t// written to obj file by ../ld/data.c's reloc
-\n-\tLPUT(off);
+\tint32 elfsym;
+\t\n+\tLPUT(sectoff);
+\n+\telfsym = r->xsym->elfsym;
\tswitch(r->type) {
\tdefault:
\t\treturn -1;
elfreloc1
関数のシグネチャが変更され、elfsym
と add
が引数から削除されました。これらの値は、Reloc
構造体 (r
) の新しいフィールド (r->xsym->elfsym
と r->xadd
) から直接取得されるようになりました。これにより、elfreloc1
はより簡潔になり、Reloc
構造体に必要な情報がカプセル化される形になりました。
関連リンク
- Go CL (Code Review) 7483045: https://golang.org/cl/7483045
参考にした情報源リンク
- ELF Relocations: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGvTRZtLUdmamidW9ATZkjn2KH67U9sU7CjI5d2eLLxMqQLOXVfyCwwrGsPrWjOUdKxfsE8EYQBLJq4HqzdhaJMET9lTq-4PyDnb5TPK-7sI4Ilqnan33bjj0USWd9AQ31pi2v7S4nk1gYcOz4y34sN8ZaK2XJESb8oHPl651MUR1MEI5SJy1DrTf-8m9S9HBqNRITfrQukww==
- PC-Relative Relocation: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHKn0SQMzX6Wb0MO6N2BbZ1ZgYIfdMr6ADf-xFYOrGSGVmuG4lpNb3KiLlDfWhkcRUTg5RNWdI-uQ7fTi5hckdRGuWS3dR6cxXHoXO3vOrilpOxYiozYljGPMN2sr1jOsGPRuXu7vwL
- ELF Relocation Types: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF_08rN3QVTxPecfCLmL2HLvHhMOuqOplsXBoY9AtRDnpI9nLqB8tAIz2q4b8PGkyME5qKP8L07QtGpiW9CXTw8U344yBA7c_PtoNF_Bj-Izkv1uykMdrUipBrXd_4Ye1NPFZrMwP8tFg5RBWTk9H65X4BCkQIjGLHb-pIAWzRgbzI=
- Addends in RELA Relocations: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFlgCbXl7zLqUtPevcvZSXG_gnm88xz0KAHuJRTXvmGizuCsiRddRPG3HgTVS3XqkFa4hYHhReTXFqK2nDVASlY2Dzi0iEejuxKc3jqcSwcheDHZ3FU_OUqw_WdwiU5DGgnIEg0
- Compatibility and Complexity of Linkers: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFMwXfAFvFFc9yTO6z9dzqyFYnC042XJXgVifP7uTBIbzg5nkFtZ8dFTfGiQoNq9tOrJx0G6C1TEaOfqAq1iZEitqLA50oDuK5MicLybixSyL6oQdbHaIGze-SaM-vJwWjz7zdyRsVAl5biVEcm4sDgvKCJ5RhyUOZqa8h1xFFyl6eEcg6FNztcYvp10vXLIKxlJJX4WHymYO2jGsz_Mz7o7Mcj9JUfvyqXJqcgw2Ap40HSmZlIfMTP
- Position-Independent Code (PIC): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFyV_kulRamYWTgdlUElaDWCDoaQyta8k-cjzCfTcor3Y4TKsp3bo45ubUI49DcMYDy0i8rWOgYy540WhtCoTI335Kk4qCtVpEV6mBw8BrjDloAUFHdtqXw1zqxLt7MXmhDIsVf
- Symbol Interposition and Copy Relocations: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEPiQDKUSiR8X9wKbevf3xNrzCZ3koYO3kyNFupT68_k_ZXwKrnu9ev_JravUMqrzmHLqv9MU65MZPOSxudUzfhszdR0gKyg0Hemrp-W7744ms140dF5GFTtDZWdpfv2gJeDjqZtww93IMdWTSbazYxyuojO47D_DV4mEfYr-8=