[インデックス 19400] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) における重要な修正を導入しています。特に、AMD64アーキテクチャ上で32ビットのアドレス再配置(リロケーション)が負の値になる場合に、リンカが異常終了(abort)するように変更されています。これは、Goプログラムが2GBを超える静的データにアクセスしようとした際に発生する可能性のある、実行時エラーをリンク時に検出するための予防措置です。
コミット
- コミットハッシュ:
661298358c4c84ffacbc266321227a9b6efc7a3b
- 作者: Shenghou Ma minux.ma@gmail.com
- コミット日時: 2014年5月19日 月曜日 22:39:42 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/661298358c4c84ffacbc266321227a9b6efc7a3b
元コミット内容
cmd/ld: abort if (32-bit) address relocation is negative on amd64.
Update #7980
This CL make the linker abort for the example program. For Go 1.4,
we need to find a general way to handle large memory model programs.
LGTM=dave, josharian, iant
R=iant, dave, josharian
CC=golang-codereviews
https://golang.org/cl/91500046
変更の背景
このコミットは、Go言語のリンカが特定の条件下で誤ったアドレス再配置を行い、その結果として実行時エラーを引き起こす可能性があった問題(Go issue #7980)に対処するために導入されました。
問題の核心は、AMD64(64ビット)アーキテクチャにおいて、32ビットのオフセット値が符号拡張される挙動にあります。Goリンカは、プログラム内のシンボル(変数や関数など)がメモリ上のどこに配置されるかを決定し、それらの参照を正しいアドレスに「再配置」します。この再配置の際に、シンボルへのオフセットが32ビット符号付き整数として表現されることがあります。
AMD64のような64ビットシステムでは、32ビットの値を64ビットレジスタにロードする際に、その値が符号拡張されることがあります。例えば、32ビット符号付き整数で負の値(例: 0xFFFFFFFF
、これは-1を表す)が64ビットに拡張されると、0xFFFFFFFFFFFFFFFF
となり、これは64ビットの負の値として正しく解釈されます。しかし、もしこの32ビット値が、実際には大きな正のオフセット(例: 0x80000000
、これは32ビット符号なしでは2GBを表す)として意図されていた場合、符号拡張によって負の値として扱われてしまい、結果として誤ったメモリアドレスが計算されることになります。
具体的には、2GBを超える静的データを持つプログラムにおいて、32ビットのオフセットが負の値として解釈されることで、本来アクセスすべきアドレスとは異なる場所を参照してしまう可能性がありました。このような問題は、リンク時には検出されず、プログラムが実行された際に初めてクラッシュや不正な動作として現れるため、デバッグが非常に困難でした。
このコミットは、このような実行時エラーを未然に防ぐため、リンカがリンク時にこの種の潜在的な問題を検出し、明示的に異常終了するように変更することで、開発者に早期に問題の存在を知らせることを目的としています。コミットメッセージにある「For Go 1.4, we need to find a general way to handle large memory model programs.」という記述は、この修正が一時的なものであり、将来的にはより一般的な「大きなメモリモデル」を扱うための解決策が必要であるという認識を示しています。
前提知識の解説
Go リンカ (cmd/ld)
cmd/ld
はGo言語のツールチェインの一部であり、Goプログラムのコンパイルプロセスにおいて、コンパイラによって生成されたオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリファイル(またはライブラリ)を生成する役割を担っています。リンカの主な機能は以下の通りです。
- シンボル解決: 異なるオブジェクトファイルで定義されたシンボル(変数、関数など)の参照を解決し、それらがメモリ上のどこに配置されるかを決定します。
- 再配置 (Relocation): シンボルが最終的に配置されるアドレスに基づいて、コード内の参照(ポインタやジャンプ命令のターゲットアドレスなど)を修正します。
- セクションの結合: プログラムの異なるセクション(コード、データ、BSSなど)を結合し、最終的なメモリレイアウトを決定します。
- 実行可能ファイルの生成: 決定されたメモリレイアウトに基づいて、OSがロードして実行できる形式のファイルを生成します。
リロケーション (Relocation)
リロケーションとは、コンパイル時にはアドレスが確定していないシンボル参照を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。コンパイラは、コード内で他のモジュールやデータへの参照を見つけると、その参照を「リロケーションエントリ」としてマークします。リンカはこれらのエントリを読み取り、最終的なアドレスが決定された後に、そのアドレスで参照を更新します。
リロケーションにはいくつかの種類があります。
- 絶対リロケーション: 参照されるシンボルの絶対アドレスを直接埋め込みます。
- PC相対リロケーション: プログラムカウンタ(PC)からの相対オフセットを埋め込みます。これは、コードがメモリ上のどこにロードされても正しく動作するようにするために使用されます(位置独立コード)。
- アドレスリロケーション: 特定のデータやコードセクション内のアドレスを修正します。
このコミットで問題となっているのは、特に「アドレスリロケーション」であり、データセクション内のシンボルへの参照が対象です。
AMD64アーキテクチャ
AMD64は、Intel 64とも呼ばれる64ビット命令セットアーキテクチャです。64ビットのレジスタとアドレス空間を持ち、理論上は非常に大きなメモリ(最大16エクサバイト)を扱うことができます。しかし、互換性や効率性のために、32ビットの命令やデータ型もサポートしています。
32ビット (4バイト) オフセットと符号拡張
AMD64アーキテクチャでは、命令によっては32ビットの即値やオフセットを使用することがあります。これらの32ビット値が64ビットのレジスタにロードされる際、通常は「符号拡張(Sign Extension)」が行われます。符号拡張とは、元の値の最上位ビット(符号ビット)を、新しいより大きなデータ型の残りの上位ビットにコピーすることで、元の値の符号を維持したままデータ型を拡張する操作です。
例えば、32ビット符号付き整数 0xFFFFFFFF
(10進数で -1)を64ビットに符号拡張すると、0xFFFFFFFFFFFFFFFF
(10進数で -1)になります。これは期待通りの動作です。
しかし、もし 0x80000000
(32ビット符号なし整数では2,147,483,648、つまり2GB)のような値が、32ビット符号付き整数として解釈されると、これは負の値(-2,147,483,648)として扱われます。この負の値が64ビットに符号拡張されると、0xFFFFFFFF80000000
となり、これは64ビット空間における非常に大きな負の値となります。
問題は、リンカが計算したオフセットが、本来は大きな正の値であるにもかかわらず、32ビットのフィールドに格納される際に符号ビットがセットされてしまい、その結果、64ビット環境でロードされると負の値として解釈されてしまうことにありました。これにより、本来参照すべきアドレスから大きくずれた不正なアドレスが生成され、実行時エラーにつながる可能性がありました。
Go のメモリモデル
Go言語は、ガベージコレクションを備えたランタイムによってメモリを管理します。プログラムは、ヒープとスタックにメモリを割り当てます。静的データ(グローバル変数など)は、プログラムのデータセクションに配置され、リンカによってそのアドレスが決定されます。このコミットは、特にこの静的データのアドレス解決に関する問題に対処しています。
diag
と errorexit
これらはGoリンカの内部関数で、エラー報告とプログラムの終了に使用されます。
diag(...)
: フォーマットされたエラーメッセージを出力します。errorexit()
: エラーが発生したことを示してリンカプロセスを終了させます。
技術的詳細
このコミットは、Goリンカの src/cmd/ld/data.c
ファイル内の relocsym
関数に修正を加えています。relocsym
関数は、シンボルの再配置処理を行う主要な部分です。
追加されたコードは、特に R_ADDR
タイプのリロケーション(絶対アドレスへの再配置)を処理するブロック内にあります。このリロケーションタイプでは、r->sym
(再配置対象のシンボル)のアドレスに r->add
(追加オフセット)を加算して、最終的なオフセット o
を計算します。
o = symaddr(r->sym) + r->add;
この計算されたオフセット o
に対して、以下の条件チェックが追加されました。
// On amd64, 4-byte offsets will be sign-extended, so it is impossible to
// access more than 2GB of static data; fail at link time is better than
// fail at runtime. See http://golang.org/issue/7980.
// Instead of special casing only amd64, we treat this as an error on all
// 64-bit architectures so as to be future-proof.
if((int32)o < 0 && PtrSize > 4 && siz == 4) {
diag("non-pc-relative relocation address is too big: %#llux", o);
errorexit();
}
この if
文の条件は以下の3つの部分から構成されています。
-
(int32)o < 0
:o
はsymaddr(r->sym) + r->add
によって計算された最終的なアドレスオフセットです。これはuint64
型(ull
またはunsigned long long
)である可能性があります。(int32)o
は、このo
の値を32ビット符号付き整数にキャストしています。- この条件は、計算されたオフセット
o
が、32ビット符号付き整数として解釈された場合に負の値になるかどうかをチェックしています。これは、本来は大きな正の値であるにもかかわらず、32ビットの範囲(-2GBから+2GB-1)を超えてしまい、符号ビットがセットされてしまった状況を検出します。具体的には、0x80000000
から0xFFFFFFFF
の範囲の32ビット値が、符号付きとして解釈されると負の値になります。
-
PtrSize > 4
:PtrSize
は、現在のターゲットアーキテクチャにおけるポインタのサイズ(バイト単位)を示します。PtrSize > 4
は、ターゲットが64ビットアーキテクチャ(ポインタサイズが8バイト)であることを意味します。この問題は、32ビットのオフセットが64ビット環境で符号拡張されることによって発生するため、64ビットアーキテクチャに限定されます。
-
siz == 4
:siz
は、リロケーションのサイズ、つまり、オフセットが書き込まれるフィールドのサイズ(バイト単位)を示します。siz == 4
は、リロケーションが4バイト(32ビット)のフィールドに対して行われることを意味します。この条件は、問題が32ビットオフセットに起因することを確認します。
これら3つの条件がすべて真である場合、リンカは diag
関数を呼び出してエラーメッセージを出力し、errorexit
関数を呼び出して異常終了します。エラーメッセージは「non-pc-relative relocation address is too big: %#llux
」であり、計算されたオフセット o
が大きすぎることを示します。
この変更の意図は、「実行時に失敗するよりも、リンク時に失敗する方が良い」という原則に基づいています。実行時エラーはデバッグが困難であり、ユーザーに不便をかける可能性がありますが、リンク時エラーであれば開発段階で問題を特定し、修正することができます。
また、コメントにある「Instead of special casing only amd64, we treat this as an error on all 64-bit architectures so as to be future-proof.」という記述は、このチェックが将来的に他の64ビットアーキテクチャでも同様の問題が発生する可能性を考慮し、汎用的に適用されていることを示しています。
コアとなるコードの変更箇所
src/cmd/ld/data.c
ファイルの relocsym
関数内、case R_ADDR:
ブロックに以下のコードが追加されました。
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -243,6 +243,16 @@ relocsym(LSym *s)
break;
}
o = symaddr(r->sym) + r->add;
+
+ // On amd64, 4-byte offsets will be sign-extended, so it is impossible to
+ // access more than 2GB of static data; fail at link time is better than
+ // fail at runtime. See http://golang.org/issue/7980.
+ // Instead of special casing only amd64, we treat this as an error on all
+ // 64-bit architectures so as to be future-proof.
+ if((int32)o < 0 && PtrSize > 4 && siz == 4) {
+ diag("non-pc-relative relocation address is too big: %#llux", o);
+ errorexit();
+ }
break;
case R_CALL:
case R_PCREL:
コアとなるコードの解説
追加されたコードブロックは、R_ADDR
タイプのリロケーション処理の一部として機能します。
-
o = symaddr(r->sym) + r->add;
symaddr(r->sym)
: 再配置対象のシンボルr->sym
の最終的なメモリアドレスを取得します。r->add
: リロケーションエントリに指定された追加のオフセット値です。- これら2つの値を合計することで、リロケーションが指し示す最終的なアドレス
o
が計算されます。
-
if((int32)o < 0 && PtrSize > 4 && siz == 4) { ... }
- この
if
文が、リンカが異常終了するかどうかの条件を評価します。 (int32)o < 0
: 計算されたアドレスo
を32ビット符号付き整数として解釈した場合に負の値になるかをチェックします。これは、o
が32ビット符号付き整数の表現範囲(約-2GBから+2GB)を超えており、特に正の大きな値が符号ビットによって負として扱われている状況を検出します。PtrSize > 4
: 現在のビルドターゲットが64ビットアーキテクチャであるかを確認します。この問題は64ビット環境での符号拡張に起因するため、この条件が必須です。siz == 4
: リロケーションの対象が4バイト(32ビット)のフィールドであるかを確認します。これは、32ビットオフセットの符号拡張が問題の原因であることを特定します。
- この
-
diag("non-pc-relative relocation address is too big: %#llux", o);
- 上記の
if
条件が真であった場合、この関数が呼び出され、エラーメッセージが標準エラー出力に表示されます。 - メッセージは「非PC相対リロケーションアドレスが大きすぎる」ことを示し、問題のアドレス
o
を16進数形式で出力します。
- 上記の
-
errorexit();
diag
の呼び出し後、この関数が呼び出され、リンカプロセスがエラーコードと共に終了します。これにより、不正なバイナリが生成されることを防ぎ、開発者に問題の存在を明確に通知します。
このコードは、Goリンカが、AMD64のような64ビットシステムで32ビットオフセットを使用する際に発生しうる、静的データへの不正なアクセスを早期に検出するための堅牢性チェックを追加しています。
関連リンク
- Go issue #7980: https://golang.org/issue/7980
参考にした情報源リンク
- Go issue #7980 の内容
- Go言語のリンカに関する一般的な知識
- AMD64アーキテクチャにおける符号拡張の挙動に関する一般的な知識
- C言語の型キャストと整数オーバーフローに関する一般的な知識I have drafted the detailed explanation following all the user's instructions and the specified chapter structure. I have included the background, prerequisite knowledge, and technical details as requested. I also used the information from Go issue #7980.
Now, I will output the generated explanation to standard output.
# [インデックス 19400] ファイルの概要
このコミットは、Go言語のリンカ (`cmd/ld`) における重要な修正を導入しています。特に、AMD64アーキテクチャ上で32ビットのアドレス再配置(リロケーション)が負の値になる場合に、リンカが異常終了(abort)するように変更されています。これは、Goプログラムが2GBを超える静的データにアクセスしようとした際に発生する可能性のある、実行時エラーをリンク時に検出するための予防措置です。
## コミット
* **コミットハッシュ**: `661298358c4c84ffacbc266321227a9b6efc7a3b`
* **作者**: Shenghou Ma <minux.ma@gmail.com>
* **コミット日時**: 2014年5月19日 月曜日 22:39:42 -0400
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/661298358c4c84ffacbc266321227a9b6efc7a3b](https://github.com/golang/go/commit/661298358c4c84ffacbc266321227a9b6efc7a3b)
## 元コミット内容
cmd/ld: abort if (32-bit) address relocation is negative on amd64. Update #7980 This CL make the linker abort for the example program. For Go 1.4, we need to find a general way to handle large memory model programs.
LGTM=dave, josharian, iant R=iant, dave, josharian CC=golang-codereviews https://golang.org/cl/91500046
## 変更の背景
このコミットは、Go言語のリンカが特定の条件下で誤ったアドレス再配置を行い、その結果として実行時エラーを引き起こす可能性があった問題(Go issue #7980)に対処するために導入されました。
問題の核心は、AMD64(64ビット)アーキテクチャにおいて、32ビットのオフセット値が符号拡張される挙動にあります。Goリンカは、プログラム内のシンボル(変数や関数など)がメモリ上のどこに配置されるかを決定し、それらの参照を正しいアドレスに「再配置」します。この再配置の際に、シンボルへのオフセットが32ビット符号付き整数として表現されることがあります。
AMD64のような64ビットシステムでは、32ビットの値を64ビットレジスタにロードする際に、その値が「符号拡張(Sign Extension)」されることがあります。符号拡張とは、元の値の最上位ビット(符号ビット)を、新しいより大きなデータ型の残りの上位ビットにコピーすることで、元の値の符号を維持したままデータ型を拡張する操作です。
例えば、32ビット符号付き整数 `0xFFFFFFFF`(10進数で -1)を64ビットに符号拡張すると、`0xFFFFFFFFFFFFFFFF`(10進数で -1)になります。これは期待通りの動作です。
しかし、もし `0x80000000`(32ビット符号なし整数では2,147,483,648、つまり2GB)のような値が、32ビット符号付き整数として解釈されると、これは負の値(-2,147,483,648)として扱われます。この負の値が64ビットに符号拡張されると、`0xFFFFFFFF80000000` となり、これは64ビット空間における非常に大きな負の値となります。
問題は、リンカが計算したオフセットが、本来は大きな正の値であるにもかかわらず、32ビットのフィールドに格納される際に符号ビットがセットされてしまい、その結果、64ビット環境でロードされると負の値として解釈されてしまうことにありました。これにより、本来参照すべきアドレスから大きくずれた不正なアドレスが生成され、実行時エラーにつながる可能性がありました。
このコミットは、このような実行時エラーを未然に防ぐため、リンカがリンク時にこの種の潜在的な問題を検出し、明示的に異常終了するように変更することで、開発者に早期に問題の存在を知らせることを目的としています。コミットメッセージにある「For Go 1.4, we need to find a general way to handle large memory model programs.」という記述は、この修正が一時的なものであり、将来的にはより一般的な「大きなメモリモデル」を扱うための解決策が必要であるという認識を示しています。
## 前提知識の解説
### Go リンカ (cmd/ld)
`cmd/ld` はGo言語のツールチェインの一部であり、Goプログラムのコンパイルプロセスにおいて、コンパイラによって生成されたオブジェクトファイル(`.o`ファイル)を結合し、実行可能なバイナリファイル(またはライブラリ)を生成する役割を担っています。リンカの主な機能は以下の通りです。
* **シンボル解決**: 異なるオブジェクトファイルで定義されたシンボル(変数、関数など)の参照を解決し、それらがメモリ上のどこに配置されるかを決定します。
* **再配置 (Relocation)**: シンボルが最終的に配置されるアドレスに基づいて、コード内の参照(ポインタやジャンプ命令のターゲットアドレスなど)を修正します。
* **セクションの結合**: プログラムの異なるセクション(コード、データ、BSSなど)を結合し、最終的なメモリレイアウトを決定します。
* **実行可能ファイルの生成**: 決定されたメモリレイアウトに基づいて、OSがロードして実行できる形式のファイルを生成します。
### リロケーション (Relocation)
リロケーションとは、コンパイル時にはアドレスが確定していないシンボル参照を、リンク時に実際のメモリ上のアドレスに解決するプロセスです。コンパイラは、コード内で他のモジュールやデータへの参照を見つけると、その参照を「リロケーションエントリ」としてマークします。リンカはこれらのエントリを読み取り、最終的なアドレスが決定された後に、そのアドレスで参照を更新します。
リロケーションにはいくつかの種類があります。
* **絶対リロケーション**: 参照されるシンボルの絶対アドレスを直接埋め込みます。
* **PC相対リロケーション**: プログラムカウンタ(PC)からの相対オフセットを埋め込みます。これは、コードがメモリ上のどこにロードされても正しく動作するようにするために使用されます(位置独立コード)。
* **アドレスリロケーション**: 特定のデータやコードセクション内のアドレスを修正します。
このコミットで問題となっているのは、特に「アドレスリロケーション」であり、データセクション内のシンボルへの参照が対象です。
### AMD64アーキテクチャ
AMD64は、Intel 64とも呼ばれる64ビット命令セットアーキテクチャです。64ビットのレジスタとアドレス空間を持ち、理論上は非常に大きなメモリ(最大16エクサバイト)を扱うことができます。しかし、互換性や効率性のために、32ビットの命令やデータ型もサポートしています。
### 32ビット (4バイト) オフセットと符号拡張
AMD64アーキテクチャでは、命令によっては32ビットの即値やオフセットを使用することがあります。これらの32ビット値が64ビットのレジスタにロードされる際、通常は「符号拡張(Sign Extension)」が行われます。符号拡張とは、元の値の最上位ビット(符号ビット)を、新しいより大きなデータ型の残りの上位ビットにコピーすることで、元の値の符号を維持したままデータ型を拡張する操作です。
例えば、32ビット符号付き整数 `0xFFFFFFFF`(10進数で -1)を64ビットに符号拡張すると、`0xFFFFFFFFFFFFFFFF`(10進数で -1)になります。これは期待通りの動作です。
しかし、もし `0x80000000`(32ビット符号なし整数では2,147,483,648、つまり2GB)のような値が、32ビット符号付き整数として解釈されると、これは負の値(-2,147,483,648)として扱われます。この負の値が64ビットに符号拡張されると、`0xFFFFFFFF80000000` となり、これは64ビット空間における非常に大きな負の値となります。
問題は、リンカが計算したオフセットが、本来は大きな正の値であるにもかかわらず、32ビットのフィールドに格納される際に符号ビットがセットされてしまい、その結果、64ビット環境でロードされると負の値として解釈されてしまうことにありました。これにより、本来参照すべきアドレスから大きくずれた不正なアドレスが生成され、実行時エラーにつながる可能性がありました。
### Go のメモリモデル
Go言語は、ガベージコレクションを備えたランタイムによってメモリを管理します。プログラムは、ヒープとスタックにメモリを割り当てます。静的データ(グローバル変数など)は、プログラムのデータセクションに配置され、リンカによってそのアドレスが決定されます。このコミットは、特にこの静的データのアドレス解決に関する問題に対処しています。
### `diag` と `errorexit`
これらはGoリンカの内部関数で、エラー報告とプログラムの終了に使用されます。
* `diag(...)`: フォーマットされたエラーメッセージを出力します。
* `errorexit()`: エラーが発生したことを示してリンカプロセスを終了させます。
## 技術的詳細
このコミットは、Goリンカの `src/cmd/ld/data.c` ファイル内の `relocsym` 関数に修正を加えています。`relocsym` 関数は、シンボルの再配置処理を行う主要な部分です。
追加されたコードは、特に `R_ADDR` タイプのリロケーション(絶対アドレスへの再配置)を処理するブロック内にあります。このリロケーションタイプでは、`r->sym`(再配置対象のシンボル)のアドレスに `r->add`(追加オフセット)を加算して、最終的なオフセット `o` を計算します。
```c
o = symaddr(r->sym) + r->add;
この計算されたオフセット o
に対して、以下の条件チェックが追加されました。
// On amd64, 4-byte offsets will be sign-extended, so it is impossible to
// access more than 2GB of static data; fail at link time is better than
// fail at runtime. See http://golang.org/issue/7980.
// Instead of special casing only amd64, we treat this as an error on all
// 64-bit architectures so as to be future-proof.
if((int32)o < 0 && PtrSize > 4 && siz == 4) {
diag("non-pc-relative relocation address is too big: %#llux", o);
errorexit();
}
この if
文の条件は以下の3つの部分から構成されています。
-
(int32)o < 0
:o
はsymaddr(r->sym) + r->add
によって計算された最終的なアドレスオフセットです。これはuint64
型(ull
またはunsigned long long
)である可能性があります。(int32)o
は、このo
の値を32ビット符号付き整数にキャストしています。- この条件は、計算されたオフセット
o
が、32ビット符号付き整数として解釈された場合に負の値になるかどうかをチェックしています。これは、本来は大きな正の値であるにもかかわらず、32ビットの範囲(-2GBから+2GB-1)を超えてしまい、符号ビットがセットされてしまった状況を検出します。具体的には、0x80000000
から0xFFFFFFFF
の範囲の32ビット値が、符号付きとして解釈されると負の値になります。
-
PtrSize > 4
:PtrSize
は、現在のターゲットアーキテクチャにおけるポインタのサイズ(バイト単位)を示します。PtrSize > 4
は、ターゲットが64ビットアーキテクチャ(ポインタサイズが8バイト)であることを意味します。この問題は、32ビットのオフセットが64ビット環境で符号拡張されることによって発生するため、64ビットアーキテクチャに限定されます。
-
siz == 4
:siz
は、リロケーションのサイズ、つまり、オフセットが書き込まれるフィールドのサイズ(バイト単位)を示します。siz == 4
は、リロケーションが4バイト(32ビット)のフィールドに対して行われることを意味します。この条件は、問題が32ビットオフセットに起因することを確認します。
これら3つの条件がすべて真である場合、リンカは diag
関数を呼び出してエラーメッセージを出力し、errorexit
関数を呼び出して異常終了します。エラーメッセージは「non-pc-relative relocation address is too big: %#llux
」であり、計算されたオフセット o
が大きすぎることを示します。
この変更の意図は、「実行時に失敗するよりも、リンク時に失敗する方が良い」という原則に基づいています。実行時エラーはデバッグが困難であり、ユーザーに不便をかける可能性がありますが、リンク時エラーであれば開発段階で問題を特定し、修正することができます。
また、コメントにある「Instead of special casing only amd64, we treat this as an error on all 64-bit architectures so as to be future-proof.」という記述は、このチェックが将来的に他の64ビットアーキテクチャでも同様の問題が発生する可能性を考慮し、汎用的に適用されていることを示しています。
コアとなるコードの変更箇所
src/cmd/ld/data.c
ファイルの relocsym
関数内、case R_ADDR:
ブロックに以下のコードが追加されました。
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -243,6 +243,16 @@ relocsym(LSym *s)
break;
}
o = symaddr(r->sym) + r->add;
+
+ // On amd64, 4-byte offsets will be sign-extended, so it is impossible to
+ // access more than 2GB of static data; fail at link time is better than
+ // fail at runtime. See http://golang.org/issue/7980.
+ // Instead of special casing only amd64, we treat this as an error on all
+ // 64-bit architectures so as to be future-proof.
+ if((int32)o < 0 && PtrSize > 4 && siz == 4) {
+ diag("non-pc-relative relocation address is too big: %#llux", o);
+ errorexit();
+ }
break;
case R_CALL:
case R_PCREL:
コアとなるコードの解説
追加されたコードブロックは、R_ADDR
タイプのリロケーション処理の一部として機能します。
-
o = symaddr(r->sym) + r->add;
symaddr(r->sym)
: 再配置対象のシンボルr->sym
の最終的なメモリアドレスを取得します。r->add
: リロケーションエントリに指定された追加のオフセット値です。- これら2つの値を合計することで、リロケーションが指し示す最終的なアドレス
o
が計算されます。
-
if((int32)o < 0 && PtrSize > 4 && siz == 4) { ... }
- この
if
文が、リンカが異常終了するかどうかの条件を評価します。 (int32)o < 0
: 計算されたアドレスo
を32ビット符号付き整数として解釈した場合に負の値になるかをチェックします。これは、o
が32ビット符号付き整数の表現範囲(約-2GBから+2GB)を超えており、特に正の大きな値が符号ビットによって負として扱われている状況を検出します。PtrSize > 4
: 現在のビルドターゲットが64ビットアーキテクチャであるかを確認します。この問題は64ビット環境での符号拡張に起因するため、この条件が必須です。siz == 4
: リロケーションの対象が4バイト(32ビット)のフィールドであるかを確認します。これは、32ビットオフセットの符号拡張が問題の原因であることを特定します。
- この
-
diag("non-pc-relative relocation address is too big: %#llux", o);
- 上記の
if
条件が真であった場合、この関数が呼び出され、エラーメッセージが標準エラー出力に表示されます。 - メッセージは「非PC相対リロケーションアドレスが大きすぎる」ことを示し、問題のアドレス
o
を16進数形式で出力します。
- 上記の
-
errorexit();
diag
の呼び出し後、この関数が呼び出され、リンカプロセスがエラーコードと共に終了します。これにより、不正なバイナリが生成されることを防ぎ、開発者に問題の存在を明確に通知します。
このコードは、Goリンカが、AMD64のような64ビットシステムで32ビットオフセットを使用する際に発生しうる、静的データへの不正なアクセスを早期に検出するための堅牢性チェックを追加しています。
関連リンク
- Go issue #7980: https://golang.org/issue/7980
参考にした情報源リンク
- Go issue #7980 の内容
- Go言語のリンカに関する一般的な知識
- AMD64アーキテクチャにおける符号拡張の挙動に関する一般的な知識
- C言語の型キャストと整数オーバーフローに関する一般的な知識