[インデックス 15878] ファイルの概要
このコミットは、Go言語の標準ライブラリである debug/elf
パッケージにおいて、ELF (Executable and Linkable Format) ファイルのシンボルテーブルを扱う際のセマンティクスをGo 1.0の挙動に戻すことを目的としています。具体的には、(*File).Symbols
メソッドが返すシンボルリストから、ELFシンボルテーブルの最初の要素であるヌルシンボルを再びスキップするように変更し、それに伴い applyRelocationsAMD64
の実装も調整しています。これにより、以前の変更によって発生したテストの失敗を修正し、Go 1.0との互換性を維持しています。
コミット
commit aafc444b74ba2a4dc56e6d5d26f8242f0857856a
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 21 17:01:39 2013 -0400
debug/elf: restore Go 1.0 semantics for (*File).Symbols
Also adjust the implementation of applyRelocationsAMD64
so that the test added in CL 6848044 still passes.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/7686049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aafc444b74ba2a4dc56e6d5d26f8242f0857856a
元コミット内容
このコミットの目的は、debug/elf
パッケージにおける (*File).Symbols
メソッドの動作を、Go 1.0のセマンティクス(意味論)に復元することです。加えて、applyRelocationsAMD64
の実装も調整し、以前の変更(CL 6848044で追加されたテスト)が引き続きパスするようにしています。これは、Go 1.1のリリースに向けた変更の一部であり、以前の変更が意図しない副作用をもたらしたため、その修正として行われました。
変更の背景
この変更の背景には、debug/elf
パッケージの (*File).Symbols
メソッドが、ELFシンボルテーブルの最初の「ヌルシンボル」(すべてゼロのエントリ)をスキップするかどうかの挙動に関する問題がありました。Go 1.0ではこのヌルシンボルをスキップしていましたが、Go 1.1の開発過程で導入されたCL 6848044("debug/elf: do not skip first symbol")によって、このスキップ処理が削除されました。
この変更は、シンボルテーブルのインデックスが元のELFシンボルテーブルのインデックスと一致するようにするという意図があったと考えられます。しかし、この変更は既存のコードやテストに影響を与え、特に applyRelocationsAMD64
のようなリロケーション処理を行う部分で問題を引き起こしました。CL 6848044で追加されたテストが、この変更によって失敗するようになったため、Go 1.0のセマンティクスに戻すことが決定されました。
つまり、このコミットは、Go 1.1のリリースに向けて、debug/elf
パッケージの安定性と後方互換性を確保するための修正パッチとして適用されました。
前提知識の解説
ELF (Executable and Linkable Format)
ELFは、Unix系オペレーティングシステムで実行可能ファイル、オブジェクトファイル、共有ライブラリ、コアダンプなどを表現するために使用される標準的なファイル形式です。プログラムのコード、データ、シンボル情報、リロケーション情報などが構造化されて格納されています。
ELFシンボルテーブル
ELFファイルには、プログラム内の関数や変数などのシンボルに関する情報が格納されたシンボルテーブルが含まれています。各シンボルエントリは、シンボルの名前、値(アドレス)、サイズ、型、バインディングなどの情報を持っています。
ELFシンボルテーブルの**最初の要素(インデックス0)は、慣例的に「ヌルシンボル」**として予約されています。このヌルシンボルは、名前が空で、値やサイズもゼロであることが多く、特定のシンボルを参照しない場合や、無効なシンボル参照を示すために使用されます。多くのツールやライブラリは、このヌルシンボルを特別扱いし、スキップすることが一般的です。
リロケーション (Relocation)
リロケーションとは、プログラムがメモリにロードされる際に、シンボル参照(関数呼び出しや変数アクセスなど)のアドレスを修正するプロセスです。コンパイル時には、これらの参照は相対アドレスや仮のアドレスで記述されており、実行時に実際のメモリ上のアドレスに解決される必要があります。リロケーションエントリは、どの場所を、どのシンボルに基づいて、どのように修正するかを記述します。
debug/elf
パッケージ (Go言語)
Go言語の標準ライブラリである debug/elf
パッケージは、GoプログラムからELFファイルを読み込み、その構造や内容(セクション、シンボルテーブル、リロケーションなど)を解析するための機能を提供します。デバッガやプロファイラ、その他のツールがELFファイルを扱う際に利用されます。
Go 1.0 セマンティクス
Go 1.0はGo言語の最初の安定版リリースであり、その時点での debug/elf
パッケージの挙動、特に (*File).Symbols
メソッドがヌルシンボルをスキップするというセマンティクスを指します。このコミットは、Go 1.1の開発中に変更されたこの挙動をGo 1.0のそれに「復元」しています。
技術的詳細
このコミットの技術的な核心は、debug/elf
パッケージがELFシンボルテーブルをどのように解釈し、Goの Symbol
構造体にマッピングするかという点にあります。
以前のCL 6848044では、debug/elf
の (*File).Symbols
メソッドが、ELFシンボルテーブルの最初のヌルシンボルをスキップしないように変更されました。この変更の意図は、debug/elf
が返すシンボルリストのインデックスが、元のELFシンボルテーブルのインデックスと直接一致するようにすることでした。しかし、この変更は、シンボルテーブルのインデックスに依存する他の処理、特にリロケーション処理に影響を与えました。
リロケーション処理では、リロケーションエントリが参照するシンボルのインデックスが、ELFシンボルテーブル内のシンボルの位置を示します。もし debug/elf
がヌルシンボルをスキップしない場合、Goの Symbol
スライス内のインデックスと、元のELFシンボルテーブルのインデックスが一致します。しかし、もし debug/elf
がヌルシンボルをスキップする場合、Goの Symbol
スライス内のインデックスは、元のELFシンボルテーブルのインデックスから1を引いた値になります(ヌルシンボルが取り除かれるため)。
このコミットでは、getSymbols32
および getSymbols64
関数(それぞれ32ビットおよび64ビットELFファイル用)において、シンボルテーブルを読み込む際に明示的に最初の Sym32Size
または Sym64Size
バイトを読み飛ばす(symtab.Read(skip[:])
)ことで、ヌルシンボルをスキップする挙動を復元しています。
これにより、(*File).Symbols
が返す []Symbol
スライスは、Go 1.0と同様にヌルシンボルを含まない形になります。この変更に伴い、applyRelocationsAMD64
関数内のリロケーション処理も調整されました。具体的には、リロケーションエントリが参照するシンボル番号 symNo
を使用して symbols
スライスからシンボルを取得する際に、symbols[symNo-1]
のようにインデックスを1つずらすことで、ヌルシンボルがスキップされた後の正しいシンボルを参照するように修正されています。また、symNo == 0
のケースも考慮され、ヌルシンボルへのリロケーションはスキップされるようになっています。
doc/go1.1.html
から関連する説明が削除されたのは、この変更によってGo 1.1の挙動がGo 1.0に戻ったため、以前の変更に関する注意書きが不要になったためです。
コアとなるコードの変更箇所
doc/go1.1.html
Go 1.1のリリースノートから、debug/elf
パッケージに関する以下の記述が削除されました。これは、ヌルシンボルをスキップしないという変更が元に戻されたため、その変更に関する注意書きが不要になったためです。
--- a/doc/go1.1.html
+++ b/doc/go1.1.html
@@ -258,28 +258,6 @@ TODO introduction
TODO
</p>
-<h3 id=\"debug_elf\">debug/elf</h3>
-
-<p>
-TODO: Decide whether to keep this change. See CL 6848044.
-</p>
-
-<p>
-Previous versions of the <a href=\"/debug/elf/\"><code>debug/elf</code></a> package
--intentionally skipped over the first
--symbol in the ELF symbol table, since it is always an empty symbol.
--This symbol
--is no longer skipped since indexes into the symbol table returned by <code>debug/elf</code>
--will be different from indexes into the original ELF symbol table.
--Any code that calls the methods
--<a href=\"/pkg/debug/elf/#File.Symbols\"><code>Symbols</code></a>
--or
--<a href=\"/pkg/debug/elf/#File.ImportedSymbols\"><code>ImportedSymbols</code></a>
--of the
--<a href=\"/pkg/debug/elf/#File\"><code>elf.File</code></a>
--type may need to be adjusted to account for the additional symbol and the change in symbol offsets.
-</p>
-
<h3 id=\"net\">net</h3>
<p>
src/pkg/debug/elf/file.go
getSymbols32
および getSymbols64
関数
これらの関数は、32ビットおよび64ビットのELFファイルからシンボルを読み込む役割を担っています。変更点として、シンボルテーブルのリーダー (symtab
) から最初のヌルシンボルを明示的に読み飛ばす処理が追加されました。
// getSymbols32
@@ -422,6 +422,10 @@ func (f *File) getSymbols32(typ SectionType) ([]Symbol, []byte, error) {
return nil, nil, errors.New("cannot load string table section")
}
+ // The first entry is all zeros.
+ var skip [Sym32Size]byte
+ symtab.Read(skip[:])
+
symbols := make([]Symbol, symtab.Len()/Sym32Size)
i := 0
// getSymbols64
@@ -461,6 +465,10 @@ func (f *File) getSymbols64(typ SectionType) ([]Symbol, []byte, error) {
return nil, nil, errors.New("cannot load string table section")
}
+ // The first entry is all zeros.
+ var skip [Sym64Size]byte
+ symtab.Read(skip[:])
+
symbols := make([]Symbol, symtab.Len()/Sym64Size)
i := 0
applyRelocationsAMD64
関数
AMD64アーキテクチャ向けのリロケーションを適用する関数です。シンボル参照のインデックス計算が調整されました。
@@ -533,10 +541,10 @@ func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) error {
symNo := rela.Info >> 32
t := R_X86_64(rela.Info & 0xffff)
- if symNo >= uint64(len(symbols)) {
+ if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
- sym := &symbols[symNo]
+ sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
Symbols
メソッドのコメント更新
Symbols
メソッドのコメントが更新され、Go 1.0との互換性のためにヌルシンボルが省略されること、および外部から提供されるインデックス x
が symtab[x-1]
に対応することが明記されました。
@@ -597,6 +605,10 @@ func (f *File) DWARF() (*dwarf.Data, error) {
}
// Symbols returns the symbol table for f.
+//
+// For compatibility with Go 1.0, Symbols omits the null symbol at index 0.
+// After retrieving the symbols as symtab, an externally supplied index x
+// corresponds to symtab[x-1], not symtab[x].
func (f *File) Symbols() ([]Symbol, error) {
sym, _, err := f.getSymbols(SHT_SYMTAB)
return sym, err
コアとなるコードの解説
ヌルシンボルのスキップ
getSymbols32
と getSymbols64
関数に追加された以下のコードが、ヌルシンボルをスキップする核心部分です。
// The first entry is all zeros.
var skip [Sym32Size]byte // or Sym64Size for 64-bit
symtab.Read(skip[:])
symtab
はELFシンボルテーブルの生データを読み込むためのリーダーです。symtab.Read(skip[:])
は、Sym32Size
(または Sym64Size
)バイト、つまり最初のシンボルエントリのサイズ分だけデータを読み進めます。これにより、リーダーの現在位置が最初のシンボルエントリの直後に移動し、その後の make([]Symbol, ...)
や symtab.Read(symBytes[:])
で読み込まれるシンボルデータには、ヌルシンボルが含まれなくなります。これは、Go 1.0の挙動を明示的に復元するものです。
リロケーション処理の調整
applyRelocationsAMD64
関数では、リロケーションエントリが参照するシンボル番号 symNo
を使って symbols
スライスから対応するシンボルを取得します。ヌルシンボルがスキップされるようになったため、symbols
スライス内のインデックスは元のELFシンボルテーブルのインデックスよりも1つ小さくなります。
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
symNo == 0
のチェックが追加されました。これは、リロケーションがヌルシンボルを参照している場合(通常は無効な参照)に、そのリロケーションをスキップするためです。sym := &symbols[symNo-1]
の変更は、ヌルシンボルがスキップされた後のsymbols
スライスにおいて、正しいシンボルにアクセスするためのオフセット調整です。例えば、元のELFシンボルテーブルでインデックス1のシンボルは、ヌルシンボルがスキップされた後のsymbols
スライスではインデックス0に位置します。したがって、symNo
から1を引くことで、正しいシンボルを取得できます。
これらの変更により、debug/elf
パッケージはGo 1.0のセマンティクスに準拠し、リロケーション処理もそれに合わせて正しく動作するようになりました。
関連リンク
- このコミットのGo CL: https://golang.org/cl/7686049
- このコミットのGitHubページ: https://github.com/golang/go/commit/aafc444b74ba2a4dc56e6d5d26f8242f0857856a
- 関連するGo CL (CL 6848044): https://golang.org/cl/6848044 (debug/elf: do not skip first symbol)
参考にした情報源リンク
- ELF (Executable and Linkable Format) の概要:
- Wikipedia: https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
- Linux man page (elf): https://man7.org/linux/man-pages/man5/elf.5.html
- Go言語
debug/elf
パッケージ:- GoDoc: https://pkg.go.dev/debug/elf
- Go言語のリロケーション:
- Goのコンパイラとリンカに関するドキュメントやブログ記事(一般的な情報源)
- Go 1.0 リリースノート:
- Go言語公式サイト: https://go.dev/doc/go1
- Go 1.1 リリースノート:
- Go言語公式サイト: https://go.dev/doc/go1.1
- Go言語のコードレビューシステム (Gerrit):
- GoのCL (Change List) はGerrit上で管理されており、関連する議論や背景情報が参照できます。
- CL 6848044: https://go-review.googlesource.com/c/go/+/6848044
- CL 7686049: https://go-review.googlesource.com/c/go/+/7686049