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

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

このコミットは、Go言語のリンカである cmd/ld におけるデバッグ情報 (DWARF) の生成方法に関する重要な変更を導入しています。特に、hostobj モードでの DWARF 情報のリロケーション(再配置)と、macOS (OS X) の gdb との互換性のため DWARF バージョンを 2 にダウングレードする点が主要な変更点です。

コミット

commit 40d356e9ab116fbf0f44b5b562ceb787f69fdc09
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 19 16:31:52 2013 -0400

    cmd/ld: generate relocated DWARF in hostobj mode
    
    While we're here, downgrade DWARF to version 2.
    We're not using any version 3 features, and OS X gdb
    only supports version 2.
    
    Fixes #3436.
    
    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/7891044

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

https://github.com/golang/go/commit/40d356e9ab116fbf0f44b5b562ceb787f69fdc09

元コミット内容

cmd/ld: generate relocated DWARF in hostobj modecmd/ld: hostobj モードで再配置された DWARF を生成する)

While we're here, downgrade DWARF to version 2. (ついでに、DWARF をバージョン 2 にダウングレードする)

We're not using any version 3 features, and OS X gdb only supports version 2. (バージョン 3 の機能は使用しておらず、OS X の gdb はバージョン 2 のみをサポートしているため)

Fixes #3436. (Issue #3436 を修正)

変更の背景

このコミットの背景には、主に以下の2つの問題がありました。

  1. hostobj モードでの DWARF デバッグ情報の不完全性: Go のリンカ (cmd/ld) は、Cgo を使用する際に hostobj モードで動作することがあります。このモードでは、Go のコードとC/C++のコードが連携してコンパイル・リンクされます。しかし、このモードで生成される DWARF デバッグ情報が、外部リンカによって適切に処理されず、デバッガが正確な情報を提供できないという問題がありました。特に、アドレス参照が正しく解決されないことが原因でした。
  2. macOS (OS X) の gdb との互換性問題: 当時の macOS に同梱されていた gdb (GNU Debugger) は、DWARF デバッグ情報のバージョン 3 を完全にサポートしていませんでした。Go のリンカが DWARF バージョン 3 を出力すると、gdb がデバッグ情報を正しく解釈できず、デバッグ体験が損なわれるという問題が発生していました。Go は DWARF バージョン 3 の特定の機能を使用していなかったため、バージョン 2 にダウングレードしても機能的な損失はないと判断されました。

これらの問題を解決し、Go プログラムのデバッグ体験を向上させることが、このコミットの目的でした。特に、Fixes #3436 は、この DWARF バージョンと gdb の互換性に関する具体的なバグ報告を指しています。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARF は、ソースコードとコンパイルされたバイナリコードとの間のマッピングを記述するための標準的なデバッグ情報フォーマットです。デバッガは DWARF 情報を使用して、実行中のプログラムの変数、関数、ソースコードの行番号などを特定し、開発者がデバッグ作業を行うのを助けます。

  • DWARF の構造: DWARF 情報は、主に以下のセクションで構成されます。
    • .debug_info: プログラムの構造(コンパイル単位、関数、変数、型など)を記述する主要なセクション。DIE (Debugging Information Entry) と呼ばれるツリー構造で表現されます。
    • .debug_abbrev: DIE の構造を定義するアブレビエーションコードを格納します。これにより、.debug_info のサイズを削減します。
    • .debug_line: ソースコードの行番号と実行可能コードのアドレスとのマッピングを記述します。
    • .debug_loc: 変数のメモリ上の位置(ロケーション)を記述します。
    • .debug_aranges: アドレス範囲とコンパイル単位のマッピングを記述します。
  • DWARF バージョン: DWARF には複数のバージョンが存在し、新しいバージョンでは機能拡張や改善が行われます。
    • DWARF Version 2: 広くサポートされている初期の安定バージョン。
    • DWARF Version 3: DWARF Version 2 の拡張版で、より多くの情報や柔軟性を提供します。しかし、古いデバッガではサポートされていない場合があります。
    • DWARF Version 4, 5: さらに新しいバージョンも存在します。

Go のリンカ (cmd/ld)

cmd/ld は Go 言語の標準リンカです。Go のコンパイラによって生成されたオブジェクトファイル (.o ファイル) を結合し、実行可能なバイナリファイルやライブラリを生成します。リンカは、シンボルの解決、セクションの配置、デバッグ情報の埋め込みなど、様々な重要なタスクを実行します。

Cgo

Cgo は、Go プログラムから C 言語のコードを呼び出すための Go の機能です。Cgo を使用すると、既存の C ライブラリを Go プロジェクトに統合したり、パフォーマンスが重要な部分を C で記述したりすることができます。Cgo を使用する際には、Go のリンカが C のリンカと連携して動作する必要があり、これが hostobj モードの背景となります。

hostobj モードと外部リンカ

Go のビルドプロセスにおいて、Cgo を使用する場合、Go のリンカ (cmd/ld) は、最終的なバイナリの生成を外部のシステムリンカ(例: gccclang)に委ねることがあります。このモードが LinkExternal または hostobj モードと呼ばれます。このモードでは、Go のリンカは中間的なオブジェクトファイルを生成し、それを外部リンカに渡して最終的な実行ファイルを生成させます。このプロセスにおいて、デバッグ情報のアドレスが外部リンカによって再配置されるため、Go のリンカが生成する DWARF 情報もこの再配置に対応している必要があります。

gdb (GNU Debugger)

gdb は、GNU プロジェクトによって開発された強力なコマンドラインデバッガです。C, C++, Go, Fortran など、多くのプログラミング言語をサポートしています。gdb は、プログラムの実行を制御し、ブレークポイントを設定し、変数の値を検査し、メモリをダンプするなどの機能を提供します。デバッグ情報を解釈するために DWARF などのフォーマットを使用します。

リロケーション (Relocation)

リロケーションとは、コンパイル時に決定できなかったアドレス参照を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。プログラムが複数のオブジェクトファイルから構成される場合、各オブジェクトファイル内のコードやデータは、最終的な実行ファイル内のどこに配置されるかを知りません。リンカは、これらの相対的な参照を、最終的な実行ファイル内の絶対アドレスに変換します。デバッグ情報も同様に、プログラム内の特定のアドレスを参照するため、リロケーションの対象となります。

技術的詳細

このコミットは、主に src/cmd/ld/dwarf.csrc/cmd/ld/dwarf_defs.hsrc/cmd/ld/lib.csrc/cmd/ld/macho.c の4つのファイルにわたる変更を含んでいます。

DWARF バージョンのダウングレード

  • src/cmd/ld/dwarf.c の変更:
    • flushunit 関数と writelines 関数内で、DWARF のバージョンを WPUT(3) から WPUT(2) に変更しています。これは、生成される DWARF 情報のバージョンを 3 から 2 に明示的にダウングレードするものです。
    • writeinfo 関数でも同様に、コンパイル単位のヘッダで DWARF バージョンを 2 に設定しています。
  • src/cmd/ld/lib.c の変更:
    • hostlink 関数内で、外部リンカ (gcc など) に渡す引数に -ggdb の代わりに -gdwarf-2 を追加しています。これにより、外部リンカが生成するデバッグ情報も DWARF バージョン 2 になります。

hostobj モードでの DWARF リロケーションのサポート

  • src/cmd/ld/dwarf.c の変更:
    • putattr 関数に abbrev 引数が追加され、DW_FORM_block1 形式の属性が DW_CLS_ADDRESS クラスを持つ場合に、リロケーション情報を追加するロジックが導入されました。
      • linkmode == LinkExternal の場合、addrel を呼び出してリロケーションエントリを作成します。このリロケーションエントリは、シンボル (r->sym)、オフセット (r->off)、サイズ (r->siz)、タイプ (D_ADDR)、および加算値 (r->add) を含みます。これにより、外部リンカがこのアドレスを適切に再配置できるようになります。
    • newabslocexprattr 関数が変更され、Sym *sym 引数が追加されました。これにより、DW_AT_location 属性が参照するアドレスが、単なる数値ではなく、関連するシンボルと共に扱われるようになります。これは、リロケーションが必要なアドレスを識別するために重要です。
    • flushunit 関数と writelines 関数で、DW_AT_high_pc および DW_AT_low_pc 属性にシンボル情報 (pcsym, s) を渡すように変更されました。これにより、これらのアドレスもリロケーションの対象としてマークされます。
    • writeinforeloc 関数が新しく追加されました。この関数は、.debug_info セクション内のリロケーションエントリを生成し、リンカがこれらのリロケーションを処理できるようにします。
    • dwarfemitdebugsections 関数内で writeinforeloc が呼び出され、生成されたリロケーション情報のオフセットとサイズが inforelocoinforelocsize に格納されます。
  • src/cmd/ld/dwarf_defs.h の変更:
    • 新しい DWARF クラス DW_CLS_ADDRLOC が追加されました。これは、アドレスロケーションを表すために使用されます。
  • src/cmd/ld/macho.c の変更:
    • dwarfaddmachoheaders 関数内で、Mach-O 形式のデバッグ情報セクション (__debug_info) にリロケーション情報 (msect->relocmsect->nreloc) を追加するように変更されました。これにより、Mach-O リンカが DWARF 情報内のアドレス参照を正しく解決できるようになります。
    • isobj フラグの代わりに linkmode == LinkExternal を使用するように、多くの条件分岐が変更されました。これは、リンカの動作モードをより明確に反映し、hostobj モードでの外部リンカとの連携を強化するためです。
    • machosymtab 関数内で、C シンボルにのみ _ プレフィックスを追加するロジックが追加されました。Go シンボルはすでに . を含むため、重複を避けるためです。

型定義の改善

  • src/cmd/ld/dwarf.c の変更:
    • walktypedef 関数が追加されました。この関数は、DW_ABRV_TYPEDECL (typedef) を解決し、実際の型定義を返すようにします。これにより、デバッガが型エイリアスを追跡し、基になる型を正しく表示できるようになります。
    • dotypedef 関数が追加されました。この関数は、Go の型 (KindArray, KindFunc, KindInterface, KindPtr, KindSlice, KindStruct) に対して DWARF の DW_ABRV_TYPEDECL エントリを生成します。これにより、Go の複雑な型システムが DWARF でより正確に表現され、デバッガでの型の表示が改善されます。
    • synthesizestringtypes, synthesizeslicetypes, synthesizemaptypes, synthesizechantypes 関数内で walktypedef が使用されるようになりました。これにより、これらの合成された型も typedef を適切に解決できるようになります。

Cgo 関連のリンカモードの調整

  • src/cmd/ld/lib.c の変更:
    • loadlib 関数内で、linkmodeLinkExternal でなく、かつ iscgo (Cgo を使用しているか) でない場合、linkmodeLinkInternal に設定するロジックが追加されました。これは、Cgo を使用しない場合は内部リンカを使用することを保証します。
    • LinkAuto モードの場合、最終的に LinkInternal に切り替える際に、SHOSTOBJ タイプのシンボル(外部リンカが必要とするシンボル)を削除するロジックが追加されました。これは、内部リンカを使用する場合にこれらのシンボルが不要になるためです。
    • internalpkg リストに crypto/x509runtime/race が追加されました。これらのパッケージは、Cgo を使用していても内部リンカで処理できることを示しています。

これらの変更により、Go のリンカは hostobj モードでより正確な DWARF デバッグ情報を生成できるようになり、特に macOS 上の gdb との互換性が向上しました。

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

src/cmd/ld/dwarf.c

  • putattr 関数の変更: DW_FORM_block1 形式で DW_CLS_ADDRESS クラスの属性に対してリロケーション情報を追加するロジックが追加されました。
    static void
    putattr(int abbrev, int form, int cls, vlong value, char *data)
    {
        Reloc *r;
    
        switch(form) {
        // ...
        case DW_FORM_block1:    // block
            if(cls == DW_CLS_ADDRESS) {
                cput(1+PtrSize);
                cput(DW_OP_addr);
                if(linkmode == LinkExternal) {
                    r = addrel(dsym);
                    r->sym = (Sym*)data;
                    r->xsym = r->sym;
                    r->off = cpos() - infoo;
                    r->siz = PtrSize;
                    r->type = D_ADDR;
                    r->add = value - r->sym->value;
                    r->xadd = r->add;
                    value = r->add;
                }
                addrput(value);
                break;
            }
            // ...
        }
        // ...
    }
    
  • newabslocexprattr 関数の変更: シンボル情報を引数として受け取るようになりました。
    static void
    newabslocexprattr(DWDie *die, vlong addr, Sym *sym)
    {
        newattr(die, DW_AT_location, DW_CLS_ADDRESS, addr, (char*)sym);
    }
    
  • flushunit 関数の変更: high_pc にシンボル情報を渡すようになりました。
    static void
    flushunit(DWDie *dwinfo, vlong pc, Sym *pcsym, vlong unitstart, int32 header_length)
    {
        vlong here;
    
        if (dwinfo != nil && pc != 0) {
            newattr(dwinfo, DW_AT_high_pc, DW_CLS_ADDRESS, pc+1, (char*)pcsym);
        }
        // ...
        WPUT(2);  // dwarf version (changed from 3)
        // ...
    }
    
  • writelines 関数の変更: low_pchigh_pc にシンボル情報を渡すようになりました。また、DWARF バージョンを 2 に設定しています。
    static void
    writelines(void)
    {
        // ...
        // In new compilation unit block
        newattr(dwinfo, DW_AT_low_pc, DW_CLS_ADDRESS, s->text->pc, (char*)s);
        WPUT(2);   // dwarf version (changed from 3)
        // ...
        // In function block
        newattr(dwfunc, DW_AT_low_pc, DW_CLS_ADDRESS, s->value, (char*)s);
        newattr(dwfunc, DW_AT_high_pc, DW_CLS_ADDRESS, epc, (char*)s);
        // ...
    }
    
  • writeinforeloc 関数の追加: DWARF 情報のリロケーションエントリを書き込みます。
    static vlong
    writeinforeloc(void)
    {
        int i;
        vlong start;
        Reloc *r;
    
        start = cpos();
        for(r = dsym->r; r < dsym->r+dsym->nr; r++) {
            if(iself)
                i = elfreloc1(r, r->off);
            else if(HEADTYPE == Hdarwin)
                i = machoreloc1(r, r->off);
            else
                i = -1;
            if(i < 0)
                diag("unsupported obj reloc %d/%d to %s", r->type, r->siz, r->sym->name);
        }
        return start;
    }
    
  • dwarfemitdebugsections 関数の変更: writeinforeloc を呼び出すようになりました。
    void
    dwarfemitdebugsections(void)
    {
        // ...
        inforeloco = writeinforeloc();
        inforelocsize = cpos() - inforeloco;
        align(inforelocsize);
    }
    
  • walktypedef および dotypedef 関数の追加: 型エイリアスを解決し、typedef エントリを生成します。

src/cmd/ld/dwarf_defs.h

  • DW_CLS_ADDRLOC の追加:
    enum
    {
        // ...
        DW_CLS_FLAG,
        DW_CLS_PTR, // lineptr, loclistptr, macptr, rangelistptr
        DW_CLS_REFERENCE,
        DW_CLS_ADDRLOC, // New class
        DW_CLS_STRING
    };
    

src/cmd/ld/lib.c

  • hostlink 関数の変更: 外部リンカに -gdwarf-2 を渡すようになりました。
    void
    hostlink(void)
    {
        // ...
        if(!debug['s'])
            argv[argc++] = "-gdwarf-2"; // Changed from "-ggdb"
        // ...
    }
    
  • loadlib 関数の変更: linkmode の調整と SHOSTOBJ シンボルの削除ロジック。
    void
    loadlib(void)
    {
        // ...
        if(linkmode == LinkExternal && !iscgo)
            linkmode = LinkInternal;
    
        if(linkmode == LinkAuto) {
            linkmode = LinkInternal;
            // Drop all the cgo_import_static declarations.
            // Turns out we won't be needing them.
            for(s = allsym; s != S; s = s->allsym)
                if(s->type == SHOSTOBJ)
                    s->type = 0;
        }
        // ...
    }
    
  • internalpkg の更新:
    const char *internalpkg[] = {
        "crypto/x509", // Added
        "net",
        "os/user",
        "runtime/cgo",
        "runtime/race" // Added
    };
    

src/cmd/ld/macho.c

  • dwarfaddmachoheaders 関数の変更: Mach-O の DWARF セクションにリロケーション情報を追加します。
    void
    dwarfaddmachoheaders(void)
    {
        // ...
        msect = newMachoSect(ms, "__debug_info", "__DWARF");
        msect->off = infoo;
        msect->size = infosize;
        msect->reloc = inforeloco; // Added
        msect->nreloc = inforelocsize / 8; // Added
        ms->filesize += msect->size;
        // ...
    }
    
  • isobj から linkmode == LinkExternal への変更: 多くの場所で条件分岐が変更されました。
    // Example from machowrite
    if(linkmode == LinkExternal) // Changed from isobj
        LPUT(1);    /* file type - mach object */
    else
        LPUT(2);    /* file type - mach executable */
    
  • machosymtab 関数の変更: C シンボルにのみ _ プレフィックスを追加。
    void
    machosymtab(void)
    {
        // ...
        // Only add _ to C symbols. Go symbols have dot in the name.
        if(strstr(s->extname, ".") == nil)
            adduint8(symstr, '_');
        addstring(symstr, s->extname);
        // ...
    }
    

コアとなるコードの解説

このコミットの核となる変更は、Go のリンカが DWARF デバッグ情報を生成する際に、外部リンカによるアドレスのリロケーションを考慮に入れるようになった点と、macOS の gdb との互換性を確保するために DWARF バージョンをダウングレードした点です。

  1. DWARF リロケーションのサポート:

    • 以前は、hostobj モードで生成された DWARF 情報内のアドレス参照は、外部リンカによって再配置されることを想定していませんでした。そのため、デバッガがこれらのアドレスを正しく解決できず、デバッグが困難になることがありました。
    • このコミットでは、putattr 関数内で DW_CLS_ADDRESS クラスの属性(特に DW_AT_location など、アドレスを指す属性)に対して、明示的にリロケーションエントリ (Reloc 構造体) を生成するようになりました。これにより、Go のリンカは、これらのアドレスが外部リンカによって再配置される必要があることを示します。
    • writeinforeloc 関数は、これらのリロケーションエントリを収集し、Mach-O などのオブジェクトファイル形式に埋め込むための処理を行います。これにより、外部リンカは DWARF 情報内のアドレスを、最終的な実行ファイル内の正しいアドレスに調整できます。
    • newabslocexprattrflushunitwritelines でシンボル情報 (Sym *sym) をアドレスと共に渡すようになったのは、どのアドレスがどのシンボルに関連しているかをリンカが把握し、正確なリロケーションエントリを生成するために不可欠です。
  2. DWARF バージョンのダウングレード:

    • src/cmd/ld/dwarf.c 内の WPUT(3)WPUT(2) に変更することで、生成される DWARF のバージョンが 3 から 2 に明示的に設定されます。
    • src/cmd/ld/lib.c で外部リンカに渡すフラグを -ggdb から -gdwarf-2 に変更したことも、このダウングレードを補完します。
    • この変更は、Go が DWARF バージョン 3 の特定の機能を使用していなかったことと、当時の macOS の gdb がバージョン 2 のみを完全にサポートしていたという実用的な理由に基づいています。これにより、macOS ユーザーのデバッグ体験が向上しました。
  3. 型定義の改善:

    • walktypedefdotypedef の導入は、Go の複雑な型システム(特に構造体、配列、関数、インターフェースなど)が DWARF でより正確に表現されるようにするためのものです。これにより、デバッガが Go の型エイリアスや複合型を正しく解釈し、ユーザーに分かりやすい形で表示できるようになります。
  4. リンカモードの調整:

    • isobj フラグから linkmode == LinkExternal への移行は、リンカの動作モードをより明確に表現し、外部リンカとの連携ロジックを整理するためのものです。
    • internalpkg の更新や SHOSTOBJ シンボルの削除ロジックは、Cgo を使用しない場合に不必要な外部リンカへの依存を排除し、ビルドプロセスを最適化するためのものです。

これらの変更は、Go のデバッグツールチェーンの堅牢性と互換性を大幅に向上させ、特に Cgo を使用するプロジェクトや macOS 環境での開発において、よりスムーズなデバッグ体験を提供することに貢献しました。

関連リンク

  • Go Issue #3436: https://github.com/golang/go/issues/3436
    • このコミットが修正した具体的なバグ報告。DWARF バージョンと gdb の互換性に関する議論が含まれています。
  • Go CL 7891044: https://golang.org/cl/7891044
    • このコミットに対応する Go のコードレビューシステム (Gerrit) のチェンジリスト。詳細な議論や変更履歴が含まれています。

参考にした情報源リンク

  • DWARF Debugging Information Format:
  • Go のリンカ (cmd/ld) について:
    • Go のソースコード (src/cmd/ld ディレクトリ)
    • Go のビルドプロセスに関するドキュメントやブログ記事 (例: "Go's linker" で検索)
  • Cgo について:
  • GNU Debugger (GDB) について:
  • Mach-O ファイル形式について:
  • リロケーションについて:
    • リンカの動作に関する一般的なコンピュータサイエンスの教科書やオンラインリソース。