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 <stevemk14ebr@gmail.com>
This commit is contained in:
jtbennett-fe
2026-03-05 21:29:56 +00:00
committed by GitHub
parent e5bc135355
commit 78c02cc730
15 changed files with 508 additions and 2098 deletions
+3
View File
@@ -19,3 +19,6 @@ Dockerfile.test
# Dependency directories (remove the comment below to include it)
# vendor/
# Agent files
.agent/
.agents/
+1 -1
View File
@@ -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
+9 -2
View File
@@ -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
}
+2 -1
View File
@@ -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") ||
+8
View File
@@ -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
}
+8
View File
@@ -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
}
+1
View File
@@ -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
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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
}
+9 -1225
View File
File diff suppressed because it is too large Load Diff
+254 -190
View File
@@ -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
+79 -529
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+129 -146
View File
@@ -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 {"
+2 -1
View File
@@ -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 {
}
}