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

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

このコミットは、Go言語のリンカであるcmd/ldが、Darwin/386(macOS上の32ビットIntelアーキテクチャ)環境における特定の「scattered relocation 2/1」という特殊なケースを正しく処理できるようにするための修正です。具体的には、misc/cgo/test/issue1635.goという新しいテストファイルが追加され、src/cmd/ld/ldmacho.c内のMach-Oリンカの処理ロジックが変更されています。

変更されたファイル:

  • misc/cgo/test/cgo_test.go: 新しいテストケースTest1635が追加されました。
  • misc/cgo/test/issue1635.go: scatter関数とhola変数、testHola関数を含むCgoテストファイルが新規追加されました。これは、Darwin/386上のGCCが生成するscattered relocation 2/1の問題を再現するためのものです。
  • src/cmd/ld/ldmacho.c: Mach-O形式のオブジェクトファイルを処理するリンカのコードが修正され、scattered relocation 2/1の特殊なケースを適切に処理できるようになりました。

コミット

commit e607380ff69de8dd6235e662895644b56b4114ff
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Aug 29 23:42:05 2012 +0800

    cmd/ld: handle a special case of scattered relocation 2/1 on Darwin/386
            Fixes #1635.
    
    R=golang-dev, dave, r
    CC=golang-dev
    https://golang.org/cl/6496043

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

https://github.com/golang/go/commit/e607380ff69de8dd6235e662895644b56b4114ff

元コミット内容

cmd/ld: handle a special case of scattered relocation 2/1 on Darwin/386
        Fixes #1635.

R=golang-dev, dave, r
CC=golang-dev
https://golang.org/cl/6496043

変更の背景

この変更は、Go言語のリンカ(cmd/ld)が、macOS(Darwin)の32ビットIntelアーキテクチャ(386)上で生成される特定の種類の再配置(relocation)を正しく処理できないという問題(Go Issue 1635)を解決するために行われました。

具体的には、Mac OS XのGCCコンパイラが、特定のCコードに対して「scattered relocation 2/1」という形式の再配置情報を生成することがありました。Goのリンカは、この特殊な形式の再配置を認識せず、結果としてリンクエラーや不正なバイナリを生成する可能性がありました。

この問題は、Cgo(GoとC言語を連携させるための機能)を使用する際に顕在化し、GoプログラムがCライブラリとリンクする際にリンカが正しく動作しない原因となっていました。このコミットは、リンカがこの特殊なケースを適切に処理できるようにすることで、Cgoの互換性と安定性を向上させることを目的としています。

前提知識の解説

Mach-O (Mach Object)

Mach-Oは、macOS、iOS、watchOS、tvOSなどのApple製オペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、ダイナミックロード可能バンドル、およびコアダンプのファイル形式です。WindowsのPE (Portable Executable) やLinuxのELF (Executable and Linkable Format) に相当します。Mach-Oファイルは、ヘッダ、ロードコマンド、セグメント、セクション、シンボルテーブル、再配置情報など、プログラムの実行に必要な様々な情報を構造化して格納します。

再配置 (Relocation)

再配置とは、コンパイル時にアドレスが確定できないシンボル(変数や関数のアドレスなど)について、リンカが最終的な実行可能ファイルを生成する際に、正しいメモリアドレスを埋め込む処理のことです。コンパイラは、未解決の参照に対してプレースホルダを生成し、リンカはそのプレースホルダを実際のメモリアドレスに置き換えます。

Scattered Relocation (散在再配置)

通常の再配置情報が、対象となる命令やデータのアドレスと、そのアドレスに適用すべきオフセットやシンボル情報などを直接的に記述するのに対し、Scattered Relocationは、より複雑な再配置シナリオを扱うために設計されたMach-Oの特殊な再配置メカニズムです。これは、特に32ビットアーキテクチャで、アドレス空間が限られている場合や、特定の最適化を行う場合に用いられることがあります。

「scattered relocation 2/1」という表記は、Mach-Oの再配置エントリのタイプと、それに続く追加の情報の組み合わせを示唆しています。

  • rel->type: 再配置のタイプを示します。このコミットでは24が言及されています。
  • (rel+1)->type: 続く再配置エントリのタイプを示します。このコミットでは1が言及されています。
  • scattered: その再配置エントリが散在再配置であることを示すフラグです。

これらの組み合わせは、リンカが特定のメモリアドレスをどのように解決すべきかを示す複雑な指示を構成します。

Darwin/386

DarwinはmacOSの基盤となるオープンソースのUNIX系オペレーティングシステムです。386はIntel 80386プロセッサアーキテクチャを指し、これは32ビットのIntel互換プロセッサの総称です。したがって、Darwin/386は、macOSが32ビットIntelプロセッサ上で動作する環境を意味します。Go言語は、かつてこのターゲットをサポートしていましたが、macOSが32ビットアプリケーションのサポートを終了したため、Go 1.15以降ではdarwin/386のビルドターゲットは公式にサポートされなくなっています。しかし、このコミットが作成された2012年当時は、まだ現役のターゲットでした。

Goリンカ (cmd/ld)

cmd/ldは、Go言語のツールチェインの一部であるリンカです。Goのコンパイラ(cmd/compileなど)によって生成されたオブジェクトファイルを結合し、実行可能なバイナリやライブラリを生成する役割を担います。Goのリンカは、クロスコンパイルを容易にするために、様々なプラットフォームやアーキテクチャのオブジェクトファイル形式(ELF、PE、Mach-Oなど)を内部で処理する能力を持っています。

技術的詳細

このコミットの核心は、src/cmd/ld/ldmacho.c内のMach-Oリンカが、特定の散在再配置のパターンを認識し、それをGoリンカが理解できる「擬似PC相対参照」に変換するロジックを追加した点にあります。

Mach-Oの散在再配置は、通常のリロケーションエントリとは異なり、複数のエントリが連携して一つの再配置を表現することがあります。この問題のケースでは、「scattered relocation 2/1」というパターンが問題となっていました。これは、rel->type2で、それに続くrel+1のエントリがscatteredフラグを持ち、type1であるという組み合わせを指します。

元のコードでは、386アーキテクチャにおいて「scattered 4/1 relocation」を処理するロジックは存在しましたが、「2/1」のケースは考慮されていませんでした。このため、Mac OS XのGCCが生成する特定のCgoコードが、この「2/1」パターンを生成すると、Goリンカがそれを未サポートとしてエラーを出すか、誤った処理をしていました。

修正の目的は、この「scattered 2/1」のパターンも「擬似PC相対参照」として解釈し、Goリンカの内部表現に変換することです。PC相対参照とは、プログラムカウンタ(PC)からの相対的なオフセットでアドレスを指定する方式で、位置独立コード(PIC)の生成などに利用されます。

リンカは、再配置エントリを読み込む際に、そのタイプとフラグを検査します。もしそれが散在再配置であり、かつthechar(ターゲットアーキテクチャ、ここでは'8'で386を意味)が386である場合、リンカは続く再配置エントリも検査します。

  • rel->type4または2であること。
  • (rel+1)->scatteredが真であること。
  • (rel+1)->type1であること。
  • (rel+1)->valueが現在のセクションのアドレス範囲内にあること。

これらの条件が満たされた場合、リンカはこれらの散在再配置を、Goリンカが処理できる単一のPC相対参照に変換します。これにより、GCCが生成する特定のCgoコードがGoリンカによって正しくリンクされるようになります。

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

変更はsrc/cmd/ld/ldmacho.cファイルのldmacho関数内、特に散在再配置を処理する部分に集中しています。

--- a/src/cmd/ld/ldmacho.c
+++ b/src/cmd/ld/ldmacho.c
@@ -680,19 +680,28 @@ ldmacho(Biobuf *f, char *pkg, int64 len, char *pn)
 			int k;
 			MachoSect *ks;
 
-			if(thechar != '8')
+			if(thechar != '8') {
+				// mach-o only uses scattered relocation on 32-bit platforms
 				diag("unexpected scattered relocation");
+				continue;
+			}
 
-			// on 386, rewrite scattered 4/1 relocation into
-			// the pseudo-pc-relative reference that it is.
+			// on 386, rewrite scattered 4/1 relocation and some
+			// scattered 2/1 relocation into the pseudo-pc-relative
+			// reference that it is.
 			// assume that the second in the pair is in this section
 			// and use that as the pc-relative base.
-			if(thechar != '8' || rel->type != 4 || j+1 >= sect->nreloc ||
-					!(rel+1)->scattered || (rel+1)->type != 1 ||
-					(rel+1)->value < sect->addr || (rel+1)->value >= sect->addr+sect->size) {
+			if(j+1 >= sect->nreloc) {
+				werrstr("unsupported scattered relocation %d", (int)rel->type);
+				goto bad;
+			}
+			if(!(rel+1)->scattered || (rel+1)->type != 1 ||
+			   (rel->type != 4 && rel->type != 2) ||
+			   (rel+1)->value < sect->addr || (rel+1)->value >= sect->addr+sect->size) {
 				werrstr("unsupported scattered relocation %d/%d", (int)rel->type, (int)(rel+1)->type);
 				goto bad;
 			}
+
 			rp->siz = rel->length;
 			rp->off = rel->addr;
 			

コアとなるコードの解説

変更点の詳細な解説は以下の通りです。

  1. アーキテクチャチェックの追加と早期終了:

    -			if(thechar != '8')
    +			if(thechar != '8') {
    +				// mach-o only uses scattered relocation on 32-bit platforms
    				diag("unexpected scattered relocation");
    +				continue;
    +			}
    

    この部分では、thechar'8'(386アーキテクチャ)でない場合に、予期せぬ散在再配置であると診断し、現在の再配置エントリの処理をスキップして次のエントリに進むように変更されました。これは、Mach-Oの散在再配置が主に32ビットプラットフォームで使用されるという前提に基づいています。これにより、不必要な処理を避け、リンカの堅牢性を向上させています。

  2. 散在再配置のタイプチェックの強化:

    -			if(thechar != '8' || rel->type != 4 || j+1 >= sect->nreloc ||
    -					!(rel+1)->scattered || (rel+1)->type != 1 ||
    -					(rel+1)->value < sect->addr || (rel+1)->value >= sect->addr+sect->size) {
    +			if(j+1 >= sect->nreloc) {
    +				werrstr("unsupported scattered relocation %d", (int)rel->type);
    +				goto bad;
    +			}
    +			if(!(rel+1)->scattered || (rel+1)->type != 1 ||
    +			   (rel->type != 4 && rel->type != 2) ||
    +			   (rel+1)->value < sect->addr || (rel+1)->value >= sect->addr+sect->size) {
    				werrstr("unsupported scattered relocation %d/%d", (int)rel->type, (int)(rel+1)->type);
    				goto bad;
    			}
    

    このブロックは、散在再配置のペアが有効であるかどうかのチェックを強化しています。

    • j+1 >= sect->nreloc: まず、現在の再配置エントリの次に、もう一つ再配置エントリが存在するかどうかを確認します。散在再配置は通常ペアで機能するため、これが必須です。存在しない場合はエラーとして処理されます。
    • !(rel+1)->scattered || (rel+1)->type != 1: 続く再配置エントリが散在再配置であり、かつそのタイプが1であることを確認します。これは「scattered relocation X/1」の「/1」の部分に対応します。
    • (rel->type != 4 && rel->type != 2): ここが最も重要な変更点です。 以前はrel->type != 4(つまり、タイプ4の散在再配置のみを期待)でしたが、この条件に&& rel->type != 2が追加されました。これにより、rel->type2である散在再配置も、タイプ4と同様に有効なパターンとして認識されるようになりました。これが、Issue 1635で報告された「scattered relocation 2/1」のケースを処理するための直接的な修正です。
    • (rel+1)->value < sect->addr || (rel+1)->value >= sect->addr+sect->size: 続く再配置エントリの参照値が、現在のセクションのアドレス範囲内にあることを確認します。これは、参照が有効なセクション内を指していることを保証するためです。

これらの変更により、GoリンカはDarwin/386環境でGCCが生成する「scattered relocation 2/1」の特殊なケースを正しく解釈し、Goの内部再配置表現に変換できるようになりました。これにより、Cgoを使用したGoプログラムが、この環境で問題なくリンクされるようになります。

関連リンク

参考にした情報源リンク