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

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

src/cmd/ld/dwarf.c ファイルは、Go言語のリンカ(cmd/ld)の一部であり、Dwarfデバッグ情報、特にスタックアンワインドや変数情報に関連する部分を処理します。Dwarfは、コンパイルされたバイナリと元のソースコードとの間のマッピングを提供する標準的なデバッグ情報フォーマットです。このファイルは、Dwarfセクションの生成、特に.debug_frameセクションの処理、およびそれに関連する再配置(relocation)の管理を担当しています。

コミット

commit 28bbc6c27a2be9f6af9786394acb22ed4f8b81d3
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Sep 5 12:55:21 2013 -0700

    cmd/ld: emit relocations for .debug_frame in external link mode
    
    This should have been part of revision 16731:cdedb129e020, but
    I missed it.  This fixes printing local variables when doing
    an external link.
    
    No test because we aren't doing any debug info testing yet.
    
    Fixes #5719.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/13464046

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

https://github.com/golang/go/commit/28bbc6c27a2be9f6af9786394acb22ed4f8b81d3

元コミット内容

このコミットは、Goリンカ(cmd/ld)が外部リンカモードでリンクする際に、.debug_frameセクションに対する再配置(relocation)を正しく出力するように修正するものです。コミットメッセージによると、この変更は以前のコミット 16731:cdedb129e020 の一部として含まれるべきでしたが、見落とされていたとのことです。この修正により、外部リンカを使用した場合にローカル変数を正しく表示できるようになります。また、この変更はGoのIssue #5719を修正するものです。デバッグ情報に関するテストがまだ存在しないため、このコミットには新しいテストは含まれていません。

変更の背景

このコミットの背景には、Goリンカの外部リンカモードにおけるデバッグ情報の不完全性がありました。特に、Dwarfデバッグ情報の一部である.debug_frameセクションが、外部リンカを使用した場合に正しく処理されていませんでした。

  • Go Issue #5719: このコミットが修正するIssue #5719は、「go build -ldflags=-linkmode=external でビルドされたプログラムでGDBがローカル変数を表示できない」という問題でした。これは、外部リンカが.debug_frameセクション内のアドレスを正しく解決するための再配置情報が欠落していたためと考えられます。
  • 先行コミット 16731:cdedb129e020: コミットメッセージで言及されている revision 16731:cdedb129e020 は、GoのリンカがDwarfデバッグ情報を生成する際の一般的な改善に関連するコミットであると推測されます。このコミットは、Dwarfデバッグ情報の生成と処理をより堅牢にするための広範な取り組みの一部であり、今回の修正はその見落としを補完するものです。

外部リンカモードでは、Goのツールチェインが生成したオブジェクトファイルを、システムのリンカ(例: gccclang)が最終的な実行可能ファイルにリンクします。このプロセスにおいて、Dwarfデバッグ情報内のアドレス参照が正しく解決されるためには、適切な再配置エントリが必要となります。欠落していた再配置エントリが、GDBのようなデバッガがローカル変数のアドレスを特定できない原因となっていました。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を理解しておく必要があります。

  1. Dwarfデバッグ情報:

    • Dwarfは、ソースレベルのデバッグを可能にするために、コンパイルされたプログラムに埋め込まれる標準的なデバッグ情報フォーマットです。
    • デバッガはDwarf情報を使用して、ソースコードの行番号、変数名、型情報、関数呼び出しスタックなどを、実行中のバイナリのメモリ位置やレジスタ値にマッピングします。
    • Dwarf情報は複数のセクションに分かれており、それぞれが特定の種類のデバッグ情報を格納します。
  2. .debug_frameセクション:

    • Dwarfのセクションの一つで、関数呼び出しフレームの情報を格納します。
    • 具体的には、Call Frame Information (CFI) と呼ばれるデータを含み、これによりデバッガはプログラムの実行中にスタックトレースを再構築したり、特定の時点でのレジスタの状態や変数の位置を特定したりすることができます。
    • CFIは、Function Descriptor Entry (FDE) と Common Information Entry (CIE) から構成されます。FDEは特定の関数のフレーム情報を記述し、CIEは複数のFDEで共有される共通の情報を記述します。
  3. 再配置(Relocation):

    • コンパイルされたオブジェクトファイルには、まだ最終的なアドレスが決定されていないシンボルへの参照が含まれています。これらの参照は、プログラムがメモリにロードされる際に、リンカによって実際のメモリ上のアドレスに「再配置」される必要があります。
    • 再配置エントリは、どの場所のどのシンボル参照を、どのように修正すべきかをリンカに指示するデータ構造です。
    • Dwarf情報内にも、コードやデータセクション内の特定のアドレスを参照する箇所があり、これらも再配置の対象となります。
  4. Goリンカ(cmd/ld:

    • Go言語のビルドツールチェインの一部であり、Goのソースコードから生成されたオブジェクトファイルを結合して実行可能ファイルを生成します。
    • Goリンカは、Go独自のオブジェクトファイルフォーマットを理解し、Goランタイムの特性を考慮したリンク処理を行います。
  5. 外部リンカモード(external link mode:

    • Goリンカは通常、独自の内部リンカを使用して実行可能ファイルを生成します。
    • しかし、-ldflags=-linkmode=external オプションを使用すると、Goリンカはオブジェクトファイルを生成するまでにとどまり、最終的な実行可能ファイルの生成はシステムのリンカ(例: Linux上のld、macOS上のclang)に委ねられます。
    • このモードは、Cgoを使用する場合や、特定のシステムライブラリにリンクする必要がある場合などに使用されます。外部リンカを使用する場合、Goリンカは外部リンカが理解できる形式(ELFやMach-Oなど)で再配置情報を出力する必要があります。

技術的詳細

このコミットの技術的詳細は、Goリンカが外部リンカモードでDwarfの.debug_frameセクションを処理する方法に焦点を当てています。

DwarfのFDE(Function Descriptor Entry)は、通常、そのFDEが記述する関数の開始アドレスと終了アドレスを含みます。これらのアドレスは、最終的な実行可能ファイル内のコードセクションへのオフセットとして表現されます。外部リンカモードでは、Goリンカが生成したオブジェクトファイルがシステムのリンカによって処理されるため、これらのアドレスは「再配置可能」である必要があります。つまり、リンカが最終的なロードアドレスに基づいてこれらのオフセットを調整できるように、再配置エントリが提供されなければなりません。

以前の実装では、.debug_frameセクション内のFDEヘッダにおいて、関数の開始アドレス(pc)や関連するシンボルへの参照に対して、外部リンカが必要とする再配置エントリが適切に生成されていませんでした。これにより、GDBのようなデバッガが.debug_frame情報を解析する際に、不正なアドレスを参照してしまい、結果としてローカル変数の位置を特定できないという問題が発生していました。

この修正は、writeframes関数内でFDEヘッダを書き込む際に、linkmode == LinkExternal(外部リンカモード)の場合に、adddwarfrel関数を呼び出して適切な再配置エントリを追加するように変更しています。具体的には、FDEの開始アドレスと、関連するシンボル(関数シンボル)への参照に対して再配置エントリが追加されます。

また、このコミットは、.debug_frameセクションに関連する再配置セクション(.rela.debug_frameまたは.rel.debug_frame)の管理も改善しています。具体的には、リンカがELFヘッダにこの再配置セクションの情報を追加し、リンカがそれを認識して処理できるようにしています。これにより、外部リンカが.debug_frameセクション内のアドレス参照を正しく解決できるようになり、デバッガがローカル変数を正確に表示できるようになります。

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

変更は src/cmd/ld/dwarf.c ファイルに集中しています。

  1. 新しい静的変数の追加:

    static Sym*  framesym;
    static vlong framesympos;
    // ...
    static Sym *framesec;
    static vlong framereloco;
    static vlong framerelocsize;
    

    .debug_frameセクションに関連するシンボル、その位置、および再配置セクションの情報を保持するための変数が追加されました。

  2. writeframes関数内の変更:

    // ...
    	if(framesec == S)
    		framesec = lookup(".dwarfframe", 0);
    	framesec->nr = 0;
    // ...
    	// Emit the FDE header for real, Section 6.4.1.
    	cseek(fdeo);
    	LPUT(fdesize);
    	if(linkmode == LinkExternal) {
    		adddwarfrel(framesec, framesym, frameo, 4, 0);
    		adddwarfrel(framesec, s, frameo, PtrSize, 0);
    	}
    	else {
    		LPUT(0);
    		addrput(p->pc);
    	}
    	addrput(s->size);
    	cseek(fdeo + 4 + fdesize);
    // ...
    

    writeframes関数内で、FDEヘッダを書き込む際に、外部リンカモードであればadddwarfrelを呼び出して再配置エントリを追加するロジックが追加されました。

  3. dwarfemitdebugsections関数内の変更:

    // ...
    	framereloco = writedwarfreloc(framesec);
    	framerelocsize = cpos() - framereloco;
    	align(framerelocsize);
    

    .debug_frameセクションの再配置情報を書き込むための処理が追加されました。

  4. dwarfaddshstrings関数内の変更:

    // ...
    	ElfStrRelDebugFrame,
    // ...
    	if(linkmode == LinkExternal) {
    		// ...
    		elfstrdbg[ElfStrRelDebugFrame] = addstring(shstrtab, ".rela.debug_frame");
    	} else {
    		// ...
    		elfstrdbg[ElfStrRelDebugFrame] = addstring(shstrtab, ".rel.debug_frame");
    	}
    // ...
    	framesym = lookup(".debug_frame", 0);
    	framesym->hide = 1;
    

    .debug_frame再配置セクションの名前(.rela.debug_frameまたは.rel.debug_frame)を文字列テーブルに追加し、.debug_frameシンボルをルックアップする処理が追加されました。

  5. dwarfaddelfsectionsyms関数内の変更:

    // ...
    	if(framesym != nil) {
    		framesympos = cpos();
    		putelfsectionsym(framesym, 0);
    	}
    

    .debug_frameシンボルが存在する場合、ELFセクションシンボルとして追加する処理が追加されました。

  6. dwarfaddelfheaders関数内の変更:

    // ...
    	ElfShdr *sh, *shinfo, *sharanges, *shline, *shframe;
    // ...
    	if(framesympos > 0)
    		putelfsymshndx(framesympos, sh->shnum);
    	shframe = sh;
    // ...
    	if(framerelocsize)
    		dwarfaddelfrelocheader(ElfStrRelDebugFrame, shframe, framereloco, framerelocsize);
    

    ELFヘッダに.debug_frameセクションの情報を追加し、その再配置ヘッダを追加する処理が追加されました。

コアとなるコードの解説

このコミットの主要な変更点は、Goリンカが外部リンカモードで.debug_frameセクション内のアドレス参照に対して、適切な再配置エントリを生成するようにしたことです。

  • framesym, framesec, framereloco, framerelocsize: これらの新しい静的変数は、.debug_frameセクション自体、その再配置セクション、および再配置情報のオフセットとサイズを追跡するために導入されました。これにより、リンカは.debug_frameに関連するすべての情報を一貫して管理できます。
  • writeframes内のadddwarfrel呼び出し:
    • adddwarfrel(framesec, framesym, frameo, 4, 0); は、FDEの先頭(frameo)から4バイト目(FDEの長さフィールドの次)に、.debug_frameセクション自体への再配置エントリを追加します。これは、FDEが参照するCIE(Common Information Entry)へのオフセットが、.debug_frameセクションの開始からの相対オフセットとして表現されるためです。
    • adddwarfrel(framesec, s, frameo, PtrSize, 0); は、FDEの先頭からPtrSize(ポインタのサイズ、通常は4または8バイト)のオフセットに、FDEが記述する関数の開始アドレス(sシンボル)への再配置エントリを追加します。これにより、外部リンカは関数の実際のロードアドレスに基づいてこの参照を修正できます。
  • dwarfemitdebugsections内のwritedwarfreloc: この関数は、指定されたセクション(ここではframesec、つまり.debug_frameの再配置セクション)に対するすべての再配置エントリを実際にファイルに書き込みます。これにより、外部リンカが処理できる形式で再配置情報がバイナリに埋め込まれます。
  • ELFヘッダと文字列テーブルの更新: .debug_frameの再配置セクションの名前(.rela.debug_frameまたは.rel.debug_frame)がELFの文字列テーブルに追加され、ELFヘッダにこのセクションの情報が適切に記述されるようになりました。これは、外部リンカがこの再配置セクションを認識し、処理するために不可欠です。

これらの変更により、外部リンカは.debug_frameセクション内のアドレス参照を正しく解決できるようになり、結果としてGDBのようなデバッガがGoプログラムのローカル変数を正確に表示できるようになりました。

関連リンク

参考にした情報源リンク