[インデックス 19167] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) におけるPE32形式の絶対加算値 (absolute addend) の取り扱いに関するバグ修正です。具体的には、PE32形式の実行ファイルにおけるリロケーション処理において、絶対加算値が正しく int32
型にキャストされていなかった問題を修正し、リンカの正確性を向上させます。
コミット
commit d0d425a9872105e8f5c44e6d489ee9088ecf1bdd
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Apr 16 01:46:56 2014 -0400
cmd/ld: cast PE32 absolute addend to int32.
Didn't manage to find a way to write test cases.
Fixes #7769.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/88000045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d0d425a9872105e8f5c44e6d489ee9088ecf1bdd
元コミット内容
cmd/ld: cast PE32 absolute addend to int32.
Didn't manage to find a way to write test cases.
Fixes #7769.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/88000045
変更の背景
このコミットは、Go言語のリンカ (cmd/ld
) がWindowsの実行ファイル形式であるPE32 (Portable Executable 32-bit) を処理する際に発生していた潜在的な問題を解決するために行われました。コミットメッセージにある Fixes #7769
が示すように、この変更は特定のバグ報告に対応するものです。
PE32形式では、プログラムの実行時にアドレスを解決するための「リロケーション」というメカニズムが使用されます。リロケーション情報には、修正対象のアドレスと、そのアドレスに加算されるべき「加算値 (addend)」が含まれます。この加算値が、Goリンカの内部処理で正しく型変換されていなかったため、特に32ビットの符号付き整数として扱われるべき絶対加算値が、意図しない値として解釈される可能性がありました。これにより、生成される実行ファイルの動作が不安定になったり、正しくリンクされなかったりする問題が発生する可能性がありました。
この問題は、特にWindows環境でのGoプログラムのビルドと実行に影響を与えるものであり、リンカの堅牢性と正確性を確保するために修正が必要でした。
前提知識の解説
1. リンカ (cmd/ld
)
リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するツールです。Go言語では、cmd/ld
が標準のリンカとして機能します。リンカの主な役割は以下の通りです。
- シンボル解決: 異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
- リロケーション: プログラムがメモリ上のどこにロードされるかに関わらず、コード内のアドレス参照を修正し、正しいメモリ位置を指すように調整します。
- 実行ファイル形式の生成: オペレーティングシステムが理解できる特定の形式(WindowsではPE、LinuxではELF、macOSではMach-Oなど)で実行ファイルを生成します。
2. PE32 (Portable Executable 32-bit) 形式
PE32は、Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL (Dynamic Link Library) などの標準ファイル形式です。PE32ファイルは、ヘッダ、セクション、リロケーション情報など、プログラムを実行するために必要な様々な情報を含んでいます。
3. リロケーション (Relocation)
リロケーションとは、プログラム内のアドレス参照を、プログラムが実際にメモリにロードされるアドレスに合わせて調整するプロセスです。コンパイル時やリンク時には、最終的なメモリ上のアドレスが不明なため、相対アドレスや仮のアドレスが使用されます。リンカは、これらの仮のアドレスを、最終的な実行時のアドレスに変換するためにリロケーション情報を利用します。
- リロケーションエントリ: PEファイル内のリロケーション情報は、通常、リロケーションテーブルに格納されます。各エントリは、修正が必要なアドレスと、その修正方法(タイプ)を指定します。
- 加算値 (Addend): リロケーションにおいて、修正対象のアドレスに加算されるオフセット値です。この値は、シンボルのベースアドレスからの相対的な位置を示す場合や、特定の定数オフセットを示す場合があります。
4. IMAGE_REL_I386_DIR32
これは、PE32形式におけるリロケーションタイプの一つで、Intel x86 (i386) アーキテクチャ向けの32ビット絶対アドレスリロケーションを示します。このタイプのリロケーションは、32ビットの絶対アドレスを直接修正する必要がある場合に使用されます。リンカは、このタイプのリロケーションエントリを見つけると、指定されたメモリ位置にある32ビットの値を、最終的なターゲットアドレスに更新します。
5. R_ADDR
Goリンカの内部で使われるリロケーションタイプの一つで、絶対アドレスへのリロケーションを示します。IMAGE_REL_I386_DIR32
のようなPE形式固有のリロケーションタイプが、Goリンカの内部表現である R_ADDR
にマッピングされることで、リンカはプラットフォームに依存しない形でリロケーション処理を行うことができます。
6. le32
関数
le32
は "little-endian 32-bit" の略で、32ビットの値をリトルエンディアン形式で読み取る関数を指します。PEファイルは通常リトルエンディアン形式で格納されているため、ファイルから32ビットの値を読み取る際にはこの関数が使用されます。
技術的詳細
このコミットが修正している問題は、PE32形式のリロケーション処理における「加算値」の解釈に関するものです。
PE32ファイルのリロケーションテーブルには、IMAGE_REL_I386_DIR32
のようなリロケーションタイプが含まれます。このタイプのリロケーションは、32ビットの絶対アドレスを修正するために使用され、その修正には「加算値」が関与します。この加算値は、通常、リロケーション対象のメモリ位置に既に存在する値であり、リンカはこれを読み取り、シンボルの最終アドレスと組み合わせて最終的なアドレスを計算します。
問題は、src/cmd/ld/ldpe.c
内の該当箇所で、この加算値がファイルから le32
関数によって読み取られた後、明示的に int32
型にキャストされていなかった点にあります。le32
関数は32ビットの符号なし整数 (unsigned 32-bit integer) を返す可能性がありますが、リロケーションの加算値は符号付き整数 (signed 32-bit integer) として扱われるべき場合があります。
符号なし整数として読み取られた値が、その後符号付き整数として解釈されると、特に値が 2^31
(約20億) を超える場合に、符号ビットが誤って解釈され、負の値として扱われてしまう可能性があります。これにより、リロケーション計算が狂い、最終的なアドレスが不正になることで、プログラムのクラッシュや予期せぬ動作を引き起こす可能性がありました。
このコミットでは、rp->add = (int32)le32(rsect->base+rp->off);
のように明示的に (int32)
へキャストすることで、le32
から読み取られた値が確実に32ビット符号付き整数として扱われるようにします。これにより、加算値の解釈の不一致が解消され、PE32形式のリロケーション処理が正確に行われるようになります。
コアとなるコードの変更箇所
--- a/src/cmd/ld/ldpe.c
+++ b/src/cmd/ld/ldpe.c
@@ -297,7 +297,7 @@ ldpe(Biobuf *f, char *pkg, int64 len, char *pn)
case IMAGE_REL_I386_DIR32:
rp->type = R_ADDR;
// load addend from image
- rp->add = le32(rsect->base+rp->off);
+ rp->add = (int32)le32(rsect->base+rp->off);
break;
case IMAGE_REL_AMD64_ADDR64: // R_X86_64_64
rp->siz = 8;
コアとなるコードの解説
変更は src/cmd/ld/ldpe.c
ファイルの ldpe
関数内、IMAGE_REL_I386_DIR32
のケースにあります。
-
変更前:
rp->add = le32(rsect->base+rp->off);
ここでは、
le32
関数によってファイルから読み取られた32ビットの値が、そのままrp->add
に代入されていました。rp->add
はリロケーションの加算値を格納するフィールドですが、この代入時に明示的な型キャストが行われていませんでした。もしle32
が符号なしの32ビット値を返し、rp->add
が符号付きの型である場合、値の範囲によっては符号が誤って解釈される可能性がありました。 -
変更後:
rp->add = (int32)le32(rsect->base+rp->off);
この変更では、
le32
関数から返された値がrp->add
に代入される前に、明示的に(int32)
型にキャストされています。これにより、読み取られた32ビットの値が、常に符号付き32ビット整数として正しく解釈されることが保証されます。特に、値が0x80000000
(2^31) 以上の場合は、負の値として扱われるようになり、PE32形式のリロケーションにおける絶対加算値の正しいセマンティクスに合致します。
この修正により、GoリンカがWindows環境でPE32形式の実行ファイルを生成する際のリロケーション処理の正確性が向上し、潜在的なバグや不正なアドレス計算が回避されます。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/88000045
- Go Issue #7769: (直接的なリンクは見つかりませんでしたが、CLページで参照されています)
参考にした情報源リンク
- Go CL 88000045 の内容
- PE (Portable Executable) Format の一般的な情報 (Microsoft Learn など)
- リンカとリロケーションに関する一般的な情報 (コンピュータサイエンスの教科書やオンラインリソース)
- Go言語のリンカ (
cmd/ld
) のソースコード (GoプロジェクトのGitHubリポジトリ) IMAGE_REL_I386_DIR32
の定義 (Windows SDK ドキュメントなど)