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

[インデックス 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ファイルを解析できるようにすることを目的としています。

前提知識の解説

このコミットの理解には、以下の技術的概念の知識が役立ちます。

  1. ELF (Executable and Linkable Format):

    • Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
    • ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして多数のセクションで構成されます。
    • セクション: コード(.text)、初期化済みデータ(.data)、未初期化データ(.bss)、シンボルテーブル(.symtab)、文字列テーブル(.strtab)、再配置情報(.rel.rela)、デバッグ情報(.debug_infoなど)など、様々な種類のデータを含みます。
  2. DWARF (Debugging With Attributed Record Formats):

    • コンパイルされたプログラムのデバッグ情報を表現するための標準フォーマットです。
    • ソースコードの行番号、変数名、型情報、関数、スタックフレームの構造など、デバッガがソースレベルでプログラムを理解するために必要な情報を提供します。
    • DWARF情報は通常、ELFファイル内の.debug_info, .debug_abbrev, .debug_strなどのセクションに格納されます。
  3. 再配置 (Relocation):

    • コンパイル時やリンク時には、プログラム内の特定のアドレスがまだ最終的に決定されていない場合があります(例: 共有ライブラリ内の関数のアドレス、グローバル変数のアドレス)。
    • 再配置エントリは、これらの未解決のアドレスを指し示し、プログラムがメモリにロードされる際に、リンカやローダが正しいアドレスに修正するための指示を提供します。
    • ELFファイルには、再配置情報を含むセクション(例: .rel.text, .rela.data)があります。
    • 再配置エントリは、通常、修正すべきオフセット、シンボルインデックス、再配置タイプ(例: R_386_32, R_X86_64_PC32)を含みます。
    • R_386_32: 32ビットアーキテクチャ(386)における再配置タイプの一つで、32ビットの絶対アドレスを修正するために使用されます。通常、シンボルの値が直接加算されます。
  4. debug/elfパッケージ (Go言語):

    • Go標準ライブラリの一部で、ELFファイルを解析し、その構造や内容にアクセスするための機能を提供します。
    • このパッケージは、ELFヘッダ、セクション、シンボルテーブル、再配置エントリなどを読み取り、Goのデータ構造にマッピングします。
    • File構造体はELFファイル全体を表し、そのメソッドを通じて様々な情報にアクセスできます。
  5. EM_386ELFCLASS32:

    • EM_386は、ELFヘッダのe_machineフィールドで指定されるマシンアーキテクチャタイプで、Intel 80386互換プロセッサ(32ビット)を意味します。
    • ELFCLASS32は、ELFヘッダのe_ident[EI_CLASS]フィールドで指定されるELFファイルのクラスで、32ビットアーキテクチャ向けのファイルであることを示します。
  6. Clangコンパイラ:

    • LLVMプロジェクトの一部であるC、C++、Objective-C、Objective-C++のコンパイラフロントエンドです。
    • GCC(GNU Compiler Collection)と同様に、様々なプラットフォーム向けのバイナリを生成できます。コンパイラの実装によっては、DWARF情報の生成方法や再配置の扱いが異なる場合があります。

技術的詳細

このコミットの核心は、32ビット(386)アーキテクチャのELFファイルにおいて、DWARFデバッグ情報が格納されているセクション(例: .debug_info)自体が再配置を必要とする場合に、その再配置を正しく適用するロジックを追加することです。

ELFファイル内の再配置は、通常、コードセクションやデータセクション内のアドレスを修正するために使用されます。しかし、デバッグ情報(DWARF)もまた、プログラム内の特定のアドレスやオフセットを参照することがあり、これらの参照がコンパイル時やリンク時に未解決である場合、デバッグ情報セクション自体に再配置エントリが付随することがあります。

このコミットでは、以下の主要な変更が行われました。

  1. 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情報内の参照が正しいアドレスに修正されます。
  2. 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情報が正しく修正されます。
  3. applyRelocations関数の拡張:

    • 既存のapplyRelocations関数は、ELFクラスとマシンタイプに基づいて適切な再配置適用関数をディスパッチする役割を担っています。
    • このコミットにより、ELFCLASS32かつEM_386の場合に、新しく追加されたapplyRelocations386関数を呼び出すように変更されました。

これらの変更により、debug/elfパッケージは、386アーキテクチャのELFファイルに含まれるDWARF情報が再配置を必要とする場合でも、その情報を正確に解析し、デバッガが利用できる形式で提供できるようになりました。

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

このコミットにおける主要なコード変更は、src/pkg/debug/elf/file.goファイルに集中しています。

  1. 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 {
      
  2. 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{
      
  3. 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.Machinef.Classに基づいて適切なapplyRelocations386(このケースでは)を呼び出します。

この一連の処理により、debug/dwarfパッケージに渡される前に、386アーキテクチャのELFファイル内のDWARF情報が、必要な再配置が適用された状態で提供されるようになります。これにより、デバッガは正確なデバッグ情報を利用できるようになり、デバッグ体験が向上します。

関連リンク

参考にした情報源リンク