Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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とサイズ)の配列。

シンボルの絶対アドレスを計算する際には、ImageBaseAddressOfEntryPoint、そして各セクションの仮想アドレスとサイズ、さらにデータディレクトリの情報が組み合わせて使用されます。

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のデータ構造にマッピングすることにあります。

  1. File 構造体の拡張: src/pkg/debug/pe/file.go 内の File 構造体に OptionalHeader interface{} フィールドが追加されました。このフィールドは、PE32ファイルの場合は *OptionalHeader32 型のポインタ、PE32+(64ビット)ファイルの場合は *OptionalHeader64 型のポインタを保持するように設計されています。interface{} を使用することで、32ビットと64ビットの異なる構造体を柔軟に扱うことができます。

  2. オプションヘッダの読み込みロジック: 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 に格納します。
  3. 新しい構造体の定義: src/pkg/debug/pe/pe.go に、DataDirectoryOptionalHeader32OptionalHeader64 の各構造体が新しく定義されました。

    • DataDirectory は、PEファイルのデータディレクトリのエントリ(仮想アドレスとサイズ)を表します。
    • OptionalHeader32OptionalHeader64 は、それぞれ32ビットと64ビットのPEオプションヘッダの全フィールドをGoの構造体として厳密に定義しています。これらの構造体には、AddressOfEntryPointImageBase、そして DataDirectory の配列など、シンボルアドレス計算に不可欠な情報が含まれています。
  4. テストケースの追加と更新: src/pkg/debug/pe/file_test.go に、新しいテストデータ(gcc-amd64-mingw-execgcc-amd64-mingw-obj)が追加され、既存のテスト構造体 fileTestopthdr interface{} フィールドが追加されました。

    • TestOpen 関数では、新しく追加された isOptHdrEq ヘルパー関数を使用して、解析されたオプションヘッダが期待される値と一致するかどうかが検証されます。これにより、オプションヘッダの正確な読み込みとパースが保証されます。
    • バイナリテストデータファイル (testdata/gcc-amd64-mingw-exectestdata/gcc-amd64-mingw-obj) が追加され、実際のPEファイルに対するテストが可能になりました。

この変更により、debug/pe パッケージはPEファイルのより完全な情報をGoのプログラムで扱えるようになり、cmd/nm のようなツールがシンボルの絶対アドレスを正確に解決するための基盤が提供されました。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/debug/pe/file.go:

    • File 構造体に OptionalHeader interface{} フィールドが追加されました。
    • NewFile 関数内で、FileHeader の読み込み後に SizeOfOptionalHeader を基に OptionalHeader32 または OptionalHeader64 を読み込むロジックが追加されました。
    • unsafe パッケージがインポートされました。
  2. src/pkg/debug/pe/pe.go:

    • DataDirectory 構造体が新しく定義されました。
    • OptionalHeader32 構造体が新しく定義されました。
    • OptionalHeader64 構造体が新しく定義されました。
  3. 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の構造体として定義しています。特に AddressOfEntryPointImageBase、そして 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 関数によって、読み込んだオプションヘッダが期待される値と一致するかどうかが厳密に検証されます。これにより、新しい解析ロジックの正確性が保証されます。

関連リンク

参考にした情報源リンク

  • 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 (直接的な公開情報は見つからず、内部的な参照の可能性)