[インデックス 16246] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) の src/cmd/ld/data.c
ファイルに対する変更です。data.c
は、リンカがシンボルやデータセクションの処理、特に再配置(relocation)を行う際に使用されるコードを含んでいます。このファイルは、プログラムの実行可能ファイルを生成する過程で、シンボル間の参照を解決し、正しいメモリアドレスに配置する役割を担っています。
コミット
cmd/ld: another attempt at the relocation overflow fix
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/9036046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5c0d782ab8a5bffab7de9c91ebfb7fe3f32de1bd
元コミット内容
cmd/ld: another attempt at the relocation overflow fix
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/9036046
変更の背景
このコミットは、「再配置オーバーフローの修正の再試行 (another attempt at the relocation overflow fix)」と明記されており、以前の修正が不十分であったか、新たな問題が発見されたことを示唆しています。再配置オーバーフローは、リンカがシンボル間の参照を解決する際に、ターゲットアドレスが指定された再配置タイプ(例: 32ビット符号付き/符号なしオフセット)で表現できる範囲を超えてしまう場合に発生します。
具体的には、Go言語のリンカは、生成されるバイナリ内の参照(例えば、関数呼び出しやグローバル変数へのアクセス)を、最終的なメモリアドレスに解決する役割を担っています。この解決プロセス中に、参照元から参照先までのオフセットが、その参照を格納するために割り当てられた領域(例えば、32ビットのフィールド)に収まらない場合にオーバーフローが発生します。これは特に、PC相対再配置(Program Counter Relative Relocation)において問題となりやすいです。PC相対再配置では、現在のプログラムカウンタ(PC)からの相対オフセットでアドレスが指定されるため、参照元と参照先の距離が非常に離れている場合に、オフセットが32ビット符号付き整数の範囲(約±2GB)を超えてしまう可能性があります。
この問題が発生すると、リンカは正しいアドレスをバイナリに書き込むことができず、結果として実行時に不正なメモリアクセスやクラッシュを引き起こす可能性があります。したがって、リンカはこのようなオーバーフローを検出し、適切なエラーメッセージを報告する必要があります。このコミットは、その検出ロジックを改善し、より正確な診断を提供する目的で導入されました。
前提知識の解説
1. リンカ (Linker)
リンカは、コンパイラによって生成された複数のオブジェクトファイル(.o
ファイルなど)やライブラリを結合し、最終的な実行可能ファイルや共有ライブラリを生成するプログラムです。リンカの主な役割は以下の通りです。
- シンボル解決 (Symbol Resolution): オブジェクトファイル内で未解決のシンボル参照(例: 別のファイルで定義された関数や変数)を、そのシンボルが定義されている実際のアドレスに解決します。
- 再配置 (Relocation): プログラム内のアドレス依存のコードやデータを、最終的なロードアドレスに基づいて調整します。これは、コンパイル時には絶対アドレスが不明な場合が多いため必要です。
- セクション結合 (Section Merging): オブジェクトファイル内の同じ種類のセクション(例: コードセクション
.text
、データセクション.data
)を結合し、最終的な実行可能ファイルのセクションを構築します。
2. 再配置 (Relocation)
再配置とは、コンパイル時に仮のアドレスで生成されたコードやデータ内の参照を、リンカが最終的な実行時アドレスに基づいて修正するプロセスです。再配置エントリは、オブジェクトファイル内に含まれており、どの場所のどの値をどのように修正すべきかをリンカに指示します。
再配置にはいくつかの種類があります。
- 絶対再配置 (Absolute Relocation): 参照先の絶対アドレスを直接書き込むタイプ。
- PC相対再配置 (PC-relative Relocation): プログラムカウンタ(PC)からの相対オフセットで参照先を指定するタイプ。これは、コードがメモリ上のどこにロードされても正しく動作するように、位置独立コード (PIC: Position-Independent Code) を生成する際によく使用されます。PC相対再配置では、通常、参照元のアドレスと参照先のアドレスの差分が計算され、その差分がバイナリに書き込まれます。
3. オーバーフロー (Overflow)
コンピュータの数値表現において、あるデータ型で表現できる最大値を超えた場合に発生する現象です。例えば、32ビット符号付き整数(int32
)は、約 -2,147,483,648 から 2,147,483,647 までの値を表現できます。もし計算結果がこの範囲を超えると、オーバーフローが発生し、予期しない値になります。
リンカの再配置においては、計算されたオフセット値が、そのオフセットを格納するフィールドのビット幅(例: 32ビット)や符号の有無(符号付き/符号なし)で表現できる範囲を超えた場合にオーバーフローとなります。
4. int32
と uint32
int32
: 32ビット符号付き整数型。負の値と正の値を表現できます。uint32
: 32ビット符号なし整数型。0以上の正の値のみを表現できます。
再配置のオフセットは、PC相対の場合には負の値(後方参照)も取りうるため、通常は符号付き整数で扱われます。一方、絶対アドレスやサイズなど、常に正の値を取る場合には符号なし整数が使われることがあります。リンカは、再配置のタイプに応じて適切な型でオフセットをチェックする必要があります。
5. diag
関数
Go言語のツールチェインにおける diag
関数は、診断メッセージ(エラーや警告)を出力するための内部関数です。リンカが再配置オーバーフローのような重大な問題を検出した場合、diag
を呼び出してユーザーに問題を報告します。
技術的詳細
このコミットは、Goリンカの relocsym
関数内の再配置チェックロジックを改善しています。relocsym
関数は、特定のシンボル s
に関連する再配置エントリ r
を処理する際に呼び出されます。
変更前のコードでは、サイズが4バイトの再配置(case 4:
)に対して、以下のようなチェックを行っていました。
if((r->type == D_PCREL && o != (int32)o) || (r->type != D_PCREL && o != (uint32)o)) {
cursym = S;
diag("relocation address is too big: %#llx", o);
}
ここで、o
は計算された再配置オフセット(またはアドレス)を表す int64
または uint66
のような大きな整数型です。このチェックは、o
が32ビット符号付き整数 (int32
) または32ビット符号なし整数 (uint32
) にキャストしたときに元の値と異なるかどうかを見ています。これは、o
が32ビットの範囲に収まらない場合に真となります。
しかし、このロジックには問題がありました。
D_PCREL
(PC相対再配置) の場合: PC相対再配置では、オフセットは符号付きであるべきです。したがって、o != (int32)o
のチェックは適切です。しかし、|| (r->type != D_PCREL && o != (uint32)o)
の部分が問題を引き起こす可能性があります。もしr->type
がD_PCREL
でない場合、o != (uint32)o
が評価されますが、PC相対オフセットが負の値を取る場合、uint32
にキャストすると値が変化し、誤ってオーバーフローと判断される可能性があります。D_PCREL
ではない再配置の場合:D_PCREL
ではない再配置(例えば、絶対アドレス再配置)の場合、オフセットは通常、符号なしで扱われることが多いです。この場合、o != (uint32)o
のチェックは適切ですが、o != (int32)o
のチェックは不要であり、負の値がオーバーフローと誤認される可能性がありました。
このコミットは、このチェックをより厳密にし、再配置のタイプに応じて適切な型チェックを行うように修正しています。
コアとなるコードの変更箇所
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -259,9 +259,12 @@ relocsym(Sym *s)
cursym = s;
diag("bad reloc size %#ux for %s", siz, r->sym->name);
case 4:
- if((r->type == D_PCREL && o != (int32)o) || (r->type != D_PCREL && o != (uint32)o)) {
- cursym = S;
- diag("relocation address is too big: %#llx", o);
+ if(r->type == D_PCREL) {
+ if(o != (int32)o)
+ diag("pc-relative relocation address is too big: %#llx", o);
+ } else {
+ if(o != (int32)o && o != (uint32)o)
+ diag("non-pc-relative relocation address is too big: %#llux", o);
}
fl = o;
cast = (uchar*)&fl;
コアとなるコードの解説
変更後のコードは、再配置タイプ r->type
が D_PCREL
であるかどうかで条件分岐しています。
-
if(r->type == D_PCREL)
ブロック:- これはPC相対再配置の場合の処理です。
if(o != (int32)o)
: ここでは、計算されたオフセットo
が32ビット符号付き整数 (int32
) の範囲に収まるかどうかをチェックしています。もしo
がint32
にキャストしたときに元の値と異なる場合、それはint32
の範囲を超えていることを意味します。diag("pc-relative relocation address is too big: %#llx", o);
: オーバーフローが検出された場合、より具体的なエラーメッセージ「pc-relative relocation address is too big」を出力します。
-
else
ブロック:- これはPC相対再配置ではない場合の処理です。
if(o != (int32)o && o != (uint32)o)
: ここでは、計算されたオフセットo
が32ビット符号付き整数 (int32
) または 32ビット符号なし整数 (uint32
) のどちらの範囲にも収まらないかどうかをチェックしています。o != (int32)o
:o
がint32
の範囲に収まらないか。o != (uint32)o
:o
がuint32
の範囲に収まらないか。- 両方の条件が真の場合、つまり
o
がint32
とuint32
のどちらの32ビット表現にも収まらない場合にオーバーフローと判断されます。これは、例えばo
が非常に大きな正の値(uint32
の最大値を超える)や、非常に小さな負の値(int32
の最小値を超える)である場合に発生します。
diag("non-pc-relative relocation address is too big: %#llux", o);
: オーバーフローが検出された場合、より具体的なエラーメッセージ「non-pc-relative relocation address is too big」を出力します。%#llux
はo
を符号なしの16進数として表示します。
この修正により、リンカは再配置のタイプに応じて適切な数値型でオーバーフローを検出し、より正確なエラーメッセージを報告できるようになりました。これにより、開発者はリンカのエラーメッセージから問題の原因を特定しやすくなります。
関連リンク
- Go言語のリンカに関するドキュメント(公式):
- Go linker documentation (Goソースコード内のドキュメント)
- Go言語のツールチェインに関する議論:
- golang-devメーリングリスト (コミットメッセージに記載されているR=golang-dev, CC=golang-dev)
- Go言語の変更リスト (CL):
- https://golang.org/cl/9036046 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
- Go source code - cmd/ld/data.c (現在のGoリンカのdata.goファイル。当時のdata.cはGoに移植されている)
- Go linker documentation
- Understanding Go Binaries (Goバイナリの構造に関するブログ記事、リンカの役割も示唆)
- Wikipedia: Relocation (computing)
- Wikipedia: Integer overflow
- Stack Overflow: What is PC-relative addressing?