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

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

コミット

commit 4e2aa9bff0eb76f98bb3b238c7f56bea6330f70d
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jan 4 17:03:57 2013 -0500

    cmd/ld: use native-endian symbol values in symbol table
    
    The Plan 9 symbol table format defines big-endian symbol values
    for portability, but we want to be able to generate an ELF object file
    and let the host linker link it, as part of the solution to issue 4069.
    The symbol table itself, since it is loaded into memory at run time,
    must be filled in by the final host linker, using relocation directives
    to set the symbol values. On a little-endian machine, the linker will
    only fill in little-endian values during relocation, so we are forced
    to use little-endian symbol values.
    
    To preserve most of the original portability of the symbol table
    format, we make the table itself say whether it uses big- or
    little-endian values. If the table begins with the magic sequence
            fe ff ff ff 00 00
    then the actual table begins after those six bytes and contains
    little-endian symbol values. Otherwise, the table is in the original
    format and contains big-endian symbol values. The magic sequence
    looks like an "end of table" entry (the fifth byte is zero), so legacy
    readers will see a little-endian table as an empty table.
    
    All the gc architectures are little-endian today, so the practical
    effect of this CL is to make all the generated tables little-endian,
    but if a big-endian system comes along, ld will not generate
    the magic sequence, and the various readers will fall back to the
    original big-endian interpretation.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7066043

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

https://github.com/golang/go/commit/4e2aa9bff0eb76f98bb3b238c7f56bea6330f70d

元コミット内容

cmd/ld: use native-endian symbol values in symbol table

このコミットは、Goリンカ(cmd/ld)が生成するシンボルテーブルにおいて、シンボル値のエンディアン(バイト順序)をネイティブエンディアンに合わせるように変更するものです。これにより、特にELFオブジェクトファイルを生成し、ホストリンカでリンクする際の互換性が向上します。

変更の背景

Goのツールチェイン、特にgcコンパイラとldリンカは、元々Plan 9というオペレーティングシステムの設計思想に強く影響を受けています。Plan 9のシンボルテーブル形式は、移植性を考慮してビッグエンディアンのシンボル値を定義していました。

しかし、Goプログラムを様々なプラットフォームで実行可能にするためには、ホストシステムのリンカ(例えばLinux上のld)が生成されたオブジェクトファイルを正しく処理できる必要があります。特に、GoのリンカがELF形式のオブジェクトファイルを生成し、それをホストリンカがリンクする場合、シンボルテーブルは実行時にメモリにロードされ、最終的なホストリンカが再配置ディレクティブを使用してシンボル値を設定する必要があります。

問題は、リトルエンディアンのマシン(現在のほとんどの一般的なアーキテクチャ、例: x86-64)では、リンカは再配置中にリトルエンディアンの値しか設定しないという点です。このため、Goのリンカがビッグエンディアンのシンボル値を生成してしまうと、リトルエンディアンのホストリンカが正しく処理できず、互換性の問題が発生します。

このコミットは、この問題を解決し、Goのリンカがホストリンカと協調して動作できるようにするために、シンボルテーブルのエンディアンをネイティブエンディアンに合わせることを目的としています。これは、GoのIssue 4069(ただし、検索結果はGo言語のIssueではないため、このコミットの文脈でのIssue 4069は内部的な参照である可能性が高い)に対する解決策の一部として位置づけられています。

前提知識の解説

  • エンディアン (Endianness):
    • メモリ上で複数バイトのデータをどのように配置するかを示すバイト順序の規則です。
    • ビッグエンディアン (Big-endian): データの最上位バイト(最も重要な部分)が最も小さいアドレスに格納されます。例: 0x1234567812 34 56 78 の順で格納されます。
    • リトルエンディアン (Little-endian): データの最下位バイト(最も重要でない部分)が最も小さいアドレスに格納されます。例: 0x1234567878 56 34 12 の順で格納されます。
    • CPUアーキテクチャによってどちらのエンディアンを採用しているかが異なります。x86系はリトルエンディアン、PowerPCやARMの一部はビッグエンディアンまたはバイエンディアン(両方対応)です。
  • シンボルテーブル (Symbol Table):
    • コンパイルされたプログラム内のシンボル(変数名、関数名など)とそのアドレスや型などの情報を格納するデータ構造です。リンカがプログラムの異なる部分を結合する際に使用します。
  • リンカ (Linker):
    • コンパイラによって生成された複数のオブジェクトファイルやライブラリを結合し、実行可能なプログラムを生成するツールです。シンボルテーブルを参照して、未解決のシンボル参照を解決します。
  • ELF (Executable and Linkable Format):
    • Unix系システム(Linuxなど)で広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイル形式です。
  • 再配置 (Relocation):
    • リンカがオブジェクトファイルを結合する際に、プログラム内のアドレス参照を修正するプロセスです。例えば、ある関数が別のオブジェクトファイル内の変数にアクセスする場合、リンカはその変数の最終的なメモリ上のアドレスを決定し、参照を更新します。
  • Plan 9:
    • ベル研究所で開発された分散オペレーティングシステムです。Go言語の設計思想や一部のツールチェインの基盤に影響を与えています。

技術的詳細

この変更の核心は、Goのリンカが生成するシンボルテーブルのフォーマットに、エンディアンを示す「マジックシーケンス」を導入することです。

  • マジックシーケンスの導入:
    • シンボルテーブルの先頭に fe ff ff ff 00 00 という6バイトのシーケンスが追加されます。
    • このシーケンスが存在する場合、その後のシンボルテーブルはリトルエンディアンでエンコードされたシンボル値を含みます。
    • このシーケンスが存在しない場合、従来のフォーマット(ビッグエンディアン)として解釈されます。
  • レガシー互換性:
    • fe ff ff ff 00 00 というマジックシーケンスは、従来のPlan 9シンボルテーブルの「テーブルの終わり」を示すエントリ(5バイト目がゼロ)のように見えるように設計されています。
    • これにより、この変更に対応していない古いGoツールチェインやライブラリは、新しいリトルエンディアンのシンボルテーブルを「空のテーブル」として認識し、クラッシュすることなく処理を続行できます。これは、前方互換性を保つための巧妙な工夫です。
  • エンディアンの動的選択:
    • 現在のgcアーキテクチャ(Goコンパイラがサポートするアーキテクチャ)はすべてリトルエンディアンであるため、この変更により、生成されるすべてのシンボルテーブルは実質的にリトルエンディアンになります。
    • 将来的にビッグエンディアンのシステムが登場した場合、ldリンカはマジックシーケンスを生成せず、既存のリーダーは元のビッグエンディアン解釈にフォールバックします。これにより、将来的なビッグエンディアンシステムへの対応も可能になります。
  • src/cmd/ld/symtab.c の変更:
    • slputl という新しい関数が追加され、リトルエンディアンで32ビット整数を書き込むようになりました。
    • slput という関数ポインタが導入され、現在のエンディアン設定(ビッグエンディアンまたはリトルエンディアン)に応じて slputb (ビッグエンディアン) または slputl (リトルエンディアン) のいずれかを指すように切り替えられます。
    • symtab 関数内で、アーキテクチャ(thechar)に基づいて slput が初期化されます。'5', '6', '8' (それぞれARM, x86, x86-64などに対応) の場合はリトルエンディアンのマジックシーケンスを書き込み、slputslputl に設定します。それ以外の場合は slputb に設定します。
  • src/libmach/sym.c の変更:
    • シンボルテーブルを読み込む syminit 関数が変更され、マジックシーケンスをチェックするようになりました。
    • マジックシーケンス \xfe\xff\xff\xff\x00\x00 が検出された場合、シンボル値の読み込みに使用するエンディアン変換関数をビッグエンディアン (beswav, beswal) からリトルエンディアン (leswav, leswal) に切り替えます。
  • src/pkg/debug/gosym/symtab.go の変更:
    • Goのデバッグパッケージ内のシンボルテーブルウォーカー (walksymtab) も同様に、マジックシーケンスをチェックし、それに応じて binary.BigEndian または binary.LittleEndian を使用してシンボル値をデコードするように変更されました。
  • src/pkg/runtime/symtab.c の変更:
    • Goランタイム内のシンボルテーブルウォーカー (walksymtab) も、マジックシーケンスをチェックし、シンボル値の読み込み時に適切なエンディアン変換を行うように変更されました。

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

このコミットは、主に以下のファイルに影響を与えています。

  1. doc/go1.1.html: Go 1.1のリリースノートにシンボルテーブルの変更に関する記述が追加されています。
  2. src/cmd/ld/data.c: リンカのデータ処理部分。エンディアンに関する直接的な処理が削除され、より汎用的なリロケーション処理に修正されています。特に、RbigRlittle といったエンディアン指定のリロケーションタイプが削除されています。
  3. src/cmd/ld/lib.h: リンカの共通ヘッダファイル。RbigRlittle の定義が削除されています。
  4. src/cmd/ld/symtab.c: リンカのシンボルテーブル生成ロジック。
    • slputl (little-endian put) 関数の追加。
    • slput 関数ポインタの導入と、それを用いたエンディアンに応じた値の書き込み。
    • シンボルテーブルの先頭にマジックシーケンスを書き込むロジックの追加。
  5. src/libmach/sym.c: libmach ライブラリのシンボル処理部分。
    • シンボルテーブルの読み込み時にマジックシーケンスをチェックし、エンディアンを切り替えるロジックの追加。
  6. src/pkg/debug/gosym/pclntab_test.go: テストファイルの小さな修正。t.Errorft.Fatalf に変更されています。これは直接的なエンディアン変更とは関係ありませんが、関連するテストの堅牢性を高めるための変更です。
  7. src/pkg/debug/gosym/symtab.go: Goのデバッグパッケージ内のシンボルテーブル処理。
    • littleEndianSymtab 定数の追加。
    • walksymtab 関数内でマジックシーケンスをチェックし、binary.BigEndian または binary.LittleEndian を選択するロジックの追加。
  8. src/pkg/runtime/symtab.c: Goランタイムのシンボルテーブル処理。
    • walksymtab 関数内でマジックシーケンスをチェックし、シンボル値の読み込み時に適切なエンディアン変換を行うロジックの追加。

コアとなるコードの解説

最も重要な変更は src/cmd/ld/symtab.c と、シンボルテーブルを読み込む側の src/libmach/sym.c, src/pkg/debug/gosym/symtab.go, src/pkg/runtime/symtab.c です。

src/cmd/ld/symtab.c の変更点:

// 新しいリトルエンディアン書き込み関数
static void
slputl(int32 v)
{
	uchar *p;

	symgrow(symt, symt->size+4);
	p = symt->p + symt->size;
	*p++ = v;
	*p++ = v>>8;
	*p++ = v>>16;
	*p = v>>24;
	symt->size += 4;
}

// エンディアンに応じた書き込み関数ポインタ
static void (*slput)(int32);

// ... (putsymb 関数内の変更) ...
// シンボル値の書き込みに slput を使用
// if(l == 8) {
//     if(slput == slputl) {
//         slputl(v);
//         slputl(v>>32);
//     } else {
//         slputb(v>>32);
//         slputb(v);
//     }
// } else
//     slput(v);

// ... (symtab 関数内の変更) ...
// アーキテクチャに応じた slput の初期化とマジックシーケンスの書き込み
switch(thechar) {
default:
	diag("unknown architecture %c", thechar);
	errorexit();
case '5': // ARM
case '6': // x86
case '8': // x86-64
	// magic entry to denote little-endian symbol table
	slputl(0xfffffffe); // マジックシーケンスの最初の4バイト
	scput(0);           // 続く2バイト
	scput(0);
	slput = slputl;     // リトルエンディアン書き込み関数を使用
	break;
case 'v': // big-endian (in case one comes along)
	slput = slputb;     // ビッグエンディアン書き込み関数を使用
	break;
}

このコードは、リンカがシンボルテーブルを生成する際に、現在のアーキテクチャがリトルエンディアンであれば、リトルエンディアンのマジックシーケンスをテーブルの先頭に書き込み、その後のシンボル値もリトルエンディアンで書き込むように設定します。これにより、生成されるバイナリのシンボルテーブルがホストリンカの期待するエンディアンに合致するようになります。

src/libmach/sym.c の変更点:

// ... (syminit 関数内の変更) ...
uvlong (*swav)(uvlong); // 64ビット値のエンディアン変換関数ポインタ
uint32 (*swal)(uint32); // 32ビット値のエンディアン変換関数ポインタ
uchar buf[6];

// ...

swav = beswav; // デフォルトはビッグエンディアン
swal = beswal;

// ...

Binit(&b, fd, OREAD);
Bseek(&b, fp->symoff, 0);
memset(buf, 0, sizeof buf);
Bread(&b, buf, sizeof buf);
if(memcmp(buf, "\xfe\xff\xff\xff\x00\x00", 6) == 0) { // マジックシーケンスをチェック
	swav = leswav; // リトルエンディアン変換関数に切り替え
	swal = leswal;
} else {
	Bseek(&b, fp->symoff, 0); // マジックシーケンスがなければ、先頭に戻る
}

// ...
// シンボル値の読み込み時に swav または swal を使用
// p->value = swav(vl);
// p->value = (u32int)swal(l);

このコードは、libmach がシンボルテーブルを読み込む際に、テーブルの先頭をチェックしてマジックシーケンスが存在するかどうかを確認します。存在すれば、その後のシンボル値はリトルエンディアンとして解釈し、対応するエンディアン変換関数 (leswav, leswal) を使用します。これにより、libmach は新しいフォーマットのシンボルテーブルも正しく読み込めるようになります。

同様のロジックが src/pkg/debug/gosym/symtab.go (Go言語で書かれたデバッグパッケージ) と src/pkg/runtime/symtab.c (Goランタイム) にも実装されており、Goのツールチェイン全体で新しいシンボルテーブルフォーマットに対応しています。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Goのリンカに関する情報 (Goのソースコード内): src/cmd/ld/ ディレクトリ

参考にした情報源リンク

  • コミットメッセージと差分: https://github.com/golang/go/commit/4e2aa9bff0eb76f98bb3b238c7f56bea6330f70d
  • Go 1.1 リリースノート (関連部分): doc/go1.1.html (コミットに含まれる差分)
  • エンディアンに関する一般的な情報 (例: Wikipedia)
  • ELFファイル形式に関する一般的な情報 (例: Wikipedia)
  • Goのツールチェインに関する一般的な情報 (Goの公式ブログやドキュメント)
  • Plan 9に関する一般的な情報 (例: Wikipedia)
  • GoのIssue 4069は、このコミットの文脈ではGo言語の公式IssueトラッカーのIssueではなく、内部的な参照である可能性が高いです。一般的なWeb検索では関連情報が見つかりませんでした。