mirror of
https://github.com/mandiant/GoReSym
synced 2026-06-08 15:40:15 +00:00
c69cbd66c3
* Add function signature reconstruction for Issue #14
1601 lines
49 KiB
Go
1601 lines
49 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
/*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/
|
|
|
|
// Package objfile implements portable access to OS-specific executable files.
|
|
package objfile
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/elliotchance/orderedmap"
|
|
"github.com/mandiant/GoReSym/debug/dwarf"
|
|
"github.com/mandiant/GoReSym/debug/gosym"
|
|
)
|
|
|
|
type StompMagicCandidate struct {
|
|
PclntabVa uint64
|
|
SuspectedModuleDataVa uint64
|
|
LittleEndian bool
|
|
}
|
|
|
|
type PclntabCandidate struct {
|
|
SecStart uint64
|
|
PclntabVA uint64
|
|
StompMagicCandidateMeta *StompMagicCandidate // some search modes might optimistically try to find moduledata or guess endianess, these hints must match the found moduleData VA later to be considered good candidate
|
|
Pclntab []byte
|
|
Symtab []byte // optional
|
|
ParsedPclntab *gosym.Table
|
|
}
|
|
|
|
type ModuleDataCandidate struct {
|
|
SecStart uint64
|
|
ModuledataVA uint64
|
|
Moduledata []byte
|
|
}
|
|
|
|
type rawFile interface {
|
|
symbols() (syms []Sym, err error)
|
|
pcln() (candidates <-chan PclntabCandidate, err error)
|
|
pcln_scan() (candidates <-chan PclntabCandidate, err error)
|
|
moduledata_scan(pclntabVA uint64, is64bit bool, littleendian bool, ignorelist []uint64) (candidate *ModuleDataCandidate, err error)
|
|
read_memory(VA uint64, size uint64) (data []byte, err error)
|
|
text() (textStart uint64, text []byte, err error)
|
|
goarch() string
|
|
loadAddress() (uint64, error)
|
|
dwarf() (*dwarf.Data, error)
|
|
}
|
|
|
|
// A File is an opened executable file.
|
|
type File struct {
|
|
r *os.File
|
|
entries []*Entry
|
|
}
|
|
|
|
type Entry struct {
|
|
name string
|
|
raw rawFile
|
|
}
|
|
|
|
// A Sym is a symbol defined in an executable file.
|
|
type Sym struct {
|
|
Name string // symbol name
|
|
Addr uint64 // virtual address of symbol
|
|
Size int64 // size in bytes
|
|
Code rune // nm code (T for text, D for data, and so on)
|
|
Type string // XXX?
|
|
Relocs []Reloc // in increasing Addr order
|
|
}
|
|
|
|
type Reloc struct {
|
|
Addr uint64 // Address of first byte that reloc applies to.
|
|
Size uint64 // Number of bytes
|
|
Stringer RelocStringer
|
|
}
|
|
|
|
type RelocStringer interface {
|
|
// insnOffset is the offset of the instruction containing the relocation
|
|
// from the start of the symbol containing the relocation.
|
|
String(insnOffset uint64) string
|
|
}
|
|
|
|
var openers = []func(io.ReaderAt) (rawFile, error){
|
|
openElf,
|
|
openMacho,
|
|
openPE,
|
|
}
|
|
|
|
// Open opens the named file.
|
|
// The caller must call f.Close when the file is no longer needed.
|
|
func Open(name string) (*File, error) {
|
|
r, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if f, err := openGoFile(r); err == nil {
|
|
return f, nil
|
|
}
|
|
for _, try := range openers {
|
|
if raw, err := try(r); err == nil {
|
|
return &File{r, []*Entry{{raw: raw}}}, nil
|
|
}
|
|
}
|
|
r.Close()
|
|
return nil, fmt.Errorf("open %s: unrecognized object file or bad filepath", name)
|
|
}
|
|
|
|
func (f *File) Close() error {
|
|
return f.r.Close()
|
|
}
|
|
|
|
func (f *File) Entries() []*Entry {
|
|
return f.entries
|
|
}
|
|
|
|
func (f *File) Symbols() ([]Sym, error) {
|
|
return f.entries[0].Symbols()
|
|
}
|
|
|
|
// previously : func (f *File) PCLineTable() (Liner, error) {
|
|
func (f *File) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) (<-chan PclntabCandidate, error) {
|
|
return f.entries[0].PCLineTable(versionOverride, knownPclntabVA, knownGoTextBase)
|
|
}
|
|
|
|
func (f *File) ModuleDataTable(pclntabVA uint64, runtimeVersion string, version string, is64bit bool, littleendian bool) (secStart uint64, moduleData *ModuleData, err error) {
|
|
return f.entries[0].ModuleDataTable(pclntabVA, runtimeVersion, version, is64bit, littleendian)
|
|
}
|
|
|
|
func (f *File) ParseType(runtimeVersion string, moduleData *ModuleData, typeAddress uint64, is64bit bool, littleendian bool) (types []Type, err error) {
|
|
return f.entries[0].ParseType(runtimeVersion, moduleData, typeAddress, is64bit, littleendian)
|
|
}
|
|
|
|
func (f *File) ParseTypeLinks(runtimeVersion string, moduleData *ModuleData, is64bit bool, littleendian bool) (types []Type, err error) {
|
|
return f.entries[0].ParseTypeLinks(runtimeVersion, moduleData, is64bit, littleendian)
|
|
}
|
|
|
|
func (f *File) ParseITabLinks(runtimeVersion string, moduleData *ModuleData, is64bit bool, littleendian bool) (types []Type, err error) {
|
|
return f.entries[0].ParseITabLinks(runtimeVersion, moduleData, is64bit, littleendian)
|
|
}
|
|
|
|
func (f *File) Text() (uint64, []byte, error) {
|
|
return f.entries[0].Text()
|
|
}
|
|
|
|
func (f *File) GOARCH() string {
|
|
return f.entries[0].GOARCH()
|
|
}
|
|
|
|
func (f *File) LoadAddress() (uint64, error) {
|
|
return f.entries[0].LoadAddress()
|
|
}
|
|
|
|
func (f *File) DWARF() (*dwarf.Data, error) {
|
|
return f.entries[0].DWARF()
|
|
}
|
|
|
|
func (f *File) Disasm() (*Disasm, error) {
|
|
return f.entries[0].Disasm()
|
|
}
|
|
|
|
func (e *Entry) Name() string {
|
|
return e.name
|
|
}
|
|
|
|
func (e *Entry) Symbols() ([]Sym, error) {
|
|
syms, err := e.raw.symbols()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Sort(byAddr(syms))
|
|
return syms, nil
|
|
}
|
|
|
|
type byAddr []Sym
|
|
|
|
func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr }
|
|
func (x byAddr) Len() int { return len(x) }
|
|
func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
|
|
func findAllOccurrences(data []byte, searches [][]byte) []int {
|
|
var results []int
|
|
|
|
for _, search := range searches {
|
|
var offset = 0
|
|
|
|
for {
|
|
var index = bytes.Index(data[offset:], search)
|
|
if index == -1 {
|
|
break
|
|
}
|
|
|
|
offset += index
|
|
results = append(results, offset)
|
|
|
|
offset += 1
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// previously: func (e *Entry) PCLineTable() (Liner, error)
|
|
func (e *Entry) PCLineTable(versionOverride string, knownPclntabVA uint64, knownGoTextBase uint64) (<-chan PclntabCandidate, error) {
|
|
// If the raw file implements Liner directly, use that.
|
|
// Currently, only Go intermediate objects and archives (goobj) use this path.
|
|
|
|
// DISABLED, We want to gopclntab table 95% of the time
|
|
// if pcln, ok := e.raw.(Liner); ok {
|
|
// return pcln, nil
|
|
// }
|
|
|
|
// Otherwise, read the pcln tables and build a Liner out of that.
|
|
// https://github.com/golang/go/blob/89f687d6dbc11613f715d1644b4983905293dd33/src/debug/gosym/pclntab.go#L169
|
|
// https://github.com/golang/go/issues/42954
|
|
ch_tab, err := e.raw.pcln()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ch := make(chan PclntabCandidate)
|
|
|
|
go func() {
|
|
defer close(ch)
|
|
for candidate := range ch_tab {
|
|
/* See https://github.com/mandiant/GoReSym/pull/11
|
|
Locating the .text base is not safe by name due to packers which mangle names. We also have to consider CGO
|
|
which appears to update the base with an 'adjusted' one to add some shim code. So, PCLineTable
|
|
get called first with the candidate.SecStart just to find symbols, just so we can find the moduledata.
|
|
Then, we invoke it again with a 'known' text base, which is found by reading data held in the moduledata.
|
|
That is, we do all this parsing twice, on purpose, to be resiliant, we have better info on round 2.
|
|
*/
|
|
if knownGoTextBase != 0 {
|
|
candidate.SecStart = knownGoTextBase
|
|
}
|
|
|
|
// using this VA a moduledata was successfully found, this time around we can avoid re-parsing known bad pclntab candidates
|
|
if knownPclntabVA != 0 && candidate.PclntabVA != knownPclntabVA {
|
|
continue
|
|
}
|
|
|
|
parsedTable, err := gosym.NewTable(candidate.Symtab, gosym.NewLineTable(candidate.Pclntab, candidate.SecStart), versionOverride)
|
|
if err != nil || parsedTable.Go12line == nil {
|
|
continue
|
|
}
|
|
|
|
// the first good one happens to be correct more often than the last
|
|
candidate.ParsedPclntab = parsedTable
|
|
ch <- candidate
|
|
}
|
|
}()
|
|
return ch, nil
|
|
}
|
|
|
|
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, ".")
|
|
if len(parts) >= 2 {
|
|
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
|
|
var ignorelist []uint64
|
|
for i := 0; i < maxattempts; i++ {
|
|
// we're trying again, ignore the previous candidate
|
|
if moduleDataCandidate != nil {
|
|
ignorelist = append(ignorelist, moduleDataCandidate.ModuledataVA)
|
|
}
|
|
|
|
moduleDataCandidate, err = e.raw.moduledata_scan(pclntabVA, is64bit, littleendian, ignorelist)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// 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 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, runtimeVersion, layoutVersion, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Validate and convert to final ModuleData struct
|
|
result, newIgnorelist, err := e.validateAndConvertModuleData(
|
|
mdIntermediate,
|
|
moduleDataCandidate.ModuledataVA,
|
|
is64bit,
|
|
littleendian,
|
|
ignorelist,
|
|
)
|
|
if err != nil {
|
|
ignorelist = newIgnorelist
|
|
continue
|
|
}
|
|
|
|
return secStart, result, nil
|
|
|
|
case "1.17", "1.16":
|
|
// Parse moduledata using generic layout-based parser
|
|
mdIntermediate, err := parseModuleDataGeneric(moduleDataCandidate.Moduledata, runtimeVersion, layoutVersion, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Validate using 1.16-specific validation (simpler, no textsectmap)
|
|
result, newIgnorelist, err := e.validateAndConvertModuleData_116(
|
|
mdIntermediate,
|
|
moduleDataCandidate.ModuledataVA,
|
|
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
|
|
}
|
|
}
|
|
|
|
// should only happen if all scan attempts and validation fail
|
|
return 0, nil, fmt.Errorf("moduledata not found")
|
|
}
|
|
|
|
func (e *Entry) readVarint(address uint64) (int, int, error) {
|
|
v := 0
|
|
for i := 0; ; i++ {
|
|
data, err := e.raw.read_memory(address+uint64(i), 1)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("Failed to read varint")
|
|
}
|
|
x := data[0]
|
|
v += int(x&0x7f) << (7 * i)
|
|
if x&0x80 == 0 {
|
|
return i + 1, v, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr uint64, is64bit bool, littleendian bool) (name string, err error) {
|
|
// name str (for <= 1.16 encodes length like this, beyond it uses a varint encoding)
|
|
// The first byte is a bit field containing:
|
|
//
|
|
// 1<<0 the name is exported
|
|
// 1<<1 tag data follows the name
|
|
// 1<<2 pkgPath nameOff follows the name and tag
|
|
//
|
|
// The next two bytes are the data length OR varint encoding if newer version OR pointer to gostring if really old
|
|
//
|
|
// l := uint16(data[1])<<8 | uint16(data[2])
|
|
//
|
|
// Bytes [3:3+l] are the string data.
|
|
|
|
// Starting in >= 1.8 An rtype's string often has an extra *, the go runtime says:
|
|
// func (t *rtype) String() string {
|
|
// if t.tflag&tflagExtraStar then strip leading *
|
|
// tflagExtraStar means the name in the str field has an
|
|
// extraneous '*' prefix. This is because for most types T in
|
|
// a program, the type *T also exists and reusing the str data
|
|
// saves binary size.
|
|
|
|
var ptrSize uint64 = 0
|
|
if is64bit {
|
|
ptrSize = 8
|
|
} else {
|
|
ptrSize = 4
|
|
}
|
|
|
|
switch runtimeVersion {
|
|
case "1.5":
|
|
fallthrough
|
|
case "1.6":
|
|
// pointer to GoString
|
|
nameLen, err := e.ReadPointerSizeMem(namePtr+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
deref, err := e.ReadPointerSizeMem(namePtr, is64bit, littleendian)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
name_raw, err := e.raw.read_memory(deref, nameLen)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
return string(name_raw), nil
|
|
case "1.7": // types flags exists >= 1.7
|
|
fallthrough
|
|
case "1.8": // type flag tflagExtraStart exists >= 1.8
|
|
fallthrough
|
|
case "1.9":
|
|
fallthrough
|
|
case "1.10":
|
|
fallthrough
|
|
case "1.11":
|
|
fallthrough
|
|
case "1.12":
|
|
fallthrough
|
|
case "1.13":
|
|
fallthrough
|
|
case "1.14":
|
|
fallthrough
|
|
case "1.15":
|
|
fallthrough
|
|
case "1.16":
|
|
name_len_raw, err := e.raw.read_memory(namePtr, 3)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
name_len := uint16(uint16(name_len_raw[1])<<8 | uint16(name_len_raw[2]))
|
|
name_raw, err := e.raw.read_memory(namePtr+3, uint64(name_len))
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
name := string(name_raw)
|
|
if typeFlags&tflagExtraStar != 0 {
|
|
return name[1:], nil
|
|
} else {
|
|
return name, nil
|
|
}
|
|
case "1.17":
|
|
fallthrough
|
|
case "1.18":
|
|
fallthrough
|
|
case "1.19":
|
|
fallthrough
|
|
case "1.20":
|
|
fallthrough
|
|
case "1.21":
|
|
fallthrough
|
|
case "1.22":
|
|
fallthrough
|
|
case "1.23":
|
|
fallthrough
|
|
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")
|
|
}
|
|
|
|
name_raw, err := e.raw.read_memory(namePtr+1+uint64(varint_len), uint64(namelen))
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
name := string(name_raw)
|
|
if typeFlags&tflagExtraStar != 0 {
|
|
return name[1:], nil
|
|
} else {
|
|
return name, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("Failed to read name")
|
|
}
|
|
|
|
func decodePtrSizeBytes(data []byte, is64bit bool, littleendian bool) (result uint64) {
|
|
if is64bit {
|
|
if littleendian {
|
|
return binary.LittleEndian.Uint64(data)
|
|
} else {
|
|
return binary.BigEndian.Uint64(data)
|
|
}
|
|
} else {
|
|
if littleendian {
|
|
return uint64(binary.LittleEndian.Uint32(data))
|
|
} else {
|
|
return uint64(binary.BigEndian.Uint32(data))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *Entry) ReadPointerSizeMem(addr uint64, is64bit bool, littleendian bool) (result uint64, err error) {
|
|
var ptrSize uint64 = 0
|
|
if is64bit {
|
|
ptrSize = 8
|
|
} else {
|
|
ptrSize = 4
|
|
}
|
|
|
|
deref, err := e.raw.read_memory(addr, ptrSize)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Failed to dereference pointer memory")
|
|
}
|
|
|
|
return decodePtrSizeBytes(deref, is64bit, littleendian), nil
|
|
}
|
|
|
|
func typename_to_c(typename string) string {
|
|
result := strings.ReplaceAll(typename, "*", "_ptr_")
|
|
result = strings.ReplaceAll(result, "[]", "_slice_")
|
|
result = strings.ReplaceAll(result, "<-", "_chan_left_")
|
|
result = strings.ReplaceAll(result, ".", "_")
|
|
result = strings.ReplaceAll(result, "[", "_")
|
|
result = strings.ReplaceAll(result, "]", "_")
|
|
result = strings.ReplaceAll(result, " ", "_")
|
|
|
|
// this one may be incorrect
|
|
result = strings.ReplaceAll(result, "{}", "")
|
|
return result
|
|
}
|
|
|
|
// not exhaustive, just the likely ones to be in Go
|
|
func replace_cpp_keywords(fieldname string) string {
|
|
switch fieldname {
|
|
case "private":
|
|
fallthrough
|
|
case "public":
|
|
fallthrough
|
|
case "protected":
|
|
fallthrough
|
|
case "friend":
|
|
fallthrough
|
|
case "register":
|
|
fallthrough
|
|
case "typename":
|
|
fallthrough
|
|
case "template":
|
|
fallthrough
|
|
case "typeid":
|
|
fallthrough
|
|
case "typedef":
|
|
fallthrough
|
|
case "default":
|
|
fallthrough
|
|
case "continue":
|
|
fallthrough
|
|
case "signed":
|
|
fallthrough
|
|
case "unsigned":
|
|
fallthrough
|
|
case "class":
|
|
return "_" + fieldname
|
|
case "_":
|
|
return "_anon" + fmt.Sprint(rand.Intn(1000))
|
|
}
|
|
return fieldname
|
|
}
|
|
|
|
func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, typeAddress uint64, is64bit bool, littleendian bool, parsedTypesIn *orderedmap.OrderedMap) (*orderedmap.OrderedMap, error) {
|
|
// all return paths must return the original map, even if there's an error. An empty map rather than a nil simplifies recursion and allows tail calls.
|
|
// exit condition: type address seen before
|
|
if _, exists := parsedTypesIn.Get(typeAddress); exists {
|
|
return parsedTypesIn, nil
|
|
}
|
|
|
|
var _type *Type = nil
|
|
|
|
// Refactored: Use generic layout-based parser for all versions
|
|
layout := getRtypeLayout(runtimeVersion)
|
|
if layout == nil {
|
|
return parsedTypesIn, fmt.Errorf("Unknown runtime version: %s", runtimeVersion)
|
|
}
|
|
|
|
// Read raw type data
|
|
var readSize uint64
|
|
if is64bit {
|
|
readSize = uint64(layout.BaseSize64)
|
|
} else {
|
|
readSize = uint64(layout.BaseSize32)
|
|
}
|
|
|
|
rtype_raw, err := e.raw.read_memory(typeAddress, readSize)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read type address")
|
|
}
|
|
|
|
// Parse using generic parser
|
|
rtype, baseSize, err := parseRtypeGeneric(rtype_raw, runtimeVersion, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse type")
|
|
}
|
|
|
|
// Read type name (version-specific handling)
|
|
var name string
|
|
var namePtr uint64
|
|
if layout.StrType == "pointer" {
|
|
// Go 1.5-1.6: Str is direct pointer
|
|
namePtr = rtype.Str
|
|
} else {
|
|
// Go 1.7+: Str is offset from moduleData.Types
|
|
namePtr = moduleData.Types + rtype.Str
|
|
}
|
|
|
|
name, err = e.readRTypeName(runtimeVersion, rtype.Tflag, namePtr, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read type name")
|
|
}
|
|
|
|
// Create Type object
|
|
_type = &Type{
|
|
VA: typeAddress,
|
|
Str: name,
|
|
CStr: typename_to_c(name),
|
|
Kind: ((Kind)(rtype.Kind & 0x1f)).String(),
|
|
baseSize: uint16(baseSize),
|
|
kindEnum: ((Kind)(rtype.Kind & 0x1f)),
|
|
flags: rtype.Tflag,
|
|
}
|
|
|
|
// insert into seen list
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
|
|
var UintType string = ""
|
|
var IntType string = ""
|
|
var ptrSize uint64 = 0
|
|
if is64bit {
|
|
ptrSize = 8
|
|
IntType = "int64"
|
|
UintType = "uint64"
|
|
} else {
|
|
ptrSize = 4
|
|
IntType = "int32"
|
|
UintType = "uint32"
|
|
}
|
|
|
|
// we must parse each type to cover other types it points to
|
|
// this list only contains root type, we optionally recurse to parse those
|
|
// and then we may update the map to insert pretty reconstructed string forms of the types
|
|
// src/runtume/type.go
|
|
switch _type.kindEnum {
|
|
case Func:
|
|
//type FuncType struct {
|
|
// Type
|
|
// InCount uint16
|
|
// OutCount uint16 // top bit is set if last input parameter is ...
|
|
//}
|
|
// Read InCount and OutCount
|
|
inCountAddr := typeAddress + uint64(_type.baseSize)
|
|
outCountAddr := typeAddress + uint64(_type.baseSize) + 2 // uint16 = 2 bytes
|
|
|
|
inCountData, err := e.raw.read_memory(inCountAddr, 2)
|
|
if err != nil {
|
|
(*_type).CStr = "void*"
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
break
|
|
}
|
|
|
|
outCountData, err := e.raw.read_memory(outCountAddr, 2)
|
|
if err != nil {
|
|
(*_type).CStr = "void*"
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
break
|
|
}
|
|
|
|
var inCount, outCount uint16
|
|
if littleendian {
|
|
inCount = binary.LittleEndian.Uint16(inCountData)
|
|
outCount = binary.LittleEndian.Uint16(outCountData)
|
|
} else {
|
|
inCount = binary.BigEndian.Uint16(inCountData)
|
|
outCount = binary.BigEndian.Uint16(outCountData)
|
|
}
|
|
|
|
// Check for variadic (top bit of outCount)
|
|
isVariadic := (outCount & 0x8000) != 0
|
|
outCount = outCount & 0x7FFF
|
|
|
|
// After InCount and OutCount, there's an array of type pointers
|
|
// Layout: [InCount types...][OutCount types...]
|
|
//
|
|
// Go runtime funcType.in() uses: uadd := unsafe.Sizeof(*t)
|
|
// sizeof(funcType) = baseSize + roundUp(4, ptrSize) = baseSize + ptrSize
|
|
// On 64-bit: baseSize(48) + 8 = 56 (4 bytes data + 4 bytes padding)
|
|
// On 32-bit: baseSize(32) + 4 = 36 (4 bytes data, already aligned)
|
|
// If tflagUncommon is set, skip sizeof(uncommonType) = 16 bytes
|
|
paramTypesStart := typeAddress + uint64(_type.baseSize) + ptrSize
|
|
if _type.flags&tflagUncommon != 0 {
|
|
paramTypesStart += 16 // sizeof(uncommonType) in Go 1.7+
|
|
}
|
|
|
|
// Build parameter and return type lists
|
|
// Collect both Go-style (.Str) and C-style (.CStr) names
|
|
var inTypesC, outTypesC []string
|
|
var inTypesGo, outTypesGo []string
|
|
|
|
// Parse input parameters
|
|
for i := uint16(0); i < inCount; i++ {
|
|
paramTypeAddr, err := e.ReadPointerSizeMem(paramTypesStart+uint64(i)*ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
inTypesC = append(inTypesC, "void*")
|
|
inTypesGo = append(inTypesGo, "unknown")
|
|
continue
|
|
}
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, paramTypeAddr, is64bit, littleendian, parsedTypesIn)
|
|
if paramType, found := parsedTypesIn.Get(paramTypeAddr); found {
|
|
inTypesC = append(inTypesC, paramType.(Type).CStr)
|
|
inTypesGo = append(inTypesGo, paramType.(Type).Str)
|
|
} else {
|
|
inTypesC = append(inTypesC, "void*")
|
|
inTypesGo = append(inTypesGo, "unknown")
|
|
}
|
|
}
|
|
|
|
// Parse output (return) parameters
|
|
for i := uint16(0); i < outCount; i++ {
|
|
returnTypeAddr, err := e.ReadPointerSizeMem(paramTypesStart+uint64(inCount+i)*ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
outTypesC = append(outTypesC, "void*")
|
|
outTypesGo = append(outTypesGo, "unknown")
|
|
continue
|
|
}
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, returnTypeAddr, is64bit, littleendian, parsedTypesIn)
|
|
if returnType, found := parsedTypesIn.Get(returnTypeAddr); found {
|
|
outTypesC = append(outTypesC, returnType.(Type).CStr)
|
|
outTypesGo = append(outTypesGo, returnType.(Type).Str)
|
|
} else {
|
|
outTypesC = append(outTypesC, "void*")
|
|
outTypesGo = append(outTypesGo, "unknown")
|
|
}
|
|
}
|
|
|
|
// --- CStr: "returnType (param1, param2)" ---
|
|
returnCStr := "void"
|
|
if len(outTypesC) == 1 {
|
|
returnCStr = outTypesC[0]
|
|
} else if len(outTypesC) > 1 {
|
|
// Multiple returns - use tuple syntax like CReconstructed
|
|
returnCStr = "tuple(" + strings.Join(outTypesC, ", ") + ")"
|
|
}
|
|
|
|
argsCStr := strings.Join(inTypesC, ", ")
|
|
if argsCStr == "" {
|
|
argsCStr = "void"
|
|
}
|
|
if isVariadic && len(inTypesC) > 0 {
|
|
argsCStr += ", ..."
|
|
}
|
|
|
|
// Save original CStr (the C-safe type name) before overwriting
|
|
origCStr := _type.CStr
|
|
|
|
(*_type).CStr = returnCStr + " (" + argsCStr + ")"
|
|
|
|
// --- Reconstructed: Go syntax ---
|
|
argsGoStr := strings.Join(inTypesGo, ", ")
|
|
returnGoStr := ""
|
|
if len(outTypesGo) == 1 {
|
|
returnGoStr = outTypesGo[0]
|
|
} else if len(outTypesGo) > 1 {
|
|
returnGoStr = "(" + strings.Join(outTypesGo, ", ") + ")"
|
|
}
|
|
|
|
funcSig := "func(" + argsGoStr + ")"
|
|
if returnGoStr != "" {
|
|
funcSig += " " + returnGoStr
|
|
}
|
|
if _type.flags&tflagNamed != 0 {
|
|
(*_type).Reconstructed = "type " + _type.Str + " " + funcSig
|
|
} else {
|
|
(*_type).Reconstructed = funcSig
|
|
}
|
|
|
|
// --- CReconstructed: "typedef <return> (*<name>_funcptr)(<args>)" ---
|
|
// Build a clean C identifier for the funcptr name from the original CStr
|
|
funcPtrName := strings.NewReplacer(
|
|
"(", "_", ")", "_", ",", "_",
|
|
).Replace(origCStr)
|
|
for strings.Contains(funcPtrName, "__") {
|
|
funcPtrName = strings.ReplaceAll(funcPtrName, "__", "_")
|
|
}
|
|
funcPtrName = strings.TrimRight(funcPtrName, "_") + "_funcptr"
|
|
|
|
cReturnStr := returnCStr
|
|
if len(outTypesC) > 1 {
|
|
// IDA tuple syntax for multiple return values
|
|
cReturnStr = "tuple(" + strings.Join(outTypesC, ", ") + ")"
|
|
}
|
|
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef %s (*%s)(%s)", cReturnStr, funcPtrName, argsCStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Array:
|
|
// type arraytype struct {
|
|
// typ _type
|
|
// elem *_type
|
|
// slice *_type
|
|
// len uintptr
|
|
// }
|
|
elemTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize), is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Array's elem")
|
|
}
|
|
|
|
sliceTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize)+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Array's slice")
|
|
}
|
|
|
|
arrayLen, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize)+ptrSize+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Array's len")
|
|
}
|
|
|
|
parsed, _ := e.ParseType_impl(runtimeVersion, moduleData, elemTypeAddress, is64bit, littleendian, parsedTypesIn)
|
|
elemType, found := parsedTypesIn.Get(elemTypeAddress)
|
|
if found {
|
|
(*_type).Reconstructed = (*_type).Str // ends up being the same for an array
|
|
(*_type).CReconstructed = "typedef " + elemType.(Type).CStr + " " + (*_type).CStr + "[" + strconv.Itoa(int(arrayLen)) + "];"
|
|
parsed.Set(typeAddress, *_type)
|
|
}
|
|
return e.ParseType_impl(runtimeVersion, moduleData, sliceTypeAddress, is64bit, littleendian, parsed)
|
|
case Chan:
|
|
// type chantype struct {
|
|
// typ _type
|
|
// elem *_type
|
|
// dir uintptr
|
|
// }
|
|
elemTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize), is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Chan's elem")
|
|
}
|
|
|
|
// append channel direction to Str of type
|
|
// channelDir, err := e.raw.read_memory(typeAddress+uint64(_type.baseSize)+ptrSize, ptrSize)
|
|
// if err == nil {
|
|
// var dir string = ""
|
|
// if is64bit {
|
|
// if littleendian {
|
|
// dir = (ChanDir)(binary.LittleEndian.Uint64(channelDir)).String()
|
|
// } else {
|
|
// dir = (ChanDir)(binary.BigEndian.Uint64(channelDir)).String()
|
|
// }
|
|
// } else {
|
|
// if littleendian {
|
|
// dir = (ChanDir)(binary.LittleEndian.Uint32(channelDir)).String()
|
|
// } else {
|
|
// dir = (ChanDir)(binary.BigEndian.Uint32(channelDir)).String()
|
|
// }
|
|
// }
|
|
//
|
|
// _type.Str += " Direction: (" + dir + ")"
|
|
// }
|
|
|
|
parsedTypesIn, err = e.ParseType_impl(runtimeVersion, moduleData, elemTypeAddress, is64bit, littleendian, parsedTypesIn)
|
|
if err != nil {
|
|
return parsedTypesIn, err
|
|
}
|
|
|
|
elemType, found := parsedTypesIn.Get(elemTypeAddress)
|
|
if found {
|
|
(*_type).Str = "chan(" + elemType.(Type).Str + ")"
|
|
(*_type).CStr = "chan_" + elemType.(Type).CStr
|
|
(*_type).Reconstructed = "chan(" + elemType.(Type).Str + ")"
|
|
(*_type).CReconstructed = "typedef void* chan_" + elemType.(Type).CStr + ";"
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
}
|
|
case Slice:
|
|
// type slicetype struct {
|
|
// typ _type
|
|
// elem *_type
|
|
// }
|
|
elemTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize), is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Slice's elem")
|
|
}
|
|
|
|
parsedTypesIn, err = e.ParseType_impl(runtimeVersion, moduleData, elemTypeAddress, is64bit, littleendian, parsedTypesIn)
|
|
if err != nil {
|
|
return parsedTypesIn, err
|
|
}
|
|
|
|
elemType, found := parsedTypesIn.Get(elemTypeAddress)
|
|
if found {
|
|
(*_type).Reconstructed = "struct " + (*_type).Str + "{ ptr *" + elemType.(Type).Str + "\nlen int\ncap int }"
|
|
(*_type).CReconstructed = "struct " + (*_type).CStr + "{ " + elemType.(Type).CStr + "* ptr;" + "size_t len; size_t cap; }"
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
}
|
|
case Pointer:
|
|
// type ptrtype struct {
|
|
// typ _type
|
|
// elem *_type
|
|
// }
|
|
elemTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize), is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Pointer's elem")
|
|
}
|
|
|
|
parsedTypesIn, err = e.ParseType_impl(runtimeVersion, moduleData, elemTypeAddress, is64bit, littleendian, parsedTypesIn)
|
|
if err != nil {
|
|
return parsedTypesIn, err
|
|
}
|
|
|
|
elemType, found := parsedTypesIn.Get(elemTypeAddress)
|
|
if found {
|
|
(*_type).Reconstructed = "type " + (*_type).Str + " = " + elemType.(Type).CStr
|
|
(*_type).CReconstructed = "typedef " + elemType.(Type).CStr + "* " + (*_type).CStr + ";"
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
}
|
|
case UnsafePointer:
|
|
// IDA doesn't reconstruct unsafe pointers, just sets them to void*, we are fine with that too
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef void* %s", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Map:
|
|
// type mapType struct {
|
|
// rtype
|
|
// key *rtype // map key type
|
|
// elem *rtype // map element (value) type
|
|
// bucket *rtype // internal bucket structure
|
|
// // function for hashing keys (ptr to key, seed) -> hash
|
|
// hasher func(unsafe.Pointer, uintptr) uintptr
|
|
// keysize uint8 // size of key slot
|
|
// valuesize uint8 // size of value slot
|
|
// bucketsize uint16 // size of bucket
|
|
// flags uint32
|
|
// }
|
|
keyTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize), is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Map's elem")
|
|
}
|
|
|
|
elemTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize)+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Array's slice")
|
|
}
|
|
|
|
bucketTypeAddress, err := e.ReadPointerSizeMem(typeAddress+uint64(_type.baseSize)+ptrSize+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to read Kind Array's slice")
|
|
}
|
|
|
|
// IDA doesn't reconstruct maps, just sets them to void*, we are fine with that too
|
|
// TODO: reconstruct this as a real map struct
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef void* %s", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
|
|
parsed, _ := e.ParseType_impl(runtimeVersion, moduleData, keyTypeAddress, is64bit, littleendian, parsedTypesIn)
|
|
parsed2, _ := e.ParseType_impl(runtimeVersion, moduleData, elemTypeAddress, is64bit, littleendian, parsed)
|
|
return e.ParseType_impl(runtimeVersion, moduleData, bucketTypeAddress, is64bit, littleendian, parsed2)
|
|
case Interface:
|
|
// type interfaceType struct {
|
|
// rtype
|
|
// pkgPath name // import path (pointer)
|
|
// methods []imethod // sorted by hash
|
|
// }
|
|
|
|
switch runtimeVersion {
|
|
case "1.5":
|
|
fallthrough
|
|
case "1.6":
|
|
//
|
|
// type interfaceType struct {
|
|
// rtype `reflect:"interface"`
|
|
// methods []imethod // sorted by hash
|
|
// }
|
|
var methodsStartAddr uint64 = typeAddress + uint64(_type.baseSize)
|
|
var methods GoSlice64 = GoSlice64{}
|
|
if is64bit {
|
|
data, err := e.raw.read_memory(methodsStartAddr, 24)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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, 12)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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)
|
|
cinterfaceDef := fmt.Sprintf("struct %s {\n", _type.CStr)
|
|
|
|
// type imethod struct {
|
|
// name *string // name of method
|
|
// pkgPath *string // nil for exported Names; otherwise import path
|
|
// typ *rtype // .(*FuncType) underneath
|
|
// }
|
|
// size = 3 * ptrsize
|
|
for i := 0; i < int(methods.Len); i++ {
|
|
imethoddata, err := e.raw.read_memory(uint64(methods.Data)+(uint64(i)*3*ptrSize), 3*ptrSize)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
typeAddr := decodePtrSizeBytes(imethoddata[ptrSize*2:ptrSize*3], is64bit, littleendian)
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn)
|
|
|
|
name_ptr := decodePtrSizeBytes(imethoddata[0:ptrSize], is64bit, littleendian)
|
|
name, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
methodfunc, found := parsedTypesIn.Get(typeAddr)
|
|
if found {
|
|
interfaceDef += strings.Replace(methodfunc.(Type).Str, "func", name, 1) + "\n"
|
|
cinterfaceDef += methodfunc.(Type).CStr + " " + name + ";\n"
|
|
}
|
|
}
|
|
interfaceDef += "\n}"
|
|
cinterfaceDef += "}"
|
|
(*_type).Reconstructed = interfaceDef
|
|
(*_type).CReconstructed = cinterfaceDef
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
return parsedTypesIn, nil
|
|
case "1.7":
|
|
fallthrough
|
|
case "1.8":
|
|
fallthrough
|
|
case "1.9":
|
|
fallthrough
|
|
case "1.10":
|
|
fallthrough
|
|
case "1.11":
|
|
fallthrough
|
|
case "1.12":
|
|
fallthrough
|
|
case "1.13":
|
|
fallthrough
|
|
case "1.14":
|
|
fallthrough
|
|
case "1.15":
|
|
fallthrough
|
|
case "1.16":
|
|
fallthrough
|
|
case "1.17":
|
|
fallthrough
|
|
case "1.18":
|
|
fallthrough
|
|
case "1.19":
|
|
fallthrough
|
|
case "1.20":
|
|
fallthrough
|
|
case "1.21":
|
|
fallthrough
|
|
case "1.22":
|
|
fallthrough
|
|
case "1.23":
|
|
fallthrough
|
|
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, 24)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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, 12)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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 {"
|
|
cinterfaceDef := "struct interface {\n"
|
|
if _type.flags&tflagNamed != 0 {
|
|
interfaceDef = fmt.Sprintf("type %s interface {", _type.Str)
|
|
cinterfaceDef = fmt.Sprintf("struct %s {\n", _type.CStr)
|
|
}
|
|
|
|
// type imethod struct {
|
|
// name nameOff // name of method
|
|
// typ typeOff // .(*FuncType) underneath
|
|
// }
|
|
// 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
|
|
}
|
|
|
|
methodNameOff := readInt32(imethoddata, 0, littleendian)
|
|
methodTypOff := readInt32(imethoddata, 4, littleendian)
|
|
|
|
typeAddr := moduleData.Types + uint64(methodTypOff)
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn)
|
|
|
|
name_ptr := moduleData.Types + uint64(methodNameOff)
|
|
name, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
methodfunc, found := parsedTypesIn.Get(typeAddr)
|
|
if found {
|
|
interfaceDef += strings.Replace(methodfunc.(Type).Str, "func", name, 1) + "\n"
|
|
cinterfaceDef += methodfunc.(Type).CStr + " " + name + ";\n"
|
|
}
|
|
}
|
|
interfaceDef += "\n}"
|
|
cinterfaceDef += "}"
|
|
(*_type).Reconstructed = interfaceDef
|
|
(*_type).CReconstructed = cinterfaceDef
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
return parsedTypesIn, nil
|
|
}
|
|
case Struct:
|
|
switch runtimeVersion {
|
|
case "1.5":
|
|
fallthrough
|
|
case "1.6":
|
|
// type structType struct {
|
|
// rtype `reflect:"struct"`
|
|
// fields []structField // sorted by offset
|
|
// }
|
|
var fieldsStartAddr uint64 = typeAddress + uint64(_type.baseSize)
|
|
var fields GoSlice64 = GoSlice64{}
|
|
if is64bit {
|
|
data, err := e.raw.read_memory(fieldsStartAddr, 24)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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, 12)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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)
|
|
cstructDef := fmt.Sprintf("struct %s {\n", _type.CStr)
|
|
|
|
// type structField struct {
|
|
// name *string // nil for embedded fields
|
|
// pkgPath *string // nil for exported Names; otherwise import path
|
|
// typ *rtype // type of field
|
|
// tag *string // nil if no tag
|
|
// offset uintptr // byte offset of field within struct
|
|
// }
|
|
// size = 5 * ptrsize
|
|
for i := 0; i < int(fields.Len); i++ {
|
|
data, err := e.raw.read_memory(uint64(fields.Data)+(uint64(i)*(ptrSize*5)), ptrSize*5)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
typeAddr := decodePtrSizeBytes(data[ptrSize*2:ptrSize*3], is64bit, littleendian)
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn)
|
|
field, found := parsedTypesIn.Get(typeAddr)
|
|
if found {
|
|
typeNameAddr := decodePtrSizeBytes(data[0:ptrSize], is64bit, littleendian)
|
|
typeName, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian)
|
|
if err == nil {
|
|
structDef += fmt.Sprintf("\n %-10s %s", typeName, field.(Type).Str)
|
|
cstructDef += fmt.Sprintf(" %-10s %s;\n", field.(Type).CStr, replace_cpp_keywords(typeName))
|
|
}
|
|
}
|
|
}
|
|
structDef += "\n}"
|
|
cstructDef += "}"
|
|
(*_type).Reconstructed = structDef
|
|
(*_type).CReconstructed = cstructDef
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
return parsedTypesIn, nil
|
|
case "1.7":
|
|
fallthrough
|
|
case "1.8":
|
|
fallthrough
|
|
case "1.9":
|
|
fallthrough
|
|
case "1.10":
|
|
fallthrough
|
|
case "1.11":
|
|
fallthrough
|
|
case "1.12":
|
|
fallthrough
|
|
case "1.13":
|
|
fallthrough
|
|
case "1.14":
|
|
fallthrough
|
|
case "1.15":
|
|
fallthrough
|
|
case "1.16":
|
|
fallthrough
|
|
case "1.17":
|
|
fallthrough
|
|
case "1.18":
|
|
fallthrough
|
|
case "1.19":
|
|
fallthrough
|
|
case "1.20":
|
|
fallthrough
|
|
case "1.21":
|
|
fallthrough
|
|
case "1.22":
|
|
fallthrough
|
|
case "1.23":
|
|
fallthrough
|
|
case "1.24":
|
|
fallthrough
|
|
case "1.25":
|
|
fallthrough
|
|
case "1.26":
|
|
// type structType struct {
|
|
// rtype
|
|
// pkgPath name // pointer
|
|
// fields []structField // sorted by offset
|
|
// }
|
|
var fieldsStartAddr uint64 = typeAddress + uint64(_type.baseSize) + ptrSize
|
|
var fields GoSlice64 = GoSlice64{}
|
|
if is64bit {
|
|
data, err := e.raw.read_memory(fieldsStartAddr, 24)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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, 12)
|
|
if err != nil {
|
|
return parsedTypesIn, fmt.Errorf("Failed to parse Kind Interface's method slice")
|
|
}
|
|
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 {"
|
|
cstructDef := "struct {\n"
|
|
if _type.flags&tflagNamed != 0 {
|
|
structDef = fmt.Sprintf("type %s struct {", _type.Str)
|
|
cstructDef = fmt.Sprintf("struct %s {\n", _type.CStr)
|
|
}
|
|
|
|
// type structField struct {
|
|
// name name // name is empty for embedded fields (ptr)
|
|
// typ *rtype // type of field
|
|
// offset uintptr // byte offset of field within struct
|
|
// }
|
|
//
|
|
// size = ptrsize * 3
|
|
for i := 0; i < int(fields.Len); i++ {
|
|
data, err := e.raw.read_memory(uint64(fields.Data)+(uint64(i)*(ptrSize*3)), ptrSize*3)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
typeAddr := decodePtrSizeBytes(data[ptrSize:ptrSize*2], is64bit, littleendian)
|
|
parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn)
|
|
|
|
field, found := parsedTypesIn.Get(typeAddr)
|
|
if found {
|
|
typeNameAddr := decodePtrSizeBytes(data[0:ptrSize], is64bit, littleendian)
|
|
typeName, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian)
|
|
if err == nil {
|
|
structDef += fmt.Sprintf("\n %-10s %s", typeName, field.(Type).Str)
|
|
cstructDef += fmt.Sprintf(" %-10s %s;\n", field.(Type).CStr, replace_cpp_keywords(typeName))
|
|
}
|
|
}
|
|
}
|
|
structDef += "\n}"
|
|
cstructDef += "}"
|
|
(*_type).Reconstructed = structDef
|
|
(*_type).CReconstructed = cstructDef
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
return parsedTypesIn, nil
|
|
}
|
|
case Int:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef %s %s;", IntType, _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s int;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uint:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef %s %s;", UintType, _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uint;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Int8:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef int8 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s int8;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uint8:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef uint8 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uint8;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Int16:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef int16 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s int16;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uint16:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef uint16 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uint16;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Int32:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef int32 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s int32;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uint32:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef uint32 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uint32;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Int64:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef int64 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s int64;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uint64:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef uint64 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uint64;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Uintptr:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef uintptr %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s uintptr;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Float32:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef float32 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s float32;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Float64:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef float64 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s float64;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Complex64:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef complex64 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s complex64;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Complex128:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef complex128 %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s complex128;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
case Bool:
|
|
(*_type).CReconstructed = fmt.Sprintf("typedef bool %s;", _type.CStr)
|
|
(*_type).Reconstructed = fmt.Sprintf("type %s bool;", _type.CStr)
|
|
parsedTypesIn.Set(typeAddress, *_type)
|
|
default:
|
|
// this is not an error, we just may not support recursion on this 'Kind'
|
|
}
|
|
return parsedTypesIn, nil
|
|
}
|
|
|
|
func (e *Entry) ParseType(runtimeVersion string, moduleData *ModuleData, typeAddress uint64, is64bit bool, littleendian bool) (_type []Type, err error) {
|
|
// Major version only, 1.15.5 -> 1.15
|
|
parts := strings.Split(runtimeVersion, ".")
|
|
if len(parts) >= 2 {
|
|
runtimeVersion = parts[0] + "." + parts[1]
|
|
}
|
|
|
|
m := orderedmap.NewOrderedMap()
|
|
|
|
parsedTypes, err := e.ParseType_impl(runtimeVersion, moduleData, typeAddress, is64bit, littleendian, m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// map values to array
|
|
values := make([]Type, 0, parsedTypes.Len())
|
|
|
|
for el := m.Front(); el != nil; el = el.Next() {
|
|
values = append(values, (el.Value).(Type))
|
|
}
|
|
|
|
return values, nil
|
|
}
|
|
|
|
func (e *Entry) ParseTypeLinks(runtimeVersion string, moduleData *ModuleData, is64bit bool, littleendian bool) (types []Type, err error) {
|
|
// Major version only, 1.15.5 -> 1.15
|
|
parts := strings.Split(runtimeVersion, ".")
|
|
if len(parts) >= 2 {
|
|
runtimeVersion = parts[0] + "." + parts[1]
|
|
}
|
|
|
|
var ptrSize uint64 = 0
|
|
if is64bit {
|
|
ptrSize = 8
|
|
} else {
|
|
ptrSize = 4
|
|
}
|
|
|
|
// Handle legacy layout first (1.5, 1.6). The typelinks is a pointer array
|
|
if moduleData.LegacyTypes.Data != 0 && moduleData.LegacyTypes.Len != 0 {
|
|
for i := 0; i < int(moduleData.LegacyTypes.Len); i++ {
|
|
typeAddress, err := e.ReadPointerSizeMem(uint64(moduleData.LegacyTypes.Data)+ptrSize*uint64(i), is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
parsed, err := e.ParseType(runtimeVersion, moduleData, typeAddress, is64bit, littleendian)
|
|
if err == nil {
|
|
types = append(types, parsed...)
|
|
}
|
|
}
|
|
return types, nil
|
|
}
|
|
|
|
// Modern layout, the typelinks is an array of offsets
|
|
for i := 0; i < int(moduleData.Typelinks.Len); i++ {
|
|
// array of int32 offsets into moduleData.Types
|
|
offset, err := e.raw.read_memory(uint64(moduleData.Typelinks.Data)+uint64(i)*4, 4)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var typeAddress uint64 = 0
|
|
if littleendian {
|
|
offset_signed := int32(binary.LittleEndian.Uint32(offset))
|
|
typeAddress = uint64(int64(moduleData.Types) + int64(offset_signed))
|
|
} else {
|
|
offset_signed := int32(binary.BigEndian.Uint32(offset))
|
|
typeAddress = uint64(int64(moduleData.Types) + int64(offset_signed))
|
|
}
|
|
|
|
parsed, err := e.ParseType(runtimeVersion, moduleData, typeAddress, is64bit, littleendian)
|
|
if err == nil {
|
|
types = append(types, parsed...)
|
|
}
|
|
}
|
|
return types, nil
|
|
}
|
|
|
|
func (e *Entry) ParseITabLinks(runtimeVersion string, moduleData *ModuleData, is64bit bool, littleendian bool) (types []Type, err error) {
|
|
// Major version only, 1.15.5 -> 1.15
|
|
parts := strings.Split(runtimeVersion, ".")
|
|
if len(parts) >= 2 {
|
|
runtimeVersion = parts[0] + "." + parts[1]
|
|
}
|
|
|
|
var ptrSize uint64 = 0
|
|
if is64bit {
|
|
ptrSize = 8
|
|
} else {
|
|
ptrSize = 4
|
|
}
|
|
|
|
for i := 0; i < int(moduleData.ITablinks.Len); i++ {
|
|
itabAddr, err := e.ReadPointerSizeMem(uint64(moduleData.ITablinks.Data)+ptrSize*uint64(i), is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
interfaceAddr, err := e.ReadPointerSizeMem(itabAddr, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
typeAddr, err := e.ReadPointerSizeMem(itabAddr+ptrSize, is64bit, littleendian)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// type itab struct {
|
|
// inter *interfacetype
|
|
// _type *_type
|
|
// hash uint32 // copy of _type.hash. Used for type switches.
|
|
// _ [4]byte
|
|
// fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
|
|
// }
|
|
parsed, err := e.ParseType(runtimeVersion, moduleData, interfaceAddr, is64bit, littleendian)
|
|
if err == nil {
|
|
types = append(types, parsed...)
|
|
}
|
|
|
|
parsed2, err2 := e.ParseType(runtimeVersion, moduleData, typeAddr, is64bit, littleendian)
|
|
if err2 == nil {
|
|
types = append(types, parsed2...)
|
|
}
|
|
|
|
// the interface itself, we need to insert as a type. We'll name it after its interface + its implementing type, the 0th of each parsed array
|
|
if err == nil && err2 == nil && len(parsed) > 0 && len(parsed2) > 0 {
|
|
interfaceName := parsed[0].Str
|
|
implementerName := parsed2[0].Str
|
|
types = append(types, Type{VA: itabAddr, Str: fmt.Sprintf("interface_%s_impl_%s", interfaceName, implementerName), Kind: Interface.String()})
|
|
}
|
|
}
|
|
return types, nil
|
|
}
|
|
|
|
func (e *Entry) Text() (uint64, []byte, error) {
|
|
return e.raw.text()
|
|
}
|
|
|
|
func (e *Entry) GOARCH() string {
|
|
return e.raw.goarch()
|
|
}
|
|
|
|
// LoadAddress returns the expected load address of the file.
|
|
// This differs from the actual load address for a position-independent
|
|
// executable.
|
|
func (e *Entry) LoadAddress() (uint64, error) {
|
|
return e.raw.loadAddress()
|
|
}
|
|
|
|
// DWARF returns DWARF debug data for the file, if any.
|
|
// This is for cmd/pprof to locate cgo functions.
|
|
func (e *Entry) DWARF() (*dwarf.Data, error) {
|
|
return e.raw.dwarf()
|
|
}
|