[インデックス 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