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

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

このコミットは、Go言語のdebug/elfパッケージにおいて、ELF (Executable and Linkable Format) ファイルのダイナミックセクションから文字列型のダイナミックタグ(DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATHなど)の値を汎用的に取得する機能を追加し、既存のImportedLibraries関数をその新機能を利用するようにリファクタリングするものです。これにより、ELFファイルの解析機能が拡張され、より柔軟な情報抽出が可能になります。

コミット

commit cf06750372c054f0faa9c79f0f957a2114b68b46
Author: Mike Rosset <mike.rosset@gmail.com>
Date:   Fri Aug 3 14:46:20 2012 -0400

    debug/elf: Add support for getting DynTag string table values.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6430064
---
 src/pkg/debug/elf/file.go      | 31 ++++++++++++++++++++++--------9
 src/pkg/debug/elf/file_test.go | 11 +++++++++++
 2 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/src/pkg/debug/elf/file.go b/src/pkg/debug/elf/file.go
index 241430e5c6..d38da4bf8e 100644
--- a/src/pkg/debug/elf/file.go
+++ b/src/pkg/debug/elf/file.go
@@ -726,6 +726,20 @@ func (f *File) gnuVersion(i int, sym *ImportedSymbol) {
 // referred to by the binary f that are expected to be
 // linked with the binary at dynamic link time.
 func (f *File) ImportedLibraries() ([]string, error) {
+	return f.DynString(DT_NEEDED)
+}
+
+// DynString returns the strings listed for the given tag in the file's dynamic
+// section.
+//
+// The tag must be one that takes string values: DT_NEEDED, DT_SONAME, DT_RPATH, or
+// DT_RUNPATH.
+func (f *File) DynString(tag DynTag) ([]string, error) {
+	switch tag {
+	case DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATH:
+	default:
+		return nil, fmt.Errorf("non-string-valued tag %v", tag)
+	}
 	ds := f.SectionByType(SHT_DYNAMIC)
 	if ds == nil {
 		// not dynamic, so no libraries
@@ -741,25 +755,24 @@ func (f *File) ImportedLibraries() ([]string, error) {
 	}
 	var all []string
 	for len(d) > 0 {
-		var tag DynTag
-		var value uint64
+		var t DynTag
+		var v uint64
 		switch f.Class {
 		case ELFCLASS32:
-			tag = DynTag(f.ByteOrder.Uint32(d[0:4]))
-			value = uint64(f.ByteOrder.Uint32(d[4:8]))
+			t = DynTag(f.ByteOrder.Uint32(d[0:4]))
+			v = uint64(f.ByteOrder.Uint32(d[4:8]))
 			d = d[8:]
 		case ELFCLASS64:
-			tag = DynTag(f.ByteOrder.Uint64(d[0:8]))
-			value = f.ByteOrder.Uint64(d[8:16])
+			t = DynTag(f.ByteOrder.Uint64(d[0:8]))
+			v = f.ByteOrder.Uint64(d[8:16])
 			d = d[16:]
 		}
-		if tag == DT_NEEDED {
-			s, ok := getString(str, int(value))
+		if t == tag {
+			s, ok := getString(str, int(v))
 			if ok {
 				all = append(all, s)
 			}
 		}
 	}
-
 	return all, nil
 }
diff --git a/src/pkg/debug/elf/file_test.go b/src/pkg/debug/elf/file_test.go
index 6ec5f4f62c..12036e816b 100644
--- a/src/pkg/debug/elf/file_test.go
+++ b/src/pkg/debug/elf/file_test.go
@@ -19,6 +19,7 @@ type fileTest struct {
 	hdr      FileHeader
 	sections []SectionHeader
 	progs    []ProgHeader
+	needed   []string
 }
 
 var fileTests = []fileTest{
@@ -64,6 +65,7 @@ var fileTests = []fileTest{
 			{PT_LOAD, PF_R + PF_W, 0x5fc, 0x80495fc, 0x80495fc, 0xd8, 0xf8, 0x1000},
 			{PT_DYNAMIC, PF_R + PF_W, 0x60c, 0x804960c, 0x804960c, 0x98, 0x98, 0x4},
 		},
+		[]string{"libc.so.6"},
 	},
 	{
 		"testdata/gcc-amd64-linux-exec",
@@ -117,6 +119,7 @@ var fileTests = []fileTest{
 			{PT_LOOS + 0x474E550, PF_R, 0x5b8, 0x4005b8, 0x4005b8, 0x24, 0x24, 0x4},
 			{PT_LOOS + 0x474E551, PF_R + PF_W, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8},
 		},
+		[]string{"libc.so.6"},
 	},
 }
 
@@ -161,6 +164,14 @@ func TestOpen(t *testing.T) {
 		if tn != fn {
 			t.Errorf("open %s: len(Progs) = %d, want %d", tt.file, fn, tn)
 		}
+		tl := tt.needed
+		fl, err := f.ImportedLibraries()
+		if err != nil {
+			t.Error(err)
+		}
+		if !reflect.DeepEqual(tl, fl) {
+			t.Errorf("open %s: DT_NEEDED = %v, want %v", tt.file, tl, fl)
+		}
 	}
 }
 

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

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

元コミット内容

debug/elfパッケージに、ダイナミックタグの文字列テーブル値を取得する機能を追加。

変更の背景

Go言語のdebug/elfパッケージは、ELF (Executable and Linkable Format) ファイルを解析するための機能を提供します。以前のImportedLibraries関数は、ELFファイルのダイナミックセクションからDT_NEEDEDタグ(プログラムが依存する共有ライブラリの名前)のみを抽出するようにハードコードされていました。しかし、ELFのダイナミックセクションにはDT_SONAME(共有オブジェクトの名称)、DT_RPATH(実行時ライブラリ検索パス)、DT_RUNPATH(実行時ライブラリ検索パス、DT_RPATHより優先)など、他にも文字列値を持つ重要なダイナミックタグが存在します。

このコミットの背景には、これらの他の文字列型ダイナミックタグの値も汎用的に取得できるように、debug/elfパッケージの機能を拡張したいというニーズがありました。既存のImportedLibraries関数を汎用的な関数に置き換えることで、コードの重複を避け、よりクリーンで拡張性の高いAPIを提供することが目的です。

前提知識の解説

ELF (Executable and Linkable Format)

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

動的リンク (Dynamic Linking)

動的リンクは、プログラムが実行される際に必要なライブラリ(共有ライブラリ)をロードし、リンクするプロセスです。これにより、複数のプログラムが同じライブラリのコピーを共有できるため、ディスクスペースとメモリを節約できます。動的リンクされたプログラムは、実行時にリンカ(ダイナミックリンカ/ローダー)によって依存する共有ライブラリが解決されます。

ダイナミックセクション (Dynamic Section)

ELFファイルには、動的リンクに関する情報が格納される特別なセクションがあります。これが.dynamicセクション(またはSHT_DYNAMICタイプを持つセクション)です。このセクションは、Elf_Dyn構造体の配列で構成されており、各エントリはタグ(d_tag)と値(d_valまたはd_ptr)を持ちます。タグはエントリの種類を示し、値はそのタグに対応する情報(数値、アドレス、文字列テーブルへのオフセットなど)を保持します。

ダイナミックタグ (DynTag)

ダイナミックセクション内の各エントリは、その目的を示すタグ(DynTag)を持っています。このコミットで特に関連するのは、文字列値を持つ以下のタグです。

  • DT_NEEDED: プログラムが実行時に必要とする共有ライブラリの名前。例えば、libc.so.6など。これらの名前は文字列テーブルへのオフセットとして格納されます。
  • DT_SONAME: 共有オブジェクト自身の名前。共有ライブラリが自身を識別するために使用します。
  • DT_RPATH: 実行時ライブラリ検索パス。プログラムが共有ライブラリを検索する際に使用するディレクトリのリスト。
  • DT_RUNPATH: 実行時ライブラリ検索パス。DT_RPATHと同様ですが、リンカのデフォルトパスや環境変数LD_LIBRARY_PATHよりも優先されます。

文字列テーブル (String Table)

ELFファイルには、シンボル名、セクション名、ファイル名、そしてDT_NEEDEDなどのダイナミックタグが参照するライブラリ名などの文字列が格納されるセクションがあります。これが文字列テーブル(.strtab.dynstrなど)です。ダイナミックセクション内のエントリが文字列を参照する場合、その値は文字列テーブル内の該当文字列へのオフセットとして表現されます。

技術的詳細

このコミットの主要な変更点は、debug/elfパッケージのFile構造体にDynStringという新しいメソッドを追加したことです。

  1. DynString関数の導入:

    • この関数はDynTag型の引数tagを受け取り、そのタグに対応する文字列のリスト([]string)とエラーを返します。
    • 関数はまず、引数として渡されたtagが文字列値を持つタグ(DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATH)のいずれかであることを検証します。これら以外のタグが指定された場合はエラーを返します。
    • 次に、ELFファイルのダイナミックセクション(SHT_DYNAMICタイプを持つセクション)を検索します。ダイナミックセクションが存在しない場合は、動的リンクされていないファイルとみなし、空のリストを返します。
    • ダイナミックセクションが見つかった場合、その内容をバイトスライスとして読み込み、ELFファイルのクラス(32ビットまたは64ビット)とエンディアン(バイトオーダー)に基づいて、各Elf_Dynエントリをパースします。
    • パースされた各エントリのタグが、DynString関数に渡されたtagと一致する場合、そのエントリの値(文字列テーブルへのオフセット)を使用して、関連する文字列テーブルから実際の文字列を取得し、結果のリストに追加します。
  2. ImportedLibraries関数のリファクタリング:

    • 既存のImportedLibraries関数は、DynString(DT_NEEDED)を呼び出すように変更されました。これにより、ImportedLibrariesの内部ロジックが簡素化され、DynStringの汎用的な実装を利用するようになりました。これは、コードの重複を排除し、保守性を向上させる良い例です。
  3. テストの追加:

    • src/pkg/debug/elf/file_test.goには、fileTest構造体にneeded []stringフィールドが追加され、テストケースに期待されるDT_NEEDEDの値が定義されました。
    • TestOpen関数内で、f.ImportedLibraries()を呼び出し、その結果が期待されるneededスライスと一致するかどうかをreflect.DeepEqualを使って検証するテストロジックが追加されました。これにより、ImportedLibraries関数の変更が正しく機能すること、そしてDynString関数がDT_NEEDEDを正しく処理できることが保証されます。

この変更により、debug/elfパッケージは、ELFファイルの動的リンク情報をより詳細かつ柔軟に解析できるようになり、デバッグツールやバイナリ解析ツールにとって有用な機能が提供されます。

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

src/pkg/debug/elf/file.go

// ImportedLibraries returns the names of the shared libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func (f *File) ImportedLibraries() ([]string, error) {
	return f.DynString(DT_NEEDED) // ここが変更点: DynStringを呼び出すように
}

// DynString returns the strings listed for the given tag in the file's dynamic
// section.
//
// The tag must be one that takes string values: DT_NEEDED, DT_SONAME, DT_RPATH, or
// DT_RUNPATH.
func (f *File) DynString(tag DynTag) ([]string, error) {
	switch tag { // 文字列値を持つタグのバリデーション
	case DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATH:
	default:
		return nil, fmt.Errorf("non-string-valued tag %v", tag)
	}
	ds := f.SectionByType(SHT_DYNAMIC)
	if ds == nil {
		// not dynamic, so no libraries
		return nil, nil
	}
	d, err := ds.Data()
	if err != nil {
		return nil, err
	}
	str, err := f.DynStringTable()
	if err != nil {
		return nil, err
	}
	var all []string
	for len(d) > 0 {
		var t DynTag
		var v uint64
		switch f.Class { // 32bit/64bit ELFクラスに応じたパース
		case ELFCLASS32:
			t = DynTag(f.ByteOrder.Uint32(d[0:4]))
			v = uint64(f.ByteOrder.Uint32(d[4:8]))
			d = d[8:]
		case ELFCLASS64:
			t = DynTag(f.ByteOrder.Uint64(d[0:8]))
			v = f.ByteOrder.Uint64(d[8:16])
			d = d[16:]
		}
		if t == tag { // 指定されたタグと一致する場合のみ処理
			s, ok := getString(str, int(v))
			if ok {
				all = append(all, s)
			}
		}
	}
	return all, nil
}

src/pkg/debug/elf/file_test.go

type fileTest struct {
	file     string
	hdr      FileHeader
	sections []SectionHeader
	progs    []ProgHeader
	needed   []string // 新しく追加されたフィールド
}

// ... (fileTestsの定義にneededフィールドの初期値が追加)

func TestOpen(t *testing.T) {
	for _, tt := range fileTests {
		// ... (既存のテストロジック)

		tl := tt.needed
		fl, err := f.ImportedLibraries() // ImportedLibrariesを呼び出し
		if err != nil {
			t.Error(err)
		}
		if !reflect.DeepEqual(tl, fl) { // 結果を検証
			t.Errorf("open %s: DT_NEEDED = %v, want %v", tt.file, tl, fl)
		}
	}
}

コアとなるコードの解説

DynString関数

  • シグネチャ: func (f *File) DynString(tag DynTag) ([]string, error)
    • f *File: ELFファイルを表現するFile構造体のポインタ。
    • tag DynTag: 取得したい文字列値を持つダイナミックタグ。
    • []string: 取得された文字列のリスト。
    • error: エラーが発生した場合。
  • タグのバリデーション: 関数冒頭のswitch tag文は、DynStringが処理できるタグをDT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATHに限定しています。これらはELF仕様で文字列値を持つと定義されているタグです。これにより、誤ったタグが渡された場合に早期にエラーを検出できます。
  • ダイナミックセクションの読み込み: f.SectionByType(SHT_DYNAMIC)でダイナミックセクションを取得し、ds.Data()でその内容をバイトスライスとして読み込みます。
  • 文字列テーブルの取得: f.DynStringTable()は、ダイナミックセクションが参照する文字列テーブル(通常は.dynstr)を取得します。このテーブルから、オフセットを使って実際の文字列を読み出します。
  • エントリのパースとフィルタリング:
    • for len(d) > 0ループで、ダイナミックセクションのバイトスライスを順次処理します。
    • ELFファイルのクラス(f.ClassELFCLASS32ELFCLASS64か)に応じて、32ビットまたは64ビットの形式でタグ(t)と値(v)を読み取ります。
    • if t == tagの条件文が重要です。これは、現在処理しているダイナミックエントリのタグ(t)が、DynString関数に引数として渡された目的のタグ(tag)と一致する場合にのみ、そのエントリを処理することを示しています。これにより、特定のタグに絞って文字列を抽出できます。
    • getString(str, int(v))は、文字列テーブルstrから、値v(文字列テーブルへのオフセット)に対応する文字列を取得するヘルパー関数です。取得した文字列はallスライスに追加されます。

ImportedLibraries関数

  • この関数は、単にf.DynString(DT_NEEDED)を呼び出すように変更されました。これにより、ImportedLibrariesの具体的な実装はDynStringに委譲され、コードが簡潔になりました。

テストコードの変更

  • fileTest構造体にneeded []stringフィールドが追加され、テスト対象のELFファイルが期待するDT_NEEDEDの値(例: {"libc.so.6"})を保持できるようになりました。
  • TestOpen関数内で、f.ImportedLibraries()の戻り値と、テストケースで定義された期待値tt.neededreflect.DeepEqualで比較されます。これにより、ImportedLibrariesが正しく共有ライブラリ名を抽出できることが自動的に検証されます。

これらの変更により、debug/elfパッケージはより汎用的なELFファイル解析機能を提供し、将来的な拡張性も向上しています。

関連リンク

参考にした情報源リンク