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

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

このコミットは、Go言語のリンカ(cmd/ld)および各アーキテクチャのアセンブラ(cmd/6l, cmd/8l, cmd/5l)において、ELFシステムにおける隠しシンボル(hidden symbols)およびローカルシンボル(local symbols)のインポートに関する問題を修正するものです。具体的には、ELFシンボルテーブルにおけるこれらのシンボルの扱いを改善し、リンカがシンボルを正しく解決できるようにするための変更が含まれています。

コミット

commit 576d648b2a961a474acd6b2236ae062b901bb404
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 23 02:32:27 2012 +0800

    cmd/ld, cmd/6l, cmd/8l, cmd/5l: fix hidden/local symbol import for ELF systems
       Introduce a newsym() to cmd/lib.c to add a symbol but don't add
    them to hash table.
       Introduce a new bit flag SHIDDEN and bit mask SMASK to handle hidden
    and/or local symbols in ELF symbol tables. Though we still need to order
    the symbol table entries correctly.
       Fix for issue 3261 comment #9.
       For CL 5822049.
    
    R=iant, rsc
    CC=golang-dev
    https://golang.org/cl/5823055

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

https://github.com/golang/go/commit/576d648b2a961a474acd6b2236ae062b901bb404

元コミット内容

Go言語のリンカおよびアセンブラにおいて、ELF(Executable and Linkable Format)システムでの隠しシンボルおよびローカルシンボルのインポートに関する修正。

主な変更点:

  1. cmd/ld/lib.cnewsym() 関数を導入。これはシンボルを追加するが、ハッシュテーブルには追加しない。
  2. ELFシンボルテーブルにおける隠しシンボルやローカルシンボルを扱うための新しいビットフラグ SHIDDEN とビットマスク SMASK を導入。
  3. シンボルテーブルのエントリを正しく順序付ける必要がある。
  4. Issue 3261 のコメント #9 に対する修正。
  5. 変更リスト (CL) 5822049 に関連。

変更の背景

このコミットの背景には、Go言語のリンカがELF形式のオブジェクトファイルや共有ライブラリを扱う際に、特定の種類のシンボル(隠しシンボルやローカルシンボル)を正しく処理できないという問題がありました。

ELFファイルでは、シンボルにはその可視性(visibility)や結合(binding)に関する情報が含まれています。

  • ローカルシンボル (STB_LOCAL): そのオブジェクトファイル内でのみ参照可能で、外部からは見えません。
  • グローバルシンボル (STB_GLOBAL): 複数のオブジェクトファイル間で参照可能で、リンカによって解決されます。
  • 隠しシンボル (STV_HIDDEN): グローバルシンボルとして定義されているが、その共有オブジェクト内でのみ参照可能で、外部の共有オブジェクトからは見えません。

従来のリンカの実装では、これらのシンボルがハッシュテーブルに不適切に追加されたり、その可視性が正しく扱われなかったりすることで、以下のような問題が発生していました。

  1. シンボル名の衝突: ローカルシンボルや隠しシンボルがグローバルシンボルと同じ名前を持つ場合、リンカがこれらを区別できず、誤ったシンボルを解決してしまう可能性がありました。
  2. 不適切なシンボル解決: 外部から参照されるべきではないシンボルが誤って参照されたり、逆に参照されるべきシンボルが見つからなかったりする問題。
  3. リンカの誤動作: シンボルテーブルの処理順序や、シンボルの属性(型、サイズなど)の解釈が不正確であるために、最終的な実行ファイルが正しく生成されない、または予期せぬ動作をする可能性がありました。

特に、コミットメッセージで言及されている "issue 3261 comment #9" は、この問題の具体的な症状を示していたと考えられます。このIssueは、Goのツールチェインが生成するELFバイナリが、特定の環境やリンカのバージョンで問題を起こす原因となっていた可能性があります。

この修正は、リンカがELFシンボルテーブルの情報をより正確に解釈し、シンボルの可視性や結合属性に基づいて適切な処理を行うことで、これらの問題を解決し、Go言語で生成されるELFバイナリの互換性と堅牢性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。

1. ELF (Executable and Linkable Format)

ELFは、Unix系システム(Linux、BSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイル形式です。ELFファイルは、プログラムのコード、データ、シンボル情報、再配置情報などを構造化して格納します。

  • セクション (Sections): ELFファイルは複数のセクションに分割されます。例えば、.text(コード)、.data(初期化済みデータ)、.bss(初期化されていないデータ)、.symtab(シンボルテーブル)、.strtab(文字列テーブル)などがあります。
  • シンボルテーブル (Symbol Table): プログラム内の関数や変数などの名前(シンボル)と、それらのアドレスや型、サイズ、結合、可視性などの情報が格納されています。リンカはシンボルテーブルを参照して、異なるオブジェクトファイル間の参照を解決します。
  • 再配置エントリ (Relocation Entries): プログラムがロードされる際に、シンボルのアドレスを解決するために必要な情報です。

2. シンボル (Symbol) とその属性

シンボルは、プログラム内の特定のメモリ位置(関数、変数など)を識別するための名前です。ELFシンボルにはいくつかの重要な属性があります。

  • シンボル名 (Name): 関数名や変数名。
  • 値 (Value): シンボルが参照するアドレス。
  • サイズ (Size): シンボルが占めるメモリ領域のサイズ。
  • 型 (Type): シンボルが関数 (STT_FUNC)、データオブジェクト (STT_OBJECT)、セクション (STT_SECTION) など、何を表すかを示します。
  • 結合 (Binding): シンボルのリンケージの範囲を定義します。
    • STB_LOCAL: シンボルは現在のオブジェクトファイル内でのみ有効です。同じ名前のシンボルが他のオブジェクトファイルに存在しても衝突しません。
    • STB_GLOBAL: シンボルはすべてのオブジェクトファイルで有効です。リンカは同じ名前のグローバルシンボルを一つに解決します。
    • STB_WEAK: STB_GLOBAL と似ていますが、同じ名前の STB_GLOBAL シンボルが存在する場合、そちらが優先されます。
  • 可視性 (Visibility): シンボルが他のモジュールからどのように参照可能かを示します。
    • STV_DEFAULT: デフォルトの可視性。
    • STV_HIDDEN: シンボルは現在の共有オブジェクト内でのみ参照可能で、他の共有オブジェクトからは見えません。
    • STV_PROTECTED: シンボルは現在の共有オブジェクト内でのみ参照可能で、他の共有オブジェクトからは見えませんが、派生クラスなどからは参照可能です。
    • STV_INTERNAL: シンボルは現在の共有オブジェクト内でのみ参照可能で、他の共有オブジェクトからは見えません。

3. リンカ (ld)

リンカは、コンパイラによって生成された複数のオブジェクトファイルやライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は以下の通りです。

  • シンボル解決: 未解決のシンボル参照(あるオブジェクトファイルで定義された関数や変数を、別のオブジェクトファイルから参照するような場合)を、対応する定義に結びつけます。
  • 再配置: プログラムがメモリにロードされる際のアドレスに基づいて、コード内のアドレス参照を調整します。
  • セクションの結合: 複数のオブジェクトファイルの同じ種類のセクション(例: すべての.textセクション)を結合し、最終的な実行ファイルのセクションを構築します。

4. Go言語のツールチェイン

Go言語のビルドプロセスでは、以下のようなツールが関与します。

  • cmd/5l, cmd/6l, cmd/8l: これらはそれぞれ、ARM (5l)、x86-64 (6l)、x86 (8l) アーキテクチャ向けのアセンブラおよびリンカのフロントエンドです。Goの初期のツールチェインでは、各アーキテクチャに特化したリンカが存在しました。
  • cmd/ld: Go言語のメインリンカです。上記のアセンブラ/リンカフロントエンドと連携し、最終的なバイナリを生成します。

このコミットは、特に cmd/ld がELFシンボルテーブルを解析し、シンボルを内部表現にマッピングする際のロジックに焦点を当てています。隠しシンボルやローカルシンボルが正しく扱われないと、リンカが誤ったシンボルを解決したり、シンボルテーブルの構造が壊れたりする可能性がありました。

技術的詳細

このコミットは、Go言語のリンカがELF形式のオブジェクトファイルからシンボルを読み込み、内部で管理する際のロジックを根本的に改善しています。特に、隠しシンボル(hidden symbols)とローカルシンボル(local symbols)の扱いが強化されています。

1. newsym() 関数の導入

src/cmd/ld/lib.cnewsym(char *symb, int v) という新しい関数が導入されました。 従来の _lookup() 関数は、シンボルを検索し、見つからない場合は新しいシンボルを作成してハッシュテーブルに追加していました。しかし、隠しシンボルやローカルシンボルは、その名前でグローバルに参照されるべきではないため、ハッシュテーブルに登録する必要がありません。

newsym() は、シンボル構造体 (Sym) を割り当て、名前、バージョン、その他の初期値を設定しますが、ハッシュテーブルには追加しません。これにより、これらのシンボルが名前によるグローバルな検索で誤って見つかることを防ぎつつ、リンカ内部でシンボルオブジェクトとして管理できるようになります。

2. SHIDDEN フラグと SMASK マスクの導入

src/cmd/ld/lib.h に新しい列挙型が追加されました。

  • SHIDDEN = 1<<9: シンボルが隠しシンボルまたはローカルシンボルであることを示す新しいビットフラグ。
  • SMASK = SSUB - 1: シンボルの型(STEXT, SDATA など)を抽出するためのビットマスク。以前は ~SSUB を使用していましたが、SMASK を導入することで、シンボルの型と可視性フラグをより明確に分離して扱えるようになります。

これらのフラグとマスクは、シンボルの型をチェックする際に使用され、シンボルの可視性情報がシンボルの型情報と混同されないようにします。

3. ldelf.c におけるシンボル処理の変更

src/cmd/ld/ldelf.c はELFファイルを読み込む主要な部分です。ここでの変更は多岐にわたります。

  • readsym 関数のシグネチャ変更: readsym(ElfObj *obj, int i, ElfSym *sym)readsym(ElfObj *obj, int i, ElfSym *sym, int needSym) に変更されました。needSym 引数は、シンボルをハッシュテーブルに登録する必要があるかどうかを示します。
  • シンボル配列の導入: ldelf 関数内で Sym **symbols; という配列が導入されました。これは、ELFシンボルテーブルのインデックスに対応するGoの内部シンボル (Sym*) へのポインタを格納するために使用されます。これにより、シンボルをそのインデックスで直接参照できるようになり、特に再配置処理において効率的かつ正確なシンボル解決が可能になります。
  • サブシンボルの処理順序の変更: 以前は、すべてのシンボルを読み込んだ後にサブシンボル(セクションに属するシンボルなど)を処理していましたが、このコミットでは、まずすべてのシンボルを symbols 配列に格納し、その後でサブシンボルを処理するロジックに変更されています。これにより、シンボル間の依存関係がより適切に解決されるようになります。
  • 隠し/ローカルシンボルの扱い:
    • readsym 内で、ElfSymBindGlobal かつ sym->other == 2 (ELFの st_other フィールドで可視性が STV_HIDDEN を示す) の場合、lookup でシンボルを取得した後、そのシンボルに SHIDDEN フラグを設定し、dupok = 1 を設定します。これは、__i686.get_pc_thunk.bx のような特定の隠しグローバルシンボルが重複定義されても問題ないようにするためのワークアラウンドです。
    • ElfSymBindLocal の場合、needSym が真であれば newsym() を使用してシンボルを作成し、SHIDDEN フラグを設定します。これにより、ローカルシンボルがハッシュテーブルに登録されず、そのインデックスによってのみ参照されるようになります。

4. symtab.c におけるELFシンボルテーブルの書き出し変更

src/cmd/ld/symtab.c は、最終的なELFバイナリのシンボルテーブルを構築する部分です。

  • putelfsyment 関数のシグネチャ変更: putelfsyment(int off, vlong addr, vlong size, int info, int shndx)putelfsyment(int off, vlong addr, vlong size, int info, int shndx, int other) に変更され、ELFシンボルエントリの st_other フィールド(可視性情報を含む)を直接設定できるようになりました。
  • putelfsym における可視性の設定: putelfsym 関数内で、Goの内部シンボル (Sym*) の SHIDDEN フラグに基づいて、ELFシンボルエントリの st_other フィールドが設定されるようになりました。x->type & SHIDDEN が真の場合、st_other2 (ELFの STV_HIDDEN に対応) に設定されます。これにより、Goのリンカが生成するELFバイナリのシンボルテーブルが、隠しシンボルやローカルシンボルの可視性情報を正しく反映するようになります。
  • シンボルテーブルの順序付けに関するコメント: putelfsym 内に「STB_LOCALシンボルはSTB_GLOBALおよびSTB_WEAKシンボルよりも前に配置する必要がある」というコメントが追加されており、ELFシンボルテーブルの慣習的な順序付けへの意識が示されています。

5. asm.c および pass.c における SMASK の使用

src/cmd/5l/asm.c, src/cmd/5l/pass.c, src/cmd/6l/asm.c, src/cmd/6l/pass.c, src/cmd/8l/asm.c, src/cmd/8l/pass.c の各ファイルで、シンボルの型をチェックする際に s->type&~SSUB の代わりに s->type&SMASK が使用されるようになりました。これは、シンボルの型を抽出する際に、新しい SHIDDEN フラグやその他の将来のフラグが型情報と混同されないようにするための変更です。

これらの変更により、GoのリンカはELFシンボルテーブルの複雑なセマンティクスをより正確に理解し、隠しシンボルやローカルシンボルを適切に処理できるようになり、結果としてより堅牢で互換性のあるELFバイナリを生成できるようになります。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/cmd/ld/lib.h: 新しいシンボル属性の定義

    --- a/src/cmd/ld/lib.h
    +++ b/src/cmd/ld/lib.h
    @@ -61,7 +61,9 @@ enum
     	SDYNIMPORT,
     
     	SSUB = 1<<8,	/* sub-symbol, linked from parent via ->sub list */
    -	
    +	SMASK = SSUB - 1,
    +	SHIDDEN = 1<<9, // hidden or local symbol
    +
     	NHASH = 100003,
     };
     
    @@ -142,6 +144,7 @@ void	addhist(int32 line, int type);\
     void	asmlc(void);\
     void	histtoauto(void);\
     void	collapsefrog(Sym *s);\
    +Sym*	newsym(char *symb, int v);\
     Sym*	lookup(char *symb, int v);\
     Sym*	rlookup(char *symb, int v);\
     void	nuxiinit(void);\
    
  2. src/cmd/ld/lib.c: newsym() 関数の追加と _lookup() の変更

    --- a/src/cmd/ld/lib.c
    +++ b/src/cmd/ld/lib.c
    @@ -548,6 +548,36 @@ eof:
     	free(pn);\
     }\
     
    +Sym*\
    +newsym(char *symb, int v)\
    +{\
    +\tSym *s;\
    +\tint l;\
    +\n\tl = strlen(symb) + 1;\
    +\ts = mal(sizeof(*s));\
    +\tif(debug['v'] > 1)\
    +\t\tBprint(&bso, "newsym %s\\n", symb);\
    +\n\ts->dynid = -1;\
    +\ts->plt = -1;\
    +\ts->got = -1;\
    +\ts->name = mal(l + 1);\
    +\tmemmove(s->name, symb, l);\
    +\n\ts->type = 0;\
    +\ts->version = v;\
    +\ts->value = 0;\
    +\ts->sig = 0;\
    +\ts->size = 0;\
    +\tnsymbol++;\
    +\n\ts->allsym = allsym;\
    +\tallsym = s;\
    +\n\treturn s;\
    +}\
    +\n static Sym*\
     _lookup(char *symb, int v, int creat)\
     {\
     	Sym *s;\
    @@ -569,27 +599,10 @@ _lookup(char *symb, int v, int creat)\
     	if(!creat)\
     		return nil;\
     
    -\ts = mal(sizeof(*s));
    -\tif(debug['v'] > 1)\
    -\t\tBprint(&bso, "lookup %s\\n", symb);\
    -\n-\ts->dynid = -1;\
    -\ts->plt = -1;\
    -\ts->got = -1;\
    -\ts->name = mal(l + 1);\
    -\tmemmove(s->name, symb, l);\
    -\n+\ts = newsym(symb, v);\
     \ts->hash = hash[h];
    -\ts->type = 0;\
    -\ts->version = v;\
    -\ts->value = 0;\
    -\ts->sig = 0;\
    -\ts->size = 0;\
     \thash[h] = s;\
    -\tnsymbol++;
    -\n-\ts->allsym = allsym;\
    -\tallsym = s;\
     \treturn s;\
     }\
     
    
  3. src/cmd/ld/ldelf.c: ELFシンボル読み込みロジックの変更

    • readsym 関数のシグネチャ変更と、シンボル処理の順序変更、symbols 配列の導入、隠し/ローカルシンボルの特別な扱い。
    • 特に、readsym 内の case ElfSymBindGlobal:case ElfSymBindLocal: ブロックの変更。
    --- a/src/cmd/ld/ldelf.c
    +++ b/src/cmd/ld/ldelf.c
    @@ -308,7 +308,7 @@ uchar ElfMagic[4] = { 0x7F, 'E', 'L', 'F' };
     
     static ElfSect*	section(ElfObj*, char*);\
     static int	map(ElfObj*, ElfSect*);\
    -static int	readsym(ElfObj*, int i, ElfSym*);\
    +static int	readsym(ElfObj*, int i, ElfSym*, int);\
     static int	reltype(char*, int, uchar*);\
     
     void
    @@ -327,6 +327,9 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\
     	Endian *e;\
     	Reloc *r, *rp;\
     	Sym *s;\
    +\tSym **symbols;\
    +\n+\tsymbols = nil;\
     
     	USED(pkg);\
     	if(debug['v'])\
    @@ -547,7 +550,71 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\
     		etextp = s;\
     	}\
     	sect->sym = s;\
    -	}\t\t
    +	}\
    +\n+\t// enter sub-symbols into symbol table.\n+\t// symbol 0 is the null symbol.\n+\tsymbols = malloc(obj->nsymtab * sizeof(symbols[0]));\
    +\tif(symbols == nil) {\
    +\t\tdiag("out of memory");\
    +\t\terrorexit();\
    +\t}\n+\tfor(i=1; i<obj->nsymtab; i++) {\
    +\t\tif(readsym(obj, i, &sym, 1) < 0)\
    +\t\t\tgoto bad;\
    +\t\tsymbols[i] = sym.sym;\
    +\t\tif(sym.type != ElfSymTypeFunc && sym.type != ElfSymTypeObject && sym.type != ElfSymTypeNone)\
    +\t\t\tcontinue;\
    +\t\tif(sym.shndx == ElfSymShnCommon) {\
    +\t\t\ts = sym.sym;\
    +\t\t\tif(s->size < sym.size)\
    +\t\t\t\ts->size = sym.size;\
    +\t\t\tif(s->type == 0 || s->type == SXREF)\
    +\t\t\t\ts->type = SBSS;\
    +\t\t\tcontinue;\
    +\t\t}\n+\t\tif(sym.shndx >= obj->nsect || sym.shndx == 0)\n+\t\t\tcontinue;\
    +\t\tsect = obj->sect+sym.shndx;\
    +\t\tif(sect->sym == nil) {\
    +\t\t\tdiag("%s: sym#%d: ignoring %s in section %d (type %d)", pn, i, sym.name, sym.shndx, sym.type);\
    +\t\t\tcontinue;\
    +\t\t}\n+\t\ts = sym.sym;\
    +\t\ts->sub = sect->sym->sub;\
    +\t\tsect->sym->sub = s;\
    +\t\ts->type = sect->sym->type | (s->type&~SMASK) | SSUB;\
    +\t\tif(!s->dynexport) {\
    +\t\t\ts->dynimplib = nil;  // satisfy dynimport\
    +\t\t\ts->dynimpname = nil;  // satisfy dynimport\
    +\t\t}\n+\t\ts->value = sym.value;\
    +\t\ts->size = sym.size;\
    +\t\ts->outer = sect->sym;\
    +\t\tif(sect->sym->type == STEXT) {\
    +\t\t\tProg *p;\
    +\n+\t\t\tif(s->text != P) {\
    +\t\t\t\tif(!s->dupok)\
    +\t\t\t\t\tdiag("%s: duplicate definition of %s", pn, s->name);\
    +\t\t\t} else {\
    +\t\t\t\t// build a TEXT instruction with a unique pc\n+\t\t\t\t// just to make the rest of the linker happy.\n+\t\t\t\tp = prg();\n+\t\t\t\tp->as = ATEXT;\n+\t\t\t\tp->from.type = D_EXTERN;\
    +\t\t\t\tp->from.sym = s;\
    +\t\t\t\tp->textflag = 7;\
    +\t\t\t\tp->to.type = D_CONST;\
    +\t\t\t\tp->link = nil;\
    +\t\t\t\tp->pc = pc++;\
    +\t\t\t\ts->text = p;\
    +\n+\t\t\t\tetextp->next = s;\
    +\t\t\t\tetextp = s;\
    +\t\t\t}\n+\t\t}\n+\t}\
     
     	// load relocations
     	for(i=0; i<obj->nsect; i++) {\
    @@ -591,8 +658,9 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\
     	\t\tif((info >> 32) == 0) { // absolute relocation, don't bother reading the null symbol\
     	\t\t\trp->sym = S;\
     	\t\t} else {\
    -\t\t\t\tif(readsym(obj, info>>32, &sym) < 0)\
    +\t\t\t\tif(readsym(obj, info>>32, &sym, 0) < 0)\
     \t\t\t\t\tgoto bad;\
    +\t\t\t\tsym.sym = symbols[info>>32];
     \t\t\t\tif(sym.sym == nil) {\
     \t\t\t\t\twerrstr("%s#%d: reloc of invalid sym #%d %s shndx=%d type=%d",\
     \t\t\t\t\t\tsect->sym->name, j, (int)(info>>32), sym.name, sym.shndx, sym.type);\
    @@ -619,67 +687,13 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\
     	\ts->r = r;\
     	\ts->nr = n;\
     	}\
    +\tfree(symbols);\
     
    -\t// enter sub-symbols into symbol table.\
    -\t// symbol 0 is the null symbol.\
    -\tfor(i=1; i<obj->nsymtab; i++) {\
    -\t\tif(readsym(obj, i, &sym) < 0)\
    -\t\t\tgoto bad;\
    -\t\tif(sym.type != ElfSymTypeFunc && sym.type != ElfSymTypeObject && sym.type != ElfSymTypeNone)\
    -\t\t\tcontinue;\
    -\t\tif(sym.shndx == ElfSymShnCommon) {\
    -\t\t\ts = sym.sym;\
    -\t\t\tif(s->size < sym.size)\
    -\t\t\t\ts->size = sym.size;\
    -\t\t\tif(s->type == 0 || s->type == SXREF)\
    -\t\t\t\ts->type = SBSS;\
    -\t\t\tcontinue;\
    -\t\t}\
    -\t\tif(sym.shndx >= obj->nsect || sym.shndx == 0)\
    -\t\t\tcontinue;\
    -\t\tif(thechar == '5' && (strcmp(sym.name, "$a") == 0 || strcmp(sym.name, "$d") == 0)) // binutils for arm generate these mapping symbols, skip these\
    -\t\t\tcontinue;\
    -\t\tsect = obj->sect+sym.shndx;\
    -\t\tif(sect->sym == nil) {\
    -\t\t\tdiag("%s: sym#%d: ignoring %s in section %d (type %d)", pn, i, sym.name, sym.shndx, sym.type);\
    -\t\t\tcontinue;\
    -\t\t}\
    -\t\ts = sym.sym;\
    -\t\ts->sub = sect->sym->sub;\
    -\t\tsect->sym->sub = s;\
    -\t\ts->type = sect->sym->type | SSUB;\
    -\t\tif(!s->dynexport) {\
    -\t\t\ts->dynimplib = nil;  // satisfy dynimport\
    -\t\t\ts->dynimpname = nil;  // satisfy dynimport\
    -\t\t}\
    -\t\ts->value = sym.value;\
    -\t\ts->size = sym.size;\
    -\t\ts->outer = sect->sym;\
    -\t\tif(sect->sym->type == STEXT) {\
    -\t\t\tProg *p;\
    -\n-\t\t\tif(s->text != P)\
    -\t\t\t\tdiag("%s: duplicate definition of %s", pn, s->name);\
    -\t\t\t// build a TEXT instruction with a unique pc\n-\t\t\t// just to make the rest of the linker happy.\n-\t\t\tp = prg();\
    -\t\t\tp->as = ATEXT;\
    -\t\t\tp->from.type = D_EXTERN;\
    -\t\t\tp->from.sym = s;\
    -\t\t\tp->textflag = 7;\
    -\t\t\tp->to.type = D_CONST;\
    -\t\t\tp->link = nil;\
    -\t\t\tp->pc = pc++;\
    -\t\t\ts->text = p;\
    -\n-\t\t\tetextp->next = s;\
    -\t\t\tetextp = s;\
    -\t\t}\
    -\t}\
     \treturn;\
     
     bad:\
     \tdiag("%s: malformed elf file: %r", pn);\
    +\tfree(symbols);\
     }\
     
     static ElfSect*
    @@ -713,7 +727,7 @@ map(ElfObj *obj, ElfSect *sect)\
     }\
     
     static int
    -readsym(ElfObj *obj, int i, ElfSym *sym)\
    +readsym(ElfObj *obj, int i, ElfSym *sym, int needSym)\
     {\
     	Sym *s;\
     
    @@ -752,8 +766,6 @@ readsym(ElfObj *obj, int i, ElfSym *sym)\
     \ts = nil;\
     \tif(strcmp(sym->name, "_GLOBAL_OFFSET_TABLE_") == 0)\
     \t\tsym->name = ".got";\
    -\tif(strcmp(sym->name, "__stack_chk_fail_local") == 0)\
    -\t\tsym->other = 0;  // rewrite hidden -> default visibility\
     \tswitch(sym->type) {\
     \tcase ElfSymTypeSection:\
     \t\ts = obj->sect[sym->shndx].sym;\
    @@ -763,14 +775,30 @@ readsym(ElfObj *obj, int i, ElfSym *sym)\
     \tcase ElfSymTypeNone:\
     \t\tswitch(sym->bind) {\
     \t\tcase ElfSymBindGlobal:\
    -\t\t\tif(sym->other != 2) {\
    +\t\t\tif(needSym) {\
     \t\t\t\ts = lookup(sym->name, 0);\
    -\t\t\t\tbreak;\
    +\t\t\t\t// for global scoped hidden symbols we should insert it into\
    +\t\t\t\t// symbol hash table, but mark them as hidden.\n+\t\t\t\t// __i686.get_pc_thunk.bx is allowed to be duplicated, to\n+\t\t\t\t// workaround that we set dupok.\n+\t\t\t\t// TODO(minux): correctly handle __i686.get_pc_thunk.bx without\n+\t\t\t\t// set dupok generally. See http://codereview.appspot.com/5823055/\n+\t\t\t\t// comment #5 for details.\n+\t\t\t\tif(s && sym->other == 2) {\n+\t\t\t\t\ts->type = SHIDDEN;\n+\t\t\t\t\ts->dupok = 1;\n+\t\t\t\t}\n     \t\t\t}\
    -\t\t\t// fall through
    +\t\t\tbreak;\
     \t\tcase ElfSymBindLocal:\
     \t\t\tif(!(thechar == '5' && (strcmp(sym->name, "$a") == 0 || strcmp(sym->name, "$d") == 0))) // binutils for arm generate these mapping symbols, ignore these\
    -\t\t\t\ts = lookup(sym->name, version);\
    +\t\t\t\tif(needSym) {\n+\t\t\t\t\t// local names and hidden visiblity global names are unique\n+\t\t\t\t\t// and should only reference by its index, not name, so we\n+\t\t\t\t\t// don't bother to add them into hash table\n+\t\t\t\t\ts = newsym(sym->name, version);\n+\t\t\t\t\ts->type = SHIDDEN;\n+\t\t\t\t}\n     \t\t\tbreak;\
     \t\tdefault:\
     \t\t\twerrstr("%s: invalid symbol binding %d", sym->name, sym->bind);\
    
  4. src/cmd/ld/symtab.c: ELFシンボルテーブル書き出しロジックの変更

    • putelfsymentputelfsym のシグネチャ変更と、st_other フィールドの設定ロジック。
    --- a/src/cmd/ld/symtab.c
    +++ b/src/cmd/ld/symtab.c
    @@ -36,7 +36,7 @@
     
     static int maxelfstr;\
     
    -int
    +static int
     putelfstr(char *s)\
     {\
     	int off, n;\
    @@ -57,14 +57,14 @@ putelfstr(char *s)\
     	return off;\
     }\
     
    -void
    -putelfsyment(int off, vlong addr, vlong size, int info, int shndx)\
    +static void
    +putelfsyment(int off, vlong addr, vlong size, int info, int shndx, int other)\
     {\
     	switch(thechar) {\
     	case '6':
     	\tLPUT(off);\
     	\tcput(info);\
    -\t\tcput(0);\
    +\t\tcput(other);\
     	\tWPUT(shndx);\
     	\tVPUT(addr);\
     	\tVPUT(size);\
    @@ -75,14 +75,14 @@ putelfsyment(int off, vlong addr, vlong size, int info, int shndx)\
     	\tLPUT(addr);\
     	\tLPUT(size);\
     	\tcput(info);\
    -\t\tcput(0);\
    +\t\tcput(other);\
     	\tWPUT(shndx);\
     	\tsymsize += ELF32SYMSIZE;\
     	\tbreak;\
     	}\
     }\
     
    -void
    +static void
     putelfsym(Sym *x, char *s, int t, vlong addr, vlong size, int ver, Sym *go)\
     {\
     	int bind, type, shndx, off;\
    @@ -97,7 +97,7 @@ putelfsym(Sym *x, char *s, int t, vlong addr, vlong size, int ver, Sym *go)\
     	\tbreak;\
     	case 'D':
     	\ttype = STT_OBJECT;\
    -\t\tif((x->type&~SSUB) == SRODATA)\
    +\t\tif((x->type&SMASK) == SRODATA)\
     \t\t\tshndx = elftextsh + 1;\
     \t\telse
     \t\t\tshndx = elftextsh + 2;\
    @@ -107,20 +107,22 @@ putelfsym(Sym *x, char *s, int t, vlong addr, vlong size, int ver, Sym *go)\
     \t\tshndx = elftextsh + 3;\
     \t\tbreak;\
     \t}\
    -\tbind = ver ? STB_LOCAL : STB_GLOBAL;\
    +\t// TODO(minux): we need to place all STB_LOCAL precede all STB_GLOBAL and\n+\t// STB_WEAK symbols in the symbol table\n+\tbind = (ver || (x->type & SHIDDEN)) ? STB_LOCAL : STB_GLOBAL;\
     \toff = putelfstr(s);\
    -\tputelfsyment(off, addr, size, (bind<<4)|(type&0xf), shndx);\
    +\tputelfsyment(off, addr, size, (bind<<4)|(type&0xf), shndx, (x->type & SHIDDEN) ? 2 : 0);\
     }\
     
     void
     asmelfsym(void)\
     {\
     \t// the first symbol entry is reserved\n-\tputelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_NOTYPE, 0);\
    +\tputelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_NOTYPE, 0, 0);\
     \tgenasmsym(putelfsym);\
     }\
     
    -void
    +static void
     putplan9sym(Sym *x, char *s, int t, vlong addr, vlong size, int ver, Sym *go)\
     {\
     \tint i;\
    
  5. src/cmd/{5l,6l,8l}/{asm.c,pass.c}: シンボル型チェックの変更

    • s->type&~SSUB から s->type&SMASK への変更。
    --- a/src/cmd/5l/asm.c
    +++ b/src/cmd/5l/asm.c
    @@ -2203,7 +2203,7 @@ genasmsym(void (*put)(Sym*, char*, int, vlong, vlong, int, Sym*))\
     		for(s=hash[h]; s!=S; s=s->hash) {\
     			if(s->hide)\
     				continue;\
    -\t\t\tswitch(s->type&~SSUB) {\
    +\t\t\tswitch(s->type&SMASK) {\
     			case SCONST:\
     			case SRODATA:\
     			case SDATA:\
    

    (他の asm.c および pass.c ファイルも同様の変更)

コアとなるコードの解説

1. SHIDDENSMASK の導入 (src/cmd/ld/lib.h)

  • SHIDDEN = 1<<9: これは、Goリンカの内部シンボル構造体 Sym に新しい状態ビットを追加するものです。このビットがセットされているシンボルは、ELFの隠しシンボルまたはローカルシンボルとして扱われるべきであることを示します。これにより、シンボルの可視性に関する情報をシンボル自体に直接保持できるようになります。
  • SMASK = SSUB - 1: 以前は ~SSUB を使ってシンボルの基本型(STEXT, SDATA など)を抽出していましたが、SHIDDEN のような新しいフラグが導入されると、~SSUB では意図しないビットまでマスクしてしまう可能性があります。SMASKSSUB - 1 と定義することで、SSUB より下位のビットのみを確実にマスクし、シンボルの基本型を正確に取得できるようになります。これは、シンボル型と可視性フラグを明確に分離するための重要な変更です。

2. newsym() 関数の追加と _lookup() の変更 (src/cmd/ld/lib.c)

  • newsym(char *symb, int v): この新しい関数は、シンボル構造体 Sym を割り当て、名前、バージョン、その他の初期値を設定します。重要なのは、この関数がシンボルをグローバルなハッシュテーブルに追加しない点です。これにより、隠しシンボルやローカルシンボルのように、名前でグローバルに検索されるべきではないシンボルを、リンカ内部で一意のオブジェクトとして管理できるようになります。
  • _lookup() の変更: 従来の _lookup() 関数は、シンボルが見つからない場合に新しいシンボルを作成し、それをハッシュテーブルに追加していました。このコミットでは、新しいシンボルを作成する部分が newsym() の呼び出しに置き換えられています。これにより、_lookup() は常にハッシュテーブルにシンボルを追加するようになり、newsym() はハッシュテーブルに追加しないシンボルを作成する役割を担うという、役割分担が明確になります。

3. ldelf.c におけるELFシンボル読み込みロジックの変更

  • readsym 関数のシグネチャ変更: readsymint needSym という新しい引数が追加されました。この引数は、現在処理しているELFシンボルをGoの内部シンボルテーブル(ハッシュテーブル)に登録する必要があるかどうかを示します。これにより、隠しシンボルやローカルシンボルのように、名前で検索されるべきではないシンボルを、ハッシュテーブルに登録せずに処理できるようになります。
  • Sym **symbols 配列の導入: ldelf 関数内で symbols という Sym* の配列が導入されました。この配列は、ELFシンボルテーブルのインデックスとGoの内部シンボルオブジェクトを1対1でマッピングするために使用されます。ELFファイルでは、シンボルはインデックスによって参照されることが多いため、この配列を使用することで、インデックスから直接対応するGoのシンボルオブジェクトを取得できるようになり、再配置処理などが効率化されます。
  • サブシンボル処理の順序変更: 以前は、すべてのシンボルを読み込んだ後にサブシンボルを処理していましたが、このコミットでは、まずすべてのシンボルを symbols 配列に格納し、その後でサブシンボルを処理するロジックに変更されています。これにより、シンボル間の依存関係がより適切に解決され、特にテキストセクション内のサブシンボル(例えば、関数内のローカル静的変数など)が正しく関連付けられるようになります。
  • 隠し/ローカルシンボルの特別な扱い:
    • readsym 内で、ELFシンボルの結合 (sym->bind) が ElfSymBindGlobal で、かつ sym->other フィールド(ELFの st_other は可視性を示す)が 2 (ELFの STV_HIDDEN に対応) の場合、lookup でシンボルを取得した後、そのシンボルに SHIDDEN フラグを設定し、dupok = 1 を設定します。これは、__i686.get_pc_thunk.bx のような特定の隠しグローバルシンボルが重複定義されても問題ないようにするためのワークアラウンドです。
    • ElfSymBindLocal の場合、needSym が真であれば newsym() を使用してシンボルを作成し、SHIDDEN フラグを設定します。これにより、ローカルシンボルがハッシュテーブルに登録されず、そのインデックスによってのみ参照されるようになります。

4. symtab.c におけるELFシンボルテーブル書き出しロジックの変更

  • putelfsymentputelfsym のシグネチャ変更: putelfsymentint other 引数が追加され、putelfsym もこの引数を渡すようになりました。これにより、ELFシンボルエントリの st_other フィールド(可視性情報を含む)をGoの内部シンボル情報に基づいて正確に設定できるようになります。
  • st_other フィールドの設定: putelfsym 関数内で、Goの内部シンボル xSHIDDEN フラグがチェックされ、もしセットされていれば、ELFシンボルエントリの st_other フィールドが 2 (ELFの STV_HIDDEN に対応) に設定されます。これにより、Goのリンカが生成するELFバイナリのシンボルテーブルが、隠しシンボルやローカルシンボルの可視性情報を正しく反映するようになります。これは、他のツール(デバッガやリンカなど)がGoのバイナリを正しく解釈するために非常に重要です。
  • シンボルテーブルの順序付けに関するコメント: putelfsym 内に、ELFの慣習として STB_LOCAL シンボルが STB_GLOBAL および STB_WEAK シンボルよりも前に配置されるべきであるというコメントが追加されています。これは、リンカがELFシンボルテーブルを生成する際のベストプラクティスへの意識を示しています。

5. asm.c および pass.c における SMASK の使用

  • 各アーキテクチャのアセンブラ (asm.c) とパス処理 (pass.c) のファイルで、シンボルの型をチェックする際に s->type&~SSUB の代わりに s->type&SMASK が使用されるようになりました。これは、シンボルの型を抽出する際に、新しい SHIDDEN フラグやその他の将来のフラグが型情報と混同されないようにするための、より堅牢な方法です。これにより、シンボルの型と可視性フラグが明確に分離され、コードの意図がより明確になります。

これらの変更は、GoのリンカがELFシンボルテーブルの複雑なセマンティクスをより正確に理解し、隠しシンボルやローカルシンボルを適切に処理できるようにすることで、Goで生成されるELFバイナリの互換性と堅牢性を大幅に向上させます。

関連リンク

参考にした情報源リンク

  • ELF (Executable and Linkable Format) の仕様:
  • リンカとローダに関する一般的な情報:
    • "Linkers and Loaders" by John R. Levine (リンカの動作に関する古典的な書籍)
  • Go言語のツールチェインに関する情報:
    • Goの公式ドキュメントやソースコード内のコメント。
    • Goのリンカの内部構造に関するブログ記事や解説(例: "Go's linker, part 1: Object files" by Russ Cox, "Go's linker, part 2: Linker internals" by Russ Cox など)