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

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

このコミットは、Go言語の debug/elf パッケージに、ELF (Executable and Linkable Format) ファイルの動的シンボルテーブルを読み取る機能と、関連するテストを追加し、シンボルの順序を明確にするものです。

コミット

commit f5b600f70cbe1e504a7623a7e0636535c49ea6c8
Author: Pietro Gagliardi <pietro10@mac.com>
Date:   Thu Jul 10 12:44:40 2014 -0700

    debug/elf: add (*File).DynamicSymbols, ErrNoSymbols, and tests for (*File).Symbols and (*File).DynamicSymbols, and formalize symbol order.
    
    Added a complement to (*File).Symbols for the dynamic symbol table.
    Would be useful, for instance, if seraching for certain shared objects
    compatible with certain libraries (for instance, LADSPA requires an
    exported symbol "ladspa_descriptor").
    
    Added a variable ErrNoSymbols that canonicalizes a return from
    (*File).Symbols and (*File).DyanmicSymbols if the file has no symbols.
    
    Added tests for both (*File).Symbols and (*File).DynamicSymbols;
    there was never a test for (*File).Symbols at all. A small C program using
    libelf, included in the test data, was used to produce the golden
    symbols to compare against.
    
    As part of the requirements for testing, (*File).Symbols and (*File).DynamicSymbols now document the order in which the symbol tables are returned (in the order the symbols appear in the file).
    
    All tests currently pass.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/107530043

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

https://github.com/golang/go/commit/f5b600f70cbe1e504a7623a7e0636535c49ea6c8

元コミット内容

debug/elf パッケージに以下の変更が加えられました。

  1. (*File).DynamicSymbols メソッドの追加: ELFファイルの動的シンボルテーブルを読み取るための新しいメソッドが導入されました。これは既存の (*File).Symbols メソッド(静的シンボルテーブル用)を補完するものです。
  2. ErrNoSymbols 変数の追加: シンボルセクションが存在しない場合に (*File).Symbols および (*File).DynamicSymbols が返すエラーを正規化するための変数です。
  3. シンボル取得メソッドのテスト追加: (*File).Symbols(*File).DynamicSymbols の両方に対してテストが追加されました。特に (*File).Symbols にはこれまでテストが存在しませんでした。テストデータには libelf を使用した小さなCプログラムで生成された「ゴールデンシンボル」が用いられています。
  4. シンボル順序の明確化: (*File).Symbols および (*File).DynamicSymbols が返すシンボルテーブルの順序が、ファイル内でのシンボルの出現順序であることが明文化されました。

変更の背景

この変更の主な背景は、Go言語の debug/elf パッケージの機能強化と堅牢性の向上です。

ELFファイルは、Unix系システムにおける実行可能ファイル、オブジェクトファイル、共有ライブラリなどの標準フォーマットです。これらのファイルには、プログラムが使用する関数や変数などのシンボル情報が含まれています。シンボル情報は、デバッグ、動的リンク、プロファイリングなど、様々な目的で利用されます。

既存の (*File).Symbols メソッドは静的シンボルテーブル(通常、コンパイル時に解決されるシンボル)へのアクセスを提供していましたが、動的シンボルテーブル(実行時に動的にリンクされるシンボル、例えば共有ライブラリからのシンボル)への直接的なアクセス手段がありませんでした。動的シンボルテーブルへのアクセスは、特定の共有オブジェクトが特定のライブラリ(例: LADSPAプラグインが ladspa_descriptor シンボルをエクスポートしているかどうかの確認)と互換性があるかを検索する際などに非常に有用です。

また、シンボル取得メソッドにはテストが不足しており、特に (*File).Symbols には全くテストがありませんでした。これにより、将来の変更に対するコードの信頼性が低い状態でした。シンボル取得の際にエラーが返される場合の処理も一貫性がなく、ErrNoSymbols のような正規化されたエラーの導入が求められていました。

さらに、シンボルが返される順序が明確に定義されていなかったため、利用者がシンボルテーブルの構造に依存するコードを書く際に不確実性がありました。これをファイル内での出現順序に固定し、ドキュメント化することで、APIの使いやすさと予測可能性が向上します。

前提知識の解説

ELF (Executable and Linkable Format)

ELFは、Unix系オペレーティングシステム(Linux、BSDなど)で広く使用されている、実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプの標準ファイルフォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクションから構成されます。

シンボルテーブル

ELFファイルには、プログラム内の関数や変数などの名前(シンボル)とそのアドレスや型などの情報が格納された「シンボルテーブル」が含まれています。シンボルテーブルは、リンカが異なるオブジェクトファイルを結合したり、デバッガがソースコードと実行中のプログラムを関連付けたりするために不可欠です。

ELFファイルには主に2種類のシンボルテーブルが存在します。

  1. 静的シンボルテーブル (Symbol Table, .symtab セクション):

    • 通常、コンパイル時に生成され、オブジェクトファイルや静的ライブラリに含まれます。
    • プログラム内のすべてのグローバルシンボル(関数、変数)や、デバッグ情報に関連するローカルシンボルが含まれます。
    • リンカがプログラムをリンクする際に使用されます。
  2. 動的シンボルテーブル (Dynamic Symbol Table, .dynsym セクション):

    • 共有ライブラリや動的にリンクされる実行可能ファイルに含まれます。
    • 実行時に動的に解決されるシンボル(例: libc.so からインポートされる printf 関数など)のみが含まれます。
    • 動的リンカ(ランタイムリンカ)がプログラムの実行時に共有ライブラリをロードし、シンボルを解決するために使用します。

LADSPA (Linux Audio Developer's Simple Plugin API)

LADSPAは、オーディオプラグインのためのオープン標準APIです。オーディオ処理アプリケーションが、様々なオーディオエフェクトや楽器のプラグインをロードして使用できるように設計されています。LADSPAプラグインは通常、共有ライブラリとして提供され、特定のシンボル(例: ladspa_descriptor)をエクスポートすることで、ホストアプリケーションから認識・ロードされます。このコミットの背景で言及されているように、動的シンボルテーブルを解析する機能は、このようなプラグインの検出や互換性チェックに役立ちます。

libelf

libelf は、ELFファイルを読み書きするためのC言語ライブラリです。ELFファイルの構造を解析し、シンボルテーブルやセクションデータなどにアクセスするためのAPIを提供します。このコミットのテストで「ゴールデンシンボル」を生成するために使用されたと述べられているのは、libelf がELFファイルの標準的な解析ツールとして信頼されているためです。

技術的詳細

このコミットは、Go言語の debug/elf パッケージの内部実装にいくつかの重要な変更を加えています。

  1. (*File).DynamicSymbols の実装:

    • この新しいメソッドは、ELFファイルの SHT_DYNSYM (Dynamic Symbol Table) セクションを検索し、その内容を解析して []Symbol 型のスライスとして返します。
    • 内部的には、既存の f.getSymbols(SHT_DYNSYM) を呼び出すことで、シンボルデータの取得とパースを行っています。
    • (*File).Symbols と同様に、インデックス0のヌルシンボルは互換性のために省略されます。
  2. ErrNoSymbols の導入と利用:

    • 以前は、シンボルセクションが見つからない場合に errors.New("no symbol section") という新しいエラーがその場で生成されていました。
    • このコミットでは、var ErrNoSymbols = errors.New("no symbol section") としてグローバル変数 ErrNoSymbols を定義し、getSymbols32 および getSymbols64 関数内でこの変数を使用するように変更されました。
    • これにより、エラーの比較が err == ErrNoSymbols のように行えるようになり、エラー処理の一貫性と効率が向上します。
  3. シンボル順序の明確化:

    • (*File).Symbols(*File).DynamicSymbols のドキュメントに、「The symbols will be listed in the order they appear in f.」(シンボルはファイル内での出現順序でリストされる)という記述が追加されました。
    • これは、ELFファイルのシンボルテーブルが通常、特定の順序(例えば、シンボル値の昇順や、ファイル内のオフセット順)で格納されていることを反映したもので、APIの振る舞いを明確にすることで、利用者がより予測可能なコードを書けるようになります。
  4. 包括的なテストの追加:

    • src/pkg/debug/elf/symbols_test.go という新しいテストファイルが追加されました。
    • このテストファイルには、TestSymbols 関数が含まれており、様々なELFファイル(gcc-amd64-linux-execgo-relocation-test-clang-x86.objhello-world-core.gz など)に対して (*File).Symbols(*File).DynamicSymbols の両方をテストします。
    • テストは、testdata/getgoldsym.c というCプログラムと libelf を使用して生成された「ゴールデンシンボル」データと比較することで、正確性を検証します。これにより、GoのELFパーサーが外部の信頼できるツールと同じ結果を返すことが保証されます。
    • symbolsGoldendynamicSymbolsGolden というマップ変数に、各テストファイルに対する期待されるシンボルデータがハードコードされています。

これらの変更により、debug/elf パッケージは、ELFファイルのシンボル情報をより包括的かつ堅牢に解析できるようになり、特に動的リンクされたプログラムや共有ライブラリの分析においてその有用性が高まりました。

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

src/pkg/debug/elf/file.go

--- a/src/pkg/debug/elf/file.go
+++ b/src/pkg/debug/elf/file.go
@@ -405,10 +405,14 @@ func (f *File) getSymbols(typ SectionType) ([]Symbol, []byte, error) {
 	return nil, nil, errors.New("not implemented")
 }
 
+// ErrNoSymbols is returned by File.Symbols and File.DynamicSymbols
+// if there is no such section in the File.
+var ErrNoSymbols = errors.New("no symbol section")
+
 func (f *File) getSymbols32(typ SectionType) ([]Symbol, []byte, error) {
 	symtabSection := f.SectionByType(typ)
 	if symtabSection == nil {
-		return nil, nil, errors.New("no symbol section")
+		return nil, nil, ErrNoSymbols
 	}
 
 	data, err := symtabSection.Data()
@@ -451,7 +455,7 @@ func (f *File) getSymbols32(typ SectionType) ([]Symbol, []byte, error) {
 func (f *File) getSymbols64(typ SectionType) ([]Symbol, []byte, error) {
 	symtabSection := f.SectionByType(typ)
 	if symtabSection == nil {
-		return nil, nil, errors.New("no symbol section")
+		return nil, nil, ErrNoSymbols
 	}
 
 	data, err := symtabSection.Data()
@@ -698,7 +702,8 @@ func (f *File) DWARF() (*dwarf.Data, error) {
 	return d, nil
 }
 
-// Symbols returns the symbol table for f.
+// Symbols returns the symbol table for f. The symbols will be listed in the order
+// they appear in 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
@@ -708,6 +713,17 @@ func (f *File) Symbols() ([]Symbol, error) {
 	return sym, err
 }
 
+// DynamicSymbols returns the dynamic symbol table for f. The symbols
+// will be listed in the order they appear in f.
+//
+// For compatibility with Symbols, DynamicSymbols 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) DynamicSymbols() ([]Symbol, error) {
+	sym, _, err := f.getSymbols(SHT_DYNSYM)
+	return sym, err
+}
+
 type ImportedSymbol struct {
 	Name    string
 	Version string

src/pkg/debug/elf/symbols_test.go (新規ファイル)

このファイルは非常に長いため、ここでは主要な構造のみを示します。

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package elf

import (
	"io"
	"path"
	"reflect"
	"testing"
)

// TODO: remove duplicate code
func TestSymbols(t *testing.T) {
	do := func(file string, ts []Symbol, getfunc func(*File) ([]Symbol, error)) {
		// ... ファイルのオープンとシンボル取得ロジック ...
		if err != nil && err != ErrNoSymbols {
			t.Error(err)
			return
		} else if err == ErrNoSymbols {
			fs = []Symbol{}
		}
		if !reflect.DeepEqual(ts, fs) {
			t.Errorf("%s: Symbols = %v, want %v", file, ts, fs)
		}
	}
	for file, ts := range symbolsGolden {
		do(file, ts, (*File).Symbols)
	}
	for file, ts := range dynamicSymbolsGolden {
		do(file, ts, (*File).DynamicSymbols)
	}
}

// golden symbol table data generated by testdata/getgoldsym.c

var symbolsGolden = map[string][]Symbol{
	"testdata/gcc-amd64-linux-exec": {
		// ... 多数のSymbol構造体定義 ...
	},
	"testdata/go-relocation-test-clang-x86.obj": {
		// ... 多数のSymbol構造体定義 ...
	},
	"testdata/hello-world-core.gz": {},
}

var dynamicSymbolsGolden = map[string][]Symbol{
	"testdata/gcc-amd64-linux-exec": {
		// ... 多数のSymbol構造体定義 ...
	},
	"testdata/go-relocation-test-clang-x86.obj": {},
	"testdata/hello-world-core.gz":              {},
}

コアとなるコードの解説

src/pkg/debug/elf/file.go の変更点

  1. ErrNoSymbols 変数の定義と利用:

    • var ErrNoSymbols = errors.New("no symbol section") が追加され、シンボルセクションが見つからない場合に返されるエラーがこの変数に統一されました。
    • getSymbols32 および getSymbols64 関数内で、symtabSection == nil のチェック時に errors.New("no symbol section") の代わりに ErrNoSymbols を返すように変更されています。これにより、エラーの比較がポインタ比較ではなく値比較で行えるようになり、エラーハンドリングがより堅牢になります。
  2. (*File).Symbols のドキュメント更新:

    • 既存の Symbols メソッドのコメントに「The symbols will be listed in the order they appear in f.」という一文が追加されました。これは、シンボルがファイル内で出現する順序で返されることを明示し、APIの振る舞いを明確にしています。
  3. (*File).DynamicSymbols メソッドの追加:

    • 新しいパブリックメソッド DynamicSymbols() ([]Symbol, error) が追加されました。
    • このメソッドは、内部的に f.getSymbols(SHT_DYNSYM) を呼び出します。SHT_DYNSYM はELFのセクションタイプの一つで、動的シンボルテーブルを示します。
    • Symbols メソッドと同様に、インデックス0のヌルシンボルは結果から除外されます。これにより、静的シンボルと動的シンボルの両方に対して一貫したAPIが提供されます。

src/pkg/debug/elf/symbols_test.go の新規追加

  1. TestSymbols 関数:

    • このテスト関数は、(*File).Symbols(*File).DynamicSymbols の両方をテストするための共通ロジックを提供します。
    • do というヘルパー関数が定義されており、ELFファイルをオープンし、指定されたシンボル取得関数((*File).Symbols または (*File).DynamicSymbols)を呼び出し、結果を「ゴールデンデータ」と比較します。
    • ErrNoSymbols が返された場合は、シンボルリストが空であることを期待します。
  2. symbolsGoldendynamicSymbolsGolden マップ:

    • これらのマップは、テスト対象のELFファイルパスをキーとし、期待される []Symbol スライスを値として持ちます。
    • これらの「ゴールデンデータ」は、testdata/getgoldsym.c というCプログラムが libelf を使用して生成したものです。これにより、GoのELFパーサーが業界標準のツールと互換性のある結果を生成することが保証されます。
    • テストは、様々な種類のELFファイル(実行可能ファイル、オブジェクトファイル、圧縮されたファイルなど)に対して実行され、GoのELFパーサーの堅牢性を確認します。

これらの変更により、Goの debug/elf パッケージは、ELFファイルのシンボル情報をより包括的に、正確に、そしてテストによって検証された形で提供できるようになりました。特に動的シンボルテーブルへのアクセスは、Goで書かれたツールがより高度なバイナリ解析や操作を行う上で重要な機能となります。

関連リンク

参考にした情報源リンク

  • Go言語のコミットメッセージと差分情報: /home/orange/Project/comemo/commit_data/19718.txt
  • Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
  • Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/107530043 (コミットメッセージに記載されているCLリンク)
  • ELFファイルフォーマットに関する一般的な知識 (Web検索)
  • シンボルテーブルに関する一般的な知識 (Web検索)
  • LADSPAに関する一般的な知識 (Web検索)
  • libelfに関する一般的な知識 (Web検索)