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

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

このコミットは、Go言語のリンカ (cmd/ld) において、ホストリンカとの連携をサポートするための重要な変更を導入しています。具体的には、Goのリンカが実行可能ファイルを直接生成する代わりに、ホストリンカが処理できるオブジェクトファイルを生成する新しいモードを追加しています。これは、特にCgoを使用する際に、Goプログラムが任意のホストオブジェクトファイルとリンクできるようにするための第一歩です。

コミット

commit 0cb0f6d0902aff683de5535565e923c45f8d5a8a
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 31 14:11:32 2013 -0800

    cmd/ld: support for linking with host linker
    
    A step toward a fix for issue 4069.
    
    To allow linking with arbitrary host object files, add a linker mode
    that can generate a host object file instead of an executable.
    Then the host linker can be invoked to generate the final executable.
    
    This CL adds a new -hostobj flag that instructs the linker to write
    a host object file instead of an executable.
    
    That is, this works:
    
            go tool 6g x.go
            go tool 6l -hostobj -o x.o x.6
            ld -e _rt0_amd64_linux x.o
            ./a.out
    
    as does:
    
            go tool 8g x.go
            go tool 8l -hostld ignored -o x.o x.8
            ld -m elf_i386 -e _rt0_386_linux x.o
            ./a.out
    
    Because 5l was never updated to use the standard relocation scheme,
    it will take more work to get this working on ARM.
    
    This is a checkpoint of the basic functionality. It does not work
    with cgo yet, and cgo is the main reason for the change.
    The command-line interface will likely change too.
    The gc linker has other information that needs to be returned to
    the caller for use when invoking the host linker besides the single
    object file.

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

https://github.com/golang/go/commit/0cb0f6d0902aff683de5535565e923c45f8d5a8a

元コミット内容

cmd/ld: support for linking with host linker

A step toward a fix for issue 4069.

To allow linking with arbitrary host object files, add a linker mode
that can generate a host object file instead of an executable.
Then the host linker can be invoked to generate the final executable.

This CL adds a new -hostobj flag that instructs the linker to write
a host object file instead of an executable.

That is, this works:

        go tool 6g x.go
        go tool 6l -hostobj -o x.o x.6
        ld -e _rt0_amd64_linux x.o
        ./a.out

as does:

        go tool 8g x.go
        go tool 8l -hostld ignored -o x.o x.8
        ld -m elf_i386 -e _rt0_386_linux x.o
        ./a.out

Because 5l was never updated to use the standard relocation scheme,
it will take more work to get this working on ARM.

This is a checkpoint of the basic functionality. It does not work
with cgo yet, and cgo is the main reason for the change.
The command-line interface will likely change too.
The gc linker has other information that needs to be returned to
the caller for use when invoking the host linker besides the single
object file.

変更の背景

この変更の主な背景は、GoプログラムがCgo(GoとC言語の相互運用機能)を使用する際に、任意のホストオブジェクトファイルとリンクできるようにすることです。従来のGoリンカは、Goのコードとライブラリをリンクして直接実行可能ファイルを生成することを主眼としていました。しかし、Cgoを介してC言語のライブラリやオブジェクトファイルを利用する場合、Goのリンカだけでは対応しきれない複雑なリンク要件が発生することがあります。

特に、システムにインストールされているCライブラリ(libcなど)や、Goの内部リンカではサポートされていない高度なリンカ機能(RELROやバイナリの強化など)を利用する場合、ホストシステムに存在する外部リンカ(通常はGCCやClangに付属するld)の力を借りる必要がありました。

コミットメッセージに「issue 4069の修正に向けた一歩」とあるように、この変更は特定の課題を解決するためのものでした。Goの内部リンカは、GoのランタイムとGoのコードを効率的にリンクするために最適化されていますが、C言語のオブジェクトファイルが持つ多様なリロケーションタイプやシンボル解決のメカニズムに完全に対応することは困難でした。

このコミットは、Goのリンカが最終的な実行可能ファイルを生成するのではなく、中間的なホストオブジェクトファイルを生成するモードを導入することで、この問題を解決しようとしています。これにより、生成されたホストオブジェクトファイルを、より汎用的なホストリンカに渡して最終的なリンク処理を行わせることが可能になります。これは、Cgoの柔軟性と機能性を大幅に向上させるための基盤となる変更です。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Goリンカ (cmd/ld): Go言語のビルドツールチェーンの一部であり、Goのコンパイラ(gc6g8gなど)によって生成されたオブジェクトファイル(.6.8など)を結合し、実行可能ファイルやライブラリを生成する役割を担います。Goのリンカは、Go独自の内部リンカとして機能し、GoのランタイムとGoのコードを効率的にリンクするように設計されています。

  2. ホストリンカ (例: ld, gcc): オペレーティングシステムに標準で備わっているリンカです。C/C++などの言語で書かれたプログラムをコンパイルする際に使用され、複数のオブジェクトファイルやライブラリを結合して実行可能ファイルや共有ライブラリを生成します。ELF (Executable and Linkable Format) などの標準的なバイナリフォーマットを扱います。

  3. Cgo: Go言語の機能の一つで、GoプログラムからC言語のコードを呼び出したり、C言語のライブラリを利用したりすることを可能にします。Cgoを使用すると、GoのコードとCのコードが混在するプログラムをビルドすることになります。この際、Goのリンカとホストリンカの連携が重要になります。

  4. オブジェクトファイル: コンパイラによって生成される中間ファイルで、機械語コード、データ、およびリンカが最終的な実行可能ファイルを生成するために必要な情報(シンボルテーブル、リロケーション情報など)を含んでいます。

  5. 実行可能ファイル: オペレーティングシステムによって直接実行できる形式のファイルです。

  6. リロケーション (Relocation): オブジェクトファイル内のコードやデータが、最終的な実行可能ファイル内のどこに配置されるかによってアドレスを調整するプロセスです。リンカは、シンボル参照を解決し、正しいメモリアドレスにポインタを修正するためにリロケーション情報を使用します。

  7. ELF (Executable and Linkable Format): Unix系システムで広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション(.text.data.rodata.symtab.strtabなど)で構成されます。

    • セクション: ELFファイル内の論理的な区画で、コード、初期化済みデータ、未初期化データ、シンボルテーブル、文字列テーブルなどが格納されます。
    • シンボルテーブル (.symtab): プログラム内の関数や変数などのシンボル名と、それらのアドレスやサイズなどの情報が格納されます。
    • 文字列テーブル (.strtab): シンボル名やセクション名などの文字列が格納されます。
    • リロケーションセクション (.rel, .rela): リロケーションエントリのリストが含まれます。各エントリは、リンカがアドレスを修正する必要がある場所と、その修正方法を記述します。.relaは加算値 (addend) を含むリロケーションで、.relは含まないものです。
  8. _rt0_amd64_linux, _rt0_386_linux: これらはGoのランタイムエントリポイントシンボルです。プログラムが実行を開始する際に最初に呼び出される関数を示します。ホストリンカでリンクする場合、このエントリポイントを明示的に指定する必要があります。

  9. 5l, 6l, 8l: Goのリンカのアーキテクチャ固有のバージョンです。

    • 5l: ARMアーキテクチャ用
    • 6l: AMD64 (x86-64) アーキテクチャ用
    • 8l: 386 (x86) アーキテクチャ用

技術的詳細

このコミットの核心は、Goのリンカに-hostobjという新しいフラグを追加し、このフラグが指定された場合にリンカの動作を変更することです。

通常、Goのリンカは、Goのソースコードから生成されたオブジェクトファイル(例: x.6x.8)を直接リンクし、最終的な実行可能ファイル(例: a.out)を生成します。しかし、-hostobjフラグが指定されると、リンカは実行可能ファイルを生成する代わりに、ホストリンカが処理できる形式のオブジェクトファイル(例: x.o)を出力します。

この新しいモードでは、以下の主要な変更と考慮事項があります。

  1. -hostobjフラグの導入:

    • src/cmd/6l/obj.csrc/cmd/8l/obj.c に、新しいコマンドラインフラグ-hostobjが追加されました。このフラグはisobjというグローバル変数にマッピングされ、リンカの動作を制御します。
    • -hostobjは、現在のところLinux環境でのみサポートされています。他のOSタイプ(HEADTYPE)でこのフラグを使用しようとすると、sysfatal(致命的なエラー)が発生します。
  2. ELFファイル生成の変更:

    • src/cmd/ld/elf.c に、ホストオブジェクトファイル生成時のELFフォーマットに関する多くの変更が加えられました。
    • セクションヘッダのsh_addr: 通常、セクションヘッダのsh_addrフィールドはセクションの仮想アドレスを示しますが、-hostobjモードでは、このフィールドは設定されません(sh->addr = sect->vaddr; の行がif(!isobj)で囲まれています)。これは、オブジェクトファイルではセクションの最終的なロードアドレスがまだ決定されていないためです。
    • プログラムヘッダのスキップ: 実行可能ファイルにはプログラムヘッダ(eh->phoffeh->phentsize)が含まれますが、オブジェクトファイルでは不要なため、-hostobjモードではこれらがスキップされます(goto elfobj;)。
    • ELFタイプ: -hostobjモードでは、ELFヘッダのe_typeフィールドがET_REL(Relocatable file)に設定されます。通常の実行可能ファイルはET_EXEC(Executable file)です。
    • エントリポイントの省略: オブジェクトファイルにはエントリポイント(eh->entry)は不要なため、-hostobjモードでは設定されません。
    • ビルド情報などの省略: NetBSD/OpenBSD固有の署名やビルド情報など、実行可能ファイルにのみ関連する情報も-hostobjモードでは省略されます。
  3. リロケーション処理の変更:

    • src/cmd/ld/data.crelocsym関数において、-hostobjが有効な場合のリロケーション処理が変更されました。
    • D_ADDRタイプのリロケーションでは、isobjがtrueの場合、シンボルのアドレスに加えて、外部シンボル(rs->outer)からのオフセットが考慮されるようになりました。これは、ホストリンカが最終的なアドレス解決を行うために必要な情報を提供するためです。
    • D_PCRELタイプのリロケーションでも、isobjがtrueの場合のオフセット計算が調整されました。
    • src/cmd/5l/asm.c, src/cmd/6l/asm.c, src/cmd/8l/asm.c に、elfreloc1関数が追加されました。この関数は、ELFリロケーションエントリを生成するためのアーキテクチャ固有のロジックを含んでいます。
    • elfreloc1は、リロケーションタイプ(D_ADDR, D_PCRELなど)とサイズ(r->siz)に基づいて、適切なELFリロケーションタイプ(例: R_ARM_ABS32, R_X86_64_32, R_386_32など)を決定し、リロケーションエントリを書き込みます。
    • elfemitreloc関数がsrc/cmd/ld/elf.cに追加され、各セクションのリロケーション情報をELFファイルに書き出す役割を担います。これは、-hostobjモードでのみ呼び出されます。
  4. シンボルテーブルの変更:

    • src/cmd/ld/symtab.cputelfsym関数において、-hostobjが有効な場合、シンボルのアドレスからセクションの仮想アドレスが減算されるようになりました(addr -= xo->sect->vaddr;)。これは、オブジェクトファイル内のシンボルアドレスがセクションの先頭からの相対オフセットとして表現されるためです。
  5. ARMアーキテクチャの課題:

    • コミットメッセージに明記されているように、5l(ARMリンカ)は標準のリロケーションスキームを使用するように更新されていなかったため、この変更をARMで完全に機能させるにはさらなる作業が必要であると述べられています。これは、ARMアーキテクチャのリロケーション処理が他のアーキテクチャ(x86/x86-64)と異なる複雑さを持っていることを示唆しています。

この変更は、Goのリンカが生成するバイナリの形式を、Go独自の実行可能ファイルから、ホストリンカが理解できる標準的なオブジェクトファイル形式へと拡張するものです。これにより、GoのビルドシステムがCgoを介して外部のCライブラリとよりシームレスに連携できるようになるための重要なステップとなります。

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

このコミットでは、主にGoのリンカ (cmd/ld) と、各アーキテクチャ固有のリンカ (cmd/5l, cmd/6l, cmd/8l) に変更が加えられています。

  • src/cmd/5l/asm.c: ARMアーキテクチャのリロケーション処理 (elfreloc1) と、オブジェクトファイル生成時のリロケーション情報の出力 (elfemitreloc) に関連するコードが追加されました。
  • src/cmd/6l/asm.c: AMD64アーキテクチャのリロケーション処理 (elfreloc1) と、オブジェクトファイル生成時のリロケーション情報の出力 (elfemitreloc) に関連するコードが追加されました。
  • src/cmd/6l/obj.c: -hostobjフラグのパースと、そのフラグが指定された場合のヘッドタイプ(OSタイプ)のチェックロジックが追加されました。
  • src/cmd/8l/asm.c: 386アーキテクチャのリロケーション処理 (elfreloc1) と、オブジェクトファイル生成時のリロケーション情報の出力 (elfemitreloc) に関連するコードが追加されました。
  • src/cmd/8l/obj.c: -hostobjフラグのパースと、そのフラグが指定された場合のヘッドタイプ(OSタイプ)のチェックロジックが追加されました。
  • src/cmd/ld/data.c: relocsym関数において、-hostobjが有効な場合のリロケーション処理(特にD_ADDRD_PCRELタイプ)が変更されました。シンボルの外部参照(rs->outer)を考慮したアドレス計算が導入されています。
  • src/cmd/ld/elf.c: ELFファイルの生成ロジックが大幅に変更されました。
    • elfshbits: -hostobjモードでのセクションヘッダのsh_addrフィールドの扱いが変更されました。
    • elfshreloc: リロケーションセクションのELFセクションヘッダを生成する新しい関数が追加されました。
    • elfrelocsect: 特定のセクション内のシンボルに対するリロケーションエントリを生成する新しい関数が追加されました。
    • elfemitreloc: 全てのリロケーションセクションをELFファイルに書き出す新しい関数が追加されました。
    • doelf: -hostobjモードで必要なリロケーションセクション名(.rela.text, .rel.textなど)を文字列テーブルに追加するロジックが追加されました。
    • asmbelf: ELFヘッダのe_typeET_RELに設定したり、プログラムヘッダやエントリポイントをスキップしたりするなど、-hostobjモードでのELFファイル構造の変更が実装されました。
  • src/cmd/ld/elf.h: elfreloc1関数のプロトタイプ宣言が追加されました。
  • src/cmd/ld/lib.c: headstr関数が追加されました。これは、ヘッドタイプ(OSタイプ)の整数値を文字列に変換するヘルパー関数です。
  • src/cmd/ld/lib.h: Section構造体にreloffrellenフィールドが追加され、isobjグローバル変数が宣言されました。また、headstr関数のプロトタイプ宣言も追加されました。
  • src/cmd/ld/symtab.c: putelfsym関数において、-hostobjが有効な場合にシンボルアドレスからセクションの仮想アドレスを減算するロジックが追加されました。

コアとなるコードの解説

このコミットの主要な変更は、Goのリンカがホストリンカと連携するための新しいモードを導入することにあります。そのためのコアとなる変更点を以下に解説します。

  1. isobjフラグの導入とリンカ動作の分岐:

    • src/cmd/6l/obj.csrc/cmd/8l/obj.cmain関数で、新しいコマンドライン引数-hostobjがパースされ、isobjというグローバルな真偽値変数にその状態が格納されます。
    • このisobj変数が、リンカの様々な処理において、実行可能ファイルを生成する通常のモードと、ホストオブジェクトファイルを生成する新しいモードとの間で動作を分岐させるための主要な制御フラグとなります。
    • 例えば、src/cmd/ld/elf.casmbelf関数では、isobjがtrueの場合にELFヘッダのe_typeET_REL(リロケータブルファイル)に設定され、プログラムヘッダやエントリポイントがスキップされます。
  2. リロケーション処理の変更 (relocsym in src/cmd/ld/data.c):

    • relocsym関数は、シンボルに対するリロケーションを処理するGoリンカの重要な部分です。
    • -hostobjモードでは、D_ADDR(絶対アドレス参照)とD_PCREL(PC相対アドレス参照)のリロケーション処理が変更されます。
    • 特に注目すべきは、D_ADDRリロケーションにおいて、isobjがtrueの場合にr->sym->outer(外部シンボル)を考慮してadd(加算値)を計算する部分です。これは、Goの内部シンボルが外部のCライブラリのシンボルを参照する場合に、ホストリンカが正しく解決できるように、リロケーション情報に適切なオフセットを含めるために必要です。
    • 通常の実行可能ファイル生成では、Goリンカが最終的なアドレスを解決しますが、ホストオブジェクトファイルでは、その解決はホストリンカに委ねられるため、リロケーションエントリにはシンボルへの相対的な参照と、ホストリンカが加算すべき値(addend)が正確に記述される必要があります。
  3. ELFリロケーションエントリの生成 (elfreloc1 in src/cmd/*/asm.c):

    • src/cmd/5l/asm.c, src/cmd/6l/asm.c, src/cmd/8l/asm.c に追加されたelfreloc1関数は、アーキテクチャ固有のELFリロケーションエントリを生成する役割を担います。
    • この関数は、Goのリロケーションタイプ(D_ADDR, D_PCREL)とサイズ(r->siz)を受け取り、対応するELFリロケーションタイプ(例: R_ARM_ABS32, R_X86_64_32, R_386_32, R_X86_64_PC32, R_386_PC32など)を決定します。
    • LPUTVPUTといったマクロを使って、リロケーションオフセット、ELFリロケーションタイプ、および加算値(addend)をELFリロケーションセクションに書き込みます。これにより、ホストリンカがオブジェクトファイル内のどの場所を、どのシンボルに対して、どのように修正すべきかを正確に理解できるようになります。
  4. リロケーションセクションの出力 (elfemitreloc in src/cmd/ld/elf.c):

    • elfemitreloc関数は、-hostobjモードでのみ呼び出され、Goのリンカが生成したリロケーション情報をELFファイル内の実際のリロケーションセクション(.rel.text, .rela.dataなど)として書き出します。
    • この関数は、elfrelocsectを呼び出して、各セクション(コードセクション、データセクションなど)に対するリロケーションエントリを生成し、それらをELFファイルに追記します。
    • これにより、Goのリンカは、Goのコードとデータが持つリロケーション要件を、ホストリンカが理解できる標準的なELFリロケーション形式で表現できるようになります。

これらの変更により、Goのリンカは、Goのオブジェクトファイルをホストリンカが処理可能な形式に変換する能力を獲得しました。これは、Cgoを介してC言語のライブラリと連携するGoプログラムのビルドプロセスにおいて、より高度な柔軟性と互換性を提供するための重要な基盤となります。

関連リンク

  • https://golang.org/cl/7060044

参考にした情報源リンク