[インデックス 10040] GoのELFリンカーにおけるELFRESERVE診断メッセージの修正
コミット
- コミットハッシュ: 033585d6755a19308314b89f1252ec1438e24fe0
- 作成者: Anthony Martin ality@pbrane.org
- 日付: 2011年10月18日
- タイトル: 5l, 6l, 8l: correct ELFRESERVE diagnostic
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/033585d6755a19308314b89f1252ec1438e24fe0
元コミット内容
5l, 6l, 8l: correct ELFRESERVE diagnostic
If the length of the interpreter string
pushes us over the ELFRESERVE limit, the
resulting error message will be comical.
I was doing some ELF tinkering with a
modified version of 8l when I hit this.
To be clear, the stock linkers wouldn't
hit this without adding about forty more
section headers. We're safe for now. ;)
Also, remove a redundant call to cflush.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5268044
変更の背景
このコミットは、Go言語の初期のELFリンカー(5l、6l、8l)において、ELF形式のバイナリを生成する際のエラー診断メッセージの問題を修正するものです。Anthony Martinが自身のELFリンカー修正版(8l)を使用した実験中に発見した問題で、インタープリター文字列の長さがELFRESERVE制限を超えた場合に不正確なエラーメッセージが表示される問題を修正しています。
作者は「現在のストックリンカーでは約40個のセクションヘッダーを追加しない限りこの問題は発生しない」と述べており、この修正は将来的な潜在的問題を予防するためのものです。
前提知識の解説
GoのPlan 9由来のリンカー
Go言語の5l、6l、8lリンカーは、Plan 9オペレーティングシステムのツールチェーンから直接派生しています。これらのリンカーは、Ken Thompson、Rob Pike、Russ Coxらによって開発されました。
- 5l: ARMアーキテクチャ用リンカー
- 6l: x86-64アーキテクチャ用リンカー
- 8l: x86-32アーキテクチャ用リンカー
これらの数字は、Plan 9のアーキテクチャ命名規則に従っており、それぞれ異なるCPUアーキテクチャを表しています。
ELFRESERVEの概念
ELFRESERVEは、Go言語のELFリンカーが使用する定数で、値は0xc00(3072バイト)です。これは、ELFファイルの先頭に確保される予約領域のサイズを示しています。
この予約領域には以下の要素が含まれます:
- ELFヘッダー(ELF Header)
- プログラムヘッダー(Program Headers)
- セクションヘッダー(Section Headers)
- インタープリター文字列(Interpreter String)
ELFインタープリター文字列
ELFインタープリター文字列は、ELFバイナリの動的リンクを担当するプログラムのパス名を指定します。典型的には、Linuxシステムにおける動的リンカー(例:/lib64/ld-linux-x86-64.so.2)のパスが格納されます。
このインタープリター文字列は、ELFファイルのPT_INTERPプログラムヘッダーセグメントに格納され、実行可能ファイルでのみ意味を持ちます。カーネルがELFバイナリを実行する際、このインタープリターが最初に呼び出され、必要な共有ライブラリの読み込みとシンボルの解決を行います。
技術的詳細
修正前の問題
修正前のコードでは、ELFRESERVE制限のチェックが以下のように行われていました:
if(a+elfwriteinterp() > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
この実装では、elfwriteinterp()
関数が呼び出されますが、その戻り値(インタープリター文字列の長さ)はa
変数に加算されずに捨てられていました。結果として、診断メッセージで表示される実際のサイズ(%d
で表示されるa
の値)は、インタープリター文字列の長さを含まない不正確な値となっていました。
修正後の改善
修正後のコードでは:
a += elfwriteinterp();
if(a > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
elfwriteinterp()
の戻り値をa
に加算a
とELFRESERVEを直接比較- エラーメッセージで表示される
a
の値が正確なサイズを反映
これにより、エラーメッセージが正確になり、デバッグが容易になります。
追加の最適化
また、このコミットではcflush()
の冗長な呼び出しも削除されています。cflush()
は出力バッファをフラッシュする関数ですが、elfwriteinterp()
を呼び出す前に実行する必要がなくなったため削除されました。
コアとなるコードの変更箇所
src/cmd/5l/asm.c:632-642
// 修正前
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
cflush();
if(a+elfwriteinterp() > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
// 修正後
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
a += elfwriteinterp();
if(a > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
src/cmd/6l/asm.c:1095-1105
// 修正前
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
cflush();
if(a+elfwriteinterp() > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
// 修正後
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
a += elfwriteinterp();
if(a > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
src/cmd/8l/asm.c:1160-1170
// 修正前
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
cflush();
if(a+elfwriteinterp() > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
// 修正後
a += elfwritehdr();
a += elfwritephdrs();
a += elfwriteshdrs();
a += elfwriteinterp();
if(a > ELFRESERVE)
diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE);
コアとなるコードの解説
asmb()関数の役割
asmb()
関数は、アセンブリ段階でELFバイナリの構造を組み立てる重要な関数です。この関数では、ELFファイルの各セクションを順序立てて書き込みます:
- elfwritehdr(): ELFヘッダーを書き込み、書き込んだバイト数を返す
- elfwritephdrs(): プログラムヘッダーテーブルを書き込み、書き込んだバイト数を返す
- elfwriteshdrs(): セクションヘッダーテーブルを書き込み、書き込んだバイト数を返す
- elfwriteinterp(): インタープリター文字列を書き込み、書き込んだバイト数を返す
サイズ追跡の仕組み
変数a
は、ELFファイルの予約領域に書き込まれたデータの累積サイズを追跡します。各書き込み関数は、書き込んだバイト数を返すため、a += 関数名()
という形式で累積サイズを更新していきます。
エラー診断の重要性
ELFRESERVE制限を超えた場合のエラーメッセージは、以下の情報を提供します:
- 実際のサイズ: 予約領域に書き込まれたデータの総サイズ
- 制限値: ELFRESERVE定数の値(3072バイト)
- 超過量: 実際のサイズが制限値をどれだけ超えているか
この情報により、開発者は以下を判断できます:
- ELFRESERVEの値を増やす必要があるか
- ELFファイルの構造を最適化する必要があるか
- 追加されたセクションヘッダーの数
動的リンクとインタープリター文字列
ELFインタープリター文字列は、動的リンクの仕組みにおいて重要な役割を果たします:
動的リンクの流れ:
- カーネルがELFバイナリを実行
- PT_INTERPプログラムヘッダーからインタープリターパスを取得
- 動的リンカー(例:ld-linux-x86-64.so.2)を起動
- 動的リンカーが必要な共有ライブラリを読み込み
- シンボルの解決とアドレスの再配置を実行
- メインプログラムに制御を移す
インタープリター文字列の例:
- Linux x86-64:
/lib64/ld-linux-x86-64.so.2
- Linux x86-32:
/lib/ld-linux.so.2
- Linux ARM:
/lib/ld-linux-armhf.so.3
これらの文字列の長さは、動的リンカーのパスによって異なり、特に長いパスを使用する場合にELFRESERVE制限に影響を与える可能性があります。
関連リンク
- Goプログラミング言語 - Wikipedia
- Ken Thompson - Wikipedia
- ELF loading and dynamic linking
- Dynamic Linking in ELF
- Go 1.3 Linker Overhaul