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

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

このコミットは、Go言語のリンカ(cmd/ldおよびアーキテクチャ固有のリンカであるcmd/5l, cmd/6l, cmd/8l)におけるシンボルの動的インポート名の管理方法を改善するものです。具体的には、Sym構造体からdynimpnameフィールドを削除し、代わりにextnameフィールドとシンボルのtypeフィールド(特にSDYNIMPORT型)を使用して、動的にインポートされるシンボルの外部名を管理し、その状態を判定するように変更しています。これにより、コードの混乱を解消し、リンカのロジックをより明確にすることを目的としています。

コミット

commit 96b243fa47d64e0e9538222e114efe53f86ba184
Author: Russ Cox <rsc@golang.org>
Date:   Sun Mar 10 18:19:53 2013 -0400

    cmd/ld: replace dynimpname with extname
    
    Dynimpname was getting too confusing.
    Replace flag-like checks with tests of s->type.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7594046

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

https://github.com/golang/go/commit/96b243fa47d64e0e9538222e114efe53f86ba184

元コミット内容

cmd/ld: replace dynimpname with extname

Dynimpname was getting too confusing.
Replace flag-like checks with tests of s->type.

R=ken2
CC=golang-dev
https://golang.org/cl/7594046

変更の背景

このコミットの主な背景は、Goリンカ内部で動的にインポートされるシンボル(共有ライブラリなどからインポートされるシンボル)の管理に使用されていたdynimpnameフィールドが「混乱を招いていた(getting too confusing)」という点にあります。

Goのリンカは、プログラムが実行時に必要とする外部のシンボル(関数や変数など)を解決する役割を担っています。特にCgo(GoとC言語の相互運用機能)を使用する場合や、共有ライブラリを扱う場合には、動的なシンボル解決が必要になります。

dynimpnameは、シンボルが動的にインポートされる際に使用される外部名(リンカが参照する名前)を保持するためのフィールドでした。しかし、このフィールドの存在チェック(targ->dynimpname != nil)が、シンボルが動的にインポートされるかどうかを判断するための「フラグのようなチェック」として使われており、その意味合いが曖昧で、コードの可読性や保守性を損ねていたと考えられます。

この混乱を解消し、リンカのロジックをより明確にするために、以下の変更が導入されました。

  1. dynimpnameの廃止: Sym構造体からdynimpnameフィールドを削除します。
  2. extnameの導入: シンボルの外部名を保持するために、より汎用的なextnameフィールドを導入します。これは動的インポートシンボルだけでなく、他の種類の外部シンボルにも適用できる可能性があります。
  3. s->typeによる状態判定: シンボルが動的にインポートされるかどうかを判断するために、dynimpname != nilのようなフラグチェックではなく、シンボルのtypeフィールドがSDYNIMPORTであるかどうかを直接チェックするように変更します。SDYNIMPORTは、シンボルが動的にインポートされることを明示的に示すシンボルタイプです。

これにより、リンカのコードは、シンボルの種類や状態をより明確に表現できるようになり、将来的な拡張やデバッグが容易になります。

前提知識の解説

このコミットを理解するためには、以下のGoリンカおよび動的リンクに関する基本的な概念を理解しておく必要があります。

1. Goリンカ (cmd/ld, cmd/5l, cmd/6l, cmd/8l)

Go言語のビルドプロセスにおいて、リンカはコンパイルされたオブジェクトファイル(.oファイル)を結合し、実行可能ファイルや共有ライブラリを生成する役割を担います。Goのリンカは、伝統的なUnix系システムのリンカ(ld)とは異なり、Go言語のランタイムやガベージコレクションなどの特性を考慮した独自のリンカが実装されています。

  • cmd/ld: Goリンカの主要な部分であり、アーキテクチャに依存しない共通のリンキングロジックを扱います。
  • cmd/5l, cmd/6l, cmd/8l: これらはそれぞれ、ARM (5l), AMD64 (6l), 386 (8l) アーキテクチャに特化したリンカのフロントエンドです。アーキテクチャ固有の命令セットやレジスタ、呼び出し規約などを考慮したコード生成やリロケーション処理を行います。

2. シンボル (Sym構造体)

リンカは、プログラム内の関数、変数、定数などを「シンボル」として扱います。Goリンカの内部では、これらのシンボルはSym構造体で表現されます。Sym構造体には、シンボルの名前、アドレス、サイズ、型、バージョンなど、リンキングに必要な情報が含まれています。

  • name: シンボルの内部名(Goコード内で使用される名前)。
  • type: シンボルの種類を示すフィールド。例えば、STEXT(テキストセクションのシンボル、つまり関数)、SDATA(データセクションのシンボル)、SDYNIMPORT(動的にインポートされるシンボル)などがあります。このコミットでは、SDYNIMPORTが重要な役割を果たします。
  • extname: このコミットで導入されたフィールドで、シンボルが外部のオブジェクトファイルや共有ライブラリで参照される際に使用される名前を保持します。
  • dynimpname: このコミットで削除されたフィールドで、かつて動的にインポートされるシンボルの外部名を保持していました。

3. 動的リンクと共有ライブラリ

動的リンクは、プログラムの実行時に必要なライブラリをロードし、リンクする仕組みです。これにより、複数のプログラムが同じライブラリを共有でき、ディスクスペースの節約やメモリ効率の向上が図れます。

  • 共有ライブラリ (Shared Libraries): .so (Linux), .dylib (macOS), .dll (Windows) などの拡張子を持つファイルで、複数のプログラムから共有されるコードやデータを含みます。
  • 動的インポートシンボル: 共有ライブラリからプログラムにインポートされる関数や変数などのシンボルです。

4. リロケーション (Relocation)

リロケーションとは、コンパイル時にアドレスが確定できないシンボル参照(例えば、別のオブジェクトファイルや共有ライブラリにある関数への呼び出し)を、リンキング時に正しいメモリアドレスに解決するプロセスです。リンカは、オブジェクトファイル内のリロケーションエントリを読み取り、それに基づいてコードやデータを修正します。

  • リロケーションタイプ: リロケーションには様々な種類があり、アーキテクチャや参照の種類によって異なります。例えば、R_ARM_PLT32 (ARMアーキテクチャのPLT相対リロケーション), R_X86_64_PC32 (AMD64アーキテクチャのPC相対リロケーション) などがあります。

5. GOT (Global Offset Table) と PLT (Procedure Linkage Table)

動的リンクにおいて、共有ライブラリ内の関数を呼び出す際に使用される重要なデータ構造です。

  • GOT (Global Offset Table): グローバル変数や関数へのオフセット(アドレス)を格納するテーブルです。プログラムが共有ライブラリ内のデータや関数にアクセスする際に、GOTを介して間接的に参照します。
  • PLT (Procedure Linkage Table): 共有ライブラリ内の関数を呼び出すためのスタブ(小さなコード片)のテーブルです。プログラムが共有ライブラリ内の関数を初めて呼び出す際に、PLTのエントリが実行され、リンカによって実際の関数のアドレスが解決され、GOTに格納されます。2回目以降の呼び出しでは、PLTは直接GOTを介して関数を呼び出すようになります。

6. Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、Goプログラムは既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。Cgoは動的リンクと密接に関連しており、Cライブラリの関数は通常、動的にインポートされるシンボルとして扱われます。

  • CgoExportDynamic / CgoExportStatic: CgoでGoの関数をCから呼び出せるようにエクスポートする際に、動的リンク(共有ライブラリとしてエクスポート)するか、静的リンク(静的ライブラリに含める)するかを示すフラグです。

これらの概念を理解することで、このコミットがGoリンカの内部でどのようにシンボル解決と動的リンクのロジックを改善しているのかを深く把握することができます。

技術的詳細

このコミットの技術的な詳細は、主にSym構造体の変更と、その変更に伴うリンカの各部分でのシンボル状態判定ロジックの修正に集約されます。

1. Sym構造体の変更

最も根本的な変更は、src/cmd/*/l.h5l, 6l, 8lのヘッダファイル)におけるSym構造体の定義です。

  • dynimpnameフィールドの削除:

    --- a/src/cmd/5l/l.h
    +++ b/src/cmd/5l/l.h
    @@ -163,7 +164,6 @@ struct	Sym
     	Sym*\treachparent;
     	Sym*\tqueue;
     	char*\tfile;
    -	char*\tdynimpname;
     	char*\tdynimplib;
     	char*\tdynimpvers;
     	struct Section*\tsect;
    

    これにより、Sym構造体からdynimpnameという特定の目的(動的インポート名)を持つフィールドが削除されました。

  • extnameフィールドの追加:

    --- a/src/cmd/5l/l.h
    +++ b/src/cmd/5l/l.h
    @@ -134,6 +134,7 @@ struct	Prog
     struct	Sym
     {
     	char*\tname;
    +	char*\textname;\t// name used in external object files
     	short\ttype;
     	short\tversion;
     	uchar\tdupok;
    

    extnameという新しいフィールドが追加されました。コメントにあるように、これは「外部オブジェクトファイルで使用される名前」を意味します。これにより、動的インポートシンボルだけでなく、他の種類の外部シンボルにも適用できる、より汎用的な外部名管理が可能になります。

2. dynimpnameチェックのs->type == SDYNIMPORTへの置き換え

dynimpname != nilという「フラグのようなチェック」が、シンボルのtypeフィールドがSDYNIMPORTであるかどうかの直接的なチェックに置き換えられました。これは、リンカのコードベース全体にわたる広範な変更です。

例1: adddynrel関数(リロケーション処理)

src/cmd/5l/asm.c, src/cmd/6l/asm.c, src/cmd/8l/asm.cadddynrel関数は、リロケーションエントリを処理し、必要に応じて動的シンボル(PLTやGOTエントリ)を追加する役割を担っています。

変更前は、targ->dynimpname != nilという条件で、ターゲットシンボルが動的にインポートされるかどうかを判断していました。

// 変更前 (src/cmd/5l/asm.c)
if(targ->dynimpname != nil && !(targ->cgoexport & CgoExportDynamic)) {
    addpltsym(targ);
    r->sym = lookup(".plt", 0);
    r->add = braddoff(r->add, targ->plt / 4);
}

変更後は、targ->type == SDYNIMPORTという、より明確な条件に置き換えられました。

// 変更後 (src/cmd/5l/asm.c)
if(targ->type == SDYNIMPORT) {
    addpltsym(targ);
    r->sym = lookup(".plt", 0);
    r->add = braddoff(r->add, targ->plt / 4);
}

この変更により、シンボルの型情報が直接的に利用され、コードの意図がより明確になりました。SDYNIMPORTは、シンボルが動的にインポートされることを明示的に示すため、dynimpnameの有無という間接的な情報よりも信頼性が高く、理解しやすいです。

例2: adddynsym関数(動的シンボルテーブルへの追加)

src/cmd/*/asm.cadddynsym関数は、動的シンボルテーブル(.dynsymセクション)にシンボルを追加する際に、そのシンボルの外部名を使用します。

変更前は、s->dynimpnameが存在しない場合にs->nameをフォールバックとして使用していました。

// 変更前 (src/cmd/5l/asm.c)
name = s->dynimpname;
if(name == nil)
    name = s->name;
adduint32(d, addstring(lookup(".dynstr", 0), name));

変更後は、直接s->extnameを使用するように変更されました。

// 変更後 (src/cmd/5l/asm.c)
name = s->extname;
adduint32(d, addstring(lookup(".dynstr", 0), name));

また、adddynsym関数内で、シンボルが未定義セクション(SHN_UNDEF)に属するかどうかの判定も、!s->cgoexport && s->dynimpname != nilからs->type == SDYNIMPORTに置き換えられています。

// 変更前 (src/cmd/5l/asm.c)
if(!(s->cgoexport & CgoExportDynamic) && s->dynimpname != nil)
    adduint16(d, SHN_UNDEF);

// 変更後 (src/cmd/5l/asm.c)
if(s->type == SDYNIMPORT)
    adduint16(d, SHN_UNDEF);

3. loadcgo関数(Cgo関連の処理)

src/cmd/ld/go.cloadcgo関数は、Cgo関連のディレクティブを処理し、シンボル情報を設定します。

変更前は、動的にインポートされるシンボルのdynimpnameを設定していました。

// 変更前 (src/cmd/ld/go.c)
s->dynimplib = lib;
s->dynimpname = remote;
s->dynimpvers = q;
s->type = SDYNIMPORT;

変更後は、extnameを設定するように変更されました。

// 変更後 (src/cmd/ld/go.c)
s->dynimplib = lib;
s->extname = remote; // dynimpname の代わりに extname を設定
s->dynimpvers = q;
s->type = SDYNIMPORT;

また、Cgoエクスポートシンボルの競合チェックもs->dynimpnameからs->extnameに切り替わっています。

4. _lookup関数(シンボルルックアップ)

src/cmd/ld/lib.c_lookup関数は、新しいシンボルが作成される際に、そのextnameを初期化するように変更されました。

// 変更後 (src/cmd/ld/lib.c)
s = newsym(symb, v);
s->extname = s->name; // 新しいシンボル作成時に extname を name で初期化
s->hash = hash[h];
hash[h] = s;

これは、デフォルトでシンボルの外部名が内部名と同じであることを保証し、後で必要に応じて変更できるようにするための初期設定です。

5. ldelf.c, ldmacho.c(オブジェクトファイル読み込み)

これらのファイルでは、オブジェクトファイルからシンボルを読み込む際に、dynimpnamenilに設定していた箇所が削除されました。これは、dynimpnameフィールド自体がなくなったため、不要になった変更です。

6. macho.c, pe.c(Mach-O/PEファイル生成)

Mach-O (macOS) および PE (Windows) 形式の実行可能ファイルを生成するリンカのコードでも、dynimpnameへの参照がextnameまたはs->type == SDYNIMPORTに置き換えられています。

特にmacho.cでは、シンボルをソートする際の比較関数scmpxsymnameヘルパー関数が使用されていましたが、この関数が削除され、直接s->extnameを比較するように変更されました。

// 変更前 (src/cmd/ld/macho.c)
// static char* xsymname(Sym *s) { ... }
// return strcmp(xsymname(s1), xsymname(s2));

// 変更後 (src/cmd/ld/macho.c)
return strcmp(s1->extname, s2->extname);

また、シンボルテーブルにシンボル名を追加する際も、xsymname(s)からs->extnameに直接変更されています。

これらの変更は、リンカの内部ロジックをより一貫性があり、理解しやすいものにすることを目的としています。dynimpnameという特定の目的を持つフィールドを廃止し、より汎用的なextnameと、シンボルの状態を明確に示すSDYNIMPORT型を使用することで、コードの意図が明確になり、将来的なメンテナンスや機能追加が容易になります。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下の3つのパターンに分類できます。

  1. Sym構造体からのdynimpnameフィールドの削除とextnameフィールドの追加:

    • src/cmd/5l/l.h
    • src/cmd/6l/l.h
    • src/cmd/8l/l.h
  2. dynimpname != nilチェックのs->type == SDYNIMPORTへの置き換え:

    • src/cmd/5l/asm.c
    • src/cmd/6l/asm.c
    • src/cmd/8l/asm.c
    • src/cmd/ld/data.c
    • src/cmd/ld/pe.c (特にinitdynimport, initdynexport, scmp関数)
  3. dynimpnameへの参照のextnameへの置き換え:

    • src/cmd/5l/asm.c (adddynsym関数)
    • src/cmd/6l/asm.c (adddynsym関数)
    • src/cmd/8l/asm.c (adddynsym関数)
    • src/cmd/ld/elf.c (elfdynhash関数)
    • src/cmd/ld/go.c (loadcgo関数)
    • src/cmd/ld/lib.c (_lookup関数)
    • src/cmd/ld/macho.c (scmp, machosymtab関数)
    • src/cmd/ld/pe.c (addimports, addexports関数)

以下に、それぞれの代表的な変更箇所を抜粋して示します。

Sym構造体の変更 (例: src/cmd/5l/l.h)

--- a/src/cmd/5l/l.h
+++ b/src/cmd/5l/l.h
@@ -134,6 +134,7 @@ struct	Prog
 struct	Sym
 {
 	char*\tname;
+	char*\textname;\t// name used in external object files
 	short\ttype;
 	short\tversion;
 	uchar\tdupok;
@@ -163,7 +164,6 @@ struct	Sym
 	Sym*\treachparent;\
 	Sym*\tqueue;\
 	char*\tfile;\
-	char*\tdynimpname;\
 	char*\tdynimplib;\
 	char*\tdynimpvers;\
 	struct Section*\tsect;

dynimpname != nilチェックの置き換え (例: src/cmd/5l/asm.c adddynrel関数)

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -125,7 +125,7 @@ adddynrel(Sym *s, Reloc *r)
 \t// Handle relocations found in ELF object files.
 \tcase 256 + R_ARM_PLT32:
 \t\tr->type = D_CALL;
-\t\tif(targ->dynimpname != nil && !(targ->cgoexport & CgoExportDynamic)) {
+\t\tif(targ->type == SDYNIMPORT) {
 \t\t\taddpltsym(targ);
 \t\t\tr->sym = lookup(\".plt\", 0);
 \t\t\tr->add = braddoff(r->add, targ->plt / 4);
@@ -138,7 +138,7 @@ adddynrel(Sym *s, Reloc *r)
 \t\treturn;
 
 \tcase 256 + R_ARM_GOT32: // R_ARM_GOT_BREL
-\t\tif(targ->dynimpname == nil || (targ->cgoexport & CgoExportDynamic)) {
+\t\tif(targ->type != SDYNIMPORT) {
 \t\t\taddgotsyminternal(targ);
 \t\t} else {
 \t\t\taddgotsym(targ);

dynimpnameへの参照のextnameへの置き換え (例: src/cmd/5l/asm.c adddynsym関数)

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -437,20 +437,13 @@ adddynsym(Sym *s)
 \tif(s->dynid >= 0)
 \t\treturn;
 
-\tif(s->dynimpname == nil) {
-\t\ts->dynimpname = s->name;
-\t\t//diag("adddynsym: no dynamic name for %s", s->name);
-\t}\
-\n \tif(iself) {
 \t\ts->dynid = nelfsym++;
 
 \t\td = lookup(\".dynsym\", 0);
 
 \t\t/* name */
-\t\tname = s->dynimpname;
-\t\tif(name == nil)
-\t\t\tname = s->name;
+\t\tname = s->extname;
 \t\tadduint32(d, addstring(lookup(\".dynstr\", 0), name));
 
 \t\t/* value */
@@ -472,7 +465,7 @@ adddynsym(Sym *s)
 \t\tadduint8(d, 0);
 
 \t\t/* shndx */
-\t\tif(!(s->cgoexport & CgoExportDynamic) && s->dynimpname != nil)
+\t\tif(s->type == SDYNIMPORT)
 \t\t\tadduint16(d, SHN_UNDEF);
 \t\telse {
 \t\t\tswitch(s->type) {

コアとなるコードの解説

1. Sym構造体の変更

  • char* extname; // name used in external object files: この新しいフィールドは、シンボルが外部のオブジェクトファイル(特に共有ライブラリ)で参照される際に使用される名前を格納します。dynimpnameが動的インポートに特化していたのに対し、extnameはより汎用的な「外部名」として機能します。これにより、リンカはシンボルの外部表現を一元的に管理できるようになります。

  • char* dynimpname; の削除: このフィールドの削除は、その役割がextnametypeフィールド(特にSDYNIMPORT)に分散されたことを意味します。これにより、シンボルの外部名と動的インポートのセマンティクスが分離され、コードの混乱が解消されます。

2. dynimpname != nilチェックのs->type == SDYNIMPORTへの置き換え

これは、リンカのロジックにおけるシンボルの状態判定の根本的な変更です。

  • 変更前: if(targ->dynimpname != nil && !(targ->cgoexport & CgoExportDynamic)) この条件は、「ターゲットシンボルに動的インポート名が設定されており、かつCgoによって動的にエクスポートされていない場合」という複雑な意味合いを持っていました。dynimpname != nilが、シンボルが動的にインポートされることを示す「フラグ」のように使われていたため、その意図が不明瞭でした。

  • 変更後: if(targ->type == SDYNIMPORT) この条件は、「ターゲットシンボルの型がSDYNIMPORTである場合」という非常に明確な意味を持ちます。SDYNIMPORTは、Goリンカが内部的に定義するシンボル型の一つで、シンボルが動的にインポートされることを明示的に示します。これにより、コードの可読性が大幅に向上し、リンカの動作を理解しやすくなります。シンボルの型情報が直接的にそのセマンティクスを表現するため、より堅牢なロジックになります。

3. dynimpnameへの参照のextnameへの置き換え

これは、シンボルの外部名を取得する際のフィールドの変更です。

  • 変更前:

    name = s->dynimpname;
    if(name == nil)
        name = s->name;
    

    このコードは、まずdynimpnameを試み、それがnilであればname(内部名)をフォールバックとして使用していました。これは、dynimpnameが設定されていないシンボル(例えば、静的にリンクされるシンボル)の場合に、その内部名を外部名として使用するというロジックでした。

  • 変更後:

    name = s->extname;
    

    新しいextnameフィールドは、シンボルが作成される際にデフォルトでs->nameで初期化される(src/cmd/ld/lib.c_lookup関数参照)ため、常に有効な外部名を保持することが期待されます。これにより、冗長なnilチェックが不要になり、コードが簡潔になります。

これらの変更は、Goリンカの内部設計におけるシンボル管理の明確化と、コードベース全体の整合性向上に貢献しています。特に、シンボルの状態をtypeフィールドで明示的に表現するアプローチは、リンカのロジックをより堅牢で理解しやすいものにする上で重要です。

関連リンク

  • Go言語のリンカに関する公式ドキュメントや設計ドキュメントは、Goのソースコードリポジトリ内のsrc/cmd/linkディレクトリや、Goの公式ブログなどで見つけることができます。
  • GoのCgoに関する公式ドキュメント: https://pkg.go.dev/cmd/cgo
  • ELF (Executable and Linkable Format) の仕様: 動的リンク、GOT、PLTなどの詳細が記述されています。
  • Mach-O (macOS) および PE (Windows) フォーマットの仕様。

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/ld, src/cmd/5l, src/cmd/6l, src/cmd/8l ディレクトリ)
  • Goのコミット履歴とコードレビュー (CL 7594046): https://golang.org/cl/7594046
  • 動的リンク、GOT、PLTに関する一般的なコンピュータサイエンスの資料や記事。
  • Goのリンカに関する非公式な解説記事やブログポスト(検索エンジンで「Go linker internals」などで検索)。
  • Goのシンボルタイプに関する情報(Goのソースコード内のsrc/cmd/internal/objsrc/cmd/linkなどで定義されているシンボルタイプ定数)。