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

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

コミット

commit e0c430d5b70570fa6bf4f56d357ba7a517ff4e72
Author: Russ Cox <rsc@golang.org>
Date:   Thu Mar 7 21:23:59 2013 -0800

    cmd/6l, cmd/8l: fix BSD builds
    
    Before this CL, running
    
            cd misc/cgo/test
            go test -c
            readelf --dyn-syms test.test | grep cgoexp
    
    turned up many UNDEF symbols corresponding to symbols actually
    in the binary but marked only cgo_export_static. Only symbols
    marked cgo_export_dynamic should be listed in this mode.
    And if the symbol is going to be listed, it should be listed with its
    actual address instead of UNDEF.
    
    The Linux dynamic linker didn't care about the seemingly missing
    symbols, but the BSD one did.
    
    This CL eliminates the symbols from the dyn-syms table.
    
    R=golang-dev
    TBR=golang-dev
    CC=golang-dev
    https://golang.org/cl/7624043

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

https://github.com/golang/go/commit/e0c430d5b70570fa6bf4f56d357ba7a517ff4e72

元コミット内容

このコミットは、Go言語のリンカであるcmd/6l(x86-64アーキテクチャ向け)とcmd/8l(x86アーキテクチャ向け)におけるBSDビルドの問題を修正するものです。具体的には、Cgo(GoとC言語の相互運用機能)を使用してエクスポートされたシンボルが、ELFバイナリの動的シンボルテーブル(readelf --dyn-symsで表示される)に不適切に表示される問題を解決します。

以前のバージョンでは、cgo_export_staticとしてマークされたシンボル(静的リンクされるべきシンボル)が、動的シンボルテーブルにUNDEF(未定義)として誤ってリストアップされていました。本来、このテーブルにはcgo_export_dynamicとしてマークされたシンボル(動的リンクされるべきシンボル)のみが、その実際のメモリアドレスと共にリストされるべきです。

Linuxの動的リンカはこの「見かけ上欠落している」シンボルを問題としませんでしたが、BSDの動的リンカはこれを問題視し、ビルドエラーや予期せぬ動作を引き起こす可能性がありました。このコミットは、これらの不適切なシンボルを動的シンボルテーブルから除外することで、BSDビルドの互換性を確保します。

変更の背景

Go言語はCgoを通じてC言語のコードと連携する機能を提供しており、Goの関数をC言語から呼び出せるようにエクスポートすることが可能です。このエクスポートされた関数は、静的にリンクされるか動的にリンクされるかによって、リンカによる処理が異なります。

問題は、cgo_export_staticとしてマークされたシンボルが、ELFバイナリの動的シンボルテーブルに誤ってUNDEFシンボルとして含まれてしまうことにありました。これは、readelf --dyn-symsのようなツールで動的シンボル情報を確認した際に顕著に現れました。

Linuxの動的リンカは、このような不整合に対して比較的寛容であり、実用上問題を引き起こすことは稀でした。しかし、BSD系のOS(FreeBSD, OpenBSDなど)の動的リンカは、より厳格なシンボル解決のルールを持っており、動的シンボルテーブルにUNDEFとしてリストされているにもかかわらず、実際にはバイナリ内に存在するシンボルに対してエラーを報告することがありました。この違いが、GoのBSDビルドにおける互換性の問題を引き起こしていました。

このコミットの目的は、BSD環境でのGoプログラムのビルドと実行の信頼性を向上させるため、動的リンカが期待するシンボル情報のみを動的シンボルテーブルに含めるようにリンカの挙動を修正することです。

前提知識の解説

1. Go言語のリンカ (cmd/6l, cmd/8l)

Go言語の初期のリンカは、ターゲットアーキテクチャごとに異なる名前を持っていました。

  • cmd/6l: x86-64 (64-bit) アーキテクチャ向けのリンカ。
  • cmd/8l: x86 (32-bit) アーキテクチャ向けのリンカ。 これらは現在ではgo tool linkとして統合されており、ユーザーが直接これらの名前を意識することは少なくなっていますが、Goの内部ツールチェインでは依然としてこれらの概念が基盤となっています。リンカの主な役割は、コンパイルされたオブジェクトファイル(.oファイル)を結合し、実行可能なバイナリやライブラリを生成することです。この過程で、シンボル解決(関数や変数のアドレスを決定すること)や再配置(コード内のアドレス参照を修正すること)が行われます。

2. Cgoとシンボルのエクスポート (cgo_export_static, cgo_export_dynamic)

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Goの関数をC言語から呼び出せるようにするには、Goのソースコード内で//exportディレクティブを使用します。

CgoでエクスポートされたGoの関数は、ビルドモードによって静的または動的にリンクされます。

  • 静的リンク (cgo_export_staticに対応する概念): go build -buildmode=c-archiveでビルドすると、GoのコードとランタイムがC/C++アプリケーションに直接組み込まれる静的ライブラリ(.aファイル)が生成されます。この場合、エクスポートされたGoのシンボルは最終的な実行可能ファイルに直接含まれ、実行時に外部の共有ライブラリを必要としません。
  • 動的リンク (cgo_export_dynamicに対応する概念): go build -buildmode=c-sharedでビルドすると、Goのコードとランタイムが共有ライブラリ(Linuxでは.so、macOSでは.dylib)として生成されます。C/C++アプリケーションは実行時にこの共有ライブラリをロードし、エクスポートされたGoの関数を呼び出します。この場合、エクスポートされたGoのシンボルは共有ライブラリの動的シンボルテーブルにリストされます。

このコミットで言及されているcgo_export_staticcgo_export_dynamicは、Cgoのビルドモードによってシンボルがどのように扱われるかを示す内部的なフラグや概念を指しています。

3. ELF (Executable and Linkable Format)

ELFは、Unix系OS(Linux、BSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(コード、データ、シンボルテーブル、再配置情報など)で構成されます。

4. readelf --dyn-syms

readelfは、ELFファイルの情報を表示するためのコマンドラインユーティリティです。 readelf --dyn-syms <ELFファイル>コマンドは、指定されたELFファイルの動的シンボルテーブル(.dynsymセクション)の内容を表示します。動的シンボルテーブルには、動的リンクに必要なシンボル(関数や変数)の情報が含まれており、実行時に動的リンカがこれらのシンボルを解決するために使用します。

5. シンボルテーブルとUNDEFシンボル

ELFファイルのシンボルテーブルには、プログラム内のすべてのシンボルに関する情報が含まれます。各シンボルエントリには、シンボル名、値(アドレス)、サイズ、型(関数、オブジェクトなど)、バインディング(グローバル、ローカルなど)、そしてセクションインデックスが含まれます。

  • SHN_UNDEF: シンボルのセクションインデックスがSHN_UNDEFである場合、そのシンボルは現在のオブジェクトファイルでは定義されておらず、外部のオブジェクトファイルや共有ライブラリで定義されていることを示します。動的リンカは、このようなUNDEFシンボルを解決するために、他の共有ライブラリを検索します。

6. 再配置 (Relocation)

再配置とは、リンカがオブジェクトファイル内のコードやデータのアドレス参照を、最終的な実行可能ファイルやライブラリ内の正しいアドレスに修正するプロセスです。再配置エントリは、どの場所(オフセット)を、どのシンボルに基づいて、どのように修正するかをリンカに指示します。

  • ELF再配置タイプ (x86-64):

    • R_X86_64_PC32: 32ビットPC相対再配置。命令ポインタ(RIP)からの相対オフセットでアドレスを計算します。関数呼び出しやジャンプによく使われます。
    • R_X86_64_PLT32: 32ビットPC相対PLT再配置。Procedure Linkage Table (PLT)のエントリへの参照に使用されます。PLTは共有ライブラリ内の関数を呼び出す際に動的リンカが使用するジャンプテーブルです。
    • R_X86_64_GOTPCREL: Global Offset Table (GOT)へのPC相対再配置。GOTは位置独立コード(PIC)でグローバル変数や関数のアドレスを解決するために使用されます。
    • R_X86_64_64: 64ビット絶対アドレス再配置。シンボルの絶対アドレスを直接配置します。
  • ELF再配置タイプ (x86):

    • R_386_PC32: 32ビットPC相対再配置。
    • R_386_PLT32: 32ビットPLT再配置。
    • R_386_GOT32: 32ビットGOT再配置。
    • R_386_32: 32ビット絶対アドレス再配置。
  • Mach-O再配置タイプ (x86-64):

    • MACHO_X86_64_RELOC_BRANCH: 分岐命令のためのPC相対再配置。
    • MACHO_X86_64_RELOC_SIGNED_2, MACHO_X86_64_RELOC_SIGNED_4: 符号付き再配置タイプ。
    • MACHO_X86_64_RELOC_GOT_LOAD, MACHO_X86_64_RELOC_GOT: Global Offset Table (GOT)に関連する再配置。
    • MACHO_GENERIC_RELOC_VANILLA: 汎用的な再配置タイプ。
    • MACHO_FAKE_GOTPCREL: Mach-OにおけるGOTPCRELに類似した再配置(詳細は不明だが、GOTとPC相対アドレッシングに関連すると推測される)。

7. 動的リンカ (Dynamic Linker)

動的リンカ(またはランタイムリンカ、プログラムインタプリタ)は、プログラムが実行される際に、共有ライブラリ内のシンボルを解決し、プログラムと共有ライブラリをメモリにロードする役割を担います。Linuxではld-linux.so、BSDでは独自の動的リンカが使用されます。動的リンカは、ELFファイルの動的シンボルテーブルを参照して、必要なシンボルを検索し、そのアドレスをプログラムに提供します。

LinuxとBSDの動的リンカは、ELFフォーマットを使用するという点では共通していますが、Cランタイム、システムコール、ABI(Application Binary Interface)などの違いにより、バイナリ互換性はありません。また、シンボル解決の厳格さにも違いがあり、今回の問題のように、Linuxでは許容される挙動がBSDでは問題となることがあります。

技術的詳細

このコミットの核心は、Goリンカ(cmd/6lおよびcmd/8l)がELFバイナリの動的シンボルテーブルを生成する際のロジックの変更にあります。

GoのCgo機能では、Goの関数をC言語から呼び出せるようにするために、//exportディレクティブを使用します。リンカは、これらのエクスポートされたシンボルを処理し、最終的なバイナリに含めます。ここで重要なのは、シンボルが静的にエクスポートされるか(cgo_export_static)、動的にエクスポートされるか(cgo_export_dynamic)によって、動的シンボルテーブルへの登録方法が異なるべきであるという点です。

以前の実装では、cgo_export_staticとしてマークされたシンボルであっても、特定の条件下で動的シンボルテーブルにUNDEFシンボルとして誤って登録されていました。これは、シンボルが実際にバイナリ内に存在し、静的に解決されるべきであるにもかかわらず、動的リンカに対して「未定義の外部シンボル」として提示されることを意味します。

BSDの動的リンカは、このような矛盾に対して厳格であり、動的シンボルテーブルにUNDEFとしてリストされているシンボルが、実際にはバイナリ内に定義されている場合に、これを不整合とみなし、エラーや警告を発生させることがありました。対照的に、Linuxの動的リンカは、この種の不整合に対してより寛容であり、通常は問題なく処理していました。

このコミットは、リンカが動的シンボルテーブルにシンボルを追加する際の条件を修正することで、この問題を解決します。具体的には、cgoexportフラグのチェックをより厳密にし、CgoExportDynamicフラグが設定されているシンボル(つまり、動的にエクスポートされるべきシンボル)のみが動的シンボルテーブルに適切に登録されるように変更されています。これにより、静的にエクスポートされるシンボルが動的シンボルテーブルに誤ってUNDEFとして表示されることがなくなり、BSD環境でのビルドの信頼性が向上します。

変更は、主にadddynrel関数(再配置エントリを追加する関数)とadddynsym関数(動的シンボルテーブルにシンボルを追加する関数)に集中しています。これらの関数内で、シンボルのcgoexportフラグとCgoExportDynamicフラグの組み合わせを評価するロジックが修正されています。

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

変更はsrc/cmd/6l/asm.csrc/cmd/8l/asm.cの2つのファイルにわたっています。これらのファイルは、それぞれx86-64とx86アーキテクチャ向けのGoリンカのバックエンドコードです。

主な変更は、シンボルのcgoexportフラグのチェック方法です。

src/cmd/6l/asm.c (x86-64リンカ)

  • adddynrel関数内の複数のif条件文:

    • 変更前: !(targ->cgoexport & CgoExportDynamic)
    • 変更後: !targ->cgoexport
    • これは、CgoExportDynamicという特定のビットフラグのチェックを削除し、単にtarg->cgoexportが設定されているかどうか(つまり、何らかのCgoエクスポートがされているか)をチェックするように変更しています。これにより、cgo_export_staticなシンボルが動的再配置の対象から外れるようになります。
  • adddynsym関数内のSTT_FUNCSHN_UNDEFの条件:

    • 変更前: (s->cgoexport & CgoExportDynamic)
    • 変更後: s->cgoexport
    • ここでも同様に、CgoExportDynamicフラグのチェックを削除し、s->cgoexportが設定されていれば関数シンボルとして扱うように変更しています。
    • SHN_UNDEFの条件も同様に修正されています。

src/cmd/8l/asm.c (x86リンカ)

  • src/cmd/6l/asm.cと同様に、adddynrel関数とadddynsym関数内のif条件文で、!(targ->cgoexport & CgoExportDynamic)!targ->cgoexportに、(s->cgoexport & CgoExportDynamic)s->cgoexportに変更されています。

これらの変更は、CgoExportDynamicという特定のフラグに依存するのではなく、シンボルがCgoによってエクスポートされているかどうか(cgoexportフラグが立っているか)をより一般的な条件として使用することで、静的エクスポートシンボルが動的シンボルテーブルに誤って含まれるのを防ぎます。

コアとなるコードの解説

このコミットのコード変更は、GoリンカがCgoによってエクスポートされたシンボルをどのように扱うか、特に動的シンボルテーブルへの登録ロジックを修正しています。

Goのリンカは、シンボルを処理する際に、そのシンボルがCgoによってエクスポートされているかどうかを示すcgoexportというフラグを持っています。さらに、このcgoexportフラグは、シンボルが動的にエクスポートされるべきか(CgoExportDynamicビットがセットされているか)という詳細な情報を含むことがあります。

変更前のコードでは、再配置エントリの追加(adddynrel)や動的シンボルテーブルへのシンボル追加(adddynsym)の際に、targ->cgoexport & CgoExportDynamicという条件が頻繁に用いられていました。これは、「ターゲットシンボルがCgoによってエクスポートされており、かつ動的エクスポートとしてマークされている場合」という意味になります。

しかし、問題はcgo_export_staticとしてマークされたシンボル(つまり、cgoexportフラグは立っているが、CgoExportDynamicビットは立っていないシンボル)が、この条件によって適切に除外されなかったり、あるいは誤って動的シンボルテーブルにUNDEFとして登録されたりすることでした。

このコミットで行われた変更は、CgoExportDynamicという特定のビットフラグのチェックを削除し、代わりにtarg->cgoexport(またはs->cgoexport)が単にtrueであるかどうか(つまり、何らかのCgoエクスポートがされているか)をチェックするように簡素化しています。

例えば、adddynrel関数内の以下の変更を見てみましょう。

// 変更前:
// if(targ->dynimpname != nil && !(targ->cgoexport & CgoExportDynamic))

// 変更後:
// if(targ->dynimpname != nil && !targ->cgoexport)

この変更により、「targ->dynimpnameが設定されており、かつtarg->cgoexportが設定されていない場合」という条件になります。これは、cgo_export_staticなシンボル(cgoexportはtrueだがCgoExportDynamicはfalse)が、この条件によって動的再配置の対象から適切に除外されることを意味します。

同様に、adddynsym関数内のシンボルタイプ(STT_FUNC)やセクションインデックス(SHN_UNDEF)を決定するロジックも修正されています。

// 変更前:
// if((s->cgoexport & CgoExportDynamic) && (s->type&SMASK) == STEXT)

// 変更後:
// if(s->cgoexport && (s->type&SMASK) == STEXT)

この変更により、「シンボルがCgoによってエクスポートされており、かつテキストセクション(コード)にある場合」にSTT_FUNCとして扱われるようになります。これにより、cgo_export_staticな関数シンボルも適切に処理され、動的シンボルテーブルにUNDEFとして誤って登録されることがなくなります。

これらの変更は、リンカが動的シンボルテーブルを生成する際のロジックをより正確にし、cgo_export_staticなシンボルが動的リンカにとって「見えない」ようにすることで、BSD環境での互換性問題を解決しています。

関連リンク

参考にした情報源リンク