[インデックス 19499] ファイルの概要
このコミットは、Go言語のdebug/elf
パッケージにおいて、32ビット(386)アーキテクチャ向けのELFファイルに含まれるDWARFデバッグ情報が、再配置(relocation)を必要とする場合に正しく処理されるようにするための修正です。特に、開発版のClangコンパイラによって生成されたELFファイルでこの問題が顕在化しました。この変更により、386アーキテクチャのELFファイルからDWARF情報を正確に読み取ることが可能になり、デバッグの信頼性が向上します。
コミット
commit 0e197515b6b2514a4be25f25e3862b74baa5c9ed
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Jun 3 16:39:40 2014 -0700
debug/elf: support DWARF that needs relocs for 386
It's not clear how widespread this issue is, but we do have a
test case generated by a development version of clang.
I don't know whether this should go into 1.3 or not; happy to
hear arguments either way.
LGTM=rsc
R=golang-codereviews, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/96680045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0e197515b6b2514a4be25f25e3862b74baa5c9ed
元コミット内容
このコミットは、Goのdebug/elf
パッケージが、386アーキテクチャ(32ビットIntel互換プロセッサ)向けのELFファイルに埋め込まれたDWARFデバッグ情報を処理する際に、再配置(relocation)が必要なケースに対応するためのものです。この問題は、特に開発版のClangコンパイラによって生成された特定のテストケースで確認されました。コミットメッセージでは、この問題の広範な影響は不明であるとしつつも、具体的なテストケースが存在するため対応が必要であると述べています。また、Go 1.3リリースに含めるべきかどうかの議論も示唆されています。
変更の背景
ELFファイルは、実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプなどを格納するための標準的なファイル形式です。これらのファイルには、プログラムの実行に必要なコードやデータだけでなく、デバッグを容易にするための情報(DWARFデバッグ情報など)も含まれることがあります。
DWARF(Debugging With Attributed Record Formats)は、ソースコードとコンパイルされたバイナリコード間のマッピングを記述するための標準的なデバッグ情報形式です。デバッガはDWARF情報を使用して、変数名、型情報、関数呼び出しスタック、ソースコードの行番号などを解決し、開発者がプログラムの内部状態を理解できるようにします。
ELFファイル内のDWARF情報の一部は、プログラムのロード時に解決されるべきアドレスやオフセットを参照している場合があります。これらの参照は「再配置(relocation)」として表現され、リンカやローダが最終的なアドレスを決定する際に適用されます。
Goのdebug/elf
パッケージは、ELFファイルを解析し、その内容(セクション、シンボル、DWARF情報など)をGoプログラムから利用できるようにするためのライブラリです。しかし、このコミット以前は、32ビット(386)アーキテクチャのELFファイルにおいて、DWARF情報自体が再配置を必要とするケース(特に特定のコンパイラによって生成された場合)が適切に処理されていませんでした。
この問題が顕在化したのは、開発版のClangコンパイラによって生成されたELFファイルでした。ClangはLLVMプロジェクトの一部であり、C、C++、Objective-C、Objective-C++などの言語をコンパイルするためのフロントエンドです。コンパイラによっては、DWARF情報を生成する際に、その情報内に再配置エントリを含めることがあります。debug/elf
パッケージがこれらの再配置を適用せずにDWARF情報を読み込もうとすると、デバッグ情報が破損したり、不正確になったりする可能性がありました。
このコミットは、このような特定のシナリオ(386アーキテクチャ、DWARF情報内の再配置、Clangコンパイラ)に対応し、debug/elf
パッケージがより堅牢にELFファイルを解析できるようにすることを目的としています。
前提知識の解説
このコミットの理解には、以下の技術的概念の知識が役立ちます。
-
ELF (Executable and Linkable Format):
- Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
- ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして多数のセクションで構成されます。
- セクション: コード(
.text
)、初期化済みデータ(.data
)、未初期化データ(.bss
)、シンボルテーブル(.symtab
)、文字列テーブル(.strtab
)、再配置情報(.rel
や.rela
)、デバッグ情報(.debug_info
など)など、様々な種類のデータを含みます。
-
DWARF (Debugging With Attributed Record Formats):
- コンパイルされたプログラムのデバッグ情報を表現するための標準フォーマットです。
- ソースコードの行番号、変数名、型情報、関数、スタックフレームの構造など、デバッガがソースレベルでプログラムを理解するために必要な情報を提供します。
- DWARF情報は通常、ELFファイル内の
.debug_info
,.debug_abbrev
,.debug_str
などのセクションに格納されます。
-
再配置 (Relocation):
- コンパイル時やリンク時には、プログラム内の特定のアドレスがまだ最終的に決定されていない場合があります(例: 共有ライブラリ内の関数のアドレス、グローバル変数のアドレス)。
- 再配置エントリは、これらの未解決のアドレスを指し示し、プログラムがメモリにロードされる際に、リンカやローダが正しいアドレスに修正するための指示を提供します。
- ELFファイルには、再配置情報を含むセクション(例:
.rel.text
,.rela.data
)があります。 - 再配置エントリは、通常、修正すべきオフセット、シンボルインデックス、再配置タイプ(例:
R_386_32
,R_X86_64_PC32
)を含みます。 R_386_32
: 32ビットアーキテクチャ(386)における再配置タイプの一つで、32ビットの絶対アドレスを修正するために使用されます。通常、シンボルの値が直接加算されます。
-
debug/elf
パッケージ (Go言語):- Go標準ライブラリの一部で、ELFファイルを解析し、その構造や内容にアクセスするための機能を提供します。
- このパッケージは、ELFヘッダ、セクション、シンボルテーブル、再配置エントリなどを読み取り、Goのデータ構造にマッピングします。
File
構造体はELFファイル全体を表し、そのメソッドを通じて様々な情報にアクセスできます。
-
EM_386
とELFCLASS32
:EM_386
は、ELFヘッダのe_machine
フィールドで指定されるマシンアーキテクチャタイプで、Intel 80386互換プロセッサ(32ビット)を意味します。ELFCLASS32
は、ELFヘッダのe_ident[EI_CLASS]
フィールドで指定されるELFファイルのクラスで、32ビットアーキテクチャ向けのファイルであることを示します。
-
Clangコンパイラ:
- LLVMプロジェクトの一部であるC、C++、Objective-C、Objective-C++のコンパイラフロントエンドです。
- GCC(GNU Compiler Collection)と同様に、様々なプラットフォーム向けのバイナリを生成できます。コンパイラの実装によっては、DWARF情報の生成方法や再配置の扱いが異なる場合があります。
技術的詳細
このコミットの核心は、32ビット(386)アーキテクチャのELFファイルにおいて、DWARFデバッグ情報が格納されているセクション(例: .debug_info
)自体が再配置を必要とする場合に、その再配置を正しく適用するロジックを追加することです。
ELFファイル内の再配置は、通常、コードセクションやデータセクション内のアドレスを修正するために使用されます。しかし、デバッグ情報(DWARF)もまた、プログラム内の特定のアドレスやオフセットを参照することがあり、これらの参照がコンパイル時やリンク時に未解決である場合、デバッグ情報セクション自体に再配置エントリが付随することがあります。
このコミットでは、以下の主要な変更が行われました。
-
applyRelocations386
関数の追加:- この新しい関数は、32ビット(386)アーキテクチャ向けのELF再配置を処理するために特化しています。
- 入力として、再配置を適用する対象のバイトスライス(
dst
)と、再配置エントリのバイトスライス(rels
)を受け取ります。 rels
スライスは、Rel32
構造体(32ビット再配置エントリ)の配列として解釈されます。Rel32
は、再配置オフセット(Off
)と再配置情報(Info
)を含みます。Info
フィールドは、再配置タイプ(下位8ビット)とシンボルインデックス(上位ビット)をエンコードしています。- 関数は、
rels
内の各Rel32
エントリをループ処理します。 - 各エントリについて、シンボルテーブルから対応するシンボル(
sym
)を取得します。 - 再配置タイプが
R_386_32
である場合、dst
の指定されたオフセット(rel.Off
)にある32ビット値に、対応するシンボル(sym.Value
)の値を加算します。これにより、DWARF情報内の参照が正しいアドレスに修正されます。
-
DWARF
関数での386向け再配置の適用:File
構造体のDWARF()
メソッドは、ELFファイルからDWARFデバッグ情報を読み取り、debug/dwarf
パッケージのData
構造体として返します。- このコミットでは、
DWARF()
関数内に、386アーキテクチャ(f.Machine == EM_386
)の場合に.rel.debug_info
セクション(DWARF情報に対する再配置を含むセクション)が存在するかどうかをチェックするロジックが追加されました。 - もし
.rel.debug_info
セクションが存在し、そのタイプがSHT_REL
(再配置セクション)であれば、そのセクションのデータ(再配置エントリ)を読み込み、applyRelocations
関数を呼び出して、.debug_info
セクションのデータ(dat[1]
)に再配置を適用します。 - これにより、
debug/dwarf
パッケージに渡される前に、DWARF情報が正しく修正されます。
-
applyRelocations
関数の拡張:- 既存の
applyRelocations
関数は、ELFクラスとマシンタイプに基づいて適切な再配置適用関数をディスパッチする役割を担っています。 - このコミットにより、
ELFCLASS32
かつEM_386
の場合に、新しく追加されたapplyRelocations386
関数を呼び出すように変更されました。
- 既存の
これらの変更により、debug/elf
パッケージは、386アーキテクチャのELFファイルに含まれるDWARF情報が再配置を必要とする場合でも、その情報を正確に解析し、デバッガが利用できる形式で提供できるようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/pkg/debug/elf/file.go
ファイルに集中しています。
-
src/pkg/debug/elf/file.go
:func (f *File) applyRelocations(dst []byte, rels []byte) error
関数に、32ビット(386)アーキテクチャ向けの条件分岐が追加されました。--- a/src/pkg/debug/elf/file.go +++ b/src/pkg/debug/elf/file.go @@ -522,13 +522,17 @@ func (f *File) applyRelocations(dst []byte, rels []byte) error { if f.Class == ELFCLASS64 && f.Machine == EM_X86_64 { return f.applyRelocationsAMD64(dst, rels) } + if f.Class == ELFCLASS32 && f.Machine == EM_386 { + return f.applyRelocations386(dst, rels) + } return errors.New("not implemented") }
- 新しい関数
func (f *File) applyRelocations386(dst []byte, rels []byte) error
が追加されました。この関数は、386アーキテクチャの32ビット再配置(R_386_32
)を処理する具体的なロジックを含みます。func (f *File) applyRelocations386(dst []byte, rels []byte) error { // 8 is the size of Rel32. if len(rels)%8 != 0 { return errors.New("length of relocation section is not a multiple of 8") } symbols, _, err := f.getSymbols(SHT_SYMTAB) if err != nil { return err } b := bytes.NewReader(rels) var rel Rel32 for b.Len() > 0 { binary.Read(b, f.ByteOrder, &rel) symNo := rel.Info >> 8 t := R_386(rel.Info & 0xff) if symNo == 0 || symNo > uint32(len(symbols)) { continue } sym := &symbols[symNo-1] if t == R_386_32 { if rel.Off+4 >= uint32(len(dst)) { continue } val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4]) val += uint32(sym.Value) f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val) } } return nil }
func (f *File) DWARF() (*dwarf.Data, error)
関数内に、386アーキテクチャのDWARF情報に対する再配置を適用するロジックが追加されました。--- a/src/pkg/debug/elf/file.go +++ b/src/pkg/debug/elf/file.go @@ -603,6 +644,19 @@ func (f *File) DWARF() (*dwarf.Data, error) { } } + // When using clang we need to process relocations even for 386. + rel := f.Section(".rel.debug_info") + if rel != nil && rel.Type == SHT_REL && f.Machine == EM_386 { + data, err := rel.Data() + if err != nil { + return nil, err + } + err = f.applyRelocations(dat[1], data) + if err != nil { + return nil, err + } + } + abbrev, info, str := dat[0], dat[1], dat[2] d, err := dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str) if err != nil {
-
src/pkg/debug/elf/file_test.go
:- 新しいテストケース
testdata/go-relocation-test-clang-x86.obj
が追加され、Clangによって生成された386アーキテクチャのELFファイルに対するDWARF情報の再配置処理が正しく行われることを検証します。--- a/src/pkg/debug/elf/file_test.go +++ b/src/pkg/debug/elf/file_test.go @@ -260,6 +260,12 @@ var relocationTests = []relocationTest{ {0, &dwarf.Entry{Offset: 0xb, Tag: dwarf.TagCompileUnit, Children: true, Field: []dwarf.Field{{Attr: dwarf.AttrProducer, Val: "GNU C 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"}, {Attr: dwarf.AttrLanguage, Val: int64(1)}, {Attr: dwarf.AttrName, Val: "go-relocation-test-gcc424.c"}, {Attr: dwarf.AttrCompDir, Val: "/tmp"}, {Attr: dwarf.AttrLowpc, Val: uint64(0x0)}, {Attr: dwarf.AttrHighpc, Val: uint64(0x6)}, {Attr: dwarf:AttrStmtList, Val: int64(0)}}}}, }, }, + { + "testdata/go-relocation-test-clang-x86.obj", + []relocationTestEntry{ + {0, &dwarf.Entry{Offset: 0xb, Tag: dwarf.TagCompileUnit, Children: true, Field: []dwarf.Field{{Attr: dwarf.AttrProducer, Val: "clang version google3-trunk (trunk r209387)"}, {Attr: dwarf.AttrLanguage, Val: int64(12)}, {Attr: dwarf.AttrName, Val: "go-relocation-test-clang.c"}, {Attr: dwarf.AttrStmtList, Val: int64(0)}, {Attr: dwarf.AttrCompDir, Val: "/tmp"}}}}, + }, + }, { "testdata/gcc-amd64-openbsd-debug-with-rela.obj", []relocationTestEntry{
- 新しいテストケース
-
src/pkg/debug/elf/testdata/go-relocation-test-clang-x86.obj
:- 新しいバイナリテストデータファイルが追加されました。これは、開発版のClangによって生成された、DWARF情報に再配置が必要な386アーキテクチャのELFオブジェクトファイルです。
コアとなるコードの解説
このコミットの主要な変更は、src/pkg/debug/elf/file.go
内のapplyRelocations386
関数と、DWARF
関数におけるその呼び出しロジックです。
applyRelocations386
関数
この関数は、32ビット(386)アーキテクチャに特化した再配置の適用ロジックを実装しています。
if len(rels)%8 != 0
:Rel32
構造体のサイズは8バイトです。再配置エントリのバイトスライスrels
の長さが8の倍数でない場合、データが破損しているか、予期しない形式であるためエラーを返します。symbols, _, err := f.getSymbols(SHT_SYMTAB)
: ELFファイルのシンボルテーブル(SHT_SYMTAB
)からシンボル情報を取得します。再配置は通常、特定のシンボルを参照するため、この情報が必要です。b := bytes.NewReader(rels)
: 再配置エントリのバイトスライスをbytes.NewReader
でラップし、binary.Read
を使って構造体を読み込めるようにします。for b.Len() > 0
:rels
スライス内のすべての再配置エントリを処理するためのループです。binary.Read(b, f.ByteOrder, &rel)
: 現在の再配置エントリをRel32
構造体として読み込みます。f.ByteOrder
はELFファイルのエンディアン(バイト順序)を示します。symNo := rel.Info >> 8
:Rel32.Info
フィールドの上位ビットからシンボルインデックスを抽出します。このインデックスは、シンボルテーブル内のどのシンボルがこの再配置に関連しているかを示します。t := R_386(rel.Info & 0xff)
:Rel32.Info
フィールドの下位8ビットから再配置タイプを抽出します。ここではR_386
型の列挙値にキャストされます。if symNo == 0 || symNo > uint32(len(symbols))
: シンボルインデックスが0(未定義シンボル)であるか、シンボルテーブルの範囲外である場合は、この再配置をスキップします。sym := &symbols[symNo-1]
: 抽出したシンボルインデックスに基づいて、シンボルテーブルから対応するシンボルを取得します。if t == R_386_32
: 再配置タイプがR_386_32
である場合にのみ、以下の処理を実行します。val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
:dst
(再配置を適用する対象のバイトスライス、ここではDWARF情報の一部)のrel.Off
オフセットから4バイト(32ビット値)を読み込みます。val += uint32(sym.Value)
: 読み込んだ値に、参照しているシンボル(sym
)の最終的な値(アドレスやオフセット)を加算します。これがR_386_32
再配置の基本的な適用方法です。f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
: 計算された新しい値をdst
の元の位置に書き戻します。これにより、DWARF情報内の参照が修正されます。
DWARF
関数における再配置の適用
DWARF
関数は、ELFファイルからDWARF情報を抽出し、debug/dwarf
パッケージが解釈できる形式で提供します。このコミットでは、この関数に以下の重要なロジックが追加されました。
rel := f.Section(".rel.debug_info")
: まず、ELFファイル内に.rel.debug_info
という名前のセクションが存在するかどうかを検索します。このセクションは、.debug_info
セクションに対する再配置エントリを格納します。if rel != nil && rel.Type == SHT_REL && f.Machine == EM_386
:.rel.debug_info
セクションが存在し(rel != nil
)、- そのセクションのタイプが再配置セクション(
SHT_REL
)であり、 - かつ、ELFファイルが386アーキテクチャ向け(
f.Machine == EM_386
)である場合にのみ、以下の再配置適用処理を実行します。 - この条件は、Clangのような特定のコンパイラが386アーキテクチャでDWARF情報に再配置を含める場合に特化して対応するためのものです。
data, err := rel.Data()
:.rel.debug_info
セクションの生データを読み込みます。これが再配置エントリのバイトスライスになります。err = f.applyRelocations(dat[1], data)
: 読み込んだ再配置エントリ(data
)を、.debug_info
セクションのデータ(dat[1]
)に適用します。applyRelocations
関数は、内部でf.Machine
とf.Class
に基づいて適切なapplyRelocations386
(このケースでは)を呼び出します。
この一連の処理により、debug/dwarf
パッケージに渡される前に、386アーキテクチャのELFファイル内のDWARF情報が、必要な再配置が適用された状態で提供されるようになります。これにより、デバッガは正確なデバッグ情報を利用できるようになり、デバッグ体験が向上します。
関連リンク
- Go CL 96680045: https://golang.org/cl/96680045
- Go
debug/elf
パッケージのドキュメント: https://pkg.go.dev/debug/elf - Go
debug/dwarf
パッケージのドキュメント: https://pkg.go.dev/debug/dwarf
参考にした情報源リンク
- ELFファイルフォーマットの概要: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- DWARFデバッグ情報フォーマットの概要: https://en.wikipedia.org/wiki/DWARF
- ELF再配置タイプ (R_386_32など): https://docs.oracle.com/cd/E19683-01/817-3677/6mbb7710o/index.html (Oracle Solaris Studioのドキュメントですが、ELF再配置の一般的な説明として有用)
- Clangコンパイラ: https://clang.llvm.org/
- LLVMプロジェクト: https://llvm.org/