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

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

このコミットは、Go言語のリンカ (cmd/ld) におけるデータアライメントの改善と、386 (x86) アーキテクチャでのホストリンキング(外部リンカ gcc の利用)に向けた重要なステップを導入するものです。具体的には、データ構造のメモリ配置の最適化、ELFセクションヘッダにおけるアライメント情報の正確な反映、および異なるアーキテクチャ(32ビット/64ビット)に応じた gcc の適切な呼び出しを可能にしています。

コミット

commit 7663ffcae641dbf06b9d5321c9afbad4fc73e336
Author: Russ Cox <rsc@golang.org>
Date:   Thu Mar 7 19:57:25 2013 -0800

    cmd/ld: steps toward 386 host linking
    
    - Introduce MaxAlign constant and use in data layout
    and ELF section header.
    
    - Allow up to 16-byte alignment for large objects
    (will help Keith's hash changes).
    
    - Emit ELF symbol for .rathole (global /dev/null used by 8c).
    
    - Invoke gcc with -m32/-m64 as appropriate.
    
    - Don't invoke gcc if writing the .o file failed.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/7563045

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

https://github.com/golang/go/commit/7663ffcae641dbf06b9d5321c9afbad4fc73e336

元コミット内容

cmd/ld: 386ホストリンキングに向けたステップ

  • MaxAlign 定数を導入し、データレイアウトとELFセクションヘッダで使用する。
  • 大きなオブジェクトに対して最大16バイトのアライメントを許可する(Keithのハッシュ変更に役立つだろう)。
  • .rathole のELFシンボルを出力する(8c で使用されるグローバルな /dev/null)。
  • 適切に -m32 または -m64 オプションを付けて gcc を呼び出す。
  • .o ファイルの書き込みに失敗した場合は gcc を呼び出さない。

変更の背景

このコミットの主な背景には、Go言語のリンカが異なるアーキテクチャ(特に386、すなわちx86 32ビット)でより効率的かつ正確に動作するようにするという目標があります。

  1. データアライメントの最適化: 現代のプロセッサは、メモリ上のデータが特定のアドレス境界に配置されている場合に、より高速にアクセスできます。これを「アライメント」と呼びます。特に大きなデータ構造や特定のアルゴリズム(コミットメッセージにある「Keithのハッシュ変更」など)では、より厳密なアライメントが必要となる場合があります。Goリンカは、生成するバイナリのデータセクションにおいて、このアライメントを適切に管理する必要があります。以前の実装では、アライメントの計算が不十分であったり、固定値に依存していたりする可能性があり、これを MaxAlign の導入と動的なアライメント計算によって改善しようとしています。

  2. ELFセクションヘッダの正確性: ELF (Executable and Linkable Format) は、Unix系システムで実行可能ファイル、オブジェクトファイル、共有ライブラリなどに用いられる標準的なファイル形式です。ELFファイル内の各セクション(コード、データ、BSSなど)には、そのセクションのアライメント要件を示す情報がセクションヘッダに記述されます。この情報が不正確だと、リンカやローダがバイナリを正しく処理できない可能性があります。このコミットでは、セクションヘッダのアライメント情報を実際のセクションのアライメントに合わせて更新することで、ELFファイルの正確性を向上させています。

  3. 386ホストリンキングの推進: Go言語のツールチェインは、クロスコンパイル(異なるアーキテクチャ向けのバイナリ生成)を強力にサポートしています。しかし、特定の状況では、システムにインストールされているネイティブのCコンパイラ(gcc など)をGoリンカが呼び出して、C言語で書かれたランタイムライブラリや外部のオブジェクトファイルをリンクする必要があります。これを「ホストリンキング」と呼びます。特に386アーキテクチャでは、32ビットと64ビットの区別が重要であり、gcc を呼び出す際に適切な -m32 または -m64 オプションを渡すことが不可欠です。このコミットは、このホストリンキングの信頼性と互換性を高めることを目的としています。

  4. .rathole シンボルの扱い: .rathole は、Goのコンパイラ(8c はx86向けのGoコンパイラ)が使用する特殊なシンボルで、特定の最適化やデバッグ目的で利用されることがあります。このシンボルがELFファイルに適切に出力されないと、デバッグやプロファイリングツールが正しく機能しない可能性があります。

  5. エラーハンドリングの改善: .o (オブジェクト) ファイルの書き込みに失敗した場合に、リンカが不必要に gcc を呼び出すのを防ぐことで、ビルドプロセスの堅牢性を向上させています。

これらの変更は、Go言語のコンパイラとリンカの内部的な堅牢性、パフォーマンス、および異なるプラットフォームへの対応能力を向上させるための継続的な取り組みの一環です。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識があると役立ちます。

  • Go言語のツールチェイン: Go言語は、go build コマンドを通じて、ソースコードのコンパイルからリンクまでを一貫して行います。このプロセスには、コンパイラ(例: 8c はx86向け、6c はx86-64向け、5c はARM向け)とリンカ(cmd/ld)が含まれます。
  • リンカ (cmd/ld): Go言語のリンカは、コンパイラが生成したオブジェクトファイルや、Goランタイムライブラリ、場合によってはC言語のライブラリなどを結合して、最終的な実行可能ファイルを生成するツールです。メモリ上のセクション配置、シンボル解決、リロケーションなど、バイナリ生成の重要な役割を担います。
  • データアライメント (Data Alignment): コンピュータのメモリはバイト単位でアドレス指定されますが、CPUは通常、ワード単位(例: 4バイト、8バイト)でデータを読み書きします。データがそのサイズと同じ境界(例: 4バイト整数が4の倍数のアドレスに配置される)に配置されている場合、CPUは効率的にデータにアクセスできます。これをアライメントと呼びます。アライメントが不適切だと、CPUは複数回のメモリアクセスを必要としたり、パフォーマンスが低下したり、最悪の場合クラッシュしたりすることがあります。
    • rnd(size, align): この関数は、sizealign の倍数に切り上げる(アライメントする)ために使用されます。例えば、rnd(10, 8) は16になります。
  • ELF (Executable and Linkable Format): Unix系OS(Linuxなど)で広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、プログラムコード、データ、シンボルテーブル、リロケーション情報などを格納する複数の「セクション」で構成されます。
  • ELFセクションヘッダ: ELFファイル内の各セクションには、そのセクションの属性(名前、サイズ、メモリ上のアドレス、ファイルオフセット、アライメント要件など)を記述するヘッダがあります。sh_addralign フィールドは、セクションがメモリ上でアライメントされるべきバイト境界を示します。
  • ホストリンキング (Host Linking): GoプログラムがC言語のコード(Cgo経由など)や、システムにインストールされているCライブラリに依存する場合、Goリンカは最終的な実行可能ファイルを生成するために、システムのCコンパイラ(通常は gcc)を呼び出すことがあります。このプロセスをホストリンキングと呼びます。
  • gcc-m32/-m64 オプション: gcc は、コンパイル対象のアーキテクチャを指定するためのオプションを提供します。
    • -m32: 32ビットのコードを生成します。x86アーキテクチャの場合、i386/i686互換のコードを生成します。
    • -m64: 64ビットのコードを生成します。x86アーキテクチャの場合、x86-64互換のコードを生成します。
  • .rathole シンボル: Goのコンパイラやリンカの内部で使われる特殊なシンボル名の一つです。特定のデバッグや最適化の目的で、一時的なメモリ領域や未使用の領域を示すために使われることがあります。コミットメッセージでは「8c で使用されるグローバルな /dev/null」と説明されており、これは特定のデータが書き込まれるが、その内容は実際には使用されない、あるいは無視される場所を指すメタファーとして使われています。

技術的詳細

このコミットは、Goリンカのデータ構造とアライメント処理に深く関わる変更を導入しています。

  1. MaxAlign 定数の導入と利用:

    • src/cmd/5l/l.h (ARM), src/cmd/6l/l.h (x86-64), src/cmd/8l/l.h (x86) の各アーキテクチャ固有のヘッダファイルに MaxAlign 定数が追加されました。
      • ARM (5l): MaxAlign = 8
      • x86-64 (6l): MaxAlign = 32
      • x86 (8l): MaxAlign = 32
    • この MaxAlign は、そのアーキテクチャでサポートされる最大のデータアライメントを示します。これにより、リンカはより大きなアライメント要件を持つデータ(例: SIMD命令で使用されるデータ)を適切に配置できるようになります。
  2. アライメント計算ロジックの刷新 (src/cmd/ld/data.c):

    • alignsymsize 関数が削除され、より汎用的な symalign(Sym *s) 関数が導入されました。
      • symalign は、シンボル s のサイズと MaxAlign を考慮して、そのシンボルが要求する最適なアライメント値を計算します。具体的には、MaxAlign から始めて、シンボルのサイズよりも小さくなるまで2で割り続け、最終的にシンボル自身が持つアライメント要件 (s->align) と比較して大きい方を採用します。これにより、シンボルのサイズに応じた適切なアライメントが動的に決定されます。
    • aligndatsize(int32 datsize, Sym *s) 関数が簡素化され、symalign(s) を利用して datsize をアライメントするようになりました。
    • 新しい maxalign(Sym *s, int type) 関数が追加されました。これは、特定のセクション内のシンボルリストを走査し、そのセクション全体で必要となる最大のアライメント値を決定します。これは、ELFセクションヘッダの sh_addralign フィールドを設定する際に重要になります。
    • dodata() 関数内で、各データセクション(.noptrdata, .data.rel.ro, .data, .bss, .noptrbss, .rodata, .typelink, .gcdata, .gcbss, .gosymtab, .gopclntab, ELFセクション)の配置ロジックが大幅に変更されました。
      • Section 構造体に align フィールドが追加され、symalignmaxalign を使ってセクションのアライメントが設定されるようになりました。
      • 以前は rnd(s->size, PtrSize) のようにポインタサイズでアライメントしていた箇所が、s->size をそのまま加算し、セクション全体の datsizealigndatsizernd(datsize, sect->align) でアライメントするように変更されました。これにより、より正確で柔軟なデータ配置が可能になります。
    • textaddress() 関数から、コードセクションの終端を128バイトにアライメントする固定ロジックが削除されました。これは、新しい汎用的なアライメントメカニズムに置き換えられたためです。
    • address() 関数では、セクションの仮想アドレス (vaddr) を計算する際に、各セクションの s->align を考慮してアライメントするようになりました。
  3. ELFセクションヘッダのアライメント設定 (src/cmd/ld/elf.c):

    • elfshbits(Section *sect) 関数内で、ELFセクションヘッダの sh->addralign フィールドが、以前の固定値 PtrSize から sect->align に変更されました。これにより、生成されるELFファイルは、各セクションの実際のアライメント要件を正確に反映するようになります。これは、他のツール(デバッガ、ローダなど)との互換性を向上させます。
  4. ホストリンキングの改善 (src/cmd/ld/lib.c):

    • hostlink() 関数(外部リンカ gcc を呼び出す部分)に、エラー発生時のガードが追加されました。nerrors > 0 の場合、gcc の呼び出しをスキップするようになり、ビルドプロセスの堅牢性が向上しました。
    • gcc の呼び出し時に、ターゲットアーキテクチャに応じて -m32 または -m64 オプションを動的に追加するロジックが導入されました。これは thechar 変数(Goのコンパイラ/リンカがターゲットアーキテクチャを示すために使用する文字、例: '8' はx86、'6' はx86-64)に基づいて行われます。これにより、クロスコンパイル環境でのホストリンキングが正しく機能するようになります。
    • addsection() 関数で、新しく作成される Sectionalign フィールドが PtrSize で初期化されるようになりました。これは、すべてのセクションが少なくともポインタサイズでアライメントされるという基本的な要件を満たすためです。
  5. .rathole シンボルの出力 (src/cmd/ld/lib.c):

    • genasmsym() 関数内で、シンボルを非表示にする条件が変更され、.rathole シンボルは非表示の対象から除外されるようになりました。これにより、.rathole シンボルがELFファイルに適切に出力されるようになります。これは、Goのコンパイラやランタイムが内部的に使用する特定のメカニズムのデバッグや分析に役立つ可能性があります。

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

このコミットにおける主要な変更箇所は以下のファイルと関数に集中しています。

  • src/cmd/5l/l.h, src/cmd/6l/l.h, src/cmd/8l/l.h:
    • MaxAlign 定数の追加。
  • src/cmd/ld/data.c:
    • alignsymsize 関数の削除。
    • symalign(Sym *s) 関数の追加。
    • aligndatsize(int32 datsize, Sym *s) 関数の変更。
    • maxalign(Sym *s, int type) 関数の追加。
    • dodata() 関数内のセクション配置とアライメント計算ロジックの大幅な変更。
    • textaddress() 関数からの固定アライメントロジックの削除。
    • address() 関数内のセクション仮想アドレス計算の変更。
  • src/cmd/ld/elf.c:
    • elfshbits(Section *sect) 関数内の sh->addralign 設定の変更。
  • src/cmd/ld/lib.c:
    • hostlink() 関数内の gcc 呼び出し条件とオプション追加ロジックの変更。
    • addsection() 関数内の sect->align 初期化。
    • genasmsym() 関数内の .rathole シンボル非表示条件の変更。
  • src/cmd/ld/lib.h:
    • Section 構造体への int32 align; フィールドの追加。

コアとなるコードの解説

src/cmd/ld/data.c におけるアライメントロジックの変更

symalign(Sym *s) 関数 (新規):

static int32
symalign(Sym *s)
{
	int32 align;

	align = MaxAlign;
	while(align > s->size && align > 1)
		align >>= 1;
	if(align < s->align)
		align = s->align;
	return align;
}

この関数は、与えられたシンボル s の最適なアライメントを計算します。まず、アーキテクチャ固有の最大アライメント MaxAlign から開始します。次に、align がシンボルのサイズ s->size よりも大きく、かつ1より大きい間、align を2で割っていきます(右シフト >>= 1)。これにより、シンボルのサイズに最も近い2のべき乗のアライメント値を見つけます。最後に、シンボル自身が持つアライメント要件 s->align と比較し、より大きい方を返します。これにより、シンボルのサイズと特定の要件の両方を満たすアライメントが保証されます。

aligndatsize(int32 datsize, Sym *s) 関数 (変更):

static int32
aligndatsize(int32 datsize, Sym *s)
{
	return rnd(datsize, symalign(s));
}

以前は複雑なロジックを持っていましたが、symalign 関数を利用することで非常に簡潔になりました。これは、現在のデータサイズ datsize を、次のシンボル s のアライメント要件 (symalign(s)) に合わせて切り上げることを意味します。

maxalign(Sym *s, int type) 関数 (新規):

static int32
maxalign(Sym *s, int type)
{
	int32 align, max;

	max = 0;
	for(; s != S && s->type <= type; s = s->next) {
		align = symalign(s);
		if(max < align)
			max = align;
	}
	return max;
}

この関数は、特定のセクション(type で指定されるシンボルタイプまで)に含まれるすべてのシンボルを走査し、その中で最も大きなアライメント要件 (symalign(s)) を持つシンボルのアライメント値を返します。この値は、そのセクション全体のアライメント要件として使用されます。

dodata() 関数 (大幅な変更): dodata 関数は、Goプログラムのデータセクション(初期化済みデータ、BSS、読み取り専用データなど)をメモリ上に配置する主要な関数です。このコミットでは、各セクションの sect->align フィールドが symalignmaxalign を使って設定されるようになり、datsize の計算も rnd(datsize, sect->align)aligndatsize(datsize, s) を通じて行われるようになりました。これにより、セクション内の個々のシンボルとセクション全体の双方で、より正確なアライメントが適用されます。

例えば、.noptrdata セクションの処理では:

	sect = addsection(&segdata, ".noptrdata", 06);
	sect->align = maxalign(s, SDATARELRO-1); // セクション全体の最大アライメントを設定
	datsize = rnd(datsize, sect->align);     // セクション開始アドレスをアライメント
	sect->vaddr = datsize;
	// ...
	for(; s != nil && s->type < SDATARELRO; s = s->next) {
		datsize = aligndatsize(datsize, s); // 各シンボルの開始アドレスをアライメント
		s->sect = sect;
		s->type = SDATA;
		s->value = datsize;
		datsize += s->size; // シンボルサイズを加算
	}
	sect->len = datsize - sect->vaddr;

このように、セクション全体の開始アドレスと、セクション内の各シンボルの開始アドレスの両方でアライメントが考慮されるようになりました。

src/cmd/ld/elf.c におけるELFセクションヘッダのアライメント設定

elfshbits(Section *sect) 関数 (変更):

	sh->addralign = sect->align; // 以前は PtrSize

この変更は、ELFセクションヘッダの sh_addralign フィールドに、リンカが計算した実際のセクションのアライメント値 (sect->align) を設定するようにします。これにより、生成されるELFファイルが、そのセクションのメモリ配置要件を正確に外部ツールに伝えることができるようになります。

src/cmd/ld/lib.c におけるホストリンキングの改善

hostlink() 関数 (変更):

	if(linkmode != LinkExternal || nerrors > 0) // nerrors > 0 の条件が追加
		return;
	// ...
	switch(thechar){ // gcc オプションの動的追加
	case '8': // x86
		argv[argc++] = "-m32";
		break;
	case '6': // x86-64
		argv[argc++] = "-m64";
		break;
	}

nerrors > 0 の条件追加により、リンキングプロセス中にエラーが発生した場合、不必要な gcc の呼び出しが抑制されます。 switch(thechar) ブロックは、Goのリンカがターゲットとするアーキテクチャ(thechar で識別)に基づいて、gcc-m32 または -m64 オプションを渡すようにします。これは、32ビットと64ビットのバイナリを正しくリンクするために不可欠です。

genasmsym() 関数 (変更):

	if(s->hide || (s->name[0] == '.' && s->version == 0 && strcmp(s->name, ".rathole") != 0)) // .rathole の除外条件が追加
		continue;

この変更により、通常は非表示にされる内部シンボルの中から、.rathole シンボルが明示的に除外され、ELFシンボルとして出力されるようになります。

関連リンク

参考にした情報源リンク