[インデックス 19360] ファイルの概要
このコミットは、Go言語のツールチェインに含まれるaddr2line
コマンドとobjdump
コマンドにおける、Windowsの実行ファイル形式であるPE (Portable Executable) ファイルのテキストセクション開始アドレスの計算に関するバグ修正です。具体的には、PEファイルの.text
セクションの仮想アドレスに、イメージベースアドレスを加算することで、正しい開始アドレスを導出するように変更されています。
コミット
commit 6c7bef551b32c2f7f2371b21cc8d51d807737ef3
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu May 15 12:44:29 2014 +1000
cmd/addr2line, cmd/objdump: fix pe text section starting address
fixes windows build
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/97500043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6c7bef551b32c2f7f2371b21cc8d51d807737ef3
元コミット内容
このコミットは、cmd/addr2line
とcmd/objdump
の2つのコマンドにおいて、PEファイルのテキストセクションの開始アドレスの計算方法を修正するものです。これにより、Windowsビルドが正しく動作するようになります。
変更の背景
Go言語のツールチェインには、デバッグやバイナリ解析に役立つユーティリティが含まれています。addr2line
は実行ファイル内のアドレスからソースコードの行番号を特定するツールであり、objdump
は実行ファイルのオブジェクトコードを逆アセンブルして表示するツールです。これらのツールがWindows上で生成されたPE形式の実行ファイルを正確に解析するためには、ファイル内の各セクション(特にコードが格納される.text
セクション)のメモリ上の正確な開始アドレスを把握する必要があります。
以前の実装では、PEファイルの.text
セクションの開始アドレスを計算する際に、セクションヘッダに記述されているVirtualAddress
のみを使用していたと考えられます。しかし、PEファイルがメモリにロードされる際には、そのファイルがロードされるベースアドレス(ImageBase
)が存在します。セクションのVirtualAddress
は、このImageBase
からの相対オフセットとして解釈されるべきです。したがって、正しいメモリ上の開始アドレスを得るためには、ImageBase
とVirtualAddress
を合算する必要がありました。
この計算の誤りが、Windows環境でのビルドやデバッグツールの動作に問題を引き起こしていたため、この修正が必要となりました。コミットメッセージにある「fixes windows build」という記述は、この問題がWindows環境でのGoプログラムのビルドプロセスや、ビルドされたバイナリの解析に影響を与えていたことを示唆しています。
前提知識の解説
PE (Portable Executable) ファイル形式
PEは、Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL (Dynamic Link Library) などのファイル形式です。PEファイルは、DOSヘッダ、PEヘッダ(ファイルヘッダとオプションヘッダを含む)、セクションヘッダ、そして実際のセクションデータで構成されます。
- DOSヘッダ: 互換性のために存在し、古いDOSシステムで実行された場合に「This program cannot be run in DOS mode.」のようなメッセージを表示します。
- PEヘッダ:
- ファイルヘッダ: マシンタイプ、セクションの数、タイムスタンプなどの基本的な情報を含みます。
- オプションヘッダ: PEファイルの最も重要な部分の一つで、実行可能イメージのメモリレイアウトに関する情報を含みます。ここには、
ImageBase
、AddressOfEntryPoint
、SectionAlignment
、FileAlignment
などのフィールドが含まれます。ImageBase
: 実行ファイルがメモリにロードされる推奨ベースアドレスです。DLLの場合、このアドレスはロード時に変更される可能性があります(リベース)。
- セクションヘッダ: 各セクション(例:
.text
、.data
、.rdata
)に関する情報を含みます。VirtualAddress
: セクションがメモリにロードされた際の、ImageBase
からの相対アドレス(RVA: Relative Virtual Address)です。VirtualSize
: メモリ上でのセクションのサイズです。PointerToRawData
: ファイル内でのセクションデータのオフセットです。SizeOfRawData
: ファイル内でのセクションデータのサイズです。
- セクションデータ: 実際のコード、初期化済みデータ、未初期化データなどが格納されます。
.text
セクションは通常、実行可能なコードを含みます。
addr2line
コマンド
addr2line
は、プログラムの実行中に発生したエラーのスタックトレースなどで表示されるメモリアドレスを、対応するソースファイル名と行番号に変換するために使用されるユーティリティです。デバッグ時に、クラッシュした場所や特定の関数が呼び出された場所を特定するのに非常に役立ちます。このツールは、実行ファイル内に含まれるデバッグ情報(DWARF形式など)を解析してアドレスとソースコードのマッピングを行います。
objdump
コマンド
objdump
は、オブジェクトファイルや実行ファイルの情報を表示するためのユーティリティです。これには、逆アセンブルされたコード、セクションヘッダ、シンボルテーブル、リロケーションエントリなどが含まれます。開発者がバイナリの内部構造を理解したり、最適化の効果を確認したりする際に利用されます。
技術的詳細
このコミットの技術的な核心は、PEファイルのメモリロードメカニズムの正確な理解と、それに基づくアドレス計算の修正です。
PEファイルがWindowsによってメモリにロードされる際、オペレーティングシステムはまず、PEヘッダのオプションヘッダに指定されたImageBase
アドレスにファイルをロードしようとします。このImageBase
は、実行ファイル全体がメモリ上で開始する仮想アドレスです。
各セクション(例: .text
、.data
)は、そのセクションヘッダ内にVirtualAddress
というフィールドを持っています。このVirtualAddress
は、ImageBase
からの相対的なオフセット(RVA)として定義されています。つまり、セクションが実際にメモリにロードされる絶対アドレスは、ImageBase + VirtualAddress
によって計算されます。
以前のaddr2line
とobjdump
の実装では、.text
セクションの開始アドレスをsect.VirtualAddress
のみで取得していました。これは、ImageBase
が0であるか、あるいはVirtualAddress
が既に絶対アドレスとして扱われるような特定の状況下では問題ないかもしれませんが、一般的なPEファイルの構造においては不正確です。特に、ImageBase
が非ゼロの値を持つ場合、この計算誤りにより、ツールが.text
セクションのコードを誤ったメモリ位置にあると認識し、結果としてデバッグ情報の解析や逆アセンブルが失敗する原因となっていました。
この修正では、pe.NewFile(f)
でPEファイルをパースした後、obj.OptionalHeader
からImageBase
を取得するロジックが追加されました。PEファイルには32ビット版と64ビット版があり、それぞれpe.OptionalHeader32
とpe.OptionalHeader64
という異なる構造体でオプションヘッダが表現されます。このため、switch
文を使って適切なヘッダタイプを判別し、そこからImageBase
フィールドを抽出しています。
最終的に、.text
セクションのVirtualAddress
にこの取得したImageBase
を加算することで、メモリ上の正しい絶対開始アドレスtextStart
を計算しています。
// 修正前:
// textStart = uint64(sect.VirtualAddress)
// 修正後:
// var imageBase uint64
// switch oh := obj.OptionalHeader.(type) {
// case *pe.OptionalHeader32:
// imageBase = uint64(oh.ImageBase)
// case *pe.OptionalHeader64:
// imageBase = oh.ImageBase
// default:
// return 0, nil, nil, fmt.Errorf("pe file format not recognized")
// }
// if sect := obj.Section(".text"); sect != nil {
// textStart = imageBase + uint64(sect.VirtualAddress)
// }
この変更により、addr2line
とobjdump
はWindowsのPEファイルをより正確に解析できるようになり、Windows環境でのGo言語開発におけるデバッグとバイナリ解析の信頼性が向上しました。
コアとなるコードの変更箇所
このコミットでは、src/cmd/addr2line/main.go
とsrc/cmd/objdump/main.go
の2つのファイルが変更されています。両ファイルにおけるloadTables
関数内で、PEファイルの.text
セクションの開始アドレスを計算するロジックが修正されています。
src/cmd/addr2line/main.go
の変更点
--- a/src/cmd/addr2line/main.go
+++ b/src/cmd/addr2line/main.go
@@ -138,8 +138,17 @@ func loadTables(f *os.File) (textStart uint64, symtab, pclntab []byte, err error)
}\n
if obj, err := pe.NewFile(f); err == nil {
+ var imageBase uint64
+ switch oh := obj.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ imageBase = uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ imageBase = oh.ImageBase
+ default:
+ return 0, nil, nil, fmt.Errorf("pe file format not recognized")
+ }
if sect := obj.Section(".text"); sect != nil {
- textStart = uint64(sect.VirtualAddress)
+ textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
return 0, nil, nil, err
src/cmd/objdump/main.go
の変更点
--- a/src/cmd/objdump/main.go
+++ b/src/cmd/objdump/main.go
@@ -318,8 +318,17 @@ func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte,
}\n
if obj, err := pe.NewFile(f); err == nil {
+ var imageBase uint64
+ switch oh := obj.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ imageBase = uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ imageBase = oh.ImageBase
+ default:
+ return 0, nil, nil, nil, fmt.Errorf("pe file format not recognized")
+ }
if sect := obj.Section(".text"); sect != nil {
- textStart = uint64(sect.VirtualAddress)
+ textStart = imageBase + uint64(sect.VirtualAddress)
textData, _ = sect.Data()
}
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
コアとなるコードの解説
両方のファイルで、loadTables
関数は実行ファイルからシンボルテーブルやPCLNテーブル(Goのプロファイリング情報)などのデバッグ関連情報をロードする役割を担っています。この関数内で、PEファイルが検出された場合に以下の変更が加えられました。
-
imageBase
変数の導入:uint64
型のimageBase
変数が新しく宣言されました。この変数は、PEファイルがメモリにロードされる際のベースアドレスを保持します。 -
オプションヘッダの型判定と
ImageBase
の取得:obj.OptionalHeader
はインターフェース型であり、実際の型はPEファイルのビット数(32ビットまたは64ビット)によって異なります。switch oh := obj.OptionalHeader.(type)
構文を使用して、obj.OptionalHeader
の具体的な型を判定しています。case *pe.OptionalHeader32:
: オプションヘッダが32ビット版の場合、oh.ImageBase
をuint64
にキャストしてimageBase
に代入します。case *pe.OptionalHeader64:
: オプションヘッダが64ビット版の場合、oh.ImageBase
を直接imageBase
に代入します。default:
: 認識できないPEファイル形式の場合、エラーを返します。これは、将来的に新しいPEヘッダのバリアントが登場した場合に備えた堅牢なエラーハンドリングです。
-
.text
セクション開始アドレスの修正計算:- 以前は、
.text
セクションのVirtualAddress
を直接textStart
として使用していました。 - 修正後は、
textStart = imageBase + uint64(sect.VirtualAddress)
という計算式に変更されました。これにより、セクションの仮想アドレスがImageBase
からの相対オフセットとして正しく解釈され、メモリ上の絶対アドレスがtextStart
に設定されるようになりました。
- 以前は、
この変更により、addr2line
とobjdump
は、WindowsのPE実行ファイルにおいて、コードセクションの正確なメモリ開始アドレスを特定できるようになり、デバッグ情報のマッピングや逆アセンブルが正しく行われるようになりました。
関連リンク
- Go言語の
debug/pe
パッケージのドキュメント: https://pkg.go.dev/debug/pe - Microsoft Portable Executable and Common Object File Format Specification: (公式ドキュメントへの直接リンクは変動する可能性があるため、検索エンジンで「Microsoft Portable Executable and Common Object File Format Specification」と検索することを推奨します。)
参考にした情報源リンク
- PEファイル形式に関する一般的な情報源(例: Wikipedia, Microsoft Learnドキュメント)
- Go言語の
debug/pe
パッケージのソースコード addr2line
およびobjdump
コマンドの一般的な動作に関する情報# [インデックス 19360] ファイルの概要
このコミットは、Go言語のツールチェインに含まれるaddr2line
コマンドとobjdump
コマンドにおける、Windowsの実行ファイル形式であるPE (Portable Executable) ファイルのテキストセクション開始アドレスの計算に関するバグ修正です。具体的には、PEファイルの.text
セクションの仮想アドレスに、イメージベースアドレスを加算することで、正しい開始アドレスを導出するように変更されています。
コミット
commit 6c7bef551b32c2f7f2371b21cc8d51d807737ef3
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu May 15 12:44:29 2014 +1000
cmd/addr2line, cmd/objdump: fix pe text section starting address
fixes windows build
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/97500043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6c7bef551b32c2f7f2371b21cc8d51d807737ef3
元コミット内容
このコミットは、cmd/addr2line
とcmd/objdump
の2つのコマンドにおいて、PEファイルのテキストセクションの開始アドレスの計算方法を修正するものです。これにより、Windowsビルドが正しく動作するようになります。
変更の背景
Go言語のツールチェインには、デバッグやバイナリ解析に役立つユーティリティが含まれています。addr2line
は実行ファイル内のアドレスからソースコードの行番号を特定するツールであり、objdump
は実行ファイルのオブジェクトコードを逆アセンブルして表示するツールです。これらのツールがWindows上で生成されたPE形式の実行ファイルを正確に解析するためには、ファイル内の各セクション(特にコードが格納される.text
セクション)のメモリ上の正確な開始アドレスを把握する必要があります。
以前の実装では、PEファイルの.text
セクションの開始アドレスを計算する際に、セクションヘッダに記述されているVirtualAddress
のみを使用していたと考えられます。しかし、PEファイルがメモリにロードされる際には、そのファイルがロードされるベースアドレス(ImageBase
)が存在します。セクションのVirtualAddress
は、このImageBase
からの相対オフセットとして解釈されるべきです。したがって、正しいメモリ上の開始アドレスを得るためには、ImageBase
とVirtualAddress
を合算する必要がありました。
この計算の誤りが、Windows環境でのビルドやデバッグツールの動作に問題を引き起こしていたため、この修正が必要となりました。コミットメッセージにある「fixes windows build」という記述は、この問題がWindows環境でのGoプログラムのビルドプロセスや、ビルドされたバイナリの解析に影響を与えていたことを示唆しています。
前提知識の解説
PE (Portable Executable) ファイル形式
PEは、Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL (Dynamic Link Library) などのファイル形式です。PEファイルは、DOSヘッダ、PEヘッダ(ファイルヘッダとオプションヘッダを含む)、セクションヘッダ、そして実際のセクションデータで構成されます。
- DOSヘッダ: 互換性のために存在し、古いDOSシステムで実行された場合に「This program cannot be run in DOS mode.」のようなメッセージを表示します。
- PEヘッダ:
- ファイルヘッダ: マシンタイプ、セクションの数、タイムスタンプなどの基本的な情報を含みます。
- オプションヘッダ: PEファイルの最も重要な部分の一つで、実行可能イメージのメモリレイアウトに関する情報を含みます。ここには、
ImageBase
、AddressOfEntryPoint
、SectionAlignment
、FileAlignment
などのフィールドが含まれます。ImageBase
: 実行ファイルがメモリにロードされる推奨ベースアドレスです。DLLの場合、このアドレスはロード時に変更される可能性があります(リベース)。
- セクションヘッダ: 各セクション(例:
.text
、.data
、.rdata
)に関する情報を含みます。VirtualAddress
: セクションがメモリにロードされた際の、ImageBase
からの相対アドレス(RVA: Relative Virtual Address)です。VirtualSize
: メモリ上でのセクションのサイズです。PointerToRawData
: ファイル内でのセクションデータのオフセットです。SizeOfRawData
: ファイル内でのセクションデータのサイズです。
- セクションデータ: 実際のコード、初期化済みデータ、未初期化データなどが格納されます。
.text
セクションは通常、実行可能なコードを含みます。
addr2line
コマンド
addr2line
は、プログラムの実行中に発生したエラーのスタックトレースなどで表示されるメモリアドレスを、対応するソースファイル名と行番号に変換するために使用されるユーティリティです。デバッグ時に、クラッシュした場所や特定の関数が呼び出された場所を特定するのに非常に役立ちます。このツールは、実行ファイル内に含まれるデバッグ情報(DWARF形式など)を解析してアドレスとソースコードのマッピングを行います。
objdump
コマンド
objdump
は、オブジェクトファイルや実行ファイルの情報を表示するためのユーティリティです。これには、逆アセンブルされたコード、セクションヘッダ、シンボルテーブル、リロケーションエントリなどが含まれます。開発者がバイナリの内部構造を理解したり、最適化の効果を確認したりする際に利用されます。
技術的詳細
このコミットの技術的な核心は、PEファイルのメモリロードメカニズムの正確な理解と、それに基づくアドレス計算の修正です。
PEファイルがWindowsによってメモリにロードされる際、オペレーティングシステムはまず、PEヘッダのオプションヘッダに指定されたImageBase
アドレスにファイルをロードしようとします。このImageBase
は、実行ファイル全体がメモリ上で開始する仮想アドレスです。
各セクション(例: .text
、.data
)は、そのセクションヘッダ内にVirtualAddress
というフィールドを持っています。このVirtualAddress
は、ImageBase
からの相対的なオフセット(RVA)として定義されています。つまり、セクションが実際にメモリにロードされる絶対アドレスは、ImageBase + VirtualAddress
によって計算されます。
以前のaddr2line
とobjdump
の実装では、.text
セクションの開始アドレスをsect.VirtualAddress
のみで取得していました。これは、ImageBase
が0であるか、あるいはVirtualAddress
が既に絶対アドレスとして扱われるような特定の状況下では問題ないかもしれませんが、一般的なPEファイルの構造においては不正確です。特に、ImageBase
が非ゼロの値を持つ場合、この計算誤りにより、ツールが.text
セクションのコードを誤ったメモリ位置にあると認識し、結果としてデバッグ情報の解析や逆アセンブルが失敗する原因となっていました。
この修正では、pe.NewFile(f)
でPEファイルをパースした後、obj.OptionalHeader
からImageBase
を取得するロジックが追加されました。PEファイルには32ビット版と64ビット版があり、それぞれpe.OptionalHeader32
とpe.OptionalHeader64
という異なる構造体でオプションヘッダが表現されます。このため、switch
文を使って適切なヘッダタイプを判別し、そこからImageBase
フィールドを抽出しています。
最終的に、.text
セクションのVirtualAddress
にこの取得したImageBase
を加算することで、メモリ上の正しい絶対開始アドレスtextStart
を計算しています。
// 修正前:
// textStart = uint64(sect.VirtualAddress)
// 修正後:
// var imageBase uint64
// switch oh := obj.OptionalHeader.(type) {
// case *pe.OptionalHeader32:
// imageBase = uint64(oh.ImageBase)
// case *pe.OptionalHeader64:
// imageBase = oh.ImageBase
// default:
// return 0, nil, nil, fmt.Errorf("pe file format not recognized")
// }
// if sect := obj.Section(".text"); sect != nil {
// textStart = imageBase + uint64(sect.VirtualAddress)
// }
この変更により、addr2line
とobjdump
はWindowsのPEファイルをより正確に解析できるようになり、Windows環境でのGo言語開発におけるデバッグとバイナリ解析の信頼性が向上しました。
コアとなるコードの変更箇所
このコミットでは、src/cmd/addr2line/main.go
とsrc/cmd/objdump/main.go
の2つのファイルが変更されています。両ファイルにおけるloadTables
関数内で、PEファイルの.text
セクションの開始アドレスを計算するロジックが修正されています。
src/cmd/addr2line/main.go
の変更点
--- a/src/cmd/addr2line/main.go
+++ b/src/cmd/addr2line/main.go
@@ -138,8 +138,17 @@ func loadTables(f *os.File) (textStart uint64, symtab, pclntab []byte, err error)
}\n
if obj, err := pe.NewFile(f); err == nil {
+ var imageBase uint64
+ switch oh := obj.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ imageBase = uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ imageBase = oh.ImageBase
+ default:
+ return 0, nil, nil, fmt.Errorf("pe file format not recognized")
+ }
if sect := obj.Section(".text"); sect != nil {
- textStart = uint64(sect.VirtualAddress)
+ textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
return 0, nil, nil, err
src/cmd/objdump/main.go
の変更点
--- a/src/cmd/objdump/main.go
+++ b/src/cmd/objdump/main.go
@@ -318,8 +318,17 @@ func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte,
}\n
if obj, err := pe.NewFile(f); err == nil {
+ var imageBase uint64
+ switch oh := obj.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ imageBase = uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ imageBase = oh.ImageBase
+ default:
+ return 0, nil, nil, nil, fmt.Errorf("pe file format not recognized")
+ }
if sect := obj.Section(".text"); sect != nil {
- textStart = uint64(sect.VirtualAddress)
+ textStart = imageBase + uint64(sect.VirtualAddress)
textData, _ = sect.Data()
}
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
コアとなるコードの解説
両方のファイルで、loadTables
関数は実行ファイルからシンボルテーブルやPCLNテーブル(Goのプロファイリング情報)などのデバッグ関連情報をロードする役割を担っています。この関数内で、PEファイルが検出された場合に以下の変更が加えられました。
-
imageBase
変数の導入:uint64
型のimageBase
変数が新しく宣言されました。この変数は、PEファイルがメモリにロードされる際のベースアドレスを保持します。 -
オプションヘッダの型判定と
ImageBase
の取得:obj.OptionalHeader
はインターフェース型であり、実際の型はPEファイルのビット数(32ビットまたは64ビット)によって異なります。switch oh := obj.OptionalHeader.(type)
構文を使用して、obj.OptionalHeader
の具体的な型を判定しています。case *pe.OptionalHeader32:
: オプションヘッダが32ビット版の場合、oh.ImageBase
をuint64
にキャストしてimageBase
に代入します。case *pe.OptionalHeader64:
: オプションヘッダが64ビット版の場合、oh.ImageBase
を直接imageBase
に代入します。default:
: 認識できないPEファイル形式の場合、エラーを返します。これは、将来的に新しいPEヘッダのバリアントが登場した場合に備えた堅牢なエラーハンドリングです。
-
.text
セクション開始アドレスの修正計算:- 以前は、
.text
セクションのVirtualAddress
を直接textStart
として使用していました。 - 修正後は、
textStart = imageBase + uint64(sect.VirtualAddress)
という計算式に変更されました。これにより、セクションの仮想アドレスがImageBase
からの相対オフセットとして正しく解釈され、メモリ上の絶対アドレスがtextStart
に設定されるようになりました。
- 以前は、
この変更により、addr2line
とobjdump
は、WindowsのPE実行ファイルにおいて、コードセクションの正確なメモリ開始アドレスを特定できるようになり、デバッグ情報のマッピングや逆アセンブルが正しく行われるようになりました。
関連リンク
- Go言語の
debug/pe
パッケージのドキュメント: https://pkg.go.dev/debug/pe - Microsoft Portable Executable and Common Object File Format Specification: (公式ドキュメントへの直接リンクは変動する可能性があるため、検索エンジンで「Microsoft Portable Executable and Common Object File Format Specification」と検索することを推奨します。)
参考にした情報源リンク
- PEファイル形式に関する一般的な情報源(例: Wikipedia, Microsoft Learnドキュメント)
- Go言語の
debug/pe
パッケージのソースコード addr2line
およびobjdump
コマンドの一般的な動作に関する情報