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

[インデックス 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言語のリンカは、コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成する重要なツールです。このプロセスにおいて、リロケーション(再配置)は、コード内のアドレス参照を最終的な実行可能ファイル内の正しいアドレスに調整するために不可欠です。

このコミットの背景には、以下の問題がありました。

  1. R_*_NONE リロケーションの不適切な処理: ELF(Executable and Linkable Format)バイナリでは、R_*_NONE というリロケーションタイプが存在します。これは、実際には何も再配置を行わないことを示す特殊なタイプです。リンカは通常、このようなリロケーションを無視またはスキップする必要があります。しかし、Goのリンカがこれを適切に処理できていませんでした。
  2. Linux/386ビルドの失敗: 特にLinux/386アーキテクチャにおいて、この不適切なリロケーション処理が原因でビルドが失敗していました。これは、特定のコンパイラやツールチェーンが生成するオブジェクトファイルに R_386_NONE リロケーションが含まれており、リンカがこれを誤って解釈していたためと考えられます。
  3. C言語の演算子優先順位の誤解: 以前の修正試行では、C言語のビット演算子 (&) と比較演算子 (==) の優先順位に関する誤解がありました。これにより、リロケーション情報をチェックする条件式が意図した通りに機能せず、NONE リロケーションがスキップされずに処理されてしまい、問題が再発していました。
  4. .eh_frame セクション: 問題のリロケーションが .eh_frame セクションに存在することが特定されました。このセクションは、例外処理(unwinding)に関する情報を含むため、その内容が正しく処理されないと、プログラムの安定性やデバッグに影響を与える可能性があります。

これらの問題が複合的に作用し、GoのLinux/386ビルドが不安定になっていたため、このコミットで根本的な修正が試みられました。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を理解しておく必要があります。

  1. ELF (Executable and Linkable Format):

    • LinuxやUnix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
    • ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(.text, .data, .rodata, .bss など)で構成されます。
    • セクション: コード、データ、デバッグ情報など、プログラムの異なる部分を論理的に分割したものです。
    • リロケーション (Relocation): オブジェクトファイルがリンクされる際に、シンボル参照(変数や関数のアドレス)を最終的な実行可能ファイル内の正しいメモリアドレスに調整するプロセスです。リンカは、リロケーションエントリに基づいて、コード内のプレースホルダを実際のメモリアドレスに置き換えます。
  2. リロケーションタイプ (Relocation Types):

    • ELFでは、様々なリロケーションタイプが定義されており、それぞれが特定のアドレス調整方法を示します。例えば、R_X86_64_PC32 はPC相対アドレスの32ビット再配置、R_X86_64_GLOB_DAT はグローバルデータシンボルの再配置などです。
    • R_*_NONE: これは特殊なリロケーションタイプで、実際には何も再配置を行わないことを示します。リンカは通常、このタイプのリロケーションを無視する必要があります。これは、アライメントの目的や、将来の拡張のために予約されたエントリとして使用されることがあります。
    • R_386_NONE: 32ビットx86アーキテクチャ(i386)における NONE タイプのリロケーションです。
  3. .eh_frame セクション:

    • ELFバイナリにおける標準的なセクションの一つで、C++の例外処理や、デバッガがスタックトレースを生成するために必要な情報(unwinding information)を格納します。
    • 関数呼び出しの際にスタックフレームがどのように構築され、関数から戻る際にどのようにクリーンアップされるかといった情報が含まれます。
    • このセクション内のデータは、通常、実行時には直接実行されませんが、デバッグや例外処理のメカニズムによって参照されます。
  4. C言語の演算子優先順位:

    • C言語では、演算子には明確な優先順位があります。例えば、ビットAND演算子 (&) は、比較演算子 (==) よりも優先順位が低いです。
    • A & B == C という式は、A & (B == C) と解釈されます。これは、B == C が先に評価され、その結果(真偽値、通常は0または1)と A のビットANDが取られることを意味します。
    • 意図した動作が (A & B) == C である場合、明示的に括弧を使用する必要があります。
  5. 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 リロケーションをスキップする際に、ループカウンタ jn をデクリメントする処理が追加されています。これは、リロケーションエントリの配列を処理するループにおいて、スキップされたエントリが後続の処理に影響を与えないように、インデックスを適切に調整するためです。具体的には、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;
}
  1. if((info & 0xffffffff) == 0):

    • info & 0xffffffff: info 変数の下位32ビットを抽出します。ELFリロケーションエントリの r_info フィールドは、通常、下位ビットにリロケーションタイプ、上位ビットにシンボルインデックスを格納します。0xffffffff とのビットAND演算により、リロケーションタイプのみを分離します。
    • == 0: 抽出されたリロケーションタイプが 0 であるかどうかをチェックします。リロケーションタイプ 0 は、R_*_NONE を意味します。
    • この修正により、条件式が正しく評価され、真に R_*_NONE リロケーションである場合にのみ、そのブロック内のコードが実行されるようになります。
  2. j--;n--;:

    • これらの行は、R_*_NONE リロケーションがスキップされる際に、リロケーションエントリを追跡するためのカウンタをデクリメントします。
    • j はおそらく、現在のリロケーションエントリのインデックスまたは処理済みのエントリ数を表す変数です。
    • n はおそらく、残りのリロケーションエントリの総数を表す変数です。
    • これらのカウンタを減らすことで、リンカが次の有効なリロケーションエントリに正しく進み、スキップされたエントリが後続の処理に誤って含まれないようにします。
  3. continue;:

    • 現在のリロケーションエントリの処理を終了し、ループの次のイテレーションに進みます。これにより、R_*_NONE リロケーションに対するさらなる処理が回避されます。

この修正により、Goリンカは R_*_NONE リロケーションを正しく識別し、適切にスキップできるようになり、Linux/386環境でのビルドの安定性が向上しました。

関連リンク

参考にした情報源リンク