[インデックス 19185] ファイルの概要
このコミットは、Go言語の debug/pe
パッケージにオプションヘッダの情報を追加するものです。これにより、cmd/nm
ツールがシンボルの絶対アドレスを計算するために必要な情報にアクセスできるようになります。具体的には、PE (Portable Executable) ファイルのオプションヘッダ(PE32またはPE32+)を debug/pe.File
構造体に取り込み、その解析ロジックとテストを追加しています。
コミット
debug/pe: add optional header to File
This information is required by cmd/nm
to calculate absolute symbol addresses.
Update #6936
Update #7738
LGTM=rsc
R=golang-codereviews, bradfitz, gobot, rsc
CC=golang-codereviews
https://golang.org/cl/87500043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/387895f9ac5d34bfd32f23c1640b598c105bcb73
元コミット内容
commit 387895f9ac5d34bfd32f23c1640b598c105bcb73
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Wed Apr 16 22:10:59 2014 -0400
debug/pe: add optional header to File
This information is required by cmd/nm
to calculate absolute symbol addresses.
Update #6936
Update #7738
LGTM=rsc
R=golang-codereviews, bradfitz, gobot, rsc
CC=golang-codereviews
https://golang.org/cl/87500043
---
src/pkg/debug/pe/file.go | 35 ++++++-\
src/pkg/debug/pe/file_test.go | 139 ++++++++++++++++++++++---\
src/pkg/debug/pe/pe.go | 72 +++++++++++++\
src/pkg/debug/pe/testdata/gcc-amd64-mingw-exec | Bin 0 -> 37376 bytes
src/pkg/debug/pe/testdata/gcc-amd64-mingw-obj | Bin 0 -> 736 bytes
5 files changed, 226 insertions(+), 20 deletions(-)
diff --git a/src/pkg/debug/pe/file.go b/src/pkg/debug/pe/file.go
index d0005bacf3..ce6f1408fe 100644
--- a/src/pkg/debug/pe/file.go
+++ b/src/pkg/debug/pe/file.go
@@ -13,13 +13,15 @@ import (
"io"
"os"
"strconv"
+ "unsafe"
)
// A File represents an open PE file.
type File struct {
FileHeader
- Sections []*Section
- Symbols []*Symbol
+ OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64
+ Sections []*Section
+ Symbols []*Symbol
closer io.Closer
}
@@ -196,10 +198,33 @@ func NewFile(r io.ReaderAt) (*File, error) {
}
}\n
-\t// Process sections.\n+\t// Read optional header.\n \tsr.Seek(base, os.SEEK_SET)\n-\tbinary.Read(sr, binary.LittleEndian, &f.FileHeader)\n-\tsr.Seek(int64(f.FileHeader.SizeOfOptionalHeader), os.SEEK_CUR) //Skip OptionalHeader\n+\tif err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {\n+\t\treturn nil, err\n+\t}\n+\tvar oh32 OptionalHeader32\n+\tvar oh64 OptionalHeader64\n+\tswitch uintptr(f.FileHeader.SizeOfOptionalHeader) {\n+\tcase unsafe.Sizeof(oh32):\n+\t\tif err := binary.Read(sr, binary.LittleEndian, &oh32); err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\tif oh32.Magic != 0x10b { // PE32\n+\t\t\treturn nil, fmt.Errorf(\"pe32 optional header has unexpected Magic of 0x%x\", oh32.Magic)\n+\t\t}\n+\t\tf.OptionalHeader = &oh32\n+\tcase unsafe.Sizeof(oh64):\n+\t\tif err := binary.Read(sr, binary.LittleEndian, &oh64); err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\tif oh64.Magic != 0x20b { // PE32+\n+\t\t\treturn nil, fmt.Errorf(\"pe32+ optional header has unexpected Magic of 0x%x\", oh64.Magic)\n+\t\t}\n+\t\tf.OptionalHeader = &oh64\n+\t}\n+\n+\t// Process sections.\n \tf.Sections = make([]*Section, f.FileHeader.NumberOfSections)\n \tfor i := 0; i < int(f.FileHeader.NumberOfSections); i++ {\n \t\tsh := new(SectionHeader32)\ndiff --git a/src/pkg/debug/pe/file_test.go b/src/pkg/debug/pe/file_test.go
index c0f9fcb95d..ddbb271744 100644
--- a/src/pkg/debug/pe/file_test.go
+++ b/src/pkg/debug/pe/file_test.go
@@ -12,6 +12,7 @@ import (
type fileTest struct {
file string
hdr FileHeader
+ opthdr interface{}
sections []*SectionHeader
symbols []*Symbol
}
@@ -20,6 +21,7 @@ var fileTests = []fileTest{
{
"testdata/gcc-386-mingw-obj",
FileHeader{0x014c, 0x000c, 0x0, 0x64a, 0x1e, 0x0, 0x104},
+ nil,
[]*SectionHeader{
{".text", 0, 0, 36, 500, 1440, 0, 3, 0, 0x60300020},
{".data", 0, 0, 0, 0, 0, 0, 0, 0, 3224371264},
@@ -56,27 +58,130 @@ var fileTests = []fileTest{
{
"testdata/gcc-386-mingw-exec",
FileHeader{0x014c, 0x000f, 0x4c6a1b60, 0x3c00, 0x282, 0xe0, 0x107},
+ &OptionalHeader32{
+ 0x10b, 0x2, 0x38, 0xe00, 0x1a00, 0x200, 0x1160, 0x1000, 0x2000, 0x400000, 0x1000, 0x200, 0x4, 0x0, 0x1, 0x0, 0x4, 0x0, 0x0, 0x10000, 0x400, 0x14abb, 0x3, 0x0, 0x200000, 0x1000, 0x100000, 0x1000, 0x0, 0x10,
+ [16]DataDirectory{
+ {0x0, 0x0},
+ {0x5000, 0x3c8},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x7000, 0x18},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ {0x0, 0x0},
+ },
+ },
+ []*SectionHeader{
+ {".text", 0xcd8, 0x1000, 0xe00, 0x400, 0x0, 0x0, 0x0, 0x0, 0x60500060},
+ {".data", 0x10, 0x2000, 0x200, 0x1200, 0x0, 0x0, 0x0, 0x0, 0xc0300040},
+ {".rdata", 0x120, 0x3000, 0x200, 0x1400, 0x0, 0x0, 0x0, 0x0, 0x40300040},
+ {".bss", 0xdc, 0x4000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0400080},
+ {".idata", 0x3c8, 0x5000, 0x400, 0x1600, 0x0, 0x0, 0x0, 0x0, 0xc0300040},
+ {".CRT", 0x18, 0x6000, 0x200, 0x1a00, 0x0, 0x0, 0x0, 0x0, 0xc0300040},
+ {".tls", 0x20, 0x7000, 0x200, 0x1c00, 0x0, 0x0, 0x0, 0x0, 0xc0300040},
+ {".debug_aranges", 0x20, 0x8000, 0x200, 0x1e00, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_pubnames", 0x51, 0x9000, 0x200, 0x2000, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_pubtypes", 0x91, 0xa000, 0x200, 0x2200, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_info", 0xe22, 0xb000, 0x1000, 0x2400, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_abbrev", 0x157, 0xc000, 0x200, 0x3400, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_line", 0x144, 0xd000, 0x200, 0x3600, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ {".debug_frame", 0x34, 0xe000, 0x200, 0x3800, 0x0, 0x0, 0x0, 0x0, 0x42300000},
+ {".debug_loc", 0x38, 0xf000, 0x200, 0x3a00, 0x0, 0x0, 0x0, 0x0, 0x42100000},
+ },\n\t\t[]*Symbol{},\n\t},\n\t{\n\t\t"testdata/gcc-amd64-mingw-obj",\n\t\tFileHeader{0x8664, 0x6, 0x0, 0x198, 0x12, 0x0, 0x4},\n\t\tnil,\n\t\t[]*SectionHeader{\n\t\t\t{".text", 0x0, 0x0, 0x30, 0x104, 0x15c, 0x0, 0x3, 0x0, 0x60500020},\n\t\t\t{".data", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0500040},\n\t\t\t{".bss", 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0500080},\n\t\t\t{".rdata", 0x0, 0x0, 0x10, 0x134, 0x0, 0x0, 0x0, 0x0, 0x40500040},\n\t\t\t{".xdata", 0x0, 0xc, 0x144, 0x0, 0x0, 0x0, 0x0, 0x40300040},\n\t\t\t{".pdata", 0x0, 0xc, 0x150, 0x17a, 0x0, 0x3, 0x0, 0x40300040},\n\t\t},\n\t\t[]*Symbol{\n\t\t\t{".file", 0x0, -2, 0x0, 0x67},\n\t\t\t{"main", 0x0, 1, 0x20, 0x2},\n\t\t\t{".text", 0x0, 1, 0x0, 0x3},\n\t\t\t{".data", 0x0, 2, 0x0, 0x3},\n\t\t\t{".bss", 0x0, 3, 0x0, 0x3},\n\t\t\t{".rdata", 0x0, 4, 0x0, 0x3},\n\t\t\t{".xdata", 0x0, 5, 0x0, 0x3},\n\t\t\t{".pdata", 0x0, 6, 0x0, 0x3},\n\t\t\t{"__main", 0x0, 0, 0x20, 0x2},\n\t\t\t{"puts", 0x0, 0, 0x20, 0x2},\n\t\t},\n\t},\n\t{\n\t\t"testdata/gcc-amd64-mingw-exec",\n\t\tFileHeader{0x8664, 0x9, 0x53472993, 0x0, 0x0, 0xf0, 0x22f},\n\t\t&OptionalHeader64{\n\t\t\t0x20b, 0x2, 0x16, 0x6a00, 0x2400, 0x1600, 0x14e0, 0x1000, 0x400000, 0x1000, 0x200, 0x4, 0x0, 0x0, 0x0, 0x5, 0x2, 0x0, 0x11000, 0x400, 0x1841e, 0x3, 0x0, 0x200000, 0x1000, 0x100000, 0x1000, 0x0, 0x10,\n\t\t\t[16]DataDirectory{\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0xe000, 0x990},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0xa000, 0x498},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x10000, 0x28},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0xe254, 0x218},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t\t{0x0, 0x0},\n\t\t\t},\n\t\t},\n\t\t[]*SectionHeader{\n-\t\t\t{Name: ".text", VirtualSize: 0xcd8, VirtualAddress: 0x1000, Size: 0xe00, Offset: 0x400, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x60500060},\n-\t\t\t{Name: ".data", VirtualSize: 0x10, VirtualAddress: 0x2000, Size: 0x200, Offset: 0x1200, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0xc0300040},\n-\t\t\t{Name: ".rdata", VirtualSize: 0x120, VirtualAddress: 0x3000, Size: 0x200, Offset: 0x1400, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x40300040},\n-\t\t\t{Name: ".bss", VirtualSize: 0xdc, VirtualAddress: 0x4000, Size: 0x0, Offset: 0x0, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0xc0400080},\n-\t\t\t{Name: ".idata", VirtualSize: 0x3c8, VirtualAddress: 0x5000, Size: 0x400, Offset: 0x1600, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0xc0300040},\n-\t\t\t{Name: ".CRT", VirtualSize: 0x18, VirtualAddress: 0x6000, Size: 0x200, Offset: 0x1a00, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0xc0300040},\n-\t\t\t{Name: ".tls", VirtualSize: 0x20, VirtualAddress: 0x7000, Size: 0x200, Offset: 0x1c00, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0xc0300040},\n-\t\t\t{Name: ".debug_aranges", VirtualSize: 0x20, VirtualAddress: 0x8000, Size: 0x200, Offset: 0x1e00, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_pubnames", VirtualSize: 0x51, VirtualAddress: 0x9000, Size: 0x200, Offset: 0x2000, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_pubtypes", VirtualSize: 0x91, VirtualAddress: 0xa000, Size: 0x200, Offset: 0x2200, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_info", VirtualSize: 0xe22, VirtualAddress: 0xb000, Size: 0x1000, Offset: 0x2400, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_abbrev", VirtualSize: 0x157, VirtualAddress: 0xc000, Size: 0x200, Offset: 0x3400, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_line", VirtualSize: 0x144, VirtualAddress: 0xd000, Size: 0x200, Offset: 0x3600, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n-\t\t\t{Name: ".debug_frame", VirtualSize: 0x34, VirtualAddress: 0xe000, Size: 0x200, Offset: 0x3800, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42300000},\n-\t\t\t{Name: ".debug_loc", VirtualSize: 0x38, VirtualAddress: 0xf000, Size: 0x200, Offset: 0x3a00, PointerToRelocations: 0x0, PointerToLineNumbers: 0x0, NumberOfRelocations: 0x0, NumberOfLineNumbers: 0x0, Characteristics: 0x42100000},\n+\t\t\t{".text", 0x6860, 0x1000, 0x6a00, 0x400, 0x0, 0x0, 0x0, 0x0, 0x60500020},\n+\t\t\t{".data", 0xe0, 0x8000, 0x200, 0x6e00, 0x0, 0x0, 0x0, 0x0, 0xc0500040},\n+\t\t\t{".rdata", 0x6b0, 0x9000, 0x800, 0x7000, 0x0, 0x0, 0x0, 0x0, 0x40600040},\n+\t\t\t{".pdata", 0x498, 0xa000, 0x600, 0x7800, 0x0, 0x0, 0x0, 0x0, 0x40300040},\n+\t\t\t{".xdata", 0x488, 0xb000, 0x600, 0x7e00, 0x0, 0x0, 0x0, 0x0, 0x40300040},\n+\t\t\t{".bss", 0x1410, 0xc000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0600080},\n+\t\t\t{".idata", 0x990, 0xe000, 0xa00, 0x8400, 0x0, 0x0, 0x0, 0x0, 0xc0300040},\n+\t\t\t{".CRT", 0x68, 0xf000, 0x200, 0x8e00, 0x0, 0x0, 0x0, 0x0, 0xc0400040},\n+\t\t\t{".tls", 0x48, 0x10000, 0x200, 0x9000, 0x0, 0x0, 0x0, 0x0, 0xc0600040},\n \t\t},\n \t\t[]*Symbol{},\n \t},\n }\n \n+func isOptHdrEq(a, b interface{}) bool {\n+\tswitch va := a.(type) {\n+\tcase *OptionalHeader32:\n+\t\tvb, ok := b.(*OptionalHeader32)\n+\t\tif !ok {\n+\t\t\treturn false\n+\t\t}\n+\t\treturn *vb == *va\n+\tcase *OptionalHeader64:\n+\t\tvb, ok := b.(*OptionalHeader64)\n+\t\tif !ok {\n+\t\t\treturn false\n+\t\t}\n+\t\treturn *vb == *va\n+\tcase nil:\n+\t\treturn b == nil\n+\t}\n+\treturn false\n+}\n+\n func TestOpen(t *testing.T) {\n \tfor i := range fileTests {\n \t\ttt := &fileTests[i]\n@@ -90,6 +195,10 @@ func TestOpen(t *testing.T) {\n \t\t\tt.Errorf("open %s:\\n\\thave %#v\\n\\twant %#v\\n", tt.file, f.FileHeader, tt.hdr)\n \t\t\tcontinue\n \t\t}\n+\t\tif !isOptHdrEq(tt.opthdr, f.OptionalHeader) {\n+\t\t\tt.Errorf("open %s:\\n\\thave %#v\\n\\twant %#v\\n", tt.file, f.OptionalHeader, tt.opthdr)\n+\t\t\tcontinue\n+\t\t}\n \n \t\tfor i, sh := range f.Sections {\n \t\t\tif i >= len(tt.sections) {\ndiff --git a/src/pkg/debug/pe/pe.go b/src/pkg/debug/pe/pe.go
index 0606217b3b..8e90b1b513 100644
--- a/src/pkg/debug/pe/pe.go
+++ b/src/pkg/debug/pe/pe.go
@@ -14,6 +14,78 @@ type FileHeader struct {
Characteristics uint16
}\n
+type DataDirectory struct {
+\tVirtualAddress uint32
+\tSize uint32
+}\n+\n+type OptionalHeader32 struct {
+\tMagic uint16
+\tMajorLinkerVersion uint8
+\tMinorLinkerVersion uint8
+\tSizeOfCode uint32
+\tSizeOfInitializedData uint32
+\tSizeOfUninitializedData uint32
+\tAddressOfEntryPoint uint32
+\tBaseOfCode uint32
+\tBaseOfData uint32
+\tImageBase uint32
+\tSectionAlignment uint32
+\tFileAlignment uint32
+\tMajorOperatingSystemVersion uint16
+\tMinorOperatingSystemVersion uint16
+\tMajorImageVersion uint16
+\tMinorImageVersion uint16
+\tMajorSubsystemVersion uint16
+\tMinorSubsystemVersion uint16
+\tWin32VersionValue uint32
+\tSizeOfImage uint32
+\tSizeOfHeaders uint32
+\tCheckSum uint32
+\tSubsystem uint16
+\tDllCharacteristics uint16
+\tSizeOfStackReserve uint32
+\tSizeOfStackCommit uint32
+\tSizeOfHeapReserve uint32
+\tSizeOfHeapCommit uint32
+\tLoaderFlags uint32
+\tNumberOfRvaAndSizes uint32
+\tDataDirectory [16]DataDirectory
+}\n+\n+type OptionalHeader64 struct {
+\tMagic uint16
+\tMajorLinkerVersion uint8
+\tMinorLinkerVersion uint8
+\tSizeOfCode uint32
+\tSizeOfInitializedData uint32
+\tSizeOfUninitializedData uint32
+\tAddressOfEntryPoint uint32
+\tBaseOfCode uint32
+\tImageBase uint64
+\tSectionAlignment uint32
+\tFileAlignment uint32
+\tMajorOperatingSystemVersion uint16
+\tMinorOperatingSystemVersion uint16
+\tMajorImageVersion uint16
+\tMinorImageVersion uint16
+\tMajorSubsystemVersion uint16
+\tMinorSubsystemVersion uint16
+\tWin32VersionValue uint32
+\tSizeOfImage uint32
+\tSizeOfHeaders uint32
+\tCheckSum uint32
+\tSubsystem uint16
+\tDllCharacteristics uint16
+\tSizeOfStackReserve uint64
+\tSizeOfStackCommit uint64
+\tSizeOfHeapReserve uint64
+\tSizeOfHeapCommit uint64
+\tLoaderFlags uint32
+\tNumberOfRvaAndSizes uint32
+\tDataDirectory [16]DataDirectory
+}\n+\n type SectionHeader32 struct {
Name [8]uint8
VirtualSize uint32
diff --git a/src/pkg/debug/pe/testdata/gcc-amd64-mingw-exec b/src/pkg/debug/pe/testdata/gcc-amd64-mingw-exec
new file mode 100644
index 0000000000..78d4e5fed9
Binary files /dev/null and b/src/pkg/debug/pe/testdata/gcc-amd64-mingw-exec differ
diff --git a/src/pkg/debug/pe/testdata/gcc-amd64-mingw-obj b/src/pkg/debug/pe/testdata/gcc-amd64-mingw-obj
new file mode 100644
index 0000000000..48ae7921f3
Binary files /dev/null and b/src/pkg/debug/pe/testdata/gcc-amd64-mingw-obj differ
変更の背景
このコミットの主な背景は、Go言語のツールチェインに含まれる cmd/nm
ツールが、Windowsの実行可能ファイル(PEファイル)内のシンボルの絶対アドレスを正確に計算するために必要な情報が不足していた点にあります。
cmd/nm
は、コンパイルされたGoプログラムのシンボルテーブルを検査し、シンボルのアドレスや型、名前などを表示するユーティリティです。シンボルの絶対アドレスを特定するには、PEファイルのメモリレイアウトに関する詳細な情報が必要となります。この情報は、PEファイルの「オプションヘッダ」に格納されています。
従来の debug/pe
パッケージの File
構造体は、PEファイルのヘッダ情報の一部(FileHeader
)は保持していましたが、シンボルアドレスの計算に不可欠なオプションヘッダの詳細は含まれていませんでした。そのため、cmd/nm
がPEファイルを解析する際に、必要なアドレス関連の情報を取得できず、正確なシンボルアドレスを提供できないという問題がありました。
このコミットは、debug/pe
パッケージがPEファイルのオプションヘッダを適切に解析し、その情報を File
構造体を通じて利用可能にすることで、cmd/nm
がシンボルの絶対アドレスを計算できるようにするための基盤を整備することを目的としています。コミットメッセージに記載されている Update #6936
および Update #7738
は、この機能追加に関連する既存の課題や要望を示唆していると考えられますが、公開されているGoリポジトリのIssueトラッカーでは直接的な一致が見つかりませんでした。これは、内部的なトラッカーの参照であるか、あるいは非常に古い課題である可能性が考えられます。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
1. PE (Portable Executable) ファイルフォーマット
PEファイルフォーマットは、Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、DLL (Dynamic Link Library)、オブジェクトファイルなどの標準フォーマットです。PEファイルは、プログラムのコード、データ、リソース、およびそれらをメモリにロードして実行するために必要なメタデータを含んでいます。
PEファイルの主要な構造は以下の通りです。
- DOS Header: 互換性のために存在する古いDOS実行可能ファイルのヘッダ。
- DOS Stub: DOS環境で実行された場合に表示される小さなプログラム。
- PE Signature: "PE\0\0"というシグネチャ。
- COFF File Header: CPUアーキテクチャ、セクションの数、タイムスタンプなどの基本的なファイル情報。
- Optional Header: PEファイルのメモリレイアウト、エントリポイント、データディレクトリなど、実行時に重要な情報が含まれます。このヘッダは、PE32(32ビット)とPE32+(64ビット)の2つの形式があります。
- Section Headers: 各セクション(コード、データ、リソースなど)のメモリ上の位置、サイズ、属性などの情報。
- Sections: 実際のコードやデータが格納されている領域。
2. Optional Header (オプションヘッダ)
PEファイルのオプションヘッダは、実行可能ファイルのメモリ上の配置や動作に関する詳細な情報を提供します。これは、PEローダーがファイルをメモリにマッピングし、実行を開始するために不可欠なデータを含んでいます。
主要なフィールドには以下のようなものがあります。
- Magic: オプションヘッダのタイプ(PE32:
0x10b
、PE32+:0x20b
)を示すマジックナンバー。 - AddressOfEntryPoint: プログラムの実行が開始される相対仮想アドレス (RVA)。
- ImageBase: プログラムがメモリにロードされる推奨ベースアドレス。
- SectionAlignment: メモリ内のセクションのアライメント。
- FileAlignment: ファイル内のセクションのアライメント。
- DataDirectory: エクスポートテーブル、インポートテーブル、リソーステーブル、デバッグ情報など、重要なデータ構造へのポインタ(RVAとサイズ)の配列。
シンボルの絶対アドレスを計算する際には、ImageBase
と AddressOfEntryPoint
、そして各セクションの仮想アドレスとサイズ、さらにデータディレクトリの情報が組み合わせて使用されます。
3. シンボルテーブルと cmd/nm
ツール
- シンボルテーブル: コンパイルされたプログラムには、関数名、変数名、セクション名などの「シンボル」とそのメモリ上のアドレスをマッピングしたテーブルが含まれています。デバッガやリンカ、その他の開発ツールは、このシンボルテーブルを利用して、プログラムの構造を理解したり、特定のコードやデータにアクセスしたりします。
cmd/nm
: Go言語のツールチェインに含まれるgo tool nm
は、Unix系のnm
コマンドに似たユーティリティです。Goのオブジェクトファイル、アーカイブ、または実行可能ファイル内のシンボルをリストアップします。このツールは、デバッグやバイナリの構造解析、依存関係の理解に役立ちます。シンボルの絶対アドレスを表示するためには、PEファイルの正確なメモリレイアウト情報が必要となります。
4. debug/pe
パッケージ
Go言語の標準ライブラリ debug/pe
パッケージは、WindowsのPEファイルを解析するための機能を提供します。このパッケージを使用することで、GoプログラムからPEファイルのヘッダ、セクション、インポート/エクスポート情報などにアクセスできます。このコミット以前は、File
構造体には FileHeader
は含まれていましたが、オプションヘッダの詳細は直接的に表現されていませんでした。
技術的詳細
このコミットの技術的な核心は、debug/pe
パッケージがPEファイルのオプションヘッダを正確に読み込み、Goのデータ構造にマッピングすることにあります。
-
File
構造体の拡張:src/pkg/debug/pe/file.go
内のFile
構造体にOptionalHeader interface{}
フィールドが追加されました。このフィールドは、PE32ファイルの場合は*OptionalHeader32
型のポインタ、PE32+(64ビット)ファイルの場合は*OptionalHeader64
型のポインタを保持するように設計されています。interface{}
を使用することで、32ビットと64ビットの異なる構造体を柔軟に扱うことができます。 -
オプションヘッダの読み込みロジック:
NewFile
関数(src/pkg/debug/pe/file.go
)内で、PEファイルの読み込み処理中にオプションヘッダを解析するロジックが追加されました。- まず、
FileHeader
を読み込みます。FileHeader
にはSizeOfOptionalHeader
というフィールドがあり、これによってオプションヘッダのサイズがわかります。 SizeOfOptionalHeader
の値と、unsafe.Sizeof
を用いてOptionalHeader32
およびOptionalHeader64
構造体の実際のサイズを比較します。これにより、現在のPEファイルがPE32形式かPE32+形式かを判断します。- 適切な構造体(
OptionalHeader32
またはOptionalHeader64
)をバイナリリードし、そのマジックナンバー(0x10b
または0x20b
)を検証します。マジックナンバーが期待値と異なる場合はエラーを返します。 - 読み込んだオプションヘッダのポインタを
f.OptionalHeader
に格納します。
- まず、
-
新しい構造体の定義:
src/pkg/debug/pe/pe.go
に、DataDirectory
、OptionalHeader32
、OptionalHeader64
の各構造体が新しく定義されました。DataDirectory
は、PEファイルのデータディレクトリのエントリ(仮想アドレスとサイズ)を表します。OptionalHeader32
とOptionalHeader64
は、それぞれ32ビットと64ビットのPEオプションヘッダの全フィールドをGoの構造体として厳密に定義しています。これらの構造体には、AddressOfEntryPoint
やImageBase
、そしてDataDirectory
の配列など、シンボルアドレス計算に不可欠な情報が含まれています。
-
テストケースの追加と更新:
src/pkg/debug/pe/file_test.go
に、新しいテストデータ(gcc-amd64-mingw-exec
とgcc-amd64-mingw-obj
)が追加され、既存のテスト構造体fileTest
にopthdr interface{}
フィールドが追加されました。TestOpen
関数では、新しく追加されたisOptHdrEq
ヘルパー関数を使用して、解析されたオプションヘッダが期待される値と一致するかどうかが検証されます。これにより、オプションヘッダの正確な読み込みとパースが保証されます。- バイナリテストデータファイル (
testdata/gcc-amd64-mingw-exec
とtestdata/gcc-amd64-mingw-obj
) が追加され、実際のPEファイルに対するテストが可能になりました。
この変更により、debug/pe
パッケージはPEファイルのより完全な情報をGoのプログラムで扱えるようになり、cmd/nm
のようなツールがシンボルの絶対アドレスを正確に解決するための基盤が提供されました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/debug/pe/file.go
:File
構造体にOptionalHeader interface{}
フィールドが追加されました。NewFile
関数内で、FileHeader
の読み込み後にSizeOfOptionalHeader
を基にOptionalHeader32
またはOptionalHeader64
を読み込むロジックが追加されました。unsafe
パッケージがインポートされました。
-
src/pkg/debug/pe/pe.go
:DataDirectory
構造体が新しく定義されました。OptionalHeader32
構造体が新しく定義されました。OptionalHeader64
構造体が新しく定義されました。
-
src/pkg/debug/pe/file_test.go
:fileTest
構造体にopthdr interface{}
フィールドが追加されました。isOptHdrEq
ヘルパー関数が追加され、OptionalHeader32
またはOptionalHeader64
の内容を比較できるようになりました。TestOpen
関数内で、OptionalHeader
の比較を行うテストロジックが追加されました。- 新しいテストデータ (
testdata/gcc-amd64-mingw-exec
,testdata/gcc-amd64-mingw-obj
) の定義と、それに対応する期待されるOptionalHeader32
およびOptionalHeader64
の値が追加されました。
コアとなるコードの解説
src/pkg/debug/pe/file.go
の変更点
// A File represents an open PE file.
type File struct {
FileHeader
OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64
Sections []*Section
Symbols []*Symbol
closer io.Closer
}
func NewFile(r io.ReaderAt) (*File, error) {
// ... (既存のコード) ...
// Read optional header.
sr.Seek(base, os.SEEK_SET) // ファイルのベース位置にシーク
if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
return nil, err
}
var oh32 OptionalHeader32
var oh64 OptionalHeader64
switch uintptr(f.FileHeader.SizeOfOptionalHeader) {
case unsafe.Sizeof(oh32): // オプションヘッダのサイズがPE32のサイズと一致する場合
if err := binary.Read(sr, binary.LittleEndian, &oh32); err != nil {
return nil, err
}
if oh32.Magic != 0x10b { // PE32のマジックナンバーを検証
return nil, fmt.Errorf("pe32 optional header has unexpected Magic of 0x%x", oh32.Magic)
}
f.OptionalHeader = &oh32 // PE32ヘッダを格納
case unsafe.Sizeof(oh64): // オプションヘッダのサイズがPE32+のサイズと一致する場合
if err := binary.Read(sr, binary.LittleEndian, &oh64); err != nil {
return nil, err
}
if oh64.Magic != 0x20b { // PE32+のマジックナンバーを検証
return nil, fmt.Errorf("pe32+ optional header has unexpected Magic of 0x%x", oh64.Magic)
}
f.OptionalHeader = &oh64 // PE32+ヘッダを格納
}
// Process sections. (セクションの処理はオプションヘッダの後に続く)
f.Sections = make([]*Section, f.FileHeader.NumberOfSections)
// ... (既存のコード) ...
}
File
構造体に OptionalHeader
が追加され、NewFile
関数内で FileHeader.SizeOfOptionalHeader
を基に32ビットまたは64ビットのオプションヘッダを動的に読み込むようになりました。unsafe.Sizeof
を使用して構造体のサイズを比較し、正しいヘッダタイプを識別しています。
src/pkg/debug/pe/pe.go
の変更点
type DataDirectory struct {
VirtualAddress uint32
Size uint32
}
type OptionalHeader32 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
BaseOfData uint32
ImageBase uint32
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint32
SizeOfStackCommit uint32
SizeOfHeapReserve uint32
SizeOfHeapCommit uint32
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory // 重要なデータディレクトリの配列
}
type OptionalHeader64 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
ImageBase uint64 // 64ビットではImageBaseがuint64
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint64 // 64ビットではスタック/ヒープ予約サイズがuint64
SizeOfStackCommit uint64
SizeOfHeapReserve uint64
SizeOfHeapCommit uint64
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory
}
PEファイルのオプションヘッダの構造を正確にGoの構造体として定義しています。特に AddressOfEntryPoint
や ImageBase
、そして DataDirectory
の配列は、シンボルアドレスの計算に直接的に関わる重要な情報です。
src/pkg/debug/pe/file_test.go
の変更点
type fileTest struct {
file string
hdr FileHeader
opthdr interface{} // オプションヘッダのテストデータが追加
sections []*SectionHeader
symbols []*Symbol
}
func isOptHdrEq(a, b interface{}) bool {
switch va := a.(type) {
case *OptionalHeader32:
vb, ok := b.(*OptionalHeader32)
if !ok {
return false
}
return *vb == *va // 32ビットヘッダの値を比較
case *OptionalHeader64:
vb, ok := b.(*OptionalHeader64)
if !ok {
return false
}
return *vb == *va // 64ビットヘッダの値を比較
case nil:
return b == nil
}
return false
}
func TestOpen(t *testing.T) {
for i := range fileTests {
tt := &fileTests[i]
// ... (既存のテストコード) ...
if !isOptHdrEq(tt.opthdr, f.OptionalHeader) {
t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.OptionalHeader, tt.opthdr)
continue
}
// ... (既存のテストコード) ...
}
}
テスト構造体に opthdr
フィールドが追加され、isOptHdrEq
関数によって、読み込んだオプションヘッダが期待される値と一致するかどうかが厳密に検証されます。これにより、新しい解析ロジックの正確性が保証されます。
関連リンク
- Go
debug/pe
パッケージのドキュメント: https://pkg.go.dev/debug/pe - Go
cmd/nm
ツールのドキュメント: https://pkg.go.dev/cmd/nm - Portable Executable (PE) Format - Wikipedia: https://en.wikipedia.org/wiki/Portable_Executable
参考にした情報源リンク
- PE (Portable Executable) file format's optional header symbol addresses - Web Search Results
- Go debug/pe package - Web Search Results
- Go cmd/nm tool - Web Search Results
- Microsoft Docs - PE Format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
- Go issues #6936 and #7738 (直接的な公開情報は見つからず、内部的な参照の可能性)