[インデックス 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
という新しいメソッドを追加したことです。
-
DynString
関数の導入:- この関数は
DynTag
型の引数tag
を受け取り、そのタグに対応する文字列のリスト([]string
)とエラーを返します。 - 関数はまず、引数として渡された
tag
が文字列値を持つタグ(DT_NEEDED
,DT_SONAME
,DT_RPATH
,DT_RUNPATH
)のいずれかであることを検証します。これら以外のタグが指定された場合はエラーを返します。 - 次に、ELFファイルのダイナミックセクション(
SHT_DYNAMIC
タイプを持つセクション)を検索します。ダイナミックセクションが存在しない場合は、動的リンクされていないファイルとみなし、空のリストを返します。 - ダイナミックセクションが見つかった場合、その内容をバイトスライスとして読み込み、ELFファイルのクラス(32ビットまたは64ビット)とエンディアン(バイトオーダー)に基づいて、各
Elf_Dyn
エントリをパースします。 - パースされた各エントリのタグが、
DynString
関数に渡されたtag
と一致する場合、そのエントリの値(文字列テーブルへのオフセット)を使用して、関連する文字列テーブルから実際の文字列を取得し、結果のリストに追加します。
- この関数は
-
ImportedLibraries
関数のリファクタリング:- 既存の
ImportedLibraries
関数は、DynString(DT_NEEDED)
を呼び出すように変更されました。これにより、ImportedLibraries
の内部ロジックが簡素化され、DynString
の汎用的な実装を利用するようになりました。これは、コードの重複を排除し、保守性を向上させる良い例です。
- 既存の
-
テストの追加:
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.Class
がELFCLASS32
かELFCLASS64
か)に応じて、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.needed
がreflect.DeepEqual
で比較されます。これにより、ImportedLibraries
が正しく共有ライブラリ名を抽出できることが自動的に検証されます。
これらの変更により、debug/elf
パッケージはより汎用的なELFファイル解析機能を提供し、将来的な拡張性も向上しています。
関連リンク
- Go CL 6430064: https://golang.org/cl/6430064
参考にした情報源リンク
- ELF (Executable and Linkable Format) の概要:
- Wikipedia: https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
- System V Application Binary Interface (ABI) - DRAFT Version 0.99 (ELF仕様の公式ドキュメント): https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib-elf.html
- 動的リンクとダイナミックセクション:
- Linux Program Library HOWTO - Shared Libraries: https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
man ld.so
(Linux dynamic linker/loader): https://man7.org/linux/man-pages/man8/ld.so.8.html
- ELF ダイナミックタグ (DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATH):
man elf
(Linux ELF man page): https://man7.org/linux/man-pages/man5/elf.5.html (特にd_tag
のセクションを参照)- Oracle Solaris Linker and Libraries Guide - Dynamic Section: https://docs.oracle.com/cd/E19253-01/817-1984/6m661111j/index.html
- Go言語
debug/elf
パッケージ:- GoDoc: https://pkg.go.dev/debug/elf
- Go言語のソースコード (このコミットが適用された後の状態): https://github.com/golang/go/tree/master/src/debug/elf