[インデックス 13658] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) におけるバグ修正に関するものです。特に、Linux/386アーキテクチャでのビルド時に発生する R_*_NONE
型のリロケーション(再配置)の処理に関する問題に対処しています。以前の修正がC言語の演算子優先順位の誤解と、NONE
リロケーションを適切にスキップできなかったために、ビルドが再び失敗していた問題を解決します。問題のリロケーションは、.eh_frame
セクション内の絶対リロケーションである R_386_NONE
でした。
コミット
commit b1532344ef58fe644c85bbc6e268102528d39c61
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Aug 21 00:34:06 2012 +0800
cmd/ld: skip R_*_NONE relocations, fix Linux/386 build again
The last fix was wrong w.r.t C's operator precedence,
and it also failed to really skip the NONE relocation.
The offending R_386_NONE relocation is a absolute
relocation in section .eh_frame.
TBR=golang-dev
CC=golang-dev
https://golang.org/cl/6463058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b1532344ef58fe644c85bbc6e268102528d39c61
元コミット内容
このコミットは、Go言語のリンカ (cmd/ld
) が R_*_NONE
型のリロケーションを適切に処理せず、特にLinux/386環境でのビルドに失敗するという問題を修正します。以前の修正がC言語の演算子優先順位の誤解により不正確であり、NONE
リロケーションを実際にスキップできていなかったことが原因でした。このコミットは、問題のある R_386_NONE
リロケーションが .eh_frame
セクション内の絶対リロケーションであることを特定し、そのスキップ処理を修正することで、ビルドの問題を再修正します。
変更の背景
Go言語のリンカは、コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成する重要なツールです。このプロセスにおいて、リロケーション(再配置)は、コード内のアドレス参照を最終的な実行可能ファイル内の正しいアドレスに調整するために不可欠です。
このコミットの背景には、以下の問題がありました。
R_*_NONE
リロケーションの不適切な処理: ELF(Executable and Linkable Format)バイナリでは、R_*_NONE
というリロケーションタイプが存在します。これは、実際には何も再配置を行わないことを示す特殊なタイプです。リンカは通常、このようなリロケーションを無視またはスキップする必要があります。しかし、Goのリンカがこれを適切に処理できていませんでした。- Linux/386ビルドの失敗: 特にLinux/386アーキテクチャにおいて、この不適切なリロケーション処理が原因でビルドが失敗していました。これは、特定のコンパイラやツールチェーンが生成するオブジェクトファイルに
R_386_NONE
リロケーションが含まれており、リンカがこれを誤って解釈していたためと考えられます。 - C言語の演算子優先順位の誤解: 以前の修正試行では、C言語のビット演算子 (
&
) と比較演算子 (==
) の優先順位に関する誤解がありました。これにより、リロケーション情報をチェックする条件式が意図した通りに機能せず、NONE
リロケーションがスキップされずに処理されてしまい、問題が再発していました。 .eh_frame
セクション: 問題のリロケーションが.eh_frame
セクションに存在することが特定されました。このセクションは、例外処理(unwinding)に関する情報を含むため、その内容が正しく処理されないと、プログラムの安定性やデバッグに影響を与える可能性があります。
これらの問題が複合的に作用し、GoのLinux/386ビルドが不安定になっていたため、このコミットで根本的な修正が試みられました。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念を理解しておく必要があります。
-
ELF (Executable and Linkable Format):
- LinuxやUnix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
- ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(
.text
,.data
,.rodata
,.bss
など)で構成されます。 - セクション: コード、データ、デバッグ情報など、プログラムの異なる部分を論理的に分割したものです。
- リロケーション (Relocation): オブジェクトファイルがリンクされる際に、シンボル参照(変数や関数のアドレス)を最終的な実行可能ファイル内の正しいメモリアドレスに調整するプロセスです。リンカは、リロケーションエントリに基づいて、コード内のプレースホルダを実際のメモリアドレスに置き換えます。
-
リロケーションタイプ (Relocation Types):
- ELFでは、様々なリロケーションタイプが定義されており、それぞれが特定のアドレス調整方法を示します。例えば、
R_X86_64_PC32
はPC相対アドレスの32ビット再配置、R_X86_64_GLOB_DAT
はグローバルデータシンボルの再配置などです。 R_*_NONE
: これは特殊なリロケーションタイプで、実際には何も再配置を行わないことを示します。リンカは通常、このタイプのリロケーションを無視する必要があります。これは、アライメントの目的や、将来の拡張のために予約されたエントリとして使用されることがあります。R_386_NONE
: 32ビットx86アーキテクチャ(i386)におけるNONE
タイプのリロケーションです。
- ELFでは、様々なリロケーションタイプが定義されており、それぞれが特定のアドレス調整方法を示します。例えば、
-
.eh_frame
セクション:- ELFバイナリにおける標準的なセクションの一つで、C++の例外処理や、デバッガがスタックトレースを生成するために必要な情報(unwinding information)を格納します。
- 関数呼び出しの際にスタックフレームがどのように構築され、関数から戻る際にどのようにクリーンアップされるかといった情報が含まれます。
- このセクション内のデータは、通常、実行時には直接実行されませんが、デバッグや例外処理のメカニズムによって参照されます。
-
C言語の演算子優先順位:
- C言語では、演算子には明確な優先順位があります。例えば、ビットAND演算子 (
&
) は、比較演算子 (==
) よりも優先順位が低いです。 A & B == C
という式は、A & (B == C)
と解釈されます。これは、B == C
が先に評価され、その結果(真偽値、通常は0または1)とA
のビットANDが取られることを意味します。- 意図した動作が
(A & B) == C
である場合、明示的に括弧を使用する必要があります。
- C言語では、演算子には明確な優先順位があります。例えば、ビットAND演算子 (
-
Go言語のリンカ (
cmd/ld
):- Go言語のツールチェーンの一部であり、Goプログラムのコンパイル済みオブジェクトファイルをリンクして実行可能ファイルを生成する役割を担います。
- クロスコンパイルをサポートしており、異なるアーキテクチャやOS向けのバイナリを生成できます。
- ELFファイルの構造を理解し、リロケーションを解決するロジックを含んでいます。
技術的詳細
このコミットの技術的な核心は、src/cmd/ld/ldelf.c
ファイル内のリロケーション処理ロジックの修正にあります。
問題のコードは、リロケーションエントリの info
フィールドをチェックして、それが R_*_NONE
タイプであるかどうかを判断していました。info
フィールドは、リロケーションタイプとシンボルインデックスをパックしたものです。リロケーションタイプは通常、info
の下位ビットに格納されます。
以前の誤ったコードは以下のようでした。
if(info & 0xffffffff == 0) // R_*_NONE relocation
ここで、C言語の演算子優先順位が問題を引き起こしていました。==
演算子は &
演算子よりも優先順位が高いため、この式は info & (0xffffffff == 0)
と解釈されます。
0xffffffff == 0
は偽(false)であり、C言語では 0
に評価されます。したがって、式全体は info & 0
となり、常に 0
に評価されます。
これにより、if
文の条件は常に真となり、実際には R_*_NONE
リロケーションではない場合でも、そのブロック内のコードが実行されてしまっていました。結果として、NONE
リロケーションが適切にスキップされず、リンカが不正な処理を行っていたと考えられます。
新しい修正では、この優先順位の問題を括弧を使って明示的に解決しています。
if((info & 0xffffffff) == 0) { // skip R_*_NONE relocation
この修正により、info
の下位32ビット(info & 0xffffffff
)がまず抽出され、その結果が 0
と比較されます。R_*_NONE
リロケーションの場合、リロケーションタイプが 0
であるため、この条件が正しく真となり、リロケーションがスキップされるようになります。
さらに、R_*_NONE
リロケーションをスキップする際に、ループカウンタ j
と n
をデクリメントする処理が追加されています。これは、リロケーションエントリの配列を処理するループにおいて、スキップされたエントリが後続の処理に影響を与えないように、インデックスを適切に調整するためです。具体的には、j
は現在のリロケーションエントリのインデックス、n
は残りのリロケーションエントリの数を表していると考えられます。スキップすることで、これらのカウンタを減らし、次の有効なリロケーションエントリに進むようにします。
問題のリロケーションが .eh_frame
セクションに存在するという言及は、このセクションが通常、実行コードではなくデバッグや例外処理のメタデータを含むため、その中のリロケーションが特に注意深く扱われる必要があることを示唆しています。リンカがこのセクション内の NONE
リロケーションを誤って処理すると、バイナリの整合性が損なわれたり、デバッグ情報が破損したりする可能性がありました。
コアとなるコードの変更箇所
変更は src/cmd/ld/ldelf.c
ファイルの ldelf
関数内にあります。
--- a/src/cmd/ld/ldelf.c
+++ b/src/cmd/ld/ldelf.c
@@ -658,8 +658,11 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn)\n tp += 4;\n }\n }\n- if(info & 0xffffffff == 0) // R_*_NONE relocation
+ if((info & 0xffffffff) == 0) { // skip R_*_NONE relocation
+ j--;
+ n--;
continue;
+ }
if((info >> 32) == 0) { // absolute relocation, don't bother reading the null symbol
rp->sym = S;\n } else {\n```
## コアとなるコードの解説
変更されたコードブロックは、ELFファイルからリロケーションエントリを読み込み、処理するループの一部です。
元のコード:
```c
if(info & 0xffffffff == 0) // R_*_NONE relocation
continue;
この行は、info
変数(リロケーション情報を含む)の下位32ビットが 0
であるかどうかをチェックし、もしそうであれば、そのリロケーションをスキップして次のリロケーションエントリに進むことを意図していました。しかし、前述の通り、C言語の演算子優先順位により、0xffffffff == 0
が先に評価され、結果として info & 0
となり、常に 0
に評価されていました。これにより、if
文の条件は常に真となり、意図しないリロケーションがスキップされたり、あるいは NONE
リロケーションがスキップされなかったりする問題が発生していました。
修正後のコード:
if((info & 0xffffffff) == 0) { // skip R_*_NONE relocation
j--;
n--;
continue;
}
-
if((info & 0xffffffff) == 0)
:info & 0xffffffff
:info
変数の下位32ビットを抽出します。ELFリロケーションエントリのr_info
フィールドは、通常、下位ビットにリロケーションタイプ、上位ビットにシンボルインデックスを格納します。0xffffffff
とのビットAND演算により、リロケーションタイプのみを分離します。== 0
: 抽出されたリロケーションタイプが0
であるかどうかをチェックします。リロケーションタイプ0
は、R_*_NONE
を意味します。- この修正により、条件式が正しく評価され、真に
R_*_NONE
リロケーションである場合にのみ、そのブロック内のコードが実行されるようになります。
-
j--;
とn--;
:- これらの行は、
R_*_NONE
リロケーションがスキップされる際に、リロケーションエントリを追跡するためのカウンタをデクリメントします。 j
はおそらく、現在のリロケーションエントリのインデックスまたは処理済みのエントリ数を表す変数です。n
はおそらく、残りのリロケーションエントリの総数を表す変数です。- これらのカウンタを減らすことで、リンカが次の有効なリロケーションエントリに正しく進み、スキップされたエントリが後続の処理に誤って含まれないようにします。
- これらの行は、
-
continue;
:- 現在のリロケーションエントリの処理を終了し、ループの次のイテレーションに進みます。これにより、
R_*_NONE
リロケーションに対するさらなる処理が回避されます。
- 現在のリロケーションエントリの処理を終了し、ループの次のイテレーションに進みます。これにより、
この修正により、Goリンカは R_*_NONE
リロケーションを正しく識別し、適切にスキップできるようになり、Linux/386環境でのビルドの安定性が向上しました。
関連リンク
- Go言語のリンカ (
cmd/ld
) のソースコード: https://github.com/golang/go/tree/master/src/cmd/ld - ELFフォーマットの仕様 (System V Application Binary Interface): https://refspecs.linuxfoundation.org/elf/elf.pdf
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/6463058 (コミットメッセージに記載されているリンク)
参考にした情報源リンク
- ELF Relocation Types (Wikipedia): https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Relocation_types
- C Operator Precedence (cppreference.com): https://en.cppreference.com/w/c/language/operator_precedence
.eh_frame
section (GCC documentation, related to DWARF): https://gcc.gnu.org/onlinedocs/gccint/DWARF-Frame-Description-Entry.html- Go issue tracker (relevant issues might be linked from the CL): https://github.com/golang/go/issues
- Stack Overflow and other technical forums for
R_386_NONE
and.eh_frame
context. - Go言語の公式ドキュメントやブログ記事 (リンカの内部動作に関するものがあれば)。
- ELFバイナリ解析に関する書籍やオンラインリソース。
- Go言語のリンカのソースコード自体。
- Go言語のメーリングリストやコミュニティの議論 (特に
golang-dev
メーリングリスト)。