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

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

このコミットは、Goランタイムとリンカ(cmd/ld)におけるコードの**位置独立性(Position-Independent Code, PIC)**を向上させるための変更を導入しています。具体的には、スタックアンワインド処理とガベージコレクション(GC)命令の動作を、シンボルの動的な再配置に対応するように修正しています。

コミット

commit 44cf814d5002faf92bce26d21c3bf676d6a2c581
Author: Elias Naur <elias.naur@gmail.com>
Date:   Fri Feb 1 11:24:49 2013 -0800

    runtime, cmd/ld: make code more position-independent
    
    Change the stack unwinding code to compensate for the dynamic
    relocation of symbols.
    Change the gc instruction GC_CALL to use a relative offset instead of
    an absolute address.
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/7248048

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

https://github.com/golang/go/commit/44cf814d5002faf92bce26d21c3bf676d6a2c581

元コミット内容

このコミットの元の内容は以下の通りです。

  • コミットメッセージ: runtime, cmd/ld: make code more position-independent
  • 詳細:
    • スタックアンワインドコードを、シンボルの動的な再配置を補償するように変更。
    • GC命令 GC_CALL を絶対アドレスの代わりに相対オフセットを使用するように変更。

変更の背景

この変更の背景には、Goプログラムがより柔軟なメモリ配置で実行できるようにするという目的があります。特に、共有ライブラリや動的にロードされるモジュールにおいて、コードがロードされるメモリ上のアドレスが実行時まで確定しない場合があります。このような環境で正しく動作するためには、コードが絶対アドレスに依存せず、どこにロードされても機能する位置独立性を持つ必要があります。

Goの初期のバージョンでは、一部の内部処理(特にスタックアンワインドやGC関連のメタデータ)が絶対アドレスに依存している可能性がありました。これにより、特定の環境(例えば、ASLR (Address Space Layout Randomization) が有効なシステムや、動的リンクを使用するケース)での互換性やセキュリティが制限されることがありました。

このコミットは、これらの依存関係を解消し、Goバイナリの移植性と堅牢性を高めることを目指しています。

前提知識の解説

1. 位置独立コード (Position-Independent Code, PIC)

PICは、メモリ上の任意のアドレスにロードされても正しく実行できる機械語コードのことです。これは、コード内のアドレス参照を絶対アドレスではなく、プログラムカウンタ(PC)からの相対オフセットや、グローバルオフセットテーブル(GOT)/プロシージャリンケージテーブル(PLT)のような間接的な参照メカニズムを使用することで実現されます。

  • 絶対アドレス参照: コードが特定の固定メモリ位置を直接参照する場合、そのコードは位置独立ではありません。ロードアドレスが変わると、参照が不正になります。
  • 相対アドレス参照: コードが現在のプログラムカウンタからのオフセットでアドレスを参照する場合、ロードアドレスが変わってもオフセットは変わらないため、コードは位置独立になります。

PICは、共有ライブラリ(DLLや.soファイル)の作成に不可欠です。これにより、複数のプロセスが同じライブラリの単一のコピーをメモリにロードし、異なるアドレス空間にマッピングしても正しく動作できます。

2. 動的な再配置 (Dynamic Relocation)

プログラムが実行される前に、リンカはコード内のシンボル参照(関数や変数のアドレス)を解決します。静的リンクの場合、これらのアドレスはビルド時に固定されます。しかし、動的リンクの場合、共有ライブラリ内のシンボルの実際のアドレスは、プログラムがロードされるまで決定されません。

動的な再配置は、プログラムのロード時に、ローダーが共有ライブラリ内のシンボル参照を、そのライブラリが実際にロードされたメモリ上のアドレスに合わせて修正するプロセスです。これにより、プログラムは実行時に必要な共有ライブラリの関数やデータにアクセスできるようになります。

3. スタックアンワインド (Stack Unwinding)

スタックアンワインドは、プログラムの実行中に、現在の関数呼び出しから呼び出し元の関数へとスタックフレームを遡っていくプロセスです。これは主に以下の目的で使用されます。

  • 例外処理: 例外が発生した際に、適切な例外ハンドラを見つけるためにスタックを遡ります。
  • デバッグ: コールスタック(関数呼び出しの履歴)を表示するために使用されます。
  • プロファイリング: 実行パスを追跡し、パフォーマンスボトルネックを特定するために使用されます。
  • ガベージコレクション: GCがスタック上のポインタをスキャンし、到達可能なオブジェクトを特定するために使用されることがあります。

スタックアンワインドが正しく機能するためには、各スタックフレームのサイズ、リターンアドレス、および保存されたレジスタの値に関する正確な情報が必要です。これらの情報は、コンパイラによって生成されるメタデータ(アンワインド情報)に格納されます。

4. ガベージコレクション (Garbage Collection, GC) と GC_CALL 命令

Goは自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(使用されていない)メモリを自動的に解放するプロセスです。

GoのGCは、オブジェクトグラフを辿ることで到達可能なオブジェクトを特定します。このプロセスには、スタック上のポインタをスキャンすることも含まれます。Goランタイムは、GCがスタックをスキャンする際に使用する特別な命令やメタデータを持っています。

GC_CALL は、GoのGCがスタックをアンワインドする際に遭遇する可能性のある、特定の関数呼び出しパターンを示す内部的なGC命令の一つです。この命令は、GCがスタックフレームを正しく解釈し、ポインタを識別するために必要な情報を提供します。

技術的詳細

このコミットは、主に以下の2つの領域で位置独立性を強化しています。

  1. スタックアンワインドにおけるシンボル参照の修正:

    • 以前のスタックアンワインドコードは、関数エントリポイントなどのシンボルを絶対アドレスで参照していた可能性があります。
    • 動的に再配置されるバイナリでは、これらの絶対アドレスは実行時に変化するため、スタックアンワインドが失敗する原因となります。
    • このコミットでは、src/pkg/runtime/symtab.cdofunc 関数において、関数のエントリポイント (f->entry) を設定する際に、シンボルの値 (sym->value) に加えて、reloffset というグローバルなオフセットを加算するように変更しています。これにより、実行時にバイナリがロードされるベースアドレスの変動を吸収し、f->entry が常に正しい絶対アドレスを指すようにします。
  2. GC_CALL 命令の相対オフセット化:

    • src/cmd/ld/data.c はGoリンカの一部であり、GC関連のメタデータを生成する役割を担っています。
    • 以前は、GC_CALL 命令が参照するターゲット(おそらくGCサブルーチンや型情報)を絶対アドレスでエンコードしていました。
    • このコミットでは、gcaddsym 関数内で GC_CALL 命令に続くアドレスを addaddrplus から addaddrpcrelplus に変更しています。
      • addaddrplus は絶対アドレスを扱う関数です。
      • addaddrpcrelplus は、プログラムカウンタ(PC)からの相対オフセットを計算し、それをエンコードする関数です。
    • これにより、GC_CALL 命令が参照するアドレスが、バイナリのロードアドレスに依存しない相対オフセットとして格納されるようになります。
    • src/pkg/runtime/mgc0.cscanblock 関数では、GC_CALL 命令を処理する際に、以前は pc[2] を直接ターゲットアドレスとして使用していましたが、変更後は (uintptr*)((byte*)pc + (uintptr)pc[2]) とすることで、現在のPCからの相対オフセットとして解釈し、正しいターゲットアドレスを計算するように修正されています。
    • src/pkg/runtime/mgc0.h では、GC_CALL の引数が objgc (絶対ポインタ) から objgcrel (相対オフセット) に変更されたことを示すコメントが追加されています。

これらの変更により、Goバイナリは、動的にロードされる環境やASLRが有効なシステムにおいても、スタックアンワインドやガベージコレクションが正しく機能するようになります。

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

src/cmd/ld/data.c

--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -951,7 +949,7 @@ gcaddsym(Sym *gc, Sym *s, int32 off)
 		//print("gcaddsym:    %s    %d    %s\n", s->name, s->size, gotype->name);
 		adduintxx(gc, GC_CALL, PtrSize);
 		adduintxx(gc, off, PtrSize);
-		addaddrplus(gc, decodetype_gc(gotype), 1*PtrSize);
+		addaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize);
 	} else {
 		//print("gcaddsym:    %s    %d    <unknown type>\n", s->name, s->size);
 		for(a = -off&(PtrSize-1); a+PtrSize<=s->size; a+=PtrSize) {

src/pkg/runtime/mgc0.c

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -663,7 +663,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 			// Stack push.
 			*stack_ptr-- = stack_top;
 			stack_top = (Frame){1, 0, stack_top.b + pc[1], pc+3 /*return address*/};
-			pc = (uintptr*)pc[2];  // target of the CALL instruction
+			pc = (uintptr*)((byte*)pc + (uintptr)pc[2]);  // target of the CALL instruction
 			continue;
 
 		case GC_MAP_PTR:

src/pkg/runtime/mgc0.h

--- a/src/pkg/runtime/mgc0.h
+++ b/src/pkg/runtime/mgc0.h
@@ -12,6 +12,7 @@
 // Meaning of arguments:
 //   off      Offset (in bytes) from the start of the current object
 //   objgc    Pointer to GC info of an object
+//   objgcrel Offset to GC info of an object
 //   len      Length of an array
 //   elemsize Size (in bytes) of an element
 //   size     Size (in bytes)
@@ -21,7 +22,7 @@ enum {
 	GC_APTR,        // Pointer to an arbitrary object. Args: (off)
 	GC_ARRAY_START, // Start an array with a fixed length. Args: (off, len, elemsize)
 	GC_ARRAY_NEXT,  // The next element of an array. Args: none
-\tGC_CALL,        // Call a subroutine. Args: (off, objgc)
+\tGC_CALL,        // Call a subroutine. Args: (off, objgcrel)
 	GC_MAP_PTR,     // Go map. Args: (off, MapType*)
 	GC_STRING,      // Go string. Args: (off)
 	GC_EFACE,       // interface{}. Args: (off)

src/pkg/runtime/symtab.c

--- a/src/pkg/runtime/symtab.c
+++ b/src/pkg/runtime/symtab.c
@@ -93,6 +93,7 @@ walksymtab(void (*fn)(Sym*))
 
 static Func *func;
 static int32 nfunc;
+extern byte reloffset[];
 
 static byte **fname;
 static int32 nfname;
@@ -118,7 +119,7 @@ dofunc(Sym *sym)
 		}
 		f = &func[nfunc++];
 		f->name = runtime·gostringnocopy(sym->name);
-		f->entry = sym->value;
+		f->entry = sym->value + (uint64)reloffset;
 		if(sym->symtype == 'L' || sym->symtype == 'l')
 			f->frame = -sizeof(uintptr);
 		break;

コアとなるコードの解説

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

  • gcaddsym 関数は、GCが使用するシンボル情報を追加する役割を担っています。
  • GC_CALL 命令を生成する際に、以前は addaddrplus(gc, decodetype_gc(gotype), 1*PtrSize) を使用して、gotype のGC情報への絶対アドレスをエンコードしていました。
  • 変更後、addaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize) を使用するようになりました。
    • addaddrpcrelplus は、指定されたシンボル(ここでは decodetype_gc(gotype) が返すGC型情報)への参照を、現在のコード位置からの相対オフセットとしてエンコードします。
    • 4*PtrSize は、おそらく GC_CALL 命令自体のサイズと、それに続くオフセット値のサイズを考慮した、PCからのオフセット計算の基準点を示す値です。これにより、リンカが生成するGCメタデータが位置独立になります。

src/pkg/runtime/mgc0.c の変更

  • scanblock 関数は、GCがメモリブロックをスキャンし、ポインタを識別する主要なロジックを含んでいます。
  • GC_CALL 命令を処理する部分で、以前は pc = (uintptr*)pc[2]; となっていました。これは、pc[2] が直接ターゲットの絶対アドレスを保持していることを前提としていました。
  • 変更後、pc = (uintptr*)((byte*)pc + (uintptr)pc[2]); となりました。
    • これは、pc[2] が現在のプログラムカウンタ pc からの相対オフセットとして解釈されることを意味します。
    • ((byte*)pc + (uintptr)pc[2]) は、現在の命令ポインタ pc に相対オフセット pc[2] を加算することで、GC_CALL がジャンプすべき実際のターゲットアドレスを計算しています。これにより、ランタイムがGC命令を解釈する際も位置独立性が保たれます。

src/pkg/runtime/mgc0.h の変更

  • このヘッダーファイルは、GC命令の定義と引数の意味を記述しています。
  • GC_CALL のコメントが Call a subroutine. Args: (off, objgc) から Call a subroutine. Args: (off, objgcrel) に変更されました。
  • これは、GC_CALL 命令の2番目の引数が、絶対ポインタ (objgc) ではなく、相対オフセット (objgcrel) を表すようになったことを明確に示しています。

src/pkg/runtime/symtab.c の変更

  • dofunc 関数は、シンボルテーブルを走査し、Goの関数に関する情報を構築する際に呼び出されます。
  • 関数のエントリポイント f->entry を設定する際に、以前は f->entry = sym->value; と、シンボルの値(絶対アドレス)を直接使用していました。
  • 変更後、f->entry = sym->value + (uint64)reloffset; となりました。
    • reloffset は、バイナリがロードされたベースアドレスからのオフセットを補償するためのグローバル変数(またはリンカによって設定される値)であると推測されます。
    • この加算により、f->entry は、バイナリがメモリ上のどこにロードされても、その関数の正しい絶対エントリポイントを指すようになります。これは、スタックアンワインドやデバッグツールが関数アドレスを正確に解決するために重要です。

これらの変更は、Goのバイナリがより動的な環境(特に共有ライブラリとして使用される場合やASLRが有効なシステム)で適切に動作するための基盤を強化しています。

関連リンク

参考にした情報源リンク

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

このコミットは、Goランタイムとリンカ(cmd/ld)におけるコードの**位置独立性(Position-Independent Code, PIC)**を向上させるための変更を導入しています。具体的には、スタックアンワインド処理とガベージコレクション(GC)命令の動作を、シンボルの動的な再配置に対応するように修正しています。

コミット

commit 44cf814d5002faf92bce26d21c3bf676d6a2c581
Author: Elias Naur <elias.naur@gmail.com>
Date:   Fri Feb 1 11:24:49 2013 -0800

    runtime, cmd/ld: make code more position-independent
    
    Change the stack unwinding code to compensate for the dynamic
    relocation of symbols.
    Change the gc instruction GC_CALL to use a relative offset instead of
    an absolute address.
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/7248048

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

https://github.com/golang/go/commit/44cf814d5002faf92bce26d21c3bf676d6a2c581

元コミット内容

このコミットの元の内容は以下の通りです。

  • コミットメッセージ: runtime, cmd/ld: make code more position-independent
  • 詳細:
    • スタックアンワインドコードを、シンボルの動的な再配置を補償するように変更。
    • GC命令 GC_CALL を絶対アドレスの代わりに相対オフセットを使用するように変更。

変更の背景

この変更の背景には、Goプログラムがより柔軟なメモリ配置で実行できるようにするという目的があります。特に、共有ライブラリや動的にロードされるモジュールにおいて、コードがロードされるメモリ上のアドレスが実行時まで確定しない場合があります。このような環境で正しく動作するためには、コードが絶対アドレスに依存せず、どこにロードされても機能する位置独立性を持つ必要があります。

Goの初期のバージョンでは、一部の内部処理(特にスタックアンワインドやGC関連のメタデータ)が絶対アドレスに依存している可能性がありました。これにより、特定の環境(例えば、ASLR (Address Space Layout Randomization) が有効なシステムや、動的リンクを使用するケース)での互換性やセキュリティが制限されることがありました。

このコミットは、これらの依存関係を解消し、Goバイナリの移植性と堅牢性を高めることを目指しています。

前提知識の解説

1. 位置独立コード (Position-Independent Code, PIC)

PICは、メモリ上の任意のアドレスにロードされても正しく実行できる機械語コードのことです。これは、コード内のアドレス参照を絶対アドレスではなく、プログラムカウンタ(PC)からの相対オフセットや、グローバルオフセットテーブル(GOT)/プロシージャリンケージテーブル(PLT)のような間接的な参照メカニズムを使用することで実現されます。

  • 絶対アドレス参照: コードが特定の固定メモリ位置を直接参照する場合、そのコードは位置独立ではありません。ロードアドレスが変わると、参照が不正になります。
  • 相対アドレス参照: コードが現在のプログラムカウンタからのオフセットでアドレスを参照する場合、ロードアドレスが変わってもオフセットは変わらないため、コードは位置独立になります。

PICは、共有ライブラリ(DLLや.soファイル)の作成に不可欠です。これにより、複数のプロセスが同じライブラリの単一のコピーをメモリにロードし、異なるアドレス空間にマッピングしても正しく動作できます。

2. 動的な再配置 (Dynamic Relocation)

プログラムが実行される前に、リンカはコード内のシンボル参照(関数や変数のアドレス)を解決します。静的リンクの場合、これらのアドレスはビルド時に固定されます。しかし、動的リンクの場合、共有ライブラリ内のシンボルの実際のアドレスは、プログラムがロードされるまで決定されません。

動的な再配置は、プログラムのロード時に、ローダーが共有ライブラリ内のシンボル参照を、そのライブラリが実際にロードされたメモリ上のアドレスに合わせて修正するプロセスです。これにより、プログラムは実行時に必要な共有ライブラリの関数やデータにアクセスできるようになります。

3. スタックアンワインド (Stack Unwinding)

スタックアンワインドは、プログラムの実行中に、現在の関数呼び出しから呼び出し元の関数へとスタックフレームを遡っていくプロセスです。これは主に以下の目的で使用されます。

  • 例外処理: 例外が発生した際に、適切な例外ハンドラを見つけるためにスタックを遡ります。
  • デバッグ: コールスタック(関数呼び出しの履歴)を表示するために使用されます。
  • プロファイリング: 実行パスを追跡し、パフォーマンスボトルネックを特定するために使用されます。
  • ガベージコレクション: GCがスタック上のポインタをスキャンし、到達可能なオブジェクトを特定するために使用されることがあります。

スタックアンワインドが正しく機能するためには、各スタックフレームのサイズ、リターンアドレス、および保存されたレジスタの値に関する正確な情報が必要です。これらの情報は、コンパイラによって生成されるメタデータ(アンワインド情報)に格納されます。

4. ガベージコレクション (Garbage Collection, GC) と GC_CALL 命令

Goは自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(使用されていない)メモリを自動的に解放するプロセスです。

GoのGCは、オブジェクトグラフを辿ることで到達可能なオブジェクトを特定します。このプロセスには、スタック上のポインタをスキャンすることも含まれます。Goランタイムは、GCがスタックをスキャンする際に使用する特別な命令やメタデータを持っています。

GC_CALL は、GoのGCがスタックをアンワインドする際に遭遇する可能性のある、特定の関数呼び出しパターンを示す内部的なGC命令の一つです。この命令は、GCがスタックフレームを正しく解釈し、ポインタを識別するために必要な情報を提供します。

技術的詳細

このコミットは、主に以下の2つの領域で位置独立性を強化しています。

  1. スタックアンワインドにおけるシンボル参照の修正:

    • 以前のスタックアンワインドコードは、関数エントリポイントなどのシンボルを絶対アドレスで参照していた可能性があります。
    • 動的に再配置されるバイナリでは、これらの絶対アドレスは実行時に変化するため、スタックアンワインドが失敗する原因となります。
    • このコミットでは、src/pkg/runtime/symtab.cdofunc 関数において、関数のエントリポイント (f->entry) を設定する際に、シンボルの値 (sym->value) に加えて、reloffset というグローバルなオフセットを加算するように変更しています。これにより、実行時にバイナリがロードされるベースアドレスの変動を吸収し、f->entry が常に正しい絶対アドレスを指すようにします。
  2. GC_CALL 命令の相対オフセット化:

    • src/cmd/ld/data.c はGoリンカの一部であり、GC関連のメタデータを生成する役割を担っています。
    • 以前は、GC_CALL 命令が参照するターゲット(おそらくGCサブルーチンや型情報)を絶対アドレスでエンコードしていました。
    • このコミットでは、gcaddsym 関数内で GC_CALL 命令に続くアドレスを addaddrplus から addaddrpcrelplus に変更しています。
      • addaddrplus は絶対アドレスを扱う関数です。
      • addaddrpcrelplus は、プログラムカウンタ(PC)からの相対オフセットを計算し、それをエンコードする関数です。
    • これにより、GC_CALL 命令が参照するアドレスが、バイナリのロードアドレスに依存しない相対オフセットとして格納されるようになります。
    • src/pkg/runtime/mgc0.cscanblock 関数では、GC_CALL 命令を処理する際に、以前は pc[2] を直接ターゲットアドレスとして使用していましたが、変更後は (uintptr*)((byte*)pc + (uintptr)pc[2]) とすることで、現在のPCからの相対オフセットとして解釈し、正しいターゲットアドレスを計算するように修正されています。
    • src/pkg/runtime/mgc0.h では、GC_CALL の引数が objgc (絶対ポインタ) から objgcrel (相対オフセット) に変更されたことを示すコメントが追加されています。

これらの変更により、Goバイナリは、動的にロードされる環境やASLRが有効なシステムにおいても、スタックアンワインドやガベージコレクションが正しく機能するようになります。

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

src/cmd/ld/data.c

--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -951,7 +949,7 @@ gcaddsym(Sym *gc, Sym *s, int32 off)
 		//print("gcaddsym:    %s    %d    %s\n", s->name, s->size, gotype->name);
 		adduintxx(gc, GC_CALL, PtrSize);
 		adduintxx(gc, off, PtrSize);
-		addaddrplus(gc, decodetype_gc(gotype), 1*PtrSize);
+		addaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize);
 	} else {
 		//print("gcaddsym:    %s    %d    <unknown type>\n", s->name, s->size);
 		for(a = -off&(PtrSize-1); a+PtrSize<=s->size; a+=PtrSize) {

src/pkg/runtime/mgc0.c

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -663,7 +663,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 			// Stack push.
 			*stack_ptr-- = stack_top;
 			stack_top = (Frame){1, 0, stack_top.b + pc[1], pc+3 /*return address*/};
-			pc = (uintptr*)pc[2];  // target of the CALL instruction
+			pc = (uintptr*)((byte*)pc + (uintptr)pc[2]);  // target of the CALL instruction
 			continue;
 
 		case GC_MAP_PTR:

src/pkg/runtime/mgc0.h

--- a/src/pkg/runtime/mgc0.h
+++ b/src/pkg/runtime/mgc0.h
@@ -12,6 +12,7 @@
 // Meaning of arguments:
 //   off      Offset (in bytes) from the start of the current object
 //   objgc    Pointer to GC info of an object
+//   objgcrel Offset to GC info of an object
 //   len      Length of an array
 //   elemsize Size (in bytes) of an element
 //   size     Size (in bytes)
@@ -21,7 +22,7 @@ enum {
 	GC_APTR,        // Pointer to an arbitrary object. Args: (off)
 	GC_ARRAY_START, // Start an array with a fixed length. Args: (off, len, elemsize)
 	GC_ARRAY_NEXT,  // The next element of an array. Args: none
-\tGC_CALL,        // Call a subroutine. Args: (off, objgc)
+\tGC_CALL,        // Call a subroutine. Args: (off, objgcrel)
 	GC_MAP_PTR,     // Go map. Args: (off, MapType*)
 	GC_STRING,      // Go string. Args: (off)
 	GC_EFACE,       // interface{}. Args: (off)

src/pkg/runtime/symtab.c

--- a/src/pkg/runtime/symtab.c
+++ b/src/pkg/runtime/symtab.c
@@ -93,6 +93,7 @@ walksymtab(void (*fn)(Sym*))
 
 static Func *func;
 static int32 nfunc;
+extern byte reloffset[];
 
 static byte **fname;
 static int32 nfname;
@@ -118,7 +119,7 @@ dofunc(Sym *sym)
 		}
 		f = &func[nfunc++];
 		f->name = runtime·gostringnocopy(sym->name);
-		f->entry = sym->value;
+		f->entry = sym->value + (uint64)reloffset;
 		if(sym->symtype == 'L' || sym->symtype == 'l')
 			f->frame = -sizeof(uintptr);
 		break;

コアとなるコードの解説

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

  • gcaddsym 関数は、GCが使用するシンボル情報を追加する役割を担っています。
  • GC_CALL 命令を生成する際に、以前は addaddrplus(gc, decodetype_gc(gotype), 1*PtrSize) を使用して、gotype のGC情報への絶対アドレスをエンコードしていました。
  • 変更後、addaddrpcrelplus(gc, decodetype_gc(gotype), 4*PtrSize) を使用するようになりました。
    • addaddrpcrelplus は、指定されたシンボル(ここでは decodetype_gc(gotype) が返すGC型情報)への参照を、現在のコード位置からの相対オフセットとしてエンコードします。
    • 4*PtrSize は、おそらく GC_CALL 命令自体のサイズと、それに続くオフセット値のサイズを考慮した、PCからのオフセット計算の基準点を示す値です。これにより、リンカが生成するGCメタデータが位置独立になります。

src/pkg/runtime/mgc0.c の変更

  • scanblock 関数は、GCがメモリブロックをスキャンし、ポインタを識別する主要なロジックを含んでいます。
  • GC_CALL 命令を処理する部分で、以前は pc = (uintptr*)pc[2]; となっていました。これは、pc[2] が直接ターゲットの絶対アドレスを保持していることを前提としていました。
  • 変更後、pc = (uintptr*)((byte*)pc + (uintptr)pc[2]); となりました。
    • これは、pc[2] が現在のプログラムカウンタ pc からの相対オフセットとして解釈されることを意味します。
    • ((byte*)pc + (uintptr)pc[2]) は、現在の命令ポインタ pc に相対オフセット pc[2] を加算することで、GC_CALL がジャンプすべき実際のターゲットアドレスを計算しています。これにより、ランタイムがGC命令を解釈する際も位置独立性が保たれます。

src/pkg/runtime/mgc0.h の変更

  • このヘッダーファイルは、GC命令の定義と引数の意味を記述しています。
  • GC_CALL のコメントが Call a subroutine. Args: (off, objgc) から Call a subroutine. Args: (off, objgcrel) に変更されました。
  • これは、GC_CALL 命令の2番目の引数が、絶対ポインタ (objgc) ではなく、相対オフセット (objgcrel) を表すようになったことを明確に示しています。

src/pkg/runtime/symtab.c の変更

  • dofunc 関数は、シンボルテーブルを走査し、Goの関数に関する情報を構築する際に呼び出されます。
  • 関数のエントリポイント f->entry を設定する際に、以前は f->entry = sym->value; と、シンボルの値(絶対アドレス)を直接使用していました。
  • 変更後、f->entry = sym->value + (uint64)reloffset; となりました。
    • reloffset は、バイナリがロードされたベースアドレスからのオフセットを補償するためのグローバル変数(またはリンカによって設定される値)であると推測されます。
    • この加算により、f->entry は、バイナリがメモリ上のどこにロードされても、その関数の正しい絶対エントリポイントを指すようになります。これは、スタックアンワインドやデバッグツールが関数アドレスを正確に解決するために重要です。

これらの変更は、Goのバイナリがより動的な環境(特に共有ライブラリとして使用される場合やASLRが有効なシステム)で適切に動作するための基盤を強化しています。

関連リンク

参考にした情報源リンク