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

[インデックス 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との互換性のためにヌルシンボルが省略されること、および外部から提供されるインデックス xsymtab[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

コアとなるコードの解説

ヌルシンボルのスキップ

getSymbols32getSymbols64 関数に追加された以下のコードが、ヌルシンボルをスキップする核心部分です。

	// 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)

参考にした情報源リンク