From 78c02cc73064da84dd528220a234e9bd9f133d81 Mon Sep 17 00:00:00 2001 From: jtbennett-fe Date: Thu, 5 Mar 2026 21:29:56 +0000 Subject: [PATCH] add support for Go runtime v1.26 (#85) * clarify Go runtime version vs GoReSym layout version * Add Go 1.26 support * Cleanup debugging prints * Remove remaining debug print in main.go * Fix redundant nil check in pe.go * Final touches: fix sudo in build script and rename layout convention to 1.22 * Run go fmt * Revert unnecessary moduledata scanning changes in executable parsers * refactor fixes * better variable name for refactor * Fix lots of version issues and bugs in magic prefix check, as well as concurrent lock bug * fmt * Refactor slice, textsec, and more to use offsets. Cleanup dead code in internal.go * fmt --------- Co-authored-by: Stephen Eckels --- .gitignore | 3 + build_test_files.sh | 2 +- debug/elf/file.go | 11 +- debug/gosym/pclntab.go | 3 +- debug/macho/file.go | 8 + debug/pe/file.go | 8 + main.go | 1 + main_test.go | 2 +- objfile/elf.go | 2 +- objfile/internals.go | 1234 +-------------------------------------- objfile/layouts.go | 444 ++++++++------ objfile/layouts_test.go | 608 +++---------------- objfile/macho.go | 2 +- objfile/objfile.go | 275 ++++----- objfile/pe.go | 3 +- 15 files changed, 508 insertions(+), 2098 deletions(-) diff --git a/.gitignore b/.gitignore index 5890f31..fd0fbd6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ Dockerfile.test # Dependency directories (remove the comment below to include it) # vendor/ +# Agent files +.agent/ +.agents/ \ No newline at end of file diff --git a/build_test_files.sh b/build_test_files.sh index 5d1500a..65e3f2c 100755 --- a/build_test_files.sh +++ b/build_test_files.sh @@ -1,7 +1,7 @@ #!/bin/bash trap "exit" INT sudo rm -rf $(pwd)/test/build -versions=("1.25" "1.24" "1.23" "1.22" "1.21" "1.20" "1.19" "1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5") +versions=("1.26" "1.25" "1.24" "1.23" "1.22" "1.21" "1.20" "1.19" "1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5") for v in "${versions[@]}" do GO_TAG=$v diff --git a/debug/elf/file.go b/debug/elf/file.go index 11bcc78..a94f816 100644 --- a/debug/elf/file.go +++ b/debug/elf/file.go @@ -16,6 +16,7 @@ import ( "io" "os" "strings" + "sync" "github.com/mandiant/GoReSym/debug/dwarf" "github.com/mandiant/GoReSym/saferio" @@ -61,6 +62,7 @@ type File struct { gnuVersym []byte dataAfterSectionCache map[uint64][]byte // secVA -> dataAfterSection + dataAfterSectionMutex sync.Mutex } // A SectionHeader represents a single ELF section header. @@ -689,9 +691,12 @@ func getString(section []byte, start int) (string, bool) { } func (f *File) DataAfterSection(target *Section) []byte { - if cached, ok := f.dataAfterSectionCache[uint64(target.Addr)]; ok { + f.dataAfterSectionMutex.Lock() + if cached, ok := f.dataAfterSectionCache[target.Addr]; ok { + f.dataAfterSectionMutex.Unlock() return cached } + f.dataAfterSectionMutex.Unlock() data := []byte{} found := false @@ -712,7 +717,9 @@ func (f *File) DataAfterSection(target *Section) []byte { } } - f.dataAfterSectionCache[uint64(target.Addr)] = data + f.dataAfterSectionMutex.Lock() + f.dataAfterSectionCache[target.Addr] = data + f.dataAfterSectionMutex.Unlock() return data } diff --git a/debug/gosym/pclntab.go b/debug/gosym/pclntab.go index a0585e9..9bd6b8a 100644 --- a/debug/gosym/pclntab.go +++ b/debug/gosym/pclntab.go @@ -264,7 +264,8 @@ func (t *LineTable) parsePclnTab(versionOverride string) { t.Version = possibleVersion if len(versionOverride) > 0 { - if strings.Contains(versionOverride, "1.25") || + if strings.Contains(versionOverride, "1.26") || + strings.Contains(versionOverride, "1.25") || strings.Contains(versionOverride, "1.24") || strings.Contains(versionOverride, "1.23") || strings.Contains(versionOverride, "1.22") || diff --git a/debug/macho/file.go b/debug/macho/file.go index 564a921..9e4cd79 100644 --- a/debug/macho/file.go +++ b/debug/macho/file.go @@ -17,6 +17,7 @@ import ( "io" "os" "strings" + "sync" "github.com/mandiant/GoReSym/debug/dwarf" ) @@ -33,6 +34,7 @@ type File struct { closer io.Closer dataAfterSectionCache map[uint64][]byte // secVA -> dataAfterSection + dataAfterSectionMutex sync.Mutex } // A Load represents any Mach-O load command. @@ -575,9 +577,12 @@ func (f *File) Segment(name string) *Segment { } func (f *File) DataAfterSection(target *Section) []byte { + f.dataAfterSectionMutex.Lock() if cached, ok := f.dataAfterSectionCache[target.Addr]; ok { + f.dataAfterSectionMutex.Unlock() return cached } + f.dataAfterSectionMutex.Unlock() data := []byte{} found := false @@ -597,7 +602,10 @@ func (f *File) DataAfterSection(target *Section) []byte { } } } + + f.dataAfterSectionMutex.Lock() f.dataAfterSectionCache[target.Addr] = data + f.dataAfterSectionMutex.Unlock() return data } diff --git a/debug/pe/file.go b/debug/pe/file.go index 5f16fb9..0938382 100644 --- a/debug/pe/file.go +++ b/debug/pe/file.go @@ -15,6 +15,7 @@ import ( "io" "os" "strings" + "sync" "github.com/mandiant/GoReSym/debug/dwarf" ) @@ -33,6 +34,7 @@ type File struct { closer io.Closer dataAfterSectionCache map[uint64][]byte // secVA -> dataAfterSection + dataAfterSectionMutex sync.Mutex } // Open opens the named file using os.Open and prepares it for use as a PE binary. @@ -213,9 +215,12 @@ func (f *File) Section(name string) *Section { } func (f *File) DataAfterSection(target *Section) []byte { + f.dataAfterSectionMutex.Lock() if cached, ok := f.dataAfterSectionCache[uint64(target.VirtualAddress)]; ok { + f.dataAfterSectionMutex.Unlock() return cached } + f.dataAfterSectionMutex.Unlock() data := []byte{} found := false @@ -235,7 +240,10 @@ func (f *File) DataAfterSection(target *Section) []byte { } } } + + f.dataAfterSectionMutex.Lock() f.dataAfterSectionCache[uint64(target.VirtualAddress)] = data + f.dataAfterSectionMutex.Unlock() return data } diff --git a/main.go b/main.go index e652c1f..4ae946e 100644 --- a/main.go +++ b/main.go @@ -233,6 +233,7 @@ restartParseWithRealTextBase: // if that location works, then we must have given it the correct pclntab VA. At least in theory... // The resolved offsets within the pclntab might have used the wrong base though! We'll fix that later. _, tmpModData, err := file.ModuleDataTable(tab.PclntabVA, extractMetadata.Version, extractMetadata.TabMeta.Version, extractMetadata.TabMeta.PointerSize == 8, extractMetadata.TabMeta.Endianess == "LittleEndian") + if err == nil && tmpModData != nil { // if the search candidate relied on a moduledata va, make sure it lines up with ours now stomppedMagicMetaConstraintsValid := true diff --git a/main_test.go b/main_test.go index c8129b3..4fe8ce0 100644 --- a/main_test.go +++ b/main_test.go @@ -11,7 +11,7 @@ import ( _ "net/http/pprof" ) -var versions = []string{"125", "124", "123", "122", "121", "120", "119", "118", "117", "116", "115", "114", "113", "112", "111", "110", "19", "18", "17", "16", "15"} +var versions = []string{"126", "125", "124", "123", "122", "121", "120", "119", "118", "117", "116", "115", "114", "113", "112", "111", "110", "19", "18", "17", "16", "15"} var fileNames = []string{"testproject_lin", "testproject_lin_32", "testproject_lin_stripped", "testproject_lin_stripped_32", "testproject_mac", "testproject_mac_stripped", "testproject_win_32.exe", "testproject_win_stripped_32.exe", "testproject_win_stripped.exe", "testproject_win.exe"} func TestAllVersions(t *testing.T) { diff --git a/objfile/elf.go b/objfile/elf.go index 85df600..6d75b52 100644 --- a/objfile/elf.go +++ b/objfile/elf.go @@ -133,7 +133,7 @@ func (f *elfFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { - if bytes.Equal(candidate.Pclntab, magic) { + if bytes.HasPrefix(candidate.Pclntab, magic) { has_some_valid_magic = true break } diff --git a/objfile/internals.go b/objfile/internals.go index 63b8539..3ac77cc 100644 --- a/objfile/internals.go +++ b/objfile/internals.go @@ -1,15 +1,7 @@ /*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/ package objfile -import ( - "bytes" - "encoding/binary" -) - -type size_t64 uint64 -type size_t32 uint32 type pvoid64 uint64 -type pvoid32 uint32 // All types following this are the binary representation of internal objects. // These are 'flat', i.e. one pointer level deep. Access to pointers and such @@ -21,924 +13,10 @@ type GoSlice64 struct { Capacity uint64 } -func (slice *GoSlice64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, slice) -} - -type GoSlice32 struct { - Data pvoid32 - Len size_t32 - Capacity size_t32 -} - -func (slice *GoSlice32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, slice) -} - -// https://github.com/golang/go/blob/23adc139bf1c0c099dd075da076f5a1f3ac700d4/src/reflect/value.go#L2588 -type GoString64 struct { - Data pvoid64 - Len size_t64 -} - -type GoString32 struct { - Data pvoid32 - Len size_t32 -} - -// https://github.com/golang/go/blob/dbd3cf884986c88f5b3350709c0f51fa02330805/src/runtime/stack.go#L583 -type GoBitVector64 struct { - Bitnum int32 - Bytedata pvoid64 -} - -type GoBitVector32 struct { - Bitnum int32 - Bytedata pvoid32 -} - -// a function table entry in 'ftab' -type FuncTab12_116_64 struct { - Entryoffset pvoid64 // relative to runtime.text, ie. VA - Funcoffset pvoid64 // relative to ftab table start -} - -func (functab *FuncTab12_116_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, functab) -} - -type FuncTab12_116_32 struct { - Entryoffset pvoid32 // relative to runtime.text, ie. VA - Funcoffset pvoid32 // relative to ftab table start -} - -func (functab *FuncTab12_116_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, functab) -} - -type FuncTab118 struct { - Entryoffset uint32 // relative to runtime.text, ie. VA - Funcoffset uint32 // relative to ftab table start -} - -func (functab *FuncTab118) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, functab) -} - -// 1.2, runtime 1.5-1.6, 64bit -type ModuleData12_r15_r16_64 struct { - Pclntable GoSlice64 - Ftab GoSlice64 - Filetab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - - Typelinks GoSlice64 - - Modulename GoString64 - Modulehashes GoSlice64 - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - - Next pvoid64 -} - -func (moduledata *ModuleData12_r15_r16_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData12_r15_r16_32 struct { - Pclntable GoSlice32 - Ftab GoSlice32 - Filetab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - - Typelinks GoSlice32 - - Modulename GoString32 - Modulehashes GoSlice32 - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - - Next pvoid32 -} - -func (moduledata *ModuleData12_r15_r16_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData12_r17_64 struct { - Pclntable GoSlice64 - Ftab GoSlice64 - Filetab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - - Typelinks GoSlice64 - Itablinks GoSlice64 - - Modulename GoString64 - Modulehashes GoSlice64 - - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - - Typemap pvoid64 - Next pvoid64 -} - -func (moduledata *ModuleData12_r17_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData12_r17_32 struct { - Pclntable GoSlice32 - Ftab GoSlice32 - Filetab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - - Typelinks GoSlice32 - Itablinks GoSlice32 - - Modulename GoString32 - Modulehashes GoSlice32 - - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - - Typemap pvoid32 - Next pvoid32 -} - -func (moduledata *ModuleData12_r17_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData12_64 struct { - Pclntable GoSlice64 - Ftab GoSlice64 - FileTab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - Textsectmap GoSlice64 - Typelinks GoSlice64 - Itablinks GoSlice64 - Ptab GoSlice64 - Pluginpath GoString64 - Pkghashes GoSlice64 - Modulename GoString64 - Modulehashes GoSlice64 - Hasmain bool - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - Typemap pvoid64 - Badload bool - Next pvoid64 -} - -func (moduledata *ModuleData12_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData12_32 struct { - Pclntable GoSlice32 - Ftab GoSlice32 - FileTab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - Textsectmap GoSlice32 - Typelinks GoSlice32 - Itablinks GoSlice32 - Ptab GoSlice32 - Pluginpath GoString32 - Pkghashes GoSlice32 - Modulename GoString32 - Modulehashes GoSlice32 - Hasmain bool - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - Typemap pvoid32 - Badload bool - Next pvoid32 -} - -func (moduledata *ModuleData12_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData116_64 struct { - PcHeader pvoid64 - Funcnametab GoSlice64 - Cutab GoSlice64 - Filetab GoSlice64 - Pctab GoSlice64 - Pclntable GoSlice64 - Ftab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - Textsectmap GoSlice64 - Typelinks GoSlice64 - Itablinks GoSlice64 - Ptab GoSlice64 - Pluginpath GoString64 - Pkghashes GoSlice64 - Modulename GoString64 - Modulehashes GoSlice64 - Hasmain bool - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - Typemap pvoid64 - Badload bool - Next pvoid64 -} - -func (moduledata *ModuleData116_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData116_32 struct { - PcHeader pvoid32 - Funcnametab GoSlice32 - Cutab GoSlice32 - Filetab GoSlice32 - Pctab GoSlice32 - Pclntable GoSlice32 - Ftab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - Textsectmap GoSlice32 - Typelinks GoSlice32 - Itablinks GoSlice32 - Ptab GoSlice32 - Pluginpath GoString32 - Pkghashes GoSlice32 - Modulename GoString32 - Modulehashes GoSlice32 - Hasmain bool - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - Typemap pvoid32 - Badload bool - Next pvoid32 -} - -func (moduledata *ModuleData116_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData118_64 struct { - PcHeader pvoid64 - Funcnametab GoSlice64 - Cutab GoSlice64 - Filetab GoSlice64 - Pctab GoSlice64 - Pclntable GoSlice64 - Ftab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - Rodata pvoid64 - Gofunc pvoid64 - Textsectmap GoSlice64 - Typelinks GoSlice64 - Itablinks GoSlice64 - Ptab GoSlice64 - Pluginpath GoString64 - Pkghashes GoSlice64 - Modulename GoString64 - Modulehashes GoSlice64 - Hasmain bool - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - Typemap pvoid64 - Badload bool - Next pvoid64 -} - -func (moduledata *ModuleData118_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData118_32 struct { - PcHeader pvoid32 - Funcnametab GoSlice32 - Cutab GoSlice32 - Filetab GoSlice32 - Pctab GoSlice32 - Pclntable GoSlice32 - Ftab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - Rodata pvoid32 - Gofunc pvoid32 - Textsectmap GoSlice32 - Typelinks GoSlice32 - Itablinks GoSlice32 - Ptab GoSlice32 - Pluginpath GoString32 - Pkghashes GoSlice32 - Modulename GoString32 - Modulehashes GoSlice32 - Hasmain bool - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - Typemap pvoid32 - Badload bool - Next pvoid32 -} - -func (moduledata *ModuleData118_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData120_64 struct { - PcHeader pvoid64 - Funcnametab GoSlice64 - Cutab GoSlice64 - Filetab GoSlice64 - Pctab GoSlice64 - Pclntable GoSlice64 - Ftab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - Covctrs pvoid64 - Ecovctrs pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - Rodata pvoid64 - Gofunc pvoid64 - Textsectmap GoSlice64 - Typelinks GoSlice64 - Itablinks GoSlice64 - Ptab GoSlice64 - Pluginpath GoString64 - Pkghashes GoSlice64 - Modulename GoString64 - Modulehashes GoSlice64 - Hasmain bool - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - Typemap pvoid64 - Badload bool - Next pvoid64 -} - -func (moduledata *ModuleData120_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData120_32 struct { - PcHeader pvoid32 - Funcnametab GoSlice32 - Cutab GoSlice32 - Filetab GoSlice32 - Pctab GoSlice32 - Pclntable GoSlice32 - Ftab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - Covctrs pvoid32 - Ecovctrs pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - Rodata pvoid32 - Gofunc pvoid32 - Textsectmap GoSlice32 - Typelinks GoSlice32 - Itablinks GoSlice32 - Ptab GoSlice32 - Pluginpath GoString32 - Pkghashes GoSlice32 - Modulename GoString32 - Modulehashes GoSlice32 - Hasmain bool - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - Typemap pvoid32 - Badload bool - Next pvoid32 -} - -func (moduledata *ModuleData120_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData121_64 struct { - PcHeader pvoid64 - Funcnametab GoSlice64 - Cutab GoSlice64 - Filetab GoSlice64 - Pctab GoSlice64 - Pclntable GoSlice64 - Ftab GoSlice64 - Findfunctab pvoid64 - Minpc pvoid64 - Maxpc pvoid64 - Text pvoid64 - Etext pvoid64 - Noptrdata pvoid64 - Enoptrdata pvoid64 - Data pvoid64 - Edata pvoid64 - Bss pvoid64 - Ebss pvoid64 - Noptrbss pvoid64 - Enoptrbss pvoid64 - Covctrs pvoid64 - Ecovctrs pvoid64 - End pvoid64 - Gcdata pvoid64 - Gcbss pvoid64 - Types pvoid64 - Etypes pvoid64 - Rodata pvoid64 - Gofunc pvoid64 - Textsectmap GoSlice64 - Typelinks GoSlice64 - Itablinks GoSlice64 - Ptab GoSlice64 - Pluginpath GoString64 - Pkghashes GoSlice64 - InitTasks GoSlice64 - Modulename GoString64 - Modulehashes GoSlice64 - Hasmain bool - Gcdatamask GoBitVector64 - Gcbssmask GoBitVector64 - Typemap pvoid64 - Badload bool - Next pvoid64 -} - -func (moduledata *ModuleData121_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type ModuleData121_32 struct { - PcHeader pvoid32 - Funcnametab GoSlice32 - Cutab GoSlice32 - Filetab GoSlice32 - Pctab GoSlice32 - Pclntable GoSlice32 - Ftab GoSlice32 - Findfunctab pvoid32 - Minpc pvoid32 - Maxpc pvoid32 - Text pvoid32 - Etext pvoid32 - Noptrdata pvoid32 - Enoptrdata pvoid32 - Data pvoid32 - Edata pvoid32 - Bss pvoid32 - Ebss pvoid32 - Noptrbss pvoid32 - Enoptrbss pvoid32 - Covctrs pvoid32 - Ecovctrs pvoid32 - End pvoid32 - Gcdata pvoid32 - Gcbss pvoid32 - Types pvoid32 - Etypes pvoid32 - Rodata pvoid32 - Gofunc pvoid32 - Textsectmap GoSlice32 - Typelinks GoSlice32 - Itablinks GoSlice32 - Ptab GoSlice32 - Pluginpath GoString32 - Pkghashes GoSlice32 - InitTasks GoSlice32 - Modulename GoString32 - Modulehashes GoSlice32 - Hasmain bool - Gcdatamask GoBitVector32 - Gcbssmask GoBitVector32 - Typemap pvoid32 - Badload bool - Next pvoid32 -} - -func (moduledata *ModuleData121_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, moduledata) -} - -type Textsect_64 struct { - Vaddr pvoid64 // prelinked section vaddr - End pvoid64 // vaddr + section length - Baseaddr pvoid64 // relocated section address -} - -func (textsect *Textsect_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, textsect) -} - -type Textsect_32 struct { - Vaddr pvoid32 // prelinked section vaddr - End pvoid32 // vaddr + section length - Baseaddr pvoid32 // relocated section address -} - -func (textsect *Textsect_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, textsect) -} - -type IMethod struct { - Name nameOff - Typ typeOff -} - -func (imethod *IMethod) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, imethod) +type Textsect struct { + Vaddr uint64 // prelinked section vaddr + End uint64 // vaddr + section length + Baseaddr uint64 // relocated section address } type Kind uint8 // mask & 0x1f @@ -1053,228 +131,6 @@ type tflag uint8 type nameOff int32 type typeOff int32 -type Rtype15_64 struct { - Size size_t64 - Ptrdata size_t64 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Unused uint8 // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid64 // algorithm table - Gcdata pvoid64 // garbage collection data - Str pvoid64 // string form - UncommonType pvoid64 - PtrToThis pvoid64 // type for pointer to this type, may be zero - Zero pvoid64 -} - -func (rtype *Rtype15_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype15_32 struct { - Size size_t32 - Ptrdata size_t32 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Unused uint8 // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid32 // algorithm table - Gcdata pvoid32 // garbage collection data - Str pvoid32 // string form - UncommonType pvoid32 - PtrToThis pvoid32 // type for pointer to this type, may be zero - Zero pvoid32 -} - -func (rtype *Rtype15_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype16_64 struct { - Size size_t64 - Ptrdata size_t64 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Unused uint8 // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid64 // algorithm table - Gcdata pvoid64 // garbage collection data - Str pvoid64 // string form - UncommonType pvoid64 - PtrToThis pvoid64 // type for pointer to this type, may be zero -} - -func (rtype *Rtype16_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype16_32 struct { - Size size_t32 - Ptrdata size_t32 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Unused uint8 // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid32 // algorithm table - Gcdata pvoid32 // garbage collection data - Str pvoid32 // string form - UncommonType pvoid32 - PtrToThis pvoid32 // type for pointer to this type, may be zero -} - -func (rtype *Rtype16_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype17_18_19_110_111_112_113_64 struct { - Size size_t64 - Ptrdata size_t64 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Tflag tflag // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid64 // algorithm table - Gcdata pvoid64 // garbage collection data - Str nameOff // string form - PtrToThis typeOff // type for pointer to this type, may be zero -} - -func (rtype *Rtype17_18_19_110_111_112_113_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype17_18_19_110_111_112_113_32 struct { - Size size_t32 - Ptrdata size_t32 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Tflag tflag // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind // enumeration for C - Alg pvoid32 // algorithm table - Gcdata pvoid32 // garbage collection data - Str nameOff // string form - PtrToThis typeOff // type for pointer to this type, may be zero -} - -func (rtype *Rtype17_18_19_110_111_112_113_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype114_115_116_117_118_64 struct { - Size size_t64 - Ptrdata size_t64 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Tflag tflag // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind - Equal pvoid64 - Gcdata pvoid64 // garbage collection data - Str nameOff // string form - PtrToThis typeOff // type for pointer to this type, may be zero -} - -func (rtype *Rtype114_115_116_117_118_64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - -type Rtype114_115_116_117_118_32 struct { - Size size_t32 - Ptrdata size_t32 // number of bytes in the type that can contain pointers - Hash uint32 // hash of type; avoids computation in hash tables - Tflag tflag // extra type information flags - Align uint8 // alignment of variable with this type - FieldAlign uint8 // alignment of struct field with this type - Kind Kind - Equal pvoid32 - Gcdata pvoid32 // garbage collection data - Str nameOff // string form - PtrToThis typeOff // type for pointer to this type, may be zero -} - -func (rtype *Rtype114_115_116_117_118_32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - - return binary.Read(srcBytes, byteOrder, rtype) -} - // This is a general structure that just holds the fields I care about // this lets us return a single type, even though rtypes change between go version type Type struct { @@ -1335,88 +191,16 @@ const ( ) // https://github.com/golang/go/blob/9ecb853cf2252f3cd9ed2e7b3401d17df2d1ab06/src/runtime/symtab.go#L662 -func textAddr64(off32 uint64, text uint64, textsectmap []Textsect_64) uint64 { - off := uint64(off32) - res := uint64(text) + off +func textAddr(off uint64, text uint64, textsectmap []Textsect) uint64 { + res := text + off if len(textsectmap) > 1 { for i, sect := range textsectmap { // For the last section, include the end address (etext), as it is included in the functab. - if off >= uint64(sect.Vaddr) && off < uint64(sect.End) || (i == len(textsectmap)-1 && off == uint64(sect.End)) { - res = uint64(sect.Baseaddr) + off - uint64(sect.Vaddr) + if off >= sect.Vaddr && off < sect.End || (i == len(textsectmap)-1 && off == sect.End) { + res = sect.Baseaddr + off - sect.Vaddr break } } } - return uint64(res) -} - -func textAddr32(off32 uint64, text uint64, textsectmap []Textsect_32) uint64 { - off := uint64(off32) - res := uint64(text) + off - if len(textsectmap) > 1 { - for i, sect := range textsectmap { - // For the last section, include the end address (etext), as it is included in the functab. - if off >= uint64(sect.Vaddr) && off < uint64(sect.End) || (i == len(textsectmap)-1 && off == uint64(sect.End)) { - res = uint64(sect.Baseaddr) + off - uint64(sect.Vaddr) - break - } - } - } - return uint64(res) -} - -// ABIType{64,32} mirror internal/abi.Type for Go 1.20+ (incl. Go 1.24). - -// ABIType64 mirrors internal/abi.Type for Go 1.20+. -// Newer runtimes store type descriptors in this format and rtype is -// simply a pointer to this structure. -type ABIType64 struct { - Size size_t64 - Ptrdata size_t64 - Hash uint32 - Tflag tflag - Align uint8 - FieldAlign uint8 - Kind Kind - Equal pvoid64 - Gcdata pvoid64 - Str nameOff - PtrToThis typeOff -} - -func (t *ABIType64) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - return binary.Read(srcBytes, byteOrder, t) -} - -// ABIType32 mirrors internal/abi.Type on 32-bit architectures. -type ABIType32 struct { - Size size_t32 - Ptrdata size_t32 - Hash uint32 - Tflag tflag - Align uint8 - FieldAlign uint8 - Kind Kind - Equal pvoid32 - Gcdata pvoid32 - Str nameOff - PtrToThis typeOff -} - -func (t *ABIType32) parse(rawData []byte, littleEndian bool) error { - srcBytes := bytes.NewBuffer(rawData) - var byteOrder binary.ByteOrder - if littleEndian { - byteOrder = binary.LittleEndian - } else { - byteOrder = binary.BigEndian - } - return binary.Read(srcBytes, byteOrder, t) + return res } diff --git a/objfile/layouts.go b/objfile/layouts.go index 00f6e6e..3f96d7e 100644 --- a/objfile/layouts.go +++ b/objfile/layouts.go @@ -3,7 +3,6 @@ package objfile import ( "fmt" - "unsafe" ) // FieldName represents the name of a field in binary structures @@ -31,6 +30,13 @@ const ( FieldKind // 16 FieldStr // 17 FieldTflag // 18 + // Textsect fields + FieldVaddr // 19 + FieldEnd // 20 + FieldBaseaddr // 21 + // FuncTab fields + FieldEntryoffset // 22 + FieldFuncoffset // 23 ) // String representation for debugging/logging @@ -74,6 +80,16 @@ func (f FieldName) String() string { return "Str" case FieldTflag: return "Tflag" + case FieldVaddr: + return "Vaddr" + case FieldEnd: + return "End" + case FieldBaseaddr: + return "Baseaddr" + case FieldEntryoffset: + return "Entryoffset" + case FieldFuncoffset: + return "Funcoffset" default: return "Unknown" } @@ -85,12 +101,9 @@ type FieldType uint8 const ( FieldTypePvoid FieldType = iota // 0 - pointer/address (void *) FieldTypeSlice // 1 - Go slice (ptr, len, cap) - FieldTypeString // 2 - Go string (ptr, len) - FieldTypeInt // 3 - integer value - FieldTypeName // 4 - name offset (Go 1.18+) - FieldTypeUint32 // 5 - unsigned 32-bit integer - FieldTypeUint8 // 6 - unsigned 8-bit integer - FieldTypeInt32 // 7 - signed 32-bit integer + FieldTypeUint32 // 2 - unsigned 32-bit integer + FieldTypeUint8 // 3 - unsigned 8-bit integer + FieldTypeInt32 // 4 - signed 32-bit integer ) // String representation for debugging @@ -100,12 +113,6 @@ func (f FieldType) String() string { return "pvoid" case FieldTypeSlice: return "slice" - case FieldTypeString: - return "string" - case FieldTypeInt: - return "int" - case FieldTypeName: - return "name" case FieldTypeUint32: return "uint32" case FieldTypeUint8: @@ -131,12 +138,62 @@ type ModuleDataLayout struct { Fields []FieldInfo } +// StructLayout describes the binary layout of a generic structure +type StructLayout struct { + Fields []FieldInfo + BaseSize64 int + BaseSize32 int +} + +// MemoryReader provides utility methods for reading fields from a byte slice based on a StructLayout +type MemoryReader struct { + Data []byte + Layout *StructLayout + Is64Bit bool + LittleEndian bool +} + +func (m *MemoryReader) ReadPointer(field FieldName) uint64 { + offset, found := getFieldOffsetFromList(m.Layout.Fields, field, m.Is64Bit) + if !found { + return 0 + } + return readPointer(m.Data, offset, m.Is64Bit, m.LittleEndian) +} + +func (m *MemoryReader) ReadUint32(field FieldName) uint32 { + offset, found := getFieldOffsetFromList(m.Layout.Fields, field, m.Is64Bit) + if !found { + return 0 + } + return readUint32(m.Data, offset, m.LittleEndian) +} + +func getFieldOffsetFromList(fields []FieldInfo, fieldName FieldName, is64bit bool) (int, bool) { + for _, field := range fields { + if field.Name == fieldName { + if is64bit { + return field.Offset64, true + } + return field.Offset32, true + } + } + return 0, false +} + // getModuleDataLayout returns the layout for a given Go version -// Multiple versions may share the same layout -func getModuleDataLayout(version string) *ModuleDataLayout { +// Multiple versions may share the same layout. +// Note: Versions like "1.21" and "1.22" are "fake" layout versions. +// Go stopped bumping the pclntab magic byte after 1.20, so 1.20-1.26 all share the same layoutVersion ("1.20"). +// However, the internal moduledata struct continued to change (e.g., offsets shifted in 1.22). +// We use the runtimeVersion (extracted from buildinfo) to map to these fake layout versions +// so we can correctly parse the shifted fields. +func getModuleDataLayout(runtimeVersion string) *ModuleDataLayout { // Map version to layout name (many versions share layouts) - layoutName := version - switch version { + layoutName := runtimeVersion + switch runtimeVersion { + case "1.26": + layoutName = "1.22" case "1.25", "1.24", "1.23", "1.22", "1.21": layoutName = "1.21" case "1.20": @@ -156,14 +213,85 @@ func getModuleDataLayout(version string) *ModuleDataLayout { layout, exists := moduleDataLayouts[layoutName] if !exists { // Fallback to closest known version - return moduleDataLayouts["1.21"] + return moduleDataLayouts["1.22"] } return layout } +// IsValidLayoutForRuntime checks if the layoutVersion (derived from pclntab magic) +// is compatible with the given runtimeVersion. +// This is critical because pclntab_scan attempts to brute-force the magic byte to handle obfuscated binaries. +// Without this validation, we could incorrectly accept a candidate with a newer magic byte (e.g., 1.20) +// for an older binary (e.g., 1.17), which would cause subsequent function parsing to fail even if the +// moduledata itself happens to parse successfully. +func IsValidLayoutForRuntime(layoutVersion, runtimeVersion string) bool { + if runtimeVersion == "unknown" || layoutVersion == "unknown" { + return true + } + + expectedLayout := runtimeVersion + switch runtimeVersion { + case "1.26", "1.25", "1.24", "1.23", "1.22", "1.21": + expectedLayout = "1.20" // pclntab magic 1.20 is used for 1.20+ + case "1.20": + expectedLayout = "1.20" + case "1.19", "1.18": + expectedLayout = "1.18" + case "1.17", "1.16": + expectedLayout = "1.16" + case "1.15", "1.14", "1.13", "1.12", "1.11", "1.10", "1.9", "1.8": + expectedLayout = "1.2" // gosym uses 1.2 for 1.2-1.15 + case "1.7", "1.6", "1.5", "1.4", "1.3", "1.2": + expectedLayout = "1.2" + } + + return layoutVersion == expectedLayout +} + +var textsectLayout = &StructLayout{ + Fields: []FieldInfo{ + {Name: FieldVaddr, Offset64: 0, Offset32: 0, Type: FieldTypePvoid}, + {Name: FieldEnd, Offset64: 8, Offset32: 4, Type: FieldTypePvoid}, + {Name: FieldBaseaddr, Offset64: 16, Offset32: 8, Type: FieldTypePvoid}, + }, + BaseSize64: 24, + BaseSize32: 12, +} + +var functabLayout118 = &StructLayout{ + Fields: []FieldInfo{ + {Name: FieldEntryoffset, Offset64: 0, Offset32: 0, Type: FieldTypeUint32}, + {Name: FieldFuncoffset, Offset64: 4, Offset32: 4, Type: FieldTypeUint32}, + }, + BaseSize64: 8, + BaseSize32: 8, +} + +var functabLayoutLegacy = &StructLayout{ + Fields: []FieldInfo{ + {Name: FieldEntryoffset, Offset64: 0, Offset32: 0, Type: FieldTypePvoid}, + {Name: FieldFuncoffset, Offset64: 8, Offset32: 4, Type: FieldTypePvoid}, + }, + BaseSize64: 16, + BaseSize32: 8, +} + // moduleDataLayouts defines field layouts for different Go versions // Only fields that GoReSym actually uses are included var moduleDataLayouts = map[string]*ModuleDataLayout{ + "1.22": { + Version: "1.22", + Fields: []FieldInfo{ + {Name: FieldFtab, Offset64: 128, Offset32: 64, Type: FieldTypeSlice}, + {Name: FieldMinpc, Offset64: 160, Offset32: 80, Type: FieldTypePvoid}, + {Name: FieldText, Offset64: 176, Offset32: 88, Type: FieldTypePvoid}, + {Name: FieldTypes, Offset64: 296, Offset32: 148, Type: FieldTypePvoid}, + {Name: FieldEtypes, Offset64: 304, Offset32: 152, Type: FieldTypePvoid}, + {Name: FieldTextsectmap, Offset64: 336, Offset32: 168, Type: FieldTypeSlice}, + {Name: FieldTypelinks, Offset64: 360, Offset32: 180, Type: FieldTypeSlice}, + {Name: FieldItablinks, Offset64: 384, Offset32: 192, Type: FieldTypeSlice}, + }, + }, "1.21": { Version: "1.21", Fields: []FieldInfo{ @@ -322,9 +450,12 @@ type ModuleDataIntermediate struct { // parseModuleDataGeneric parses moduledata from raw bytes using layout tables // This replaces the version-specific switch statements with a generic approach -func parseModuleDataGeneric(rawData []byte, version string, is64bit bool, littleendian bool) (*ModuleDataIntermediate, error) { - layout := getModuleDataLayout(version) +func parseModuleDataGeneric(rawData []byte, runtimeVersion string, layoutVersion string, is64bit bool, littleendian bool) (*ModuleDataIntermediate, error) { + if !IsValidLayoutForRuntime(layoutVersion, runtimeVersion) { + return nil, fmt.Errorf("layoutVersion %s is incompatible with runtimeVersion %s", layoutVersion, runtimeVersion) + } + layout := getModuleDataLayout(runtimeVersion) md := &ModuleDataIntermediate{} // Parse fields based on layout @@ -377,19 +508,6 @@ func parseModuleDataGeneric(rawData []byte, version string, is64bit bool, little return md, nil } -// getFieldOffset returns the offset for a named field in a layout -func getFieldOffset(layout *ModuleDataLayout, fieldName FieldName, is64bit bool) (int, bool) { - for _, field := range layout.Fields { - if field.Name == fieldName { - if is64bit { - return field.Offset64, true - } - return field.Offset32, true - } - } - return 0, false -} - // validateAndConvertModuleData performs validation and converts intermediate moduledata // to the final ModuleData struct used by GoReSym // This replaces the duplicated validation logic in version-specific switch cases @@ -397,23 +515,24 @@ func getFieldOffset(layout *ModuleDataLayout, fieldName FieldName, is64bit bool) func (e *Entry) validateAndConvertModuleData( md *ModuleDataIntermediate, moduleDataVA uint64, - version string, is64bit bool, littleendian bool, ignorelist []uint64, ) (*ModuleData, []uint64, error) { // Read and validate first function from ftab - var firstFunc FuncTab118 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) + ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(functabLayout118.BaseSize64)) if err != nil { return nil, ignorelist, err } - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err + ftabReader := MemoryReader{ + Data: ftab_raw, + Layout: functabLayout118, + Is64Bit: is64bit, + LittleEndian: littleendian, } + entryOffset := ftabReader.ReadUint32(FieldEntryoffset) // Prevent loop on invalid modules with bogus length if md.Textsectmap.Len > 0x100 { @@ -421,53 +540,38 @@ func (e *Entry) validateAndConvertModuleData( } // Read textsectmap entries - var textsectmap64 []Textsect_64 - var textsectmap32 []Textsect_32 + var textsectmap []Textsect - if is64bit { - for i := 0; i < int(md.Textsectmap.Len); i++ { - var textsect Textsect_64 - var sectSize = uint64(unsafe.Sizeof(textsect)) - textsec_raw, err := e.raw.read_memory(uint64(md.Textsectmap.Data)+uint64(i)*sectSize, sectSize) - if err != nil { - return nil, ignorelist, err - } + sectSize := textsectLayout.BaseSize64 + if !is64bit { + sectSize = textsectLayout.BaseSize32 + } - err = textsect.parse(textsec_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } - textsectmap64 = append(textsectmap64, textsect) + for i := 0; i < int(md.Textsectmap.Len); i++ { + textsec_raw, err := e.raw.read_memory(uint64(md.Textsectmap.Data)+(uint64(i)*uint64(sectSize)), uint64(sectSize)) + if err != nil { + return nil, ignorelist, err } - // Validate: functab's first function should equal minpc value - if textAddr64(uint64(firstFunc.Entryoffset), md.Text, textsectmap64) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } - } else { - for i := 0; i < int(md.Textsectmap.Len); i++ { - var textsect Textsect_32 - var sectSize = uint64(unsafe.Sizeof(textsect)) - textsec_raw, err := e.raw.read_memory(uint64(md.Textsectmap.Data)+uint64(i)*sectSize, sectSize) - if err != nil { - return nil, ignorelist, err - } - - err = textsect.parse(textsec_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } - textsectmap32 = append(textsectmap32, textsect) + sectReader := MemoryReader{ + Data: textsec_raw, + Layout: textsectLayout, + Is64Bit: is64bit, + LittleEndian: littleendian, } - // Validate: functab's first function should equal minpc value - if textAddr32(uint64(firstFunc.Entryoffset), md.Text, textsectmap32) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } + textsectmap = append(textsectmap, Textsect{ + Vaddr: sectReader.ReadPointer(FieldVaddr), + End: sectReader.ReadPointer(FieldEnd), + Baseaddr: sectReader.ReadPointer(FieldBaseaddr), + }) + } + + // Validate: functab's first function should equal minpc value + if textAddr(uint64(entryOffset), md.Text, textsectmap) != md.Minpc { + // Wrong moduledata, add to ignorelist + ignorelist = append(ignorelist, moduleDataVA) + return nil, ignorelist, fmt.Errorf("minpc validation failed") } // Validation passed, create final ModuleData struct @@ -488,49 +592,37 @@ func (e *Entry) validateAndConvertModuleData( func (e *Entry) validateAndConvertModuleData_116( md *ModuleDataIntermediate, moduleDataVA uint64, - version string, is64bit bool, littleendian bool, ignorelist []uint64, ) (*ModuleData, []uint64, error) { // Read and validate first function from ftab - if is64bit { - var firstFunc FuncTab12_116_64 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabSize := functabLayoutLegacy.BaseSize64 + if !is64bit { + ftabSize = functabLayoutLegacy.BaseSize32 + } - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } + ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(ftabSize)) + if err != nil { + fmt.Printf("DEBUG: validateAndConvertModuleData_116 failed to read ftab: %v\n", err) + return nil, ignorelist, err + } - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } - } else { - var firstFunc FuncTab12_116_32 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabReader := MemoryReader{ + Data: ftab_raw, + Layout: functabLayoutLegacy, + Is64Bit: is64bit, + LittleEndian: littleendian, + } + entryOffset := ftabReader.ReadPointer(FieldEntryoffset) - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } - - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } + // Validate: functab's first function should equal minpc value + if entryOffset != md.Minpc { + fmt.Printf("DEBUG: validateAndConvertModuleData_116 minpc validation failed: %x != %x\n", entryOffset, md.Minpc) + // Wrong moduledata, add to ignorelist + ignorelist = append(ignorelist, moduleDataVA) + return nil, ignorelist, fmt.Errorf("minpc validation failed") } // Validation passed, create final ModuleData struct @@ -551,49 +643,35 @@ func (e *Entry) validateAndConvertModuleData_116( func (e *Entry) validateAndConvertModuleData_Legacy( md *ModuleDataIntermediate, moduleDataVA uint64, - version string, is64bit bool, littleendian bool, ignorelist []uint64, ) (*ModuleData, []uint64, error) { // Read and validate first function from ftab - if is64bit { - var firstFunc FuncTab12_116_64 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabSize := functabLayoutLegacy.BaseSize64 + if !is64bit { + ftabSize = functabLayoutLegacy.BaseSize32 + } - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } + ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(ftabSize)) + if err != nil { + return nil, ignorelist, err + } - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } - } else { - var firstFunc FuncTab12_116_32 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabReader := MemoryReader{ + Data: ftab_raw, + Layout: functabLayoutLegacy, + Is64Bit: is64bit, + LittleEndian: littleendian, + } + entryOffset := ftabReader.ReadPointer(FieldEntryoffset) - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } - - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } + // Validate: functab's first function should equal minpc value + if entryOffset != md.Minpc { + // Wrong moduledata, add to ignorelist + ignorelist = append(ignorelist, moduleDataVA) + return nil, ignorelist, fmt.Errorf("minpc validation failed") } // Validation passed, create final ModuleData struct @@ -614,49 +692,35 @@ func (e *Entry) validateAndConvertModuleData_Legacy( func (e *Entry) validateAndConvertModuleData_Legacy_NoTypes( md *ModuleDataIntermediate, moduleDataVA uint64, - version string, is64bit bool, littleendian bool, ignorelist []uint64, ) (*ModuleData, []uint64, error) { // Read and validate first function from ftab - if is64bit { - var firstFunc FuncTab12_116_64 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabSize := functabLayoutLegacy.BaseSize64 + if !is64bit { + ftabSize = functabLayoutLegacy.BaseSize32 + } - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } + ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(ftabSize)) + if err != nil { + return nil, ignorelist, err + } - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } - } else { - var firstFunc FuncTab12_116_32 - ftab_raw, err := e.raw.read_memory(uint64(md.Ftab.Data), uint64(unsafe.Sizeof(firstFunc))) - if err != nil { - return nil, ignorelist, err - } + ftabReader := MemoryReader{ + Data: ftab_raw, + Layout: functabLayoutLegacy, + Is64Bit: is64bit, + LittleEndian: littleendian, + } + entryOffset := ftabReader.ReadPointer(FieldEntryoffset) - err = firstFunc.parse(ftab_raw, littleendian) - if err != nil { - return nil, ignorelist, err - } - - // Validate: functab's first function should equal minpc value - if uint64(firstFunc.Entryoffset) != md.Minpc { - // Wrong moduledata, add to ignorelist - ignorelist = append(ignorelist, moduleDataVA) - return nil, ignorelist, fmt.Errorf("minpc validation failed") - } + // Validate: functab's first function should equal minpc value + if entryOffset != md.Minpc { + // Wrong moduledata, add to ignorelist + ignorelist = append(ignorelist, moduleDataVA) + return nil, ignorelist, fmt.Errorf("minpc validation failed") } // Validation passed, create final ModuleData struct @@ -702,24 +766,24 @@ type RtypeIntermediate struct { // getRtypeLayout returns the layout for a given Go runtime version func getRtypeLayout(runtimeVersion string) *RtypeLayout { - layoutName := "" + rTypeLayout := "" switch runtimeVersion { case "1.5": - layoutName = "1.5" + rTypeLayout = "1.5" case "1.6": - layoutName = "1.6" + rTypeLayout = "1.6" case "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13": - layoutName = "1.7" + rTypeLayout = "1.7" case "1.14", "1.15", "1.16", "1.17", "1.18", "1.19": - layoutName = "1.14" - case "1.20", "1.21", "1.22", "1.23", "1.24", "1.25": - layoutName = "1.20" + rTypeLayout = "1.14" + case "1.20", "1.21", "1.22", "1.23", "1.24", "1.25", "1.26": + rTypeLayout = "1.20" default: return nil } - return rtypeLayouts[layoutName] + return rtypeLayouts[rTypeLayout] } // rtypeLayouts defines field layouts for different Go runtime type versions diff --git a/objfile/layouts_test.go b/objfile/layouts_test.go index 7951152..80e1c97 100644 --- a/objfile/layouts_test.go +++ b/objfile/layouts_test.go @@ -4,235 +4,8 @@ package objfile import ( "reflect" "testing" - "unsafe" ) -// TestLayoutOffsets_Match_StructDefinitions verifies that our layout offset tables -// match the actual struct definitions from internals.go -func TestLayoutOffsets_Match_StructDefinitions(t *testing.T) { - t.Run("ModuleData121_64", func(t *testing.T) { - var md ModuleData121_64 - layout := getModuleDataLayout("1.21") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData121_32", func(t *testing.T) { - var md ModuleData121_32 - layout := getModuleDataLayout("1.21") - - testCases := []struct { - fieldName FieldName - actualOffset32 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, false) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset32 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset32) - } - } - }) - - t.Run("ModuleData120_64", func(t *testing.T) { - var md ModuleData120_64 - layout := getModuleDataLayout("1.20") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData118_64", func(t *testing.T) { - var md ModuleData118_64 - layout := getModuleDataLayout("1.18") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData116_64", func(t *testing.T) { - var md ModuleData116_64 - layout := getModuleDataLayout("1.16") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) -} - -// TestParseModuleDataGeneric_BackwardCompatibility verifies that the generic parser -// produces identical results to the old version-specific parsing -func TestParseModuleDataGeneric_BackwardCompatibility(t *testing.T) { - // Create synthetic moduledata bytes (simplified for testing) - // In reality this would be actual binary data, but for unit testing - // we just verify the parsing logic works correctly - - t.Run("64-bit little-endian", func(t *testing.T) { - // Create a buffer large enough to hold ModuleData121_64 - // Need to match actual struct size - var sizeCheck ModuleData121_64 - rawData := make([]byte, unsafe.Sizeof(sizeCheck)) - - // Manually set some test values at known offsets (from layout) - // Text at offset 176 - rawData[176] = 0x00 - rawData[177] = 0x10 - rawData[178] = 0x00 - rawData[179] = 0x00 - rawData[180] = 0x00 - rawData[181] = 0x00 - rawData[182] = 0x00 - rawData[183] = 0x00 // Text = 0x1000 - - // Types at offset 296 - rawData[296] = 0x00 - rawData[297] = 0x20 - rawData[298] = 0x00 - rawData[299] = 0x00 - rawData[300] = 0x00 - rawData[301] = 0x00 - rawData[302] = 0x00 - rawData[303] = 0x00 // Types = 0x2000 - - // Parse with new generic approach - result, err := parseModuleDataGeneric(rawData, "1.21", true, true) - if err != nil { - t.Fatalf("Generic parse failed: %v", err) - } - - // Verify results - if result.Text != 0x1000 { - t.Errorf("Text mismatch: got 0x%x, want 0x1000", result.Text) - } - if result.Types != 0x2000 { - t.Errorf("Types mismatch: got 0x%x, want 0x2000", result.Types) - } - - // Also parse with old approach for comparison - var oldModule ModuleData121_64 - err = oldModule.parse(rawData, true) - if err != nil { - t.Fatalf("Old parse failed: %v", err) - } - - // Compare results - if result.Text != uint64(oldModule.Text) { - t.Errorf("Text doesn't match old parser: new=0x%x old=0x%x", - result.Text, oldModule.Text) - } - if result.Types != uint64(oldModule.Types) { - t.Errorf("Types doesn't match old parser: new=0x%x old=0x%x", - result.Types, oldModule.Types) - } - }) -} - // TestVersionMapping verifies that version aliases work correctly func TestVersionMapping(t *testing.T) { testCases := []struct { @@ -243,6 +16,7 @@ func TestVersionMapping(t *testing.T) { {"1.22", "1.21"}, // 1.22 uses same layout as 1.21 {"1.23", "1.21"}, // 1.23 uses same layout as 1.21 {"1.24", "1.21"}, // 1.24 uses same layout as 1.21 + {"1.26", "1.22"}, // 1.26 uses 1.22 layout {"1.25", "1.21"}, // 1.25 uses same layout as 1.21 {"1.20", "1.20"}, {"1.18", "1.18"}, @@ -342,6 +116,81 @@ func TestReadSlice(t *testing.T) { }) } +// TestMemoryReader verifies the MemoryReader utility +func TestMemoryReader(t *testing.T) { + layout := &StructLayout{ + Fields: []FieldInfo{ + {Name: FieldVaddr, Offset64: 0, Offset32: 0, Type: FieldTypePvoid}, + {Name: FieldEnd, Offset64: 8, Offset32: 4, Type: FieldTypePvoid}, + {Name: FieldEntryoffset, Offset64: 16, Offset32: 8, Type: FieldTypeUint32}, + }, + BaseSize64: 24, + BaseSize32: 12, + } + + t.Run("64-bit little-endian", func(t *testing.T) { + data := make([]byte, 24) + // Vaddr = 0x1000 + data[0] = 0x00 + data[1] = 0x10 + // End = 0x2000 + data[8] = 0x00 + data[9] = 0x20 + // Entryoffset = 42 + data[16] = 42 + + reader := MemoryReader{ + Data: data, + Layout: layout, + Is64Bit: true, + LittleEndian: true, + } + + if v := reader.ReadPointer(FieldVaddr); v != 0x1000 { + t.Errorf("Vaddr: got 0x%x, want 0x1000", v) + } + if v := reader.ReadPointer(FieldEnd); v != 0x2000 { + t.Errorf("End: got 0x%x, want 0x2000", v) + } + if v := reader.ReadUint32(FieldEntryoffset); v != 42 { + t.Errorf("Entryoffset: got %d, want 42", v) + } + // Test missing field + if v := reader.ReadPointer(FieldBaseaddr); v != 0 { + t.Errorf("Baseaddr (missing): got %d, want 0", v) + } + }) + + t.Run("32-bit little-endian", func(t *testing.T) { + data := make([]byte, 12) + // Vaddr = 0x1000 + data[0] = 0x00 + data[1] = 0x10 + // End = 0x2000 + data[4] = 0x00 + data[5] = 0x20 + // Entryoffset = 42 + data[8] = 42 + + reader := MemoryReader{ + Data: data, + Layout: layout, + Is64Bit: false, + LittleEndian: true, + } + + if v := reader.ReadPointer(FieldVaddr); v != 0x1000 { + t.Errorf("Vaddr: got 0x%x, want 0x1000", v) + } + if v := reader.ReadPointer(FieldEnd); v != 0x2000 { + t.Errorf("End: got 0x%x, want 0x2000", v) + } + if v := reader.ReadUint32(FieldEntryoffset); v != 42 { + t.Errorf("Entryoffset: got %d, want 42", v) + } + }) +} + // TestModuleDataIntermediate_FieldTypes verifies the intermediate struct has correct types func TestModuleDataIntermediate_FieldTypes(t *testing.T) { var md ModuleDataIntermediate @@ -374,185 +223,6 @@ func TestModuleDataIntermediate_FieldTypes(t *testing.T) { } } -// Test legacy Go versions (1.5-1.15) -func TestLayoutOffsets_Legacy_Versions(t *testing.T) { - t.Run("ModuleData12_64_Go18-15", func(t *testing.T) { - var md ModuleData12_64 - layout := getModuleDataLayout("1.8") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData12_32_Go18-15", func(t *testing.T) { - var md ModuleData12_32 - layout := getModuleDataLayout("1.8") - - testCases := []struct { - fieldName FieldName - actualOffset32 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTextsectmap, int(unsafe.Offsetof(md.Textsectmap))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, false) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset32 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset32) - } - } - }) - - t.Run("ModuleData12_r17_64_Go17", func(t *testing.T) { - var md ModuleData12_r17_64 - layout := getModuleDataLayout("1.7") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData12_r17_32_Go17", func(t *testing.T) { - var md ModuleData12_r17_32 - layout := getModuleDataLayout("1.7") - - testCases := []struct { - fieldName FieldName - actualOffset32 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypes, int(unsafe.Offsetof(md.Types))}, - {FieldEtypes, int(unsafe.Offsetof(md.Etypes))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - {FieldItablinks, int(unsafe.Offsetof(md.Itablinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, false) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset32 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset32) - } - } - }) - - t.Run("ModuleData12_r15_r16_64_Go15-16", func(t *testing.T) { - var md ModuleData12_r15_r16_64 - layout := getModuleDataLayout("1.5") - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - }) - - t.Run("ModuleData12_r15_r16_32_Go15-16", func(t *testing.T) { - var md ModuleData12_r15_r16_32 - layout := getModuleDataLayout("1.5") - - testCases := []struct { - fieldName FieldName - actualOffset32 int - }{ - {FieldFtab, int(unsafe.Offsetof(md.Ftab))}, - {FieldMinpc, int(unsafe.Offsetof(md.Minpc))}, - {FieldText, int(unsafe.Offsetof(md.Text))}, - {FieldTypelinks, int(unsafe.Offsetof(md.Typelinks))}, - } - - for _, tc := range testCases { - layoutOffset, found := getFieldOffset(layout, tc.fieldName, false) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset32 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset32) - } - } - }) -} - // Test version mapping for legacy versions func TestVersionMapping_Legacy(t *testing.T) { testCases := []struct { @@ -575,6 +245,9 @@ func TestVersionMapping_Legacy(t *testing.T) { for _, tc := range testCases { t.Run(tc.version, func(t *testing.T) { layout := getModuleDataLayout(tc.version) + if layout == nil { + t.Fatalf("Layout for version %s is nil", tc.version) + } if layout.Version != tc.expectedName { t.Errorf("Version %s mapped to layout %s, expected %s", tc.version, layout.Version, tc.expectedName) @@ -583,129 +256,6 @@ func TestVersionMapping_Legacy(t *testing.T) { } } -// Test Rtype layout offsets -func TestRtypeLayoutOffsets(t *testing.T) { - t.Run("Rtype15_64", func(t *testing.T) { - var rt Rtype15_64 - layout := getRtypeLayout("1.5") - if layout == nil { - t.Fatal("Layout for 1.5 is nil") - } - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldSize, int(unsafe.Offsetof(rt.Size))}, - {FieldPtrdata, int(unsafe.Offsetof(rt.Ptrdata))}, - {FieldHash, int(unsafe.Offsetof(rt.Hash))}, - {FieldUnused, int(unsafe.Offsetof(rt.Unused))}, - {FieldAlign, int(unsafe.Offsetof(rt.Align))}, - {FieldFieldAlign, int(unsafe.Offsetof(rt.FieldAlign))}, - {FieldKind, int(unsafe.Offsetof(rt.Kind))}, - {FieldStr, int(unsafe.Offsetof(rt.Str))}, - } - - for _, tc := range testCases { - layoutOffset, found := getRtypeFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - - // Check size - if layout.BaseSize64 != int(unsafe.Sizeof(rt)) { - t.Errorf("BaseSize64 mismatch: layout=%d actual=%d", - layout.BaseSize64, unsafe.Sizeof(rt)) - } - }) - - t.Run("Rtype17_64", func(t *testing.T) { - var rt Rtype17_18_19_110_111_112_113_64 - layout := getRtypeLayout("1.7") - if layout == nil { - t.Fatal("Layout for 1.7 is nil") - } - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldSize, int(unsafe.Offsetof(rt.Size))}, - {FieldPtrdata, int(unsafe.Offsetof(rt.Ptrdata))}, - {FieldHash, int(unsafe.Offsetof(rt.Hash))}, - {FieldTflag, int(unsafe.Offsetof(rt.Tflag))}, - {FieldAlign, int(unsafe.Offsetof(rt.Align))}, - {FieldFieldAlign, int(unsafe.Offsetof(rt.FieldAlign))}, - {FieldKind, int(unsafe.Offsetof(rt.Kind))}, - {FieldStr, int(unsafe.Offsetof(rt.Str))}, - } - - for _, tc := range testCases { - layoutOffset, found := getRtypeFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - - // Check size - if layout.BaseSize64 != int(unsafe.Sizeof(rt)) { - t.Errorf("BaseSize64 mismatch: layout=%d actual=%d", - layout.BaseSize64, unsafe.Sizeof(rt)) - } - }) - - t.Run("ABIType64", func(t *testing.T) { - var rt ABIType64 - layout := getRtypeLayout("1.20") - if layout == nil { - t.Fatal("Layout for 1.20 is nil") - } - - testCases := []struct { - fieldName FieldName - actualOffset64 int - }{ - {FieldSize, int(unsafe.Offsetof(rt.Size))}, - {FieldPtrdata, int(unsafe.Offsetof(rt.Ptrdata))}, - {FieldHash, int(unsafe.Offsetof(rt.Hash))}, - {FieldTflag, int(unsafe.Offsetof(rt.Tflag))}, - {FieldAlign, int(unsafe.Offsetof(rt.Align))}, - {FieldFieldAlign, int(unsafe.Offsetof(rt.FieldAlign))}, - {FieldKind, int(unsafe.Offsetof(rt.Kind))}, - {FieldStr, int(unsafe.Offsetof(rt.Str))}, - } - - for _, tc := range testCases { - layoutOffset, found := getRtypeFieldOffset(layout, tc.fieldName, true) - if !found { - t.Errorf("Field %s not found in layout", tc.fieldName.String()) - continue - } - if layoutOffset != tc.actualOffset64 { - t.Errorf("Field %s offset mismatch: layout=%d actual=%d", - tc.fieldName, layoutOffset, tc.actualOffset64) - } - } - - // Check size - if layout.BaseSize64 != int(unsafe.Sizeof(rt)) { - t.Errorf("BaseSize64 mismatch: layout=%d actual=%d", - layout.BaseSize64, unsafe.Sizeof(rt)) - } - }) -} - // Test Rtype version mapping func TestRtypeVersionMapping(t *testing.T) { testCases := []struct { diff --git a/objfile/macho.go b/objfile/macho.go index 2a645b1..b8cfc4a 100644 --- a/objfile/macho.go +++ b/objfile/macho.go @@ -153,7 +153,7 @@ func (f *machoFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { - if bytes.Equal(candidate.Pclntab, magic) { + if bytes.HasPrefix(candidate.Pclntab, magic) { has_some_valid_magic = true break } diff --git a/objfile/objfile.go b/objfile/objfile.go index 76627eb..47acbc8 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -17,7 +17,6 @@ import ( "sort" "strconv" "strings" - "unsafe" "github.com/elliotchance/orderedmap" "github.com/mandiant/GoReSym/debug/dwarf" @@ -261,7 +260,7 @@ func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, known return ch, nil } -func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version string, is64bit bool, littleendian bool) (secStart uint64, moduleData *ModuleData, err error) { +func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, layoutVersion string, is64bit bool, littleendian bool) (secStart uint64, moduleData *ModuleData, err error) { moduleData = &ModuleData{} // Major version only, 1.15.5 -> 1.15 parts := strings.Split(runtimeVersion, ".") @@ -269,6 +268,12 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version runtimeVersion = parts[0] + "." + parts[1] } + if runtimeVersion == "" || runtimeVersion == "unknown" { + runtimeVersion = layoutVersion + } + + // Version validation is now handled inside parseModuleDataGeneric + var moduleDataCandidate *ModuleDataCandidate = nil const maxattempts = 5 @@ -286,25 +291,10 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version // there's really only a few versions of the structure. Multiple runtime versions share the same binary layout, // with some higher versions using the same layout as versions before it. - switch version { - // Refactored: Go 1.16-1.24 use generic parser with layout tables - case "1.25": - fallthrough - case "1.24": - fallthrough - case "1.23": - fallthrough - case "1.22": - fallthrough - case "1.21": - fallthrough - case "1.20": - fallthrough - case "1.19": - fallthrough - case "1.18": + switch runtimeVersion { + case "1.26", "1.25", "1.24", "1.23", "1.22", "1.21", "1.20", "1.19", "1.18": // Parse moduledata using generic layout-based parser - mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, version, is64bit, littleendian) + mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian) if err != nil { continue } @@ -313,7 +303,6 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version result, newIgnorelist, err := e.validateAndConvertModuleData( mdIntermediate, moduleDataCandidate.ModuledataVA, - version, is64bit, littleendian, ignorelist, @@ -325,11 +314,9 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version return secStart, result, nil - case "1.17": - fallthrough - case "1.16": + case "1.17", "1.16": // Parse moduledata using generic layout-based parser - mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, version, is64bit, littleendian) + mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian) if err != nil { continue } @@ -338,7 +325,6 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version result, newIgnorelist, err := e.validateAndConvertModuleData_116( mdIntermediate, moduleDataCandidate.ModuledataVA, - version, is64bit, littleendian, ignorelist, @@ -350,79 +336,72 @@ func (e *Entry) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version return secStart, result, nil - case "1.2": - // Refactored: Go 1.5-1.15 use generic parser with layout tables - // this layout changes <= 1.5 even though the tab version stays constant - switch runtimeVersion { - case "1.5", "1.6": - // Parse moduledata using generic layout-based parser - mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, "1.5", is64bit, littleendian) - if err != nil { - continue - } - - // Validate using legacy validation (no Types field, uses LegacyTypes) - result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy_NoTypes( - mdIntermediate, - moduleDataCandidate.ModuledataVA, - runtimeVersion, - is64bit, - littleendian, - ignorelist, - ) - if err != nil { - ignorelist = newIgnorelist - continue - } - - return secStart, result, nil - - case "1.7": - // Parse moduledata using generic layout-based parser - mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, "1.7", is64bit, littleendian) - if err != nil { - continue - } - - // Validate using legacy validation (has Types/Etypes/Itablinks) - result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy( - mdIntermediate, - moduleDataCandidate.ModuledataVA, - runtimeVersion, - is64bit, - littleendian, - ignorelist, - ) - if err != nil { - ignorelist = newIgnorelist - continue - } - - return secStart, result, nil - - case "1.8", "1.9", "1.10", "1.11", "1.12", "1.13", "1.14", "1.15": - // Parse moduledata using generic layout-based parser - mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, "1.8", is64bit, littleendian) - if err != nil { - continue - } - - // Validate using legacy validation (has Types/Etypes/Itablinks/Textsectmap) - result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy( - mdIntermediate, - moduleDataCandidate.ModuledataVA, - runtimeVersion, - is64bit, - littleendian, - ignorelist, - ) - if err != nil { - ignorelist = newIgnorelist - continue - } - - return secStart, result, nil + case "1.7": + // Parse moduledata using generic layout-based parser + mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian) + if err != nil { + continue } + + // Validate using legacy validation (has Types/Etypes/Itablinks) + result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy( + mdIntermediate, + moduleDataCandidate.ModuledataVA, + is64bit, + littleendian, + ignorelist, + ) + if err != nil { + ignorelist = newIgnorelist + continue + } + + return secStart, result, nil + + case "1.6", "1.5": + // Parse moduledata using generic layout-based parser + mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian) + if err != nil { + continue + } + + // Validate using legacy validation (no Types field, uses LegacyTypes) + result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy_NoTypes( + mdIntermediate, + moduleDataCandidate.ModuledataVA, + is64bit, + littleendian, + ignorelist, + ) + if err != nil { + ignorelist = newIgnorelist + continue + } + + return secStart, result, nil + + default: + // Parse moduledata using generic layout-based parser + // Default to 1.8 layout for unknown or older versions (1.8 - 1.15) + mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian) + if err != nil { + continue + } + + // Validate using legacy validation (has Types/Etypes/Itablinks/Textsectmap) + result, newIgnorelist, err := e.validateAndConvertModuleData_Legacy( + mdIntermediate, + moduleDataCandidate.ModuledataVA, + is64bit, + littleendian, + ignorelist, + ) + if err != nil { + ignorelist = newIgnorelist + continue + } + + return secStart, result, nil } } @@ -548,6 +527,8 @@ func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr ui case "1.24": fallthrough case "1.25": + fallthrough + case "1.26": varint_len, namelen, err := e.readVarint(namePtr + 1) if err != nil { return "", fmt.Errorf("Failed to read name") @@ -920,23 +901,23 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty var methodsStartAddr uint64 = typeAddress + uint64(_type.baseSize) var methods GoSlice64 = GoSlice64{} if is64bit { - data, err := e.raw.read_memory(methodsStartAddr, uint64(unsafe.Sizeof(GoSlice64{}))) + data, err := e.raw.read_memory(methodsStartAddr, 24) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - methods.parse(data, littleendian) + sliceData, sliceLen := readSlice(data, 0, true, littleendian) + methods.Data = pvoid64(sliceData) + methods.Len = uint64(sliceLen) + // Capacity is not used for methods slice } else { - data, err := e.raw.read_memory(methodsStartAddr, uint64(unsafe.Sizeof(GoSlice32{}))) + data, err := e.raw.read_memory(methodsStartAddr, 12) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - - var tmp GoSlice32 = GoSlice32{} - tmp.parse(data, littleendian) - - methods.Data = pvoid64(tmp.Data) - methods.Len = uint64(tmp.Len) - methods.Capacity = uint64(tmp.Capacity) + sliceData, sliceLen := readSlice(data, 0, false, littleendian) + methods.Data = pvoid64(sliceData) + methods.Len = uint64(sliceLen) + // Capacity is not used for methods slice } interfaceDef := fmt.Sprintf("type %s interface {", _type.Str) @@ -1012,26 +993,28 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty case "1.24": fallthrough case "1.25": + fallthrough + case "1.26": var methodsStartAddr uint64 = typeAddress + uint64(_type.baseSize) + ptrSize var methods GoSlice64 = GoSlice64{} if is64bit { - data, err := e.raw.read_memory(methodsStartAddr, uint64(unsafe.Sizeof(GoSlice64{}))) + data, err := e.raw.read_memory(methodsStartAddr, 24) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - methods.parse(data, littleendian) + sliceData, sliceLen := readSlice(data, 0, true, littleendian) + methods.Data = pvoid64(sliceData) + methods.Len = uint64(sliceLen) + // Capacity is not used for methods slice } else { - data, err := e.raw.read_memory(methodsStartAddr, uint64(unsafe.Sizeof(GoSlice32{}))) + data, err := e.raw.read_memory(methodsStartAddr, 12) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - - var tmp GoSlice32 = GoSlice32{} - tmp.parse(data, littleendian) - - methods.Data = pvoid64(tmp.Data) - methods.Len = uint64(tmp.Len) - methods.Capacity = uint64(tmp.Capacity) + sliceData, sliceLen := readSlice(data, 0, false, littleendian) + methods.Data = pvoid64(sliceData) + methods.Len = uint64(sliceLen) + // Capacity is not used for methods slice } interfaceDef := "type interface {" @@ -1045,23 +1028,21 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty // name nameOff // name of method // typ typeOff // .(*FuncType) underneath // } - entrySize := uint64(unsafe.Sizeof(IMethod{})) + // size = 8 bytes (two int32s) + entrySize := uint64(8) for i := 0; i < int(methods.Len); i++ { imethoddata, err := e.raw.read_memory(uint64(methods.Data)+entrySize*uint64(i), entrySize) if err != nil { continue } - var method IMethod - err = method.parse(imethoddata, littleendian) - if err != nil { - continue - } + methodNameOff := readInt32(imethoddata, 0, littleendian) + methodTypOff := readInt32(imethoddata, 4, littleendian) - typeAddr := moduleData.Types + uint64(method.Typ) + typeAddr := moduleData.Types + uint64(methodTypOff) parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn) - name_ptr := moduleData.Types + uint64(method.Name) + name_ptr := moduleData.Types + uint64(methodNameOff) name, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian) if err != nil { continue @@ -1092,23 +1073,23 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty var fieldsStartAddr uint64 = typeAddress + uint64(_type.baseSize) var fields GoSlice64 = GoSlice64{} if is64bit { - data, err := e.raw.read_memory(fieldsStartAddr, uint64(unsafe.Sizeof(GoSlice64{}))) + data, err := e.raw.read_memory(fieldsStartAddr, 24) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - fields.parse(data, littleendian) + sliceData, sliceLen := readSlice(data, 0, true, littleendian) + fields.Data = pvoid64(sliceData) + fields.Len = uint64(sliceLen) + // Capacity is not used for fields slice } else { - data, err := e.raw.read_memory(fieldsStartAddr, uint64(unsafe.Sizeof(GoSlice32{}))) + data, err := e.raw.read_memory(fieldsStartAddr, 12) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - - var tmp GoSlice32 = GoSlice32{} - tmp.parse(data, littleendian) - - fields.Data = pvoid64(tmp.Data) - fields.Len = uint64(tmp.Len) - fields.Capacity = uint64(tmp.Capacity) + sliceData, sliceLen := readSlice(data, 0, false, littleendian) + fields.Data = pvoid64(sliceData) + fields.Len = uint64(sliceLen) + // Capacity is not used for fields slice } structDef := fmt.Sprintf("type %s struct {", _type.Str) @@ -1183,6 +1164,8 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty case "1.24": fallthrough case "1.25": + fallthrough + case "1.26": // type structType struct { // rtype // pkgPath name // pointer @@ -1191,23 +1174,23 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty var fieldsStartAddr uint64 = typeAddress + uint64(_type.baseSize) + ptrSize var fields GoSlice64 = GoSlice64{} if is64bit { - data, err := e.raw.read_memory(fieldsStartAddr, uint64(unsafe.Sizeof(GoSlice64{}))) + data, err := e.raw.read_memory(fieldsStartAddr, 24) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - fields.parse(data, littleendian) + sliceData, sliceLen := readSlice(data, 0, true, littleendian) + fields.Data = pvoid64(sliceData) + fields.Len = uint64(sliceLen) + // Capacity is not used for fields slice } else { - data, err := e.raw.read_memory(fieldsStartAddr, uint64(unsafe.Sizeof(GoSlice32{}))) + data, err := e.raw.read_memory(fieldsStartAddr, 12) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice") } - - var tmp GoSlice32 = GoSlice32{} - tmp.parse(data, littleendian) - - fields.Data = pvoid64(tmp.Data) - fields.Len = uint64(tmp.Len) - fields.Capacity = uint64(tmp.Capacity) + sliceData, sliceLen := readSlice(data, 0, false, littleendian) + fields.Data = pvoid64(sliceData) + fields.Len = uint64(sliceLen) + // Capacity is not used for fields slice } structDef := "type struct {" diff --git a/objfile/pe.go b/objfile/pe.go index 2f7dc7e..8c71341 100644 --- a/objfile/pe.go +++ b/objfile/pe.go @@ -187,7 +187,7 @@ func (f *peFile) pcln_scan() (candidates <-chan PclntabCandidate, err error) { send_patched_magic_candidates := func(candidate *PclntabCandidate) { has_some_valid_magic := false for _, magic := range append(pclntab_sigs_le, pclntab_sigs_be...) { - if bytes.Equal(candidate.Pclntab, magic) { + if bytes.HasPrefix(candidate.Pclntab, magic) { has_some_valid_magic = true break } @@ -407,6 +407,7 @@ scan: found = true break + } else { } }