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

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

コミット

このコミット 3bdeaf2a64a9731fc664b6d0fc36a70e7a7e0a05 は、Go言語のリンカ (5l (ARM), 6l (x86-64), 8l (x86-32)) に、Position-Independent Code (PIC) および共有ライブラリのサポートを追加するものです。具体的には、-shared フラグを導入し、動的な再配置とRIP相対アドレッシング(x86-64の場合)を伴うPIC実行可能ファイルを生成できるようにします。これにより、Goプログラムが共有ライブラリとして機能したり、共有ライブラリを効率的に利用したりするための基盤が強化されます。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/3bdeaf2a64a9731fc664b6d0fc36a70e7a7e0a05

元コミット内容

6l/5l: PIC and shared library support for the linkers.

Added the -shared flag to 5l/6l to output a PIC executable with the required
dynamic relocations and RIP-relative addressing in machine code.
Added dummy support to 8l to avoid compilation errors

See also:
https://golang.org/cl/6822078
https://golang.org/cl/7064048

and

https://groups.google.com/d/topic/golang-nuts/P05BDjLcQ5k/discussion

R=rsc, iant
CC=golang-dev
https://golang.org/cl/6926049

変更の背景

Go言語は当初、静的リンクを強く志向しており、生成されるバイナリは自己完結型であることが一般的でした。しかし、大規模なアプリケーションやシステムにおいて、コードの再利用性、メモリ使用量の削減、およびセキュリティパッチの適用容易性といった観点から、共有ライブラリの利用は不可欠です。特に、Goプログラムを他の言語で書かれたアプリケーションから呼び出したり、複数のGoプログラム間で共通のライブラリを共有したりする場合には、PICと共有ライブラリのサポートが必須となります。

このコミットは、Go言語のリンカが共有ライブラリを生成し、また共有ライブラリとして動作するために必要な基盤を整備することを目的としています。これにより、Goエコシステムがより柔軟になり、様々なデプロイメントシナリオに対応できるようになります。特に、ARMアーキテクチャ(5l)とx86-64アーキテクチャ(6l)に焦点を当て、これらのプラットフォームでの共有ライブラリの実現を目指しています。8l(x86-32)には、コンパイルエラーを回避するためのダミーサポートが追加されています。

前提知識の解説

1. リンカ (Linker)

リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能ファイルやライブラリを生成するプログラムです。このプロセスには、シンボルの解決(あるオブジェクトファイルで定義された関数や変数を、別のオブジェクトファイルから参照できるようにする)や、再配置(コードやデータのアドレスを最終的なメモリ配置に合わせて調整する)が含まれます。

2. Position-Independent Code (PIC)

PICは、メモリ上の任意のアドレスにロードされても正しく実行される機械語コードです。共有ライブラリは複数のプロセスによって同時に使用されるため、各プロセスのアドレス空間の異なる場所にロードされる可能性があります。PICは、絶対アドレスではなく、プログラムカウンタ(PC)からの相対アドレスや、Global Offset Table (GOT) を介した間接参照を用いることで、この要件を満たします。

3. 共有ライブラリ (Shared Library)

共有ライブラリ(またはダイナミックリンクライブラリ、DLL)は、複数のプログラムで共有されるコードとデータの集合です。これにより、ディスクスペースの節約、メモリ使用量の削減、およびプログラムの更新が容易になるという利点があります。プログラムは実行時に共有ライブラリをロードし、その中の関数を呼び出します。

4. 再配置 (Relocation)

再配置は、リンカがオブジェクトファイル内のアドレス参照を、最終的な実行可能ファイルやライブラリ内の実際のアドレスに調整するプロセスです。

  • D_ADDR: 絶対アドレスへの再配置を示します。これは通常、静的リンクされたバイナリで使用されます。
  • D_PCREL: プログラムカウンタ(PC)相対アドレスへの再配置を示します。これはPICでよく使用され、コードがメモリ上のどこにロードされても正しく機能するようにします。

5. RIP相対アドレッシング (RIP-relative Addressing)

x86-64アーキテクチャにおけるRIP相対アドレッシングは、命令ポインタ(RIPレジスタ)からの相対オフセットを使用してメモリ位置を参照する方式です。これはPICの実現に不可欠であり、コードがメモリ上の任意の位置にロードされても、データや他のコードへの参照が正しく解決されるようにします。

6. ELF (Executable and Linkable Format)

ELFは、Unix系システムで広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、セクション(コード、データ、シンボルテーブルなど)とセグメント(実行時にメモリにロードされる部分)で構成されます。

  • .rel および .rela セクション: ELFファイル内の再配置情報を格納するセクションです。.rel は加算値を持たない再配置エントリを、.rela は加算値を持つ再配置エントリを格納します。
  • Global Offset Table (GOT): PICにおいて、外部シンボルへの参照を解決するために使用されるデータ構造です。GOT内のエントリは、実行時にダイナミックリンカによって実際の外部シンボルのアドレスで更新されます。
  • Procedure Linkage Table (PLT): 外部関数呼び出しを解決するために使用されるコードセクションです。PLTはGOTと連携して動作し、初回呼び出し時にダイナミックリンカが実際の関数アドレスを解決し、以降の呼び出しではGOTを介して直接関数にジャンプできるようにします。
  • DT_INIT: ELFダイナミックセクションのタグの一つで、共有ライブラリがロードされた際に実行される初期化関数のアドレスを示します。
  • DT_NEEDED: 共有ライブラリが依存する他の共有ライブラリの名前を示します。
  • ET_EXEC: ELFファイルのタイプが実行可能ファイルであることを示します。
  • ET_DYN: ELFファイルのタイプが共有オブジェクト(共有ライブラリまたはPIC実行可能ファイル)であることを示します。

7. Goリンカ (5l, 6l, 8l)

Go言語のツールチェーンでは、各アーキテクチャに対応する独自のリンカが使用されます。

  • 5l: ARMアーキテクチャ用リンカ
  • 6l: x86-64アーキテクチャ用リンカ
  • 8l: x86-32アーキテクチャ用リンカ

技術的詳細

このコミットは、GoリンカがPICと共有ライブラリをサポートするための複数の重要な変更を導入しています。

  1. -shared フラグの導入:

    • src/cmd/5l/obj.c および src/cmd/6l/obj.c-shared コマンドラインフラグが追加されました。このフラグが指定されると、リンカはPICバイナリを生成するモードで動作します。
  2. 再配置タイプの変更 (D_ADDR から D_PCREL へ):

    • src/cmd/5l/asm.c および src/cmd/6l/span.c において、flag_shared が真の場合、一部の再配置タイプが D_ADDR(絶対アドレス)から D_PCREL(PC相対アドレス)に変更されるようになりました。これにより、生成されるコードが位置独立になります。
    • 特にARM (5l) では、D_PCREL を使用する際に rel->add にPC相対オフセットが適切に計算されるよう修正されています。
  3. RIP相対アドレッシングのサポート (x86-64):

    • src/cmd/6l/span.casmandsz 関数において、flag_shared が真の場合、D_STATICD_EXTERN タイプのシンボルに対するアドレス解決がRIP相対アドレッシングを考慮するように変更されています。
    • src/cmd/6l/asm.casmins 関数では、REXプレフィックスの追加による命令長の変更が再配置オフセットに影響を与えるため、r->off の調整と r->add の再計算が行われています。
  4. Optab の変更と LPCREL フラグ:

    • src/cmd/5l/l.hLPCREL フラグが Optab 構造体に追加されました。これは、その命令がPC相対アドレッシングを必要とすることを示します。
    • src/cmd/5l/optab.c では、AMOVW, AMOVB, AMOVBU, AMOVF, AMOVH, AMOVHU などの命令に対して LPCREL フラグが設定され、pcrelsiz が追加されています。
    • src/cmd/5l/span.cbuildop 関数では、LPCREL フラグを持つ命令のサイズが flag_shared の値に基づいて調整されます。これにより、PICコード生成時に必要な追加の命令(例:PC相対アドレスを計算するための命令)のためのスペースが確保されます。
  5. 動的再配置の追加 (adddynrela):

    • src/cmd/5l/asm.c および src/cmd/6l/asm.cadddynrela 関数が追加されました。この関数は、共有ライブラリに必要な動的再配置エントリ(.rel または .rela セクション)を追加します。
    • ARM (5l) では R_ARM_RELATIVE タイプ、x86-64 (6l) では R_X86_64_RELATIVE タイプが使用されます。これらの再配置は、ロード時にダイナミックリンカによって解決され、共有ライブラリ内の絶対アドレス参照を調整します。
  6. .rel / .rela シンボルのルックアップ (lookuprel):

    • src/cmd/5l/asm.c および src/cmd/6l/asm.clookuprel 関数が追加され、それぞれ .rel および .rela シンボルをルックアップするようになりました。これらのシンボルは、動的再配置情報を格納するセクションを表します。
  7. データセクションの変更と SDATARELRO:

    • src/cmd/ld/lib.h に新しいシンボルタイプ SDATARELRO が追加されました。これは、動的に再配置される読み取り専用データ(Data Relocated Read-Only)を示します。
    • src/cmd/ld/data.cdodata 関数では、flag_shared が真の場合、s->rel_ro フラグが設定されたシンボルが SDATARELRO タイプに変換され、.data.rel.ro という新しいセクションに配置されるようになりました。このセクションは、共有ライブラリにおいて、ロード時に再配置が必要な読み取り専用データを格納するために使用されます。
  8. ELFヘッダの変更:

    • src/cmd/ld/elf.c において、flag_shared が真の場合、ELFヘッダの e_type フィールドが ET_EXEC(実行可能ファイル)から ET_DYN(共有オブジェクト)に変更されるようになりました。これにより、生成されるファイルが共有ライブラリとして正しく識別されます。
    • また、DT_INIT エントリがELFダイナミックセクションに追加され、共有ライブラリの初期化エントリポイント (LIBINITENTRY) が設定されるようになりました。
  9. 初期化エントリポイントの追加:

    • src/cmd/5l/l.h, src/cmd/6l/l.h, src/cmd/8l/l.hLIBINITENTRY が追加されました。これは共有ライブラリの初期化ルーチンのエントリポイントを指します。
    • src/cmd/ld/lib.c では、flag_shared が真の場合に _rt0_%s_%s_lib という形式で LIBINITENTRY が設定されます。
    • src/cmd/ld/go.cdeadcode 関数では、flag_shared が真の場合に LIBINITENTRY がマークされ、不要なコードとして削除されないようにしています。

コアとなるコードの変更箇所

このコミットのコアとなる変更は、主に以下のファイルに集中しています。

  • src/cmd/5l/asm.c: ARMリンカにおける再配置処理の変更、特に adddynrela 関数の追加と D_PCREL の導入。
  • src/cmd/5l/l.h: Prog 構造体への pcrel フィールドの追加、Sym 構造体への rel_ro フィールドの追加、Optab 構造体への pcrelsiz フィールドと LPCREL フラグの追加、LIBINITENTRY の定義。
  • src/cmd/5l/optab.c: Optab テーブルにおける LPCREL フラグの設定と pcrelsiz の指定。
  • src/cmd/5l/span.c: addpool および aclass 関数における flag_shared に基づく再配置タイプの決定と Optab サイズの調整。
  • src/cmd/6l/asm.c: x86-64リンカにおける adddynrela 関数の追加と asmins 関数でのREXプレフィックス考慮。
  • src/cmd/6l/span.c: x86-64リンカにおける oclass および vaddr 関数での flag_shared に基づく再配置タイプの決定、asmandsz 関数でのRIP相対アドレッシングの考慮。
  • src/cmd/ld/data.c: dynrelocsym 関数における動的再配置の追加ロジック、dodata 関数における SDATARELRO タイプの処理と .data.rel.ro セクションの作成。
  • src/cmd/ld/elf.c: doelf 関数におけるELFヘッダの e_type の変更 (ET_EXEC から ET_DYN へ) および DT_INIT エントリの追加。
  • src/cmd/ld/lib.c: libinit 関数における LIBINITENTRY の設定。
  • src/cmd/ld/lib.h: SDATARELRO シンボルタイプと flag_shared グローバル変数の定義、adddynrela および lookuprel 関数のプロトタイプ宣言。

コアとなるコードの解説

src/cmd/5l/asm.c (ARMリンカの再配置処理)

// ...
+Sym *
+lookuprel(void)
+{
+	return lookup(".rel", 0);
+}
+
+void
+adddynrela(Sym *rel, Sym *s, Reloc *r)
+{
+	addaddrplus(rel, s, r->off);
+	adduint32(rel, R_ARM_RELATIVE);
+}
+// ...
@@ -859,15 +872,22 @@ if(debug['G']) print("%ux: %s: arm %d\n", (uint32)(p->pc), p->from.sym->name, p-
 			rel = addrel(cursym);
 			rel->off = pc - cursym->value;
 			rel->siz = 4;
-			rel->type = D_ADDR;
 			rel->sym = p->to.sym;
 			rel->add = p->to.offset;
+			if(flag_shared) {
+				rel->type = D_PCREL;
+				rel->add += pc - p->pcrel->pc - 8;
+			} else
+				rel->type = D_ADDR;
 			o1 = 0;
 		}
 		break;

lookuprel.rel セクションのシンボルを取得します。adddynrela はARMアーキテクチャ用の動的再配置エントリを追加します。R_ARM_RELATIVE は、ロード時にベースアドレスからの相対オフセットで解決される再配置タイプです。 既存の再配置処理では、flag_shared が真の場合に rel->typeD_ADDR から D_PCREL に変更され、rel->add にPC相対オフセットが計算されるようになります。これは、PICを生成するために不可欠な変更です。

src/cmd/5l/l.h (構造体とフラグの定義)

// ...
 struct	Prog
 {
 	// ...
 	Prog*\tpcrel; // 追加: PC相対アドレッシングの基準となるProg
 	// ...
 };
// ...
 struct	Sym
 {
 	// ...
 	int 	rel_ro; // 追加: 動的に再配置される読み取り専用データかを示すフラグ
 };
// ...
 struct	Optab
 {
 	// ...
 	uchar\tpcrelsiz; // 追加: PC相対アドレッシングに必要な追加サイズ
 };
 enum
 {
 	// ...
 	LPCREL		= 1<<3, // 追加: PC相対アドレッシングが必要な命令を示すフラグ
 	// ...
 	C_LCONADDR, // 追加: PC相対アドレッシングを伴う定数アドレス
 	// ...
 EXTERN	char*\tLIBINITENTRY; // 追加: 共有ライブラリのエントリポイント

Prog 構造体に pcrel が追加され、PC相対アドレッシングの基準となる命令を指すようになります。Sym 構造体には rel_ro が追加され、動的に再配置される読み取り専用データであることを示します。Optab には pcrelsizLPCREL フラグが追加され、PC相対アドレッシングをサポートするための命令テーブルの拡張が行われています。LIBINITENTRY は共有ライブラリの初期化エントリポイントを定義します。

src/cmd/ld/data.c (データセクションと動的再配置の処理)

// ...
 void
 dynrelocsym(Sym *s)
 {
 	Reloc *r;
+	Sym *rel;
+	Sym *got;
+	
 	if(HEADTYPE == Hwindows) {
 		// ...
 	}
 
 	if(s->type == SDYNIMPORT || s->type == SHOSTOBJ || s->type == SEXTERN)
 		return;
 
-	for(r=s->r; r<s->r+s->nr; r++)
+	got = rel = nil;
+	if(flag_shared) {
+		rel = lookuprel();
+		got = lookup(".got", 0);
+	}
+	s->rel_ro = 0;
+	for(r=s->r; r<s->r+s->nr; r++) {
 		if(r->sym != S && r->sym->type == SDYNIMPORT || r->type >= 256)
 			adddynrel(s, r);
+		if(flag_shared && r->sym != S && (r->sym->dynimpname == nil || r->sym->dynexport) && r->type == D_ADDR
+				&& (s == got || s->type == SDATA || s->type == SGOSTRING || s->type == STYPE || s->type == SRODATA)) {
+			// Create address based RELATIVE relocation
+			adddynrela(rel, s, r);
+			if(s->type < SNOPTRDATA)
+				s->rel_ro = 1;
+		}
+	}
 }
// ...
@@ -968,6 +1007,12 @@ dodata(void)
 	}
 	*l = nil;
 
+	if(flag_shared) {
+		for(s=datap; s != nil; s = s->next) {
+			if(s->rel_ro)
+				s->type = SDATARELRO;
+		}
+	}
 	datap = datsort(datap);
 
 	/*
@@ -1004,7 +1049,7 @@ dodata(void)
 	sect->vaddr = datsize;
 	lookup("noptrdata", 0)->sect = sect;
 	lookup("enoptrdata", 0)->sect = sect;
-	for(; s != nil && s->type < SDATA; s = s->next) {
+	for(; s != nil && s->type < SDATARELRO; s = s->next) {
 		s->sect = sect;
 		s->type = SDATA;
 		t = alignsymsize(s->size);
@@ -1015,12 +1060,34 @@ dodata(void)
 	sect->len = datsize - sect->vaddr;
 	datsize = rnd(datsize, PtrSize);
 
+	/* dynamic relocated rodata */
+	if(flag_shared) {
+		sect = addsection(&segdata, ".data.rel.ro", 06);
+		sect->vaddr = datsize;
+		lookup("datarelro", 0)->sect = sect;
+		lookup("edatarelro", 0)->sect = sect;
+		for(; s != nil && s->type == SDATARELRO; s = s->next) {
+			if(s->align != 0)
+				datsize = rnd(datsize, s->align);
+			s->sect = sect;
+			s->type = SDATA;
+			s->value = datsize;
+			datsize += rnd(s->size, PtrSize);
+		}
+		sect->len = datsize - sect->vaddr;
+		datsize = rnd(datsize, PtrSize);
+	}
+
 	/* data */
 	sect = addsection(&segdata, ".data", 06);
 	sect->vaddr = datsize;
 	lookup("data", 0)->sect = sect;
 	lookup("edata", 0)->sect = sect;
 	for(; s != nil && s->type < SBSS; s = s->next) {
+		if(s->type == SDATARELRO) {
+			cursym = s;
+			diag("unexpected symbol type %d", s->type);
+		}
 		s->sect = sect;
 		s->type = SDATA;
 		t = alignsymsize(s->size);

dynrelocsym 関数は、flag_shared が真の場合に、D_ADDR タイプの再配置を持つシンボルに対して adddynrela を呼び出し、動的再配置エントリを追加します。また、s->rel_ro フラグを設定します。 dodata 関数では、flag_shared が真の場合に s->rel_ro が設定されたシンボルを SDATARELRO タイプに変換し、.data.rel.ro という新しいセクションに配置します。これは、共有ライブラリにおける読み取り専用データの動的再配置をサポートするために重要です。

src/cmd/ld/elf.c (ELFヘッダの変更)

// ...
@@ -1277,7 +1286,11 @@ asmbelf(vlong symo)
 	eh->ident[EI_DATA] = ELFDATA2LSB;
 	eh->ident[EI_VERSION] = EV_CURRENT;
 
-	eh->type = ET_EXEC;
+	if(flag_shared)
+		eh->type = ET_DYN;
+	else
+		eh->type = ET_EXEC;
+
 	eh->version = EV_CURRENT;
 	eh->entry = entryvalue();
 // ...

asmbelf 関数では、flag_shared が真の場合にELFヘッダの e_type フィールドが ET_EXEC から ET_DYN に変更されます。これにより、生成されるファイルが共有オブジェクト(共有ライブラリまたはPIC実行可能ファイル)として正しく識別され、ダイナミックリンカによって適切に処理されるようになります。

関連リンク

参考にした情報源リンク