Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15670] ファイルの概要

コミット

このコミットは、Goリンカー (cmd/ld) における外部再配置計算の冗長性を排除することを目的としています。具体的には、再配置処理の効率化を図るため、Reloc 構造体に新しいフィールド (done, xadd, xsym) を追加し、data.celf.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) が必要となる場合でも、その計算を重複して行っていたためと考えられます。この冗長な計算は、リンカーの処理時間やリソース消費に影響を与える可能性がありました。

このコミットの目的は、この冗長性を排除し、再配置処理の効率を向上させることにあります。具体的には、再配置が既に処理されたかどうかを追跡するメカニズムと、外部再配置に必要な追加情報(xaddxsym)を Reloc 構造体に持たせることで、elf.c での elfrelocsect 関数が不要な計算をスキップできるようにしています。

前提知識の解説

リンカーと再配置 (Relocation)

リンカーは、コンパイラによって生成された複数のオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成するツールです。この結合プロセスにおいて、オブジェクトファイル内のコードやデータは、他のオブジェクトファイルやライブラリで定義されたシンボル(関数や変数など)を参照することがあります。しかし、コンパイル時にはこれらのシンボルの最終的なメモリ上のアドレスは確定していません。

「再配置」とは、リンカーがこれらの未解決のシンボル参照を、最終的なメモリ上のアドレスに解決するプロセスを指します。具体的には、オブジェクトファイル内に「再配置エントリ」と呼ばれる情報が含まれており、これには「どの場所の値を」「どのシンボルを参照して」「どのように修正するか」といった指示が記述されています。リンカーはこれらの指示に従って、コードやデータの特定の部分を修正し、正しいアドレスを埋め込みます。

ELF (Executable and Linkable Format)

ELFは、Unix系システム(Linuxなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、プログラムのコード、データ、シンボルテーブル、再配置情報など、実行可能プログラムを構成する様々なセクションを含んでいます。

D_ADDRD_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_PLT32R_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_ADDRD_PCREL のような外部再配置が必要な場合に、relocsym 関数(data.c)で計算された情報を elfrelocsect 関数(elf.c)に引き渡すために使用されます。

data.c の変更

src/cmd/ld/data.crelocsym 関数は、シンボル内の再配置エントリを処理する主要な関数です。

  • r->done = 1; の追加: 各再配置エントリの処理を開始する際に、まず r->done = 1 を設定します。これは、その再配置が relocsym によって「内部的に」処理されたことを示します。
  • D_ADDR および D_PCREL の処理ロジック変更:
    • isobj (オブジェクトファイルの場合) かつ r->sym->type != SCONST (定数ではないシンボル) の場合、この再配置は外部再配置として扱われる可能性があります。
    • この場合、r->done = 0; に設定し直されます。これは、この再配置が relocsym だけでは完全に解決できず、後続の elfrelocsect 関数によるELF再配置処理が必要であることを示します。
    • r->xaddr->xsym が計算され、設定されます。xaddr->add に加えて、outer シンボルチェーンを辿った際のオフセット差分を累積した値です。xsymouter シンボルチェーンの最上位(最も外側)のシンボルを指します。
    • これにより、elfrelocsect が再配置を処理する際に、必要な addend とターゲットシンボル (elfsym) を再計算することなく、Reloc 構造体から直接取得できるようになります。

elf.c の変更

src/cmd/ld/elf.celfrelocsect 関数は、ELFセクション内の再配置エントリを処理し、ELF再配置テーブルに書き込む役割を担います。

  • if(r->done) continue; の追加: relocsym 関数で既に処理済み (r->done == 1) とマークされた再配置は、ここでスキップされます。これにより、冗長な再計算やELF再配置テーブルへの重複した書き込みが回避されます。
  • r->xsym の利用: 以前は rsadd を計算するために outer シンボルチェーンを辿るロジックがここに存在しましたが、それが削除されました。代わりに、Reloc 構造体に格納された r->xsymr->xadd を直接利用するようになりました。
    • elfsym = r->xsym->elfsym; を使用して、ELFシンボルインデックスを取得します。
    • elfreloc1(r, sym->value+r->off - sect->vaddr) を呼び出す際に、r->xaddelfreloc1 内部で利用されるように変更されました。

asm.c (5l, 6l, 8l) の変更

各アーキテクチャ固有の asm.c ファイルでは、elfreloc1 関数のシグネチャが変更されました。

  • int elfreloc1(Reloc *r, vlong off, int32 elfsym, vlong add) から int elfreloc1(Reloc *r, vlong sectoff) へと変更されました。
  • 以前は引数として渡されていた elfsymadd は、Reloc 構造体 (r) の新しいフィールド (r->xsym->elfsymr->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 フラグをリセットし、xaddxsym を計算・設定するロジックを追加。
  • src/cmd/ld/elf.c: elfrelocsect 関数内で、Reloc 構造体の done フラグをチェックして既に処理済みの再配置をスキップし、xsymxadd を利用するように変更。
  • 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_ADDRD_PCREL のような外部再配置が必要な場合(isobj かつ r->sym->type != SCONST)、r->done = 0 に設定し直されます。これは、この再配置が relocsym だけでは完全に解決できず、後続の elfrelocsect 関数によるELF再配置処理が必要であることを示します。 この際、r->xaddr->xsym が計算され、設定されます。xaddr->add に加えて、outer シンボルチェーンを辿った際のオフセット差分を累積した値です。xsymouter シンボルチェーンの最上位(最も外側)のシンボルを指します。これにより、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 関数のシグネチャが変更され、elfsymadd が引数から削除されました。これらの値は、Reloc 構造体 (r) の新しいフィールド (r->xsym->elfsymr->xadd) から直接取得されるようになりました。これにより、elfreloc1 はより簡潔になり、Reloc 構造体に必要な情報がカプセル化される形になりました。

関連リンク

参考にした情報源リンク