[インデックス 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と共有ライブラリをサポートするための複数の重要な変更を導入しています。
-
-sharedフラグの導入:src/cmd/5l/obj.cおよびsrc/cmd/6l/obj.cに-sharedコマンドラインフラグが追加されました。このフラグが指定されると、リンカはPICバイナリを生成するモードで動作します。
-
再配置タイプの変更 (
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相対オフセットが適切に計算されるよう修正されています。
-
RIP相対アドレッシングのサポート (x86-64):
src/cmd/6l/span.cのasmandsz関数において、flag_sharedが真の場合、D_STATICやD_EXTERNタイプのシンボルに対するアドレス解決がRIP相対アドレッシングを考慮するように変更されています。src/cmd/6l/asm.cのasmins関数では、REXプレフィックスの追加による命令長の変更が再配置オフセットに影響を与えるため、r->offの調整とr->addの再計算が行われています。
-
Optabの変更とLPCRELフラグ:src/cmd/5l/l.hにLPCRELフラグがOptab構造体に追加されました。これは、その命令がPC相対アドレッシングを必要とすることを示します。src/cmd/5l/optab.cでは、AMOVW,AMOVB,AMOVBU,AMOVF,AMOVH,AMOVHUなどの命令に対してLPCRELフラグが設定され、pcrelsizが追加されています。src/cmd/5l/span.cのbuildop関数では、LPCRELフラグを持つ命令のサイズがflag_sharedの値に基づいて調整されます。これにより、PICコード生成時に必要な追加の命令(例:PC相対アドレスを計算するための命令)のためのスペースが確保されます。
-
動的再配置の追加 (
adddynrela):src/cmd/5l/asm.cおよびsrc/cmd/6l/asm.cにadddynrela関数が追加されました。この関数は、共有ライブラリに必要な動的再配置エントリ(.relまたは.relaセクション)を追加します。- ARM (
5l) ではR_ARM_RELATIVEタイプ、x86-64 (6l) ではR_X86_64_RELATIVEタイプが使用されます。これらの再配置は、ロード時にダイナミックリンカによって解決され、共有ライブラリ内の絶対アドレス参照を調整します。
-
.rel/.relaシンボルのルックアップ (lookuprel):src/cmd/5l/asm.cおよびsrc/cmd/6l/asm.cにlookuprel関数が追加され、それぞれ.relおよび.relaシンボルをルックアップするようになりました。これらのシンボルは、動的再配置情報を格納するセクションを表します。
-
データセクションの変更と
SDATARELRO:src/cmd/ld/lib.hに新しいシンボルタイプSDATARELROが追加されました。これは、動的に再配置される読み取り専用データ(Data Relocated Read-Only)を示します。src/cmd/ld/data.cのdodata関数では、flag_sharedが真の場合、s->rel_roフラグが設定されたシンボルがSDATARELROタイプに変換され、.data.rel.roという新しいセクションに配置されるようになりました。このセクションは、共有ライブラリにおいて、ロード時に再配置が必要な読み取り専用データを格納するために使用されます。
-
ELFヘッダの変更:
src/cmd/ld/elf.cにおいて、flag_sharedが真の場合、ELFヘッダのe_typeフィールドがET_EXEC(実行可能ファイル)からET_DYN(共有オブジェクト)に変更されるようになりました。これにより、生成されるファイルが共有ライブラリとして正しく識別されます。- また、
DT_INITエントリがELFダイナミックセクションに追加され、共有ライブラリの初期化エントリポイント (LIBINITENTRY) が設定されるようになりました。
-
初期化エントリポイントの追加:
src/cmd/5l/l.h,src/cmd/6l/l.h,src/cmd/8l/l.hにLIBINITENTRYが追加されました。これは共有ライブラリの初期化ルーチンのエントリポイントを指します。src/cmd/ld/lib.cでは、flag_sharedが真の場合に_rt0_%s_%s_libという形式でLIBINITENTRYが設定されます。src/cmd/ld/go.cのdeadcode関数では、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->type が D_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 には pcrelsiz と LPCREL フラグが追加され、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実行可能ファイル)として正しく識別され、ダイナミックリンカによって適切に処理されるようになります。
関連リンク
参考にした情報源リンク
- Position-Independent Code (PIC) - Wikipedia
- Shared library - Wikipedia
- ELF (Executable and Linkable Format) - Wikipedia
- System V Application Binary Interface - DRAFT - 24 February 1997 (特にELFに関する章)
- Go言語のツールチェーンに関するドキュメント (公式)
- Go言語のリンカの内部構造に関する記事やブログ (一般的な情報源)
- ARM Architecture Reference Manual (ARMv7-A/R)
- Intel 64 and IA-32 Architectures Software Developer's Manuals
- ELF Relocation Types (各アーキテクチャ固有の再配置タイプ)
- Global Offset Table (GOT) and Procedure Linkage Table (PLT)
- Understanding ELF - Part 2: Sections and Segments
- Understanding ELF - Part 3: Relocations
- Understanding ELF - Part 4: Dynamic Linking