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

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

このコミットは、Go言語のリンカである cmd/ld において、外部リンカを使用する際に DWARF デバッグ情報のための再配置 (relocations) を適切に出力するように変更を加えるものです。これにより、外部リンカが DWARF 情報内のシンボル参照を正しく解決できるようになり、デバッグ体験が向上します。

コミット

commit 825b1e15916833ff6b2affdb5b0bb7c5c908ac52
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 30 14:01:05 2013 -0700

    cmd/ld: emit relocs for DWARF info when doing an external link

    I would like opinions on whether this is a good idea for 1.1.
    On the one hand it's a moderately important issue.  On the
    other hand this introduces at least the possibility of
    external linker errors due to the additional relocations and
    it may be better to wait.

    I'm fairly confident that the behaviour is unchanged when not
    using an external linker.

    Update #5221

    This CL is tested lightly on 386 and amd64 and fixes the cases
    I tested.  I have not tested it on Darwin or Windows.

    R=golang-dev, dave, daniel.morsing, rsc
    CC=golang-dev
    https://golang.org/cl/8858047

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

https://github.com/golang/go/commit/825b1e15916833ff6b2affdb5b0bb7c5c908ac52

元コミット内容

cmd/ld: 外部リンク時に DWARF 情報の再配置を出力する

これが Go 1.1 にとって良いアイデアであるか意見が欲しい。 一方で、これはそこそこ重要な問題である。 他方で、これは追加の再配置によって外部リンカエラーの可能性を少なくとも導入する可能性があり、待つ方が良いかもしれない。

外部リンカを使用しない場合の動作は変更されていないとかなり確信している。

Issue #5221 を更新。

この変更リスト (CL) は 386 および amd64 で軽くテストされており、テストしたケースを修正している。Darwin または Windows ではテストしていない。

変更の背景

この変更の背景には、Go プログラムをコンパイルしリンクする際に、デバッグ情報 (DWARF) が外部リンカによって正しく処理されないという問題がありました。Go のツールチェインは、Go 自身のリンカ (cmd/ld) を使用することもできますが、Cgo を使用する場合など、システムにインストールされている外部リンカ (例: gcc が内部的に使用する ld) を使用することがあります。

DWARF (Debugging With Attributed Record Formats) は、プログラムのソースコードとコンパイルされたバイナリの間のマッピングを記述するための標準的なデバッグ情報フォーマットです。これには、変数名、型情報、関数名、ソースファイルの行番号などが含まれます。デバッガは DWARF 情報を使用して、実行中のプログラムの状態をソースコードレベルで表示します。

外部リンカを使用する場合、Go のリンカは DWARF セクションを生成しますが、その内部に含まれるアドレス参照 (例えば、ある変数のメモリ上のアドレスや、別の DWARF エントリへの参照) が、最終的なバイナリのアドレス空間に正しく「再配置」される必要があります。Go のリンカがこれらの再配置情報を外部リンカに渡さないと、外部リンカは DWARF セクション内の参照を解決できず、結果としてデバッグ情報が破損したり、デバッガが正しく機能しなくなったりする問題が発生していました。

このコミットは、特に Issue #5221 で報告された問題を解決するために行われました。この問題は、外部リンカを使用した場合に DWARF 情報が不完全になる、または正しく機能しないというものでした。コミットメッセージでは、Go 1.1 への導入について慎重な姿勢が見られますが、これは追加の再配置が外部リンカに予期せぬ問題を引き起こす可能性を懸念していたためです。しかし、デバッグ体験の向上という観点から、この修正は重要であると判断されました。

前提知識の解説

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

  1. リンカ (Linker): リンカは、コンパイラによって生成された複数のオブジェクトファイル (.o ファイルなど) とライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は、シンボル解決 (あるオブジェクトファイルで定義された関数や変数を、別のオブジェクトファイルから参照できるようにする) と、再配置 (プログラムがメモリにロードされる際のアドレスに基づいて、コード内のアドレス参照を調整する) です。

  2. 外部リンカ (External Linker): Go のビルドシステムは、通常、Go 独自のリンカ (cmd/ld) を使用します。しかし、Cgo (Go から C のコードを呼び出す機能) を使用する場合や、特定のプラットフォームでは、システムにインストールされている標準のリンカ (例: GNU ld や LLVM lld) を使用することがあります。これを「外部リンカ」と呼びます。外部リンカを使用する場合、Go のリンカはオブジェクトファイルを生成し、そのオブジェクトファイルを外部リンカに渡して最終的な実行ファイルを生成させます。

  3. DWARF (Debugging With Attributed Record Formats): DWARF は、コンパイルされたプログラムのデバッグ情報を格納するための標準的なフォーマットです。これは、ソースコードの行番号と機械語命令のアドレスのマッピング、変数名とそのメモリ位置、関数名、型定義、スタックフレーム情報など、デバッガがソースレベルでプログラムを理解し、操作するために必要なあらゆる情報を含みます。DWARF 情報は通常、実行ファイル内の .debug_info, .debug_line, .debug_abbrev などの専用セクションに格納されます。

  4. 再配置 (Relocation): 再配置とは、コンパイル時にアドレスが確定できないシンボル参照 (例えば、別のオブジェクトファイルで定義されたグローバル変数のアドレスや、共有ライブラリ内の関数のアドレス) を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。オブジェクトファイルには、これらの未解決の参照と、それらを解決するためにリンカが必要とする情報 (再配置エントリ) が含まれています。リンカはこれらの再配置エントリを読み取り、最終的な実行ファイル内で正しいアドレスに修正します。 ELF (Executable and Linkable Format) 形式では、再配置情報は .rel または .rela セクションに格納されます。

  5. ELF (Executable and Linkable Format): ELF は、Unix系システム (Linux, BSDなど) で広く使用されている実行ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELF ファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション (コード、データ、デバッグ情報など) で構成されます。

  6. Mach-O: Mach-O は、macOS および iOS で使用される実行ファイル、オブジェクトファイル、共有ライブラリのフォーマットです。ELF と同様に、プログラムの構造とメタデータを定義します。

技術的詳細

このコミットの核心は、Go のリンカ (cmd/ld) が DWARF デバッグ情報を生成する際に、外部リンカがその情報を正しく処理できるように、必要な再配置エントリを DWARF セクションに追加することです。

Go のリンカは、通常、Go のコードをコンパイルして直接実行ファイルを生成します。この場合、リンカは DWARF 情報内のすべてのアドレス参照を自身で解決できます。しかし、linkmode == LinkExternal (外部リンカを使用するモード) の場合、Go のリンカは中間的なオブジェクトファイルを生成し、それを外部リンカに渡します。この中間オブジェクトファイルには、DWARF 情報が含まれていますが、その中のアドレス参照はまだ最終的な実行ファイルのアドレス空間にマッピングされていません。

以前の実装では、Go のリンカは DWARF セクション内のアドレス参照に対して再配置エントリを生成していませんでした。そのため、外部リンカは DWARF セクションを単なる生データとして扱い、内部のアドレス参照を修正することなく、そのまま最終バイナリにコピーしていました。結果として、デバッガが DWARF 情報を使用しようとすると、誤ったアドレスを参照してしまい、デバッグが不可能になるか、不正確な情報が表示されるという問題が発生していました。

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

  1. DWARF セクションへの再配置の追加:

    • src/cmd/ld/dwarf.c 内の adddwarfrel 関数が新しく追加されました。この関数は、指定された DWARF セクション (infosec, linesec, arangessec など) に対して再配置エントリ (Reloc 構造体) を追加する役割を担います。
    • putattr 関数 (DWARF 属性を書き込む関数) や writelines 関数 (DWARF の行番号情報を書き込む関数) など、DWARF 情報内でアドレスやシンボルへの参照を書き込む箇所で、linkmode == LinkExternal の場合に adddwarfrel を呼び出すように変更されました。これにより、DWARF 情報内のアドレス参照が、外部リンカによって解決されるべき再配置としてマークされます。
    • 特に、DW_FORM_addr (アドレス参照)、DW_OP_addr (アドレスオペランド)、DW_FORM_ref_addr (参照アドレス) などの DWARF フォームで表現されるアドレスやシンボル参照に対して、再配置が追加されるようになりました。
  2. ELF セクションシンボルの追加と管理:

    • 外部リンカが DWARF セクションを正しく参照できるように、.debug_info, .debug_abbrev, .debug_line などの DWARF セクション自体をシンボルとして扱うための変更が src/cmd/ld/symtab.csrc/cmd/ld/elf.h に加えられました。
    • putelfsectionsym および putelfsymshndx 関数が追加され、DWARF セクションに対応する ELF セクションシンボルを生成し、そのシンボルが参照するセクションのインデックスを更新できるようになりました。
    • dwarfaddelfsectionsyms 関数が導入され、ELF ファイルのシンボルテーブルに DWARF セクションのシンボルを追加する処理が、asmelfsym (ELF シンボルテーブルをアセンブルする関数) の中で呼び出されるようになりました。
  3. 再配置セクションの生成:

    • ELF 形式では、再配置情報は .rel または .rela セクションに格納されます。このコミットでは、.rela.debug_info, .rela.debug_aranges, .rela.debug_line といった、各 DWARF セクションに対応する再配置セクションを生成するロジックが dwarfaddelfrelocheader 関数を通じて追加されました。
    • writedwarfreloc 関数が、各 DWARF セクションに紐付けられた再配置エントリを実際に ELF の再配置フォーマットで書き出す役割を担います。
  4. Mach-O の変更:

    • src/cmd/ld/dwarf.cdwarfaddmachoheaders 関数から、Mach-O 形式における DWARF セクションの再配置情報 (msect->reloc, msect->nreloc) の設定が削除されました。これは、Mach-O のリンカが DWARF セクション内の再配置を処理する方法が ELF とは異なるため、Go のリンカが直接再配置情報を埋め込むのではなく、シンボル参照として渡すように変更されたためと考えられます。

これらの変更により、Go のリンカが生成する中間オブジェクトファイルには、DWARF 情報だけでなく、その DWARF 情報内のアドレス参照を外部リンカが解決するために必要な再配置エントリも含まれるようになります。これにより、外部リンカは DWARF セクションを正しく処理し、最終的な実行ファイルに完全で正確なデバッグ情報を含めることができるようになります。

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

このコミットは主に src/cmd/ld/dwarf.c に大きな変更を加えていますが、ELF 関連のヘッダやシンボルテーブルの管理にも影響を与えています。

  • src/cmd/ld/dwarf.c:

    • abbrevsym, linesym, infosym などの新しい静的 Sym* 変数が追加され、DWARF の各セクション (.debug_abbrev, .debug_line, .debug_info) に対応するシンボルを保持するようになりました。
    • adddwarfrel 関数が追加されました。これは、指定されたセクション (sec) に対して再配置エントリ (Reloc) を追加する汎用関数です。
    • putattr 関数内で、linkmode == LinkExternal の場合に DW_FORM_addr, DW_OP_addr, DW_FORM_data4 (DW_CLS_PTR の場合), DW_FORM_ref_addr などの DWARF 属性に対して adddwarfrel を呼び出すように変更されました。これにより、DWARF 情報内のアドレス参照が再配置対象となります。
    • writelines 関数内で、DW_LNE_set_address オペコードのアドレスに対して adddwarfrel を呼び出すように変更されました。
    • writeinfo 関数内で、.debug_abbrev_offset の書き込み時に linkmode == LinkExternal の場合に adddwarfrel を呼び出すように変更されました。
    • writearanges 関数内で、アドレス範囲の開始アドレス (b->value) や debug_info_offset の書き込み時に adddwarfrel を呼び出すように変更されました。
    • writedwarfreloc 関数が、各 DWARF セクションの再配置エントリを実際に書き出すように変更されました。
    • dwarfemitdebugsections 関数内で、writedwarfreloc を呼び出して .debug_info, .debug_aranges, .debug_line の再配置セクションを生成するように変更されました。
    • dwarfaddelfsectionsyms 関数が追加され、DWARF セクションの ELF シンボルを生成するようになりました。
    • dwarfaddelfrelocheader 関数が追加され、ELF の再配置セクションヘッダを生成するようになりました。
    • dwarfaddelfheaders 関数内で、dwarfaddelfrelocheader を呼び出して再配置セクションヘッダを追加するように変更されました。
    • dwarfaddmachoheaders から Mach-O の再配置関連のフィールド (msect->reloc, msect->nreloc) の設定が削除されました。
  • src/cmd/ld/elf.c:

    • elfobj 関数内で、dwarfaddelfheaders() の呼び出しが linkmode != LinkExternal の条件から外され、常に呼び出されるようになりました。これにより、外部リンカを使用する場合でも DWARF ヘッダが適切に生成されるようになります。
  • src/cmd/ld/elf.h:

    • Elf64_Shdr 構造体に secsym フィールドが追加されました。
    • dwarfaddelfsectionsyms 関数のプロトタイプが追加されました。
    • putelfsectionsyms 関数のプロトタイプが追加されました。
    • ElfStrRelDebugInfo, ElfStrRelDebugAranges, ElfStrRelDebugLine などの新しい enum 値が追加され、ELF の再配置セクション名に対応する文字列を管理するようになりました。
  • src/cmd/ld/lib.h:

    • putelfsectionsym および putelfsymshndx 関数のプロトタイプが追加されました。
  • src/cmd/ld/symtab.c:

    • putelfsectionsym 関数が追加されました。これは、ELF のセクションシンボルを生成し、シンボルテーブルに追加します。
    • putelfsymshndx 関数が追加されました。これは、既存の ELF シンボルのセクションインデックスを更新します。
    • asmelfsym 関数内で、dwarfaddelfsectionsyms() が呼び出されるようになりました。

コアとなるコードの解説

このコミットの主要な変更は、src/cmd/ld/dwarf.c に集中しており、特に adddwarfrel 関数と、それが DWARF 情報を書き込む様々な場所でどのように利用されているかが重要です。

adddwarfrel 関数

static void
adddwarfrel(Sym* sec, Sym* sym, vlong offsetbase, int siz, vlong addend)
{
	Reloc *r;

	r = addrel(sec); // 指定されたセクション (sec) に新しい再配置エントリを追加
	r->sym = sym;    // 再配置の対象となるシンボル
	r->xsym = sym;
	r->off = cpos() - offsetbase; // 再配置が適用されるオフセット (現在の書き込み位置 - ベースオフセット)
	r->siz = siz;    // 再配置のサイズ (4バイトまたは8バイト)
	r->type = D_ADDR; // 再配置のタイプ (アドレス再配置)
	r->add = addend; // 加算値 (再配置対象のアドレスに加算されるオフセット)
	r->xadd = addend;
	if(iself && thechar == '6') // ELF 64-bit の場合、addend は 0 に設定される (R_X86_64_PC32 などでは addend が不要なため)
		addend = 0;
	switch(siz) {
	case 4:
		LPUT(addend); // 4バイトの加算値を書き込む
		break;
	case 8:
		VPUT(addend); // 8バイトの加算値を書き込む
		break;
	default:
		diag("bad size in adddwarfrel");
		break;
	}
}

この関数は、DWARF セクション内の特定のアドレス参照に対して、外部リンカが解決すべき再配置エントリを生成します。sec は再配置情報が格納されるセクション (例: .rela.debug_info)、sym は再配置の対象となるシンボル (例: .text セクション内の関数シンボルや、別の DWARF セクションシンボル)、offsetbase は DWARF セクションの開始オフセット、siz は再配置のサイズ、addend は再配置対象のアドレスに加算されるオフセットです。

putattr 関数での利用

putattr 関数は、DWARF の属性 (例えば、変数のアドレスや型への参照) を書き込む際に使用されます。このコミットでは、linkmode == LinkExternal の場合に adddwarfrel が呼び出されるようになりました。

	case DW_FORM_addr: // アドレス属性
		if(linkmode == LinkExternal) {
			value -= ((Sym*)data)->value; // シンボルの値からの相対オフセットを計算
			adddwarfrel(infosec, (Sym*)data, infoo, PtrSize, value); // 再配置を追加
			break;
		}
		addrput(value); // 内部リンカの場合は直接アドレスを書き込む
		break;

DW_FORM_addr の場合、data は参照されるシンボル (Sym*) を指します。外部リンカを使用する場合、value (属性の値、通常は絶対アドレス) からシンボルのベースアドレス (((Sym*)data)->value) を引いた相対オフセットを addend として adddwarfrel に渡します。これにより、外部リンカは data シンボルの最終的なアドレスに addend を加算して、正しいアドレスを DWARF 情報に書き込むことができます。

同様に、DW_OP_addr (アドレスオペランド) や DW_FORM_ref_addr (参照アドレス) など、他のアドレス関連の DWARF フォームでも adddwarfrel が使用されます。

writelines 関数での利用

writelines 関数は、DWARF の行番号情報を生成します。ここでも、コードのアドレス (pc) を DWARF 情報に書き込む際に、外部リンカを使用する場合は再配置が追加されます。

	// ...
	// DW_LNE_set_address オペコードでアドレスを設定する箇所
	if(linkmode == LinkExternal)
		adddwarfrel(linesec, s, lineo, PtrSize, 0); // 行番号セクションに再配置を追加
	else
		addrput(pc); // 内部リンカの場合は直接アドレスを書き込む
	// ...

s はコードセクション内のシンボルを指し、lineo.debug_line セクションの開始オフセットです。これにより、行番号情報が参照するコードのアドレスも外部リンカによって正しく解決されるようになります。

ELF セクションシンボルの管理

src/cmd/ld/symtab.c に追加された putelfsectionsymputelfsymshndx は、ELF ファイルのシンボルテーブルに DWARF セクション自体をシンボルとして登録し、そのシンボルがどのセクションに対応するかをリンカに伝えるために使用されます。

void
putelfsectionsym(Sym* s, int shndx)
{
	// ELF シンボルエントリを生成し、STT_SECTION タイプでセクションシンボルとしてマーク
	putelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_SECTION, shndx, 0);
	s->elfsym = numelfsym++; // シンボルに ELF シンボルインデックスを割り当てる
}

void
putelfsymshndx(vlong sympos, int shndx)
{
	vlong here;

	here = cpos();
	// シンボルエントリ内の sh_ndx フィールド (セクションインデックス) を更新
	// 32-bit と 64-bit ELF でオフセットが異なるため、thechar で分岐
	switch(thechar) {
	case '6': // 64-bit ELF
		cseek(sympos+6);
		break;
	default: // 32-bit ELF
		cseek(sympos+14);
		break;
	}
	WPUT(shndx); // セクションインデックスを書き込む
	cseek(here); // 元の書き込み位置に戻る
}

これらの関数は、dwarfaddelfsectionsyms から呼び出され、.debug_info, .debug_abbrev, .debug_line などの DWARF セクションが ELF シンボルテーブルに登録され、外部リンカがこれらのセクションをシンボルとして参照できるようになります。

これらの変更の組み合わせにより、Go のリンカは外部リンカに対して、DWARF 情報内のすべてのアドレス参照と、DWARF セクション自体の位置に関する完全な再配置情報を提供できるようになり、外部リンカが生成するバイナリのデバッグ情報が正確になります。

関連リンク

参考にした情報源リンク

このコミットは、Go言語のリンカである cmd/ld において、外部リンカを使用する際に DWARF デバッグ情報のための再配置 (relocations) を適切に出力するように変更を加えるものです。これにより、外部リンカが DWARF 情報内のシンボル参照を正しく解決できるようになり、デバッグ体験が向上します。

コミット

commit 825b1e15916833ff6b2affdb5b0bb7c5c908ac52
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 30 14:01:05 2013 -0700

    cmd/ld: emit relocs for DWARF info when doing an external link

    I would like opinions on whether this is a good idea for 1.1.
    On the one hand it's a moderately important issue.  On the
    other hand this introduces at least the possibility of
    external linker errors due to the additional relocations and
    it may be better to wait.

    I'm fairly confident that the behaviour is unchanged when not
    using an external linker.

    Update #5221

    This CL is tested lightly on 386 and amd64 and fixes the cases
    I tested.  I have not tested it on Darwin or Windows.

    R=golang-dev, dave, daniel.morsing, rsc
    CC=golang-dev
    https://golang.org/cl/8858047

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

https://github.com/golang/go/commit/825b1e15916833ff6b2affdb5b0bb7c5c908ac52

元コミット内容

cmd/ld: 外部リンク時に DWARF 情報の再配置を出力する

これが Go 1.1 にとって良いアイデアであるか意見が欲しい。 一方で、これはそこそこ重要な問題である。 他方で、これは追加の再配置によって外部リンカエラーの可能性を少なくとも導入する可能性があり、待つ方が良いかもしれない。

外部リンカを使用しない場合の動作は変更されていないとかなり確信している。

Issue #5221 を更新。

この変更リスト (CL) は 386 および amd64 で軽くテストされており、テストしたケースを修正している。Darwin または Windows ではテストしていない。

変更の背景

この変更の背景には、Go プログラムをコンパイルしリンクする際に、デバッグ情報 (DWARF) が外部リンカによって正しく処理されないという問題がありました。Go のツールチェインは、Go 自身のリンカ (cmd/ld) を使用することもできますが、Cgo を使用する場合など、システムにインストールされている外部リンカ (例: gcc が内部的に使用する ld) を使用することがあります。

DWARF (Debugging With Attributed Record Formats) は、プログラムのソースコードとコンパイルされたバイナリの間のマッピングを記述するための標準的なデバッグ情報フォーマットです。これには、変数名、型情報、関数名、ソースファイルの行番号などが含まれます。デバッガは DWARF 情報を使用して、実行中のプログラムの状態をソースコードレベルで表示します。

外部リンカを使用する場合、Go のリンカは DWARF セクションを生成しますが、その内部に含まれるアドレス参照 (例えば、ある変数のメモリ上のアドレスや、別の DWARF エントリへの参照) が、最終的なバイナリのアドレス空間に正しく「再配置」される必要があります。Go のリンカがこれらの再配置情報を外部リンカに渡さないと、外部リンカは DWARF セクション内の参照を解決できず、結果としてデバッグ情報が破損したり、デバッガが正しく機能しなくなったりする問題が発生していました。

このコミットは、特に Issue #5221 で報告された問題を解決するために行われました。この問題は、外部リンカを使用した場合に DWARF 情報が不完全になる、または正しく機能しないというものでした。コミットメッセージでは、Go 1.1 への導入について慎重な姿勢が見られますが、これは追加の再配置が外部リンカに予期せぬ問題を引き起こす可能性を懸念していたためです。しかし、デバッグ体験の向上という観点から、この修正は重要であると判断されました。

前提知識の解説

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

  1. リンカ (Linker): リンカは、コンパイラによって生成された複数のオブジェクトファイル (.o ファイルなど) とライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は、シンボル解決 (あるオブジェクトファイルで定義された関数や変数を、別のオブジェクトファイルから参照できるようにする) と、再配置 (プログラムがメモリにロードされる際のアドレスに基づいて、コード内のアドレス参照を調整する) です。

  2. 外部リンカ (External Linker): Go のビルドシステムは、通常、Go 独自のリンカ (cmd/ld) を使用します。しかし、Cgo (Go から C のコードを呼び出す機能) を使用する場合や、特定のプラットフォームでは、システムにインストールされている標準のリンカ (例: GNU ld や LLVM lld) を使用することがあります。これを「外部リンカ」と呼びます。外部リンカを使用する場合、Go のリンカは中間的なオブジェクトファイルを生成し、それを外部リンカに渡して最終的な実行ファイルを生成させます。

  3. DWARF (Debugging With Attributed Record Formats): DWARF は、コンパイルされたプログラムのデバッグ情報を格納するための標準的なフォーマットです。これは、ソースコードの行番号と機械語命令のアドレスのマッピング、変数名とそのメモリ位置、関数名、型定義、スタックフレーム情報など、デバッガがソースレベルでプログラムを理解し、操作するために必要なあらゆる情報を含みます。DWARF 情報は通常、実行ファイル内の .debug_info, .debug_line, .debug_abbrev などの専用セクションに格納されます。

  4. 再配置 (Relocation): 再配置とは、コンパイル時にアドレスが確定できないシンボル参照 (例えば、別のオブジェクトファイルで定義されたグローバル変数のアドレスや、共有ライブラリ内の関数のアドレス) を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。オブジェクトファイルには、これらの未解決の参照と、それらを解決するためにリンカが必要とする情報 (再配置エントリ) が含まれています。リンカはこれらの再配置エントリを読み取り、最終的な実行ファイル内で正しいアドレスに修正します。 ELF (Executable and Linkable Format) 形式では、再配置情報は .rel または .rela セクションに格納されます。

  5. ELF (Executable and Linkable Format): ELF は、Unix系システム (Linux, BSDなど) で広く使用されている実行ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELF ファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション (コード、データ、デバッグ情報など) で構成されます。

  6. Mach-O: Mach-O は、macOS および iOS で使用される実行ファイル、オブジェクトファイル、共有ライブラリのフォーマットです。ELF と同様に、プログラムの構造とメタデータを定義します。

技術的詳細

このコミットの核心は、Go のリンカ (cmd/ld) が DWARF デバッグ情報を生成する際に、外部リンカがその情報を正しく処理できるように、必要な再配置エントリを DWARF セクションに追加することです。

Go のリンカは、通常、Go のコードをコンパイルして直接実行ファイルを生成します。この場合、リンカは DWARF 情報内のすべてのアドレス参照を自身で解決できます。しかし、linkmode == LinkExternal (外部リンカを使用するモード) の場合、Go のリンカは中間的なオブジェクトファイルを生成し、それを外部リンカに渡します。この中間オブジェクトファイルには、DWARF 情報が含まれていますが、その中のアドレス参照はまだ最終的な実行ファイルのアドレス空間にマッピングされていません。

以前の実装では、Go のリンカは DWARF セクション内のアドレス参照に対して再配置エントリを生成していませんでした。そのため、外部リンカは DWARF セクションを単なる生データとして扱い、内部のアドレス参照を修正することなく、そのまま最終バイナリにコピーしていました。結果として、デバッガが DWARF 情報を使用しようとすると、誤ったアドレスを参照してしまい、デバッグが不可能になるか、不正確な情報が表示されるという問題が発生していました。

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

  1. DWARF セクションへの再配置の追加:

    • src/cmd/ld/dwarf.c 内の adddwarfrel 関数が新しく追加されました。この関数は、指定された DWARF セクション (infosec, linesec, arangessec など) に対して再配置エントリ (Reloc 構造体) を追加する役割を担います。
    • putattr 関数 (DWARF 属性を書き込む関数) や writelines 関数 (DWARF の行番号情報を書き込む関数) など、DWARF 情報内でアドレスやシンボルへの参照を書き込む箇所で、linkmode == LinkExternal の場合に adddwarfrel を呼び出すように変更されました。これにより、DWARF 情報内のアドレス参照が、外部リンカによって解決されるべき再配置としてマークされます。
    • 特に、DW_FORM_addr (アドレス参照)、DW_OP_addr (アドレスオペランド)、DW_FORM_data4 (DW_CLS_PTR の場合)、DW_FORM_ref_addr (参照アドレス) などの DWARF フォームで表現されるアドレスやシンボル参照に対して、再配置が追加されるようになりました。
  2. ELF セクションシンボルの追加と管理:

    • 外部リンカが DWARF セクションを正しく参照できるように、.debug_info, .debug_abbrev, .debug_line などの DWARF セクション自体をシンボルとして扱うための変更が src/cmd/ld/symtab.csrc/cmd/ld/elf.h に加えられました。
    • putelfsectionsym および putelfsymshndx 関数が追加され、DWARF セクションに対応する ELF セクションシンボルを生成し、そのシンボルが参照するセクションのインデックスを更新できるようになりました。
    • dwarfaddelfsectionsyms 関数が導入され、ELF ファイルのシンボルテーブルに DWARF セクションのシンボルを追加する処理が、asmelfsym (ELF シンボルテーブルをアセンブルする関数) の中で呼び出されるようになりました。
  3. 再配置セクションの生成:

    • ELF 形式では、再配置情報は .rel または .rela セクションに格納されます。このコミットでは、.rela.debug_info, .rela.debug_aranges, .rela.debug_line といった、各 DWARF セクションに対応する再配置セクションを生成するロジックが dwarfaddelfrelocheader 関数を通じて追加されました。
    • writedwarfreloc 関数が、各 DWARF セクションに紐付けられた再配置エントリを実際に ELF の再配置フォーマットで書き出す役割を担います。
  4. Mach-O の変更:

    • src/cmd/ld/dwarf.cdwarfaddmachoheaders 関数から、Mach-O 形式における DWARF セクションの再配置情報 (msect->reloc, msect->nreloc) の設定が削除されました。これは、Mach-O のリンカが DWARF セクション内の再配置を処理する方法が ELF とは異なるため、Go のリンカが直接再配置情報を埋め込むのではなく、シンボル参照として渡すように変更されたためと考えられます。

これらの変更により、Go のリンカが生成する中間オブジェクトファイルには、DWARF 情報だけでなく、その DWARF 情報内のアドレス参照を外部リンカが解決するために必要な再配置エントリも含まれるようになります。これにより、外部リンカは DWARF セクションを正しく処理し、最終的な実行ファイルに完全で正確なデバッグ情報を含めることができるようになります。

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

このコミットは主に src/cmd/ld/dwarf.c に大きな変更を加えていますが、ELF 関連のヘッダやシンボルテーブルの管理にも影響を与えています。

  • src/cmd/ld/dwarf.c:

    • abbrevsym, linesym, infosym などの新しい静的 Sym* 変数が追加され、DWARF の各セクション (.debug_abbrev, .debug_line, .debug_info) に対応するシンボルを保持するようになりました。
    • adddwarfrel 関数が追加されました。これは、指定されたセクション (sec) に対して再配置エントリ (Reloc) を追加する汎用関数です。
    • putattr 関数内で、linkmode == LinkExternal の場合に DW_FORM_addr, DW_OP_addr, DW_FORM_data4 (DW_CLS_PTR の場合), DW_FORM_ref_addr などの DWARF 属性に対して adddwarfrel を呼び出すように変更されました。これにより、DWARF 情報内のアドレス参照が再配置対象となります。
    • writelines 関数内で、DW_LNE_set_address オペコードのアドレスに対して adddwarfrel を呼び出すように変更されました。
    • writeinfo 関数内で、.debug_abbrev_offset の書き込み時に linkmode == LinkExternal の場合に adddwarfrel を呼び出すように変更されました。
    • writearanges 関数内で、アドレス範囲の開始アドレス (b->value) や debug_info_offset の書き込み時に adddwarfrel を呼び出すように変更されました。
    • writedwarfreloc 関数が、各 DWARF セクションの再配置エントリを実際に書き出すように変更されました。
    • dwarfemitdebugsections 関数内で、writedwarfreloc を呼び出して .debug_info, .debug_aranges, .debug_line の再配置セクションを生成するように変更されました。
    • dwarfaddelfsectionsyms 関数が追加され、DWARF セクションの ELF シンボルを生成するようになりました。
    • dwarfaddelfrelocheader 関数が追加され、ELF の再配置セクションヘッダを生成するようになりました。
    • dwarfaddelfheaders 関数内で、dwarfaddelfrelocheader を呼び出して再配置セクションヘッダを追加するように変更されました。
    • dwarfaddmachoheaders から Mach-O の再配置関連のフィールド (msect->reloc, msect->nreloc) の設定が削除されました。
  • src/cmd/ld/elf.c:

    • elfobj 関数内で、dwarfaddelfheaders() の呼び出しが linkmode != LinkExternal の条件から外され、常に呼び出されるようになりました。これにより、外部リンカを使用する場合でも DWARF ヘッダが適切に生成されるようになります。
  • src/cmd/ld/elf.h:

    • Elf64_Shdr 構造体に secsym フィールドが追加されました。
    • dwarfaddelfsectionsyms 関数のプロトタイプが追加されました。
    • putelfsectionsyms 関数のプロトタイプが追加されました。
    • ElfStrRelDebugInfo, ElfStrRelDebugAranges, ElfStrRelDebugLine などの新しい enum 値が追加され、ELF の再配置セクション名に対応する文字列を管理するようになりました。
  • src/cmd/ld/lib.h:

    • putelfsectionsym および putelfsymshndx 関数のプロトタイプが追加されました。
  • src/cmd/ld/symtab.c:

    • putelfsectionsym 関数が追加されました。これは、ELF のセクションシンボルを生成し、シンボルテーブルに追加します。
    • putelfsymshndx 関数が追加されました。これは、既存の ELF シンボルのセクションインデックスを更新します。
    • asmelfsym 関数内で、dwarfaddelfsectionsyms() が呼び出されるようになりました。

コアとなるコードの解説

このコミットの主要な変更は、src/cmd/ld/dwarf.c に集中しており、特に adddwarfrel 関数と、それが DWARF 情報を書き込む様々な場所でどのように利用されているかが重要です。

adddwarfrel 関数

static void
adddwarfrel(Sym* sec, Sym* sym, vlong offsetbase, int siz, vlong addend)
{
	Reloc *r;

	r = addrel(sec); // 指定されたセクション (sec) に新しい再配置エントリを追加
	r->sym = sym;    // 再配置の対象となるシンボル
	r->xsym = sym;
	r->off = cpos() - offsetbase; // 再配置が適用されるオフセット (現在の書き込み位置 - ベースオフセット)
	r->siz = siz;    // 再配置のサイズ (4バイトまたは8バイト)
	r->type = D_ADDR; // 再配置のタイプ (アドレス再配置)
	r->add = addend; // 加算値 (再配置対象のアドレスに加算されるオフセット)
	r->xadd = addend;
	if(iself && thechar == '6') // ELF 64-bit の場合、addend は 0 に設定される (R_X86_64_PC32 などでは addend が不要なため)
		addend = 0;
	switch(siz) {
	case 4:
		LPUT(addend); // 4バイトの加算値を書き込む
		break;
	case 8:
		VPUT(addend); // 8バイトの加算値を書き込む
		break;
	default:
		diag("bad size in adddwarfrel");
		break;
	}
}

この関数は、DWARF セクション内の特定のアドレス参照に対して、外部リンカが解決すべき再配置エントリを生成します。sec は再配置情報が格納されるセクション (例: .rela.debug_info)、sym は再配置の対象となるシンボル (例: .text セクション内の関数シンボルや、別の DWARF セクションシンボル)、offsetbase は DWARF セクションの開始オフセット、siz は再配置のサイズ、addend は再配置対象のアドレスに加算されるオフセットです。

putattr 関数での利用

putattr 関数は、DWARF の属性 (例えば、変数のアドレスや型への参照) を書き込む際に使用されます。このコミットでは、linkmode == LinkExternal の場合に adddwarfrel が呼び出されるようになりました。

	case DW_FORM_addr: // アドレス属性
		if(linkmode == LinkExternal) {
			value -= ((Sym*)data)->value; // シンボルの値からの相対オフセットを計算
			adddwarfrel(infosec, (Sym*)data, infoo, PtrSize, value); // 再配置を追加
			break;
		}
		addrput(value); // 内部リンカの場合は直接アドレスを書き込む
		break;

DW_FORM_addr の場合、data は参照されるシンボル (Sym*) を指します。外部リンカを使用する場合、value (属性の値、通常は絶対アドレス) からシンボルのベースアドレス (((Sym*)data)->value) を引いた相対オフセットを addend として adddwarfrel に渡します。これにより、外部リンカは data シンボルの最終的なアドレスに addend を加算して、正しいアドレスを DWARF 情報に書き込むことができます。

同様に、DW_OP_addr (アドレスオペランド) や DW_FORM_ref_addr (参照アドレス) など、他のアドレス関連の DWARF フォームでも adddwarfrel が使用されます。

writelines 関数での利用

writelines 関数は、DWARF の行番号情報を生成します。ここでも、コードのアドレス (pc) を DWARF 情報に書き込む際に、外部リンカを使用する場合は再配置が追加されます。

	// ...
	// DW_LNE_set_address オペコードでアドレスを設定する箇所
	if(linkmode == LinkExternal)
		adddwarfrel(linesec, s, lineo, PtrSize, 0); // 行番号セクションに再配置を追加
	else
		addrput(pc); // 内部リンカの場合は直接アドレスを書き込む
	// ...

s はコードセクション内のシンボルを指し、lineo.debug_line セクションの開始オフセットです。これにより、行番号情報が参照するコードのアドレスも外部リンカによって正しく解決されるようになります。

ELF セクションシンボルの管理

src/cmd/ld/symtab.c に追加された putelfsectionsymputelfsymshndx は、ELF ファイルのシンボルテーブルに DWARF セクション自体をシンボルとして登録し、そのシンボルがどのセクションに対応するかをリンカに伝えるために使用されます。

void
putelfsectionsym(Sym* s, int shndx)
{
	// ELF シンボルエントリを生成し、STT_SECTION タイプでセクションシンボルとしてマーク
	putelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_SECTION, shndx, 0);
	s->elfsym = numelfsym++; // シンボルに ELF シンボルインデックスを割り当てる
}

void
putelfsymshndx(vlong sympos, int shndx)
{
	vlong here;

	here = cpos();
	// シンボルエントリ内の sh_ndx フィールド (セクションインデックス) を更新
	// 32-bit と 64-bit ELF でオフセットが異なるため、thechar で分岐
	switch(thechar) {
	case '6': // 64-bit ELF
		cseek(sympos+6);
		break;
	default: // 32-bit ELF
		cseek(sympos+14);
		break;
	}
	WPUT(shndx); // セクションインデックスを書き込む
	cseek(here); // 元の書き込み位置に戻る
}

これらの関数は、dwarfaddelfsectionsyms から呼び出され、.debug_info, .debug_abbrev, .debug_line などの DWARF セクションが ELF シンボルテーブルに登録され、外部リンカがこれらのセクションをシンボルとして参照できるようになります。

これらの変更の組み合わせにより、Go のリンカは外部リンカに対して、DWARF 情報内のすべてのアドレス参照と、DWARF セクション自体の位置に関する完全な再配置情報を提供できるようになり、外部リンカが生成するバイナリのデバッグ情報が正確になります。

関連リンク

参考にした情報源リンク