[インデックス 19280] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) におけるPE (Portable Executable) フォーマットのシンボルテーブルにおけるセクション番号の扱いに関する修正です。具体的には、シンボルが属するセクションの決定ロジックと、シンボル値の計算方法が改善されています。
コミット
commit cf78f96244ec8e3a69f10069fb43dd720f1556b0
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue May 6 11:40:43 2014 +1000
cmd/ld: correct pe section number in symbol table
Update #7899
LGTM=iant
R=golang-codereviews, rsc, iant
CC=golang-codereviews
https://golang.org/cl/97920044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cf78f96244ec8e3a69f10069fb43dd720f1556b0
元コミット内容
cmd/ld: correct pe section number in symbol table
Update #7899
変更の背景
このコミットは、Go言語のリンカがWindows向けの実行可能ファイル(PEフォーマット)を生成する際に、シンボルテーブル内のセクション番号が正しく設定されないという問題(Issue #7899)を修正するために行われました。
PEフォーマットでは、実行可能ファイルは複数のセクション(例: コードセクション、データセクション)に分割されます。シンボル(関数名や変数名など)は、そのシンボルが定義されているセクションへの参照を持つ必要があります。以前の実装では、シンボルが属するセクションの決定ロジックが不正確であり、特にデータセクション内のシンボルに対して問題が発生していました。これにより、生成された実行可能ファイルのデバッグ情報やシンボル解決に不整合が生じる可能性がありました。
Issue #7899の報告によると、この問題は特にWindows環境でのGoプログラムのデバッグにおいて、シンボル情報が正しく表示されない、あるいはシンボル解決が失敗するといった形で現れていました。この修正は、PEフォーマットの仕様に厳密に従い、シンボルがそのアドレスに基づいて正しいセクションにマッピングされるようにすることで、これらの問題を解決することを目的としています。
前提知識の解説
-
リンカ (Linker): コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するツールです。リンカは、異なるオブジェクトファイル間の参照(例えば、あるファイルで定義された関数を別のファイルから呼び出す場合)を解決し、最終的な実行可能ファイルのメモリレイアウトを決定します。Go言語では、
cmd/ld
がこのリンカの役割を担っています。 -
PE (Portable Executable) フォーマット: Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL (Dynamic Link Library) などのファイル形式です。PEファイルは、ヘッダ、セクションテーブル、および複数のセクション(例:
.text
(コード),.data
(初期化済みデータ),.rdata
(読み取り専用データ) など)で構成されます。 -
シンボルテーブル (Symbol Table): 実行可能ファイルやオブジェクトファイル内に含まれるデータ構造で、プログラム内のシンボル(関数名、変数名、ラベルなど)とそのアドレスや型などの情報が格納されています。デバッガやリンカは、このシンボルテーブルを利用して、プログラムの実行中に特定のコードやデータにアクセスしたり、デバッグ情報を表示したりします。
-
セクション (Section): PEファイル内の論理的なブロックで、特定の種類のデータやコードを格納します。例えば、
.text
セクションには実行可能なコードが、.data
セクションには初期化されたグローバル変数や静的変数が、.rdata
セクションには読み取り専用データ(文字列リテラルなど)が格納されます。各セクションは、ファイル内で特定のオフセットとサイズを持ち、メモリにロードされる際にも特定の仮想アドレスにマッピングされます。 -
セクション番号 (Section Number): シンボルテーブルのエントリには、そのシンボルが属するセクションを示す番号が含まれることがあります。これは、シンボルがどのセクションに定義されているかをリンカやデバッガが識別するために使用されます。
-
LSym
構造体: Go言語のリンカ内部で使用されるシンボルを表す構造体です。シンボルの名前、値(アドレス)、型、サイズなどの情報が含まれます。 -
COFFSym
構造体: PEフォーマットのシンボルテーブルエントリに対応する内部構造体です。このコミットでは、COFFSym
にvalue
フィールドが追加され、シンボルのセクション内オフセットを保持するようになりました。
技術的詳細
このコミットの主要な変更点は、src/cmd/ld/pe.c
ファイル内の addsym
関数と、それに付随するシンボル値の計算ロジックです。
以前の実装では、addsym
関数内でシンボルがテキストセクション (textsect
) に属するかデータセクション (datasect
) に属するかを、シンボルの type
フィールドに基づいて単純に判断していました。しかし、これはシンボルの実際のアドレスとセクションの仮想アドレス範囲との整合性を保証するものではありませんでした。特に、データセクション内のシンボルが正しく識別されないケースがありました。
新しい実装では、COFFSym
構造体に value
フィールドが追加されました。この value
フィールドは、シンボルが属するセクションの先頭からのオフセットを格納するために使用されます。
addsym
関数内では、シンボルの s->value
(シンボルの仮想アドレス) を segdata.vaddr
(データセクションの仮想アドレス) および segtext.vaddr
(テキストセクションの仮想アドレス) と比較することで、シンボルがどのセクションに属するかをより正確に判断するようになりました。
s->value >= segdata.vaddr
の場合: シンボルはデータセクションに属すると判断され、cs->value
はs->value - segdata.vaddr
として計算されます。これは、シンボルのアドレスからデータセクションの開始アドレスを引くことで、データセクション内でのオフセットを求めるものです。cs->sect
はdatasect
に設定されます。s->value >= segtext.vaddr
の場合: シンボルはテキストセクションに属すると判断され、cs->value
はs->value - segtext.vaddr
として計算されます。これは、シンボルのアドレスからテキストセクションの開始アドレスを引くことで、テキストセクション内でのオフセットを求めるものです。cs->sect
はtextsect
に設定されます。- 上記いずれにも該当しない場合: シンボルはどの既知のセクションにも属さないと判断され、
cs->value
とcs->sect
は0に設定され、診断メッセージ (diag
) が出力されます。
また、以前存在した datoffsect
関数は削除されました。この関数はシンボルのアドレスからセクションオフセットを計算しようとしていましたが、新しい addsym
関数内のロジックに置き換えられ、より正確なセクション判定とオフセット計算が可能になりました。
最終的に、addsymtable
関数内でシンボルテーブルエントリを書き込む際に、以前は datoffsect(s->sym->value)
を使用していた箇所が、新しく計算された s->value
(COFFSymのvalueフィールド) を直接使用するように変更されました。これにより、PEシンボルテーブルの Value
フィールドに、シンボルが属するセクションの先頭からの正しいオフセットが書き込まれるようになります。
この変更により、WindowsのPEフォーマットにおけるシンボルテーブルのセクション情報が正確になり、デバッガがGoプログラムのシンボルを正しく解決できるようになります。
コアとなるコードの変更箇所
src/cmd/ld/pe.c
ファイルが変更されています。
--- a/src/cmd/ld/pe.c
+++ b/src/cmd/ld/pe.c
@@ -84,6 +84,7 @@ struct COFFSym
LSym* sym;
int strtbloff;
int sect;
+ vlong value; // 追加
};
static COFFSym* coffsym;
@@ -476,6 +477,7 @@ newPEDWARFSection(char *name, vlong size)
static void
addsym(LSym *s, char *name, int type, vlong addr, vlong size, int ver, LSym *gotype)
{
+ COFFSym *cs; // 追加
USED(name);
USED(addr);
USED(size);
@@ -498,28 +500,25 @@ addsym(LSym *s, char *name, int type, vlong addr, vlong size, int ver, LSym *got
}
if(coffsym) {
- coffsym[ncoffsym].sym = s;
+ cs = &coffsym[ncoffsym]; // 変更: csポインタを使用
+ cs->sym = s;
if(strlen(s->name) > 8)
- coffsym[ncoffsym].strtbloff = strtbladd(s->name);
- if(type == 'T')
- coffsym[ncoffsym].sect = textsect;
- else
- coffsym[ncoffsym].sect = datasect;
+ cs->strtbloff = strtbladd(s->name); // 変更: csポインタを使用
+ if(s->value >= segdata.vaddr) { // 変更: シンボル値に基づいてセクションを判定
+ cs->value = s->value - segdata.vaddr;
+ cs->sect = datasect;
+ } else if(s->value >= segtext.vaddr) {
+ cs->value = s->value - segtext.vaddr;
+ cs->sect = textsect;
+ } else {
+ cs->value = 0;
+ cs->sect = 0;
+ diag("addsym %#llx", addr);
+ }
}
ncoffsym++;
}
-- static vlong // 削除: datoffsect関数
-- datoffsect(vlong addr)
-- {
-- if(addr >= segdata.vaddr)
-- return addr - segdata.vaddr;
-- if(addr >= segtext.vaddr)
-- return addr - segtext.vaddr;
-- diag("datoff %#llx", addr);
-- return 0;
-- }
--
static void
addsymtable(void)
{
@@ -551,7 +549,7 @@ addsymtable(void)
lputl(0);
lputl(s->strtbloff);
}
- lputl(datoffsect(s->sym->value)); // 変更: datoffsectの呼び出しを削除
+ lputl(s->value); // 変更: COFFSymのvalueフィールドを直接使用
wputl(s->sect);
wputl(0x0308); // "array of structs"
cput(2); // storage class: external
コアとなるコードの解説
-
COFFSym
構造体へのvalue
フィールドの追加:struct COFFSym { LSym* sym; int strtbloff; int sect; vlong value; // 追加されたフィールド };
この
value
フィールドは、PEシンボルテーブルのValue
フィールドに対応し、シンボルが属するセクションの先頭からのオフセットを格納するために導入されました。これにより、シンボルのアドレスがセクションの仮想アドレス範囲内でどこに位置するかを正確に表現できるようになります。 -
addsym
関数の変更:COFFSym *cs;
というポインタ変数が導入され、coffsym[ncoffsym]
へのアクセスを簡潔にしています。- シンボルのセクション判定ロジックが大幅に変更されました。以前はシンボルの
type
に基づいていましたが、新しいロジックではs->value
(シンボルの仮想アドレス) をsegdata.vaddr
(データセクションの開始仮想アドレス) およびsegtext.vaddr
(テキストセクションの開始仮想アドレス) と比較することで、シンボルがどのセクションに属するかを決定します。
この変更により、シンボルが実際にメモリ上で配置されるアドレスに基づいて、正しいセクションとセクション内オフセットが決定されるようになりました。if(s->value >= segdata.vaddr) { cs->value = s->value - segdata.vaddr; // データセクション内のオフセットを計算 cs->sect = datasect; } else if(s->value >= segtext.vaddr) { cs->value = s->value - segtext.vaddr; // テキストセクション内のオフセットを計算 cs->sect = textsect; } else { cs->value = 0; cs->sect = 0; diag("addsym %#llx", addr); // どのセクションにも属さない場合の診断メッセージ }
-
datoffsect
関数の削除: 以前のバージョンに存在したdatoffsect
関数は、シンボルのアドレスからセクションオフセットを計算する役割を担っていましたが、そのロジックがaddsym
関数内に統合され、より堅牢なセクション判定ロジックに置き換えられたため、削除されました。 -
addsymtable
関数の変更: シンボルテーブルエントリをファイルに書き込む際、以前はdatoffsect(s->sym->value)
を呼び出してシンボル値を計算していましたが、このコミットではs->value
(COFFSym構造体の新しいvalue
フィールド) を直接使用するように変更されました。lputl(s->value); // COFFSymのvalueフィールドを直接使用 wputl(s->sect);
これにより、
addsym
関数で正確に計算されたセクション内オフセットが、PEシンボルテーブルのValue
フィールドに正しく書き込まれることが保証されます。
これらの変更により、Goリンカが生成するWindows PE実行可能ファイルのシンボルテーブルの正確性が向上し、デバッグツールがシンボル情報をより信頼性高く利用できるようになりました。
関連リンク
- Go Issue #7899: https://code.google.com/p/go/issues/detail?id=7899 (古いGoプロジェクトのIssueトラッカーへのリンクですが、関連する問題の議論が見られる可能性があります)
- Go CL 97920044: https://golang.org/cl/97920044 (このコミットに対応するGoのコードレビューページ)
参考にした情報源リンク
- Portable Executable (PE) Format: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
- COFF Symbol Table (PE Format): https://learn.microsoft.com/en-us/windows/win32/debug/coff-symbol-table
- Go言語のリンカ (
cmd/ld
) のソースコード: https://github.com/golang/go/tree/master/src/cmd/ld - Go言語のIssueトラッカー (現在の): https://github.com/golang/go/issues (Issue #7899は古いトラッカーに存在するため、直接参照は難しい場合がありますが、一般的なGoのIssueの探し方として記載)
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (CL 97920044の元となるシステム)