[インデックス 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)システムでの隠しシンボルおよびローカルシンボルのインポートに関する修正。
主な変更点:
cmd/ld/lib.c
にnewsym()
関数を導入。これはシンボルを追加するが、ハッシュテーブルには追加しない。- ELFシンボルテーブルにおける隠しシンボルやローカルシンボルを扱うための新しいビットフラグ
SHIDDEN
とビットマスクSMASK
を導入。 - シンボルテーブルのエントリを正しく順序付ける必要がある。
- Issue 3261 のコメント #9 に対する修正。
- 変更リスト (CL) 5822049 に関連。
変更の背景
このコミットの背景には、Go言語のリンカがELF形式のオブジェクトファイルや共有ライブラリを扱う際に、特定の種類のシンボル(隠しシンボルやローカルシンボル)を正しく処理できないという問題がありました。
ELFファイルでは、シンボルにはその可視性(visibility)や結合(binding)に関する情報が含まれています。
- ローカルシンボル (STB_LOCAL): そのオブジェクトファイル内でのみ参照可能で、外部からは見えません。
- グローバルシンボル (STB_GLOBAL): 複数のオブジェクトファイル間で参照可能で、リンカによって解決されます。
- 隠しシンボル (STV_HIDDEN): グローバルシンボルとして定義されているが、その共有オブジェクト内でのみ参照可能で、外部の共有オブジェクトからは見えません。
従来のリンカの実装では、これらのシンボルがハッシュテーブルに不適切に追加されたり、その可視性が正しく扱われなかったりすることで、以下のような問題が発生していました。
- シンボル名の衝突: ローカルシンボルや隠しシンボルがグローバルシンボルと同じ名前を持つ場合、リンカがこれらを区別できず、誤ったシンボルを解決してしまう可能性がありました。
- 不適切なシンボル解決: 外部から参照されるべきではないシンボルが誤って参照されたり、逆に参照されるべきシンボルが見つからなかったりする問題。
- リンカの誤動作: シンボルテーブルの処理順序や、シンボルの属性(型、サイズなど)の解釈が不正確であるために、最終的な実行ファイルが正しく生成されない、または予期せぬ動作をする可能性がありました。
特に、コミットメッセージで言及されている "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.c
に newsym(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_other
は2
(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バイナリを生成できるようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
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);\
-
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;\ }\
-
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);\
-
src/cmd/ld/symtab.c
: ELFシンボルテーブル書き出しロジックの変更putelfsyment
とputelfsym
のシグネチャ変更と、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;\
-
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. SHIDDEN
と SMASK
の導入 (src/cmd/ld/lib.h
)
SHIDDEN = 1<<9
: これは、Goリンカの内部シンボル構造体Sym
に新しい状態ビットを追加するものです。このビットがセットされているシンボルは、ELFの隠しシンボルまたはローカルシンボルとして扱われるべきであることを示します。これにより、シンボルの可視性に関する情報をシンボル自体に直接保持できるようになります。SMASK = SSUB - 1
: 以前は~SSUB
を使ってシンボルの基本型(STEXT
,SDATA
など)を抽出していましたが、SHIDDEN
のような新しいフラグが導入されると、~SSUB
では意図しないビットまでマスクしてしまう可能性があります。SMASK
をSSUB - 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
関数のシグネチャ変更:readsym
にint 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シンボルテーブル書き出しロジックの変更
putelfsyment
とputelfsym
のシグネチャ変更:putelfsyment
にint other
引数が追加され、putelfsym
もこの引数を渡すようになりました。これにより、ELFシンボルエントリのst_other
フィールド(可視性情報を含む)をGoの内部シンボル情報に基づいて正確に設定できるようになります。st_other
フィールドの設定:putelfsym
関数内で、Goの内部シンボルx
のSHIDDEN
フラグがチェックされ、もしセットされていれば、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バイナリの互換性と堅牢性を大幅に向上させます。
関連リンク
- Go Issue 3261: https://github.com/golang/go/issues/3261 (このコミットが修正した問題の元となるIssue)
- Go Change List 5823055: https://golang.org/cl/5823055 (このコミットに対応するGoの変更リスト)
- Go Change List 5822049: https://golang.org/cl/5822049 (関連する変更リスト)
参考にした情報源リンク
- ELF (Executable and Linkable Format) の仕様:
- Tool Interface Standard (TIS) Portable Formats Specification, Version 1.1 (ELFシンボルテーブルに関する詳細)
- System V Application Binary Interface (ABI) - DRAFT (ELFの公式仕様書)
- リンカとローダに関する一般的な情報:
- "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 など)