mirror of
https://github.com/mandiant/GoReSym
synced 2026-06-08 15:40:15 +00:00
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:
@@ -19,3 +19,6 @@ Dockerfile.test
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Agent files
|
||||
.agent/
|
||||
.agents/
|
||||
+1
-1
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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") ||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+254
-190
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user