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

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

このコミットは、Go言語のリンカ (cmd/5l および cmd/ld) に、ARMアーキテクチャにおける特定のELFリロケーションタイプである R_ARM_GOT_PREL のサポートを追加するものです。これは、Android NDKのGCC 4.6が runtime/cgo のためにこのリロケーションを生成するようになったことに対応するための変更です。

コミット

commit 9de61e7c8c779dafccbcd0242e06f92eb6f0e1ee
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Mon Jul 30 18:48:00 2012 -0400

    cmd/5l, cmd/ld: add support for R_ARM_GOT_PREL
    Android NDK's gcc 4.6 generates this relocation for runtime/cgo.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6450056

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

https://github.com/golang/go/commit/9de61e7c8c779dafccbcd0242e06f92eb6f0e1ee

元コミット内容

cmd/5l, cmd/ld: add support for R_ARM_GOT_PREL
Android NDK's gcc 4.6 generates this relocation for runtime/cgo.

R=rsc
CC=golang-dev
https://golang.org/cl/6450056

変更の背景

この変更の背景には、Go言語がAndroidプラットフォームをサポートする上で直面した互換性の問題があります。具体的には、Android NDK (Native Development Kit) に含まれるGCC (GNU Compiler Collection) のバージョン4.6が、Goの runtime/cgo (C言語との相互運用を可能にするGoの機能) をコンパイルする際に、新しいリロケーションタイプ R_ARM_GOT_PREL を生成するようになったことが挙げられます。

Goのツールチェイン、特にリンカ (cmd/5l はARMアーキテクチャ向けのリンカ、cmd/ld は汎用リンカ) は、生成されたオブジェクトファイル内のリロケーションエントリを正しく解釈し、実行可能ファイルを生成する必要があります。しかし、当時のGoリンカはこの R_ARM_GOT_PREL という新しいリロケーションタイプを認識していませんでした。

この認識不足は、Android NDKのGCC 4.6でコンパイルされた runtime/cgo を含むGoプログラムをリンクする際に問題を引き起こし、最終的なバイナリが正しく生成されない、あるいは実行時にクラッシュする原因となっていました。このコミットは、この互換性の問題を解決し、GoがAndroidプラットフォーム上で cgo を利用するプログラムを適切にビルドできるようにするために導入されました。

前提知識の解説

1. リロケーション (Relocation)

リロケーションとは、コンパイル時にアドレスが確定できないシンボル(変数や関数など)の参照を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。共有ライブラリや位置独立コード (PIC: Position-Independent Code) を扱う際に特に重要になります。オブジェクトファイルには、リンカが後で修正する必要がある場所を示す「リロケーションエントリ」が含まれています。

2. ELF (Executable and Linkable Format)

ELFは、Unix系システム(Linux、Androidなど)で広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルには、コード、データ、シンボルテーブル、リロケーションエントリなど、プログラムの実行に必要な情報が構造化されて格納されています。

3. GOT (Global Offset Table) と PLT (Procedure Linkage Table)

共有ライブラリを使用するプログラムでは、実行時にライブラリ内の関数やデータのアドレスが確定します。この動的なアドレス解決を効率的に行うために、GOTとPLTが利用されます。

  • GOT (Global Offset Table): グローバル変数や共有ライブラリ内の関数のアドレスを格納するテーブルです。位置独立コードでは、直接絶対アドレスを参照する代わりに、GOTを介して間接的に参照します。これにより、ライブラリがメモリ上のどこにロードされても、コードを変更することなく動作できます。
  • PLT (Procedure Linkage Table): 共有ライブラリ内の関数を呼び出す際に使用されるテーブルです。PLTエントリは、GOTエントリと連携して、関数の初回呼び出し時にそのアドレスを解決し、以降の呼び出しではGOTにキャッシュされたアドレスを直接使用するようにします。

4. ARMアーキテクチャにおけるリロケーションの種類

ARMアーキテクチャは、RISC (Reduced Instruction Set Computer) ベースのプロセッサであり、その命令セットやアドレッシングモードの特性に合わせて、様々なリロケーションタイプが定義されています。これらは、コードの効率性や位置独立性の実現に不可欠です。

  • PC相対アドレッシング: プログラムカウンタ (PC) の現在値からのオフセットでアドレスを指定する方法です。位置独立コードでよく使われます。
  • R_ARM_GOT_PREL: このコミットの主題となるリロケーションタイプです。これは、GOT内のエントリへのPC相対オフセットを計算するために使用されます。具体的には、GOT(S) + A - P という形式で計算されます。
    • S: 参照されるシンボル
    • A: リロケーションが適用される場所の加算値 (addend)
    • P: リロケーションが適用される場所のPC値 (プログラムカウンタ) このリロケーションは、特にARMのThumbモードや、GOTへのアクセスを最適化する際に利用されます。

5. Go言語のリンカ (cmd/5l, cmd/ld)

Go言語は独自のツールチェインを持っており、コンパイラ、アセンブラ、リンカなどが含まれます。

  • cmd/5l: ARMアーキテクチャ向けのGoリンカです。GoのソースコードをARMバイナリにリンクする役割を担います。
  • cmd/ld: Goの汎用リンカです。アーキテクチャ固有のリンカ(例: 5l6l8l)によって生成された中間ファイルを最終的な実行可能ファイルにリンクします。

6. Android NDK, GCC, runtime/cgo

  • Android NDK (Native Development Kit): AndroidアプリでC/C++コードを使用するためのツールセットです。これには、クロスコンパイラ(GCCやClangなど)、ライブラリ、ヘッダファイルなどが含まれます。
  • GCC (GNU Compiler Collection): 広く使われているコンパイラスイートで、C、C++、Goなど様々な言語をサポートします。Android NDKの一部として、ARMアーキテクチャ向けのクロスコンパイラが提供されます。
  • runtime/cgo: Go言語の標準ライブラリの一部で、GoプログラムからC言語の関数を呼び出したり、C言語からGoの関数を呼び出したりするためのメカニズムを提供します。cgo を使用するプログラムは、Goコンパイラだけでなく、Cコンパイラ(この場合はAndroid NDKのGCC)によっても処理される部分があります。

技術的詳細

このコミットの核心は、Goリンカが R_ARM_GOT_PREL リロケーションを正しく処理できるようにすることです。

R_ARM_GOT_PREL は、ARMアーキテクチャにおいて、GOT (Global Offset Table) 内の特定のエントリへのPC相対オフセットを計算するために使用されるリロケーションタイプです。これは、特に位置独立コード (PIC) を生成する際に重要となります。PICは、メモリ上の任意のアドレスにロードされても正しく実行できるコードであり、共有ライブラリで不可欠です。

R_ARM_GOT_PREL の計算式は GOT(S) + A - P です。

  • GOT(S): シンボル S のGOTエントリのアドレス。
  • A: リロケーションが適用される場所の加算値 (addend)。通常は0ですが、命令によってはオフセットが含まれる場合があります。
  • P: リロケーションが適用される場所のプログラムカウンタ (PC) の値。ARMでは、PCは通常、現在実行中の命令のアドレスから8バイト進んだ位置を指します。

このリロケーションは、コード内でGOT内のデータや関数への参照を、PCからの相対的なオフセットとして表現するために使用されます。これにより、実行時にGOTのアドレスがどこにあっても、コード自体を変更することなく正しいエントリにアクセスできます。

Goリンカは、オブジェクトファイルからリロケーションエントリを読み込み、それらをGo独自の内部リロケーションタイプに変換して処理します。このコミットでは、R_ARM_GOT_PREL をGoリンカが認識し、適切な内部リロケーションタイプ (D_PCREL) にマッピングし、GOTへの参照を正しく解決するためのロジックが追加されました。

具体的には、adddynrel 関数内で R_ARM_GOT_PREL が検出された場合、リンカは以下の処理を行います。

  1. 参照されるシンボルが動的にインポートされるものか、エクスポートされるものかに応じて、GOTにシンボルエントリを追加します (addgotsyminternal または addgotsym)。
  2. リロケーションタイプをGoの内部PC相対リロケーションタイプ D_PCREL に設定します。
  3. リロケーションのシンボルを .got セクションに設定します。
  4. リロケーションの加算値 (r->add) を、参照されるシンボルのGOTエントリのアドレスと、PC相対計算に必要なオフセット (+ 4) を考慮して調整します。この + 4 は、ARMのPC相対アドレッシングにおけるPCのオフセット(通常は命令の開始アドレス + 8バイト)と、GOTエントリのサイズ(4バイト)に関連している可能性があります。

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

このコミットでは、以下の3つのファイルが変更されています。

  1. src/cmd/5l/asm.c: ARMアーキテクチャ向けリンカの主要なリロケーション処理ロジックが含まれるファイルです。
    • adddynrel 関数に R_ARM_GOT_PREL のケースが追加されました。
  2. src/cmd/ld/elf.h: ELFフォーマットに関連する定数や構造体の定義が含まれるヘッダファイルです。
    • R_ARM_GOT_PREL のELFリロケーションタイプ番号 (96) が定義に追加されました。
  3. src/cmd/ld/ldelf.c: ELFファイルのリロケーションタイプをGoリンカの内部タイプにマッピングするロジックが含まれるファイルです。
    • reltype 関数に R_ARM_GOT_PREL のマッピングが追加されました。

コアとなるコードの解説

src/cmd/5l/asm.c の変更

@@ -158,11 +158,22 @@ adddynrel(Sym *s, Reloc *r)
 		r->sym = S;
 		r->add += targ->got;
 		return;
-	
+
+	case 256 + R_ARM_GOT_PREL: // GOT(S) + A - P
+		if(targ->dynimpname == nil || targ->dynexport) {
+			addgotsyminternal(targ);
+		} else {
+			addgotsym(targ);
+		}
+		r->type = D_PCREL;
+		r->sym = lookup(".got", 0);
+		r->add += targ->got + 4;
+		return;
+
 	case 256 + R_ARM_GOTOFF: // R_ARM_GOTOFF32
 		r->type = D_GOTOFF;
 		return;
-	
+
 	case 256 + R_ARM_GOTPC: // R_ARM_BASE_PREL
 		r->type = D_PCREL;
 		r->sym = lookup(".got", 0);

adddynrel 関数は、動的リロケーションエントリを処理するGoリンカの重要な部分です。

  • case 256 + R_ARM_GOT_PREL:: ここで R_ARM_GOT_PREL リロケーションが検出された場合の処理が定義されています。256 は、GoリンカがELFリロケーションタイプを内部的に扱う際のオフセットです。
  • if(targ->dynimpname == nil || targ->dynexport) { addgotsyminternal(targ); } else { addgotsym(targ); }:
    • targ はリロケーションのターゲットとなるシンボルを表します。
    • dynimpname == nil は、シンボルが動的にインポートされるものではないことを意味します(つまり、同じ共有オブジェクト内で定義されているか、静的にリンクされる)。
    • dynexport は、シンボルが動的にエクスポートされるものであることを意味します。
    • これらの条件に基づいて、addgotsyminternal または addgotsym が呼び出されます。これらの関数は、シンボルに対応するエントリをGOTに(必要であれば)追加する役割を担います。addgotsyminternal は内部的なGOTエントリを、addgotsym は外部シンボル向けのGOTエントリを処理します。
  • r->type = D_PCREL;: Goリンカの内部リロケーションタイプを D_PCREL (PC相対リロケーション) に設定します。これは R_ARM_GOT_PREL がPC相対オフセットを扱うためです。
  • r->sym = lookup(".got", 0);: リロケーションの対象シンボルを .got セクションに設定します。これは、リロケーションがGOT内のエントリを指すことを示します。
  • r->add += targ->got + 4;: リロケーションの加算値 (r->add) を調整します。
    • targ->got: ターゲットシンボルのGOTエントリのアドレス(またはGOT内でのオフセット)。
    • + 4: これはARMのPC相対アドレッシングの特性に関連するオフセットです。ARM命令では、PC相対オフセットの計算時にPCの値が現在の命令アドレスから8バイト進んだ位置を指すことが一般的です。また、GOTエントリが4バイト幅であることも考慮されている可能性があります。この +4 は、GOT(S) + A - PA の部分に相当し、リンカが最終的なアドレスを計算する際に必要な調整です。

src/cmd/ld/elf.h の変更

@@ -564,6 +564,7 @@ typedef struct {
 #define	R_ARM_PLT32		27	/* Add PC-relative PLT offset. */
 #define	R_ARM_CALL		28
 #define	R_ARM_V4BX		40
+#define	R_ARM_GOT_PREL		96
 #define	R_ARM_GNU_VTENTRY	100
 #define	R_ARM_GNU_VTINHERIT	101
 #define	R_ARM_RSBREL32		250

この変更は、ELFヘッダファイルに R_ARM_GOT_PREL の定義を追加するものです。#define R_ARM_GOT_PREL 96 は、このリロケーションタイプがELF標準で番号 96 として定義されていることをGoリンカに認識させます。これにより、リンカはオブジェクトファイルから読み込んだリロケーションエントリのタイプ番号を正しく解釈できるようになります。

src/cmd/ld/ldelf.c の変更

@@ -847,6 +847,7 @@ reltype(char *pn, int elftype, uchar *siz)
 	case R('5', R_ARM_REL32):
 	case R('5', R_ARM_CALL):
 	case R('5', R_ARM_V4BX):
+	case R('5', R_ARM_GOT_PREL):
 	case R('6', R_X86_64_PC32):
 	case R('6', R_X86_64_PLT32):
 	case R('6', R_X86_64_GOTPCREL):

reltype 関数は、ELFリロケーションタイプをGoリンカの内部リロケーションタイプにマッピングする役割を担います。

  • case R('5', R_ARM_GOT_PREL):: ここに R_ARM_GOT_PREL が追加されたことで、GoリンカはELFオブジェクトファイル内でこのタイプのリロケーションを見つけた際に、それを正しく認識し、後続の処理(adddynrel 関数など)に渡すことができるようになります。R('5', ...) は、GoリンカがARMアーキテクチャ(Goの内部では 5 で表現されることが多い)のリロケーションタイプを処理するためのマクロまたはパターンです。

これらの変更により、GoリンカはAndroid NDKのGCC 4.6が生成する R_ARM_GOT_PREL リロケーションを含むオブジェクトファイルを正しく処理し、GoプログラムをAndroidプラットフォーム向けにビルドする際の互換性が確保されました。

関連リンク

参考にした情報源リンク