
Process is an incorrect name for the DebuggedProcess struct that the thread is "a part" of. Also, no need to export that field.
804 lines
20 KiB
Go
804 lines
20 KiB
Go
package proctl
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/dwarf"
|
|
"debug/gosym"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"github.com/derekparker/delve/dwarf/op"
|
|
"github.com/derekparker/delve/dwarf/reader"
|
|
)
|
|
|
|
const (
|
|
maxVariableRecurse = 1 // How far to recurse when evaluating nested types.
|
|
maxArrayValues = 64 // Max value for reading large arrays.
|
|
|
|
ChanRecv = "chan receive"
|
|
ChanSend = "chan send"
|
|
)
|
|
|
|
// Represents an evaluated variable.
|
|
type Variable struct {
|
|
Name string
|
|
Value string
|
|
Type string
|
|
}
|
|
|
|
// Represents a runtime M (OS thread) structure.
|
|
type M struct {
|
|
procid int // Thread ID or port.
|
|
spinning uint8 // Busy looping.
|
|
blocked uint8 // Waiting on futex / semaphore.
|
|
curg uintptr // Current G running on this thread.
|
|
}
|
|
|
|
// Represents a runtime G (goroutine) structure (at least the
|
|
// fields that Delve is interested in).
|
|
type G struct {
|
|
Id int // Goroutine ID
|
|
PC uint64 // PC of goroutine when it was parked.
|
|
SP uint64 // SP of goroutine when it was parked.
|
|
GoPC uint64 // PC of 'go' statement that created this goroutine.
|
|
WaitReason string // Reason for goroutine being parked.
|
|
|
|
// Information on goroutine location.
|
|
File string
|
|
Line int
|
|
Func *gosym.Func
|
|
|
|
// PC of entry to top-most deferred function.
|
|
DeferPC uint64
|
|
}
|
|
|
|
// Returns whether the goroutine is blocked on
|
|
// a channel read operation.
|
|
func (g *G) ChanRecvBlocked() bool {
|
|
return g.WaitReason == ChanRecv
|
|
}
|
|
|
|
// chanRecvReturnAddr returns the address of the return from a channel read.
|
|
func (g *G) chanRecvReturnAddr(dbp *DebuggedProcess) (uint64, error) {
|
|
locs, err := dbp.stacktrace(g.PC, g.SP, 4)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
topLoc := locs[len(locs)-1]
|
|
return topLoc.addr, nil
|
|
}
|
|
|
|
type NoGError struct {
|
|
tid int
|
|
}
|
|
|
|
func (ng NoGError) Error() string {
|
|
return fmt.Sprintf("no G executing on thread %d", ng.tid)
|
|
}
|
|
|
|
func parseG(thread *ThreadContext, addr uint64) (*G, error) {
|
|
gaddrbytes, err := thread.readMemory(uintptr(addr), thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error derefing *G %s", err)
|
|
}
|
|
initialInstructions := append([]byte{op.DW_OP_addr}, gaddrbytes...)
|
|
gaddr := binary.LittleEndian.Uint64(gaddrbytes)
|
|
if gaddr == 0 {
|
|
return nil, NoGError{tid: thread.Id}
|
|
}
|
|
|
|
rdr := thread.dbp.DwarfReader()
|
|
rdr.Seek(0)
|
|
entry, err := rdr.SeekToTypeNamed("runtime.g")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Let's parse all of the members we care about in order so that
|
|
// we don't have to spend any extra time seeking.
|
|
|
|
// Parse defer
|
|
deferAddr, err := rdr.AddrForMember("_defer", initialInstructions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var deferPC uint64
|
|
// Dereference *defer pointer
|
|
deferAddrBytes, err := thread.readMemory(uintptr(deferAddr), thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error derefing *G %s", err)
|
|
}
|
|
if binary.LittleEndian.Uint64(deferAddrBytes) != 0 {
|
|
initialDeferInstructions := append([]byte{op.DW_OP_addr}, deferAddrBytes...)
|
|
_, err = rdr.SeekToTypeNamed("runtime._defer")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deferPCAddr, err := rdr.AddrForMember("fn", initialDeferInstructions)
|
|
deferPC, err = thread.readUintRaw(uintptr(deferPCAddr), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deferPC, err = thread.readUintRaw(uintptr(deferPC), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = rdr.SeekToEntry(entry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Parse sched
|
|
schedAddr, err := rdr.AddrForMember("sched", initialInstructions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// From sched, let's parse PC and SP.
|
|
sp, err := thread.readUintRaw(uintptr(schedAddr), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc, err := thread.readUintRaw(uintptr(schedAddr+uint64(thread.dbp.arch.PtrSize())), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Parse goid
|
|
goidAddr, err := rdr.AddrForMember("goid", initialInstructions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
goid, err := thread.readIntRaw(uintptr(goidAddr), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Parse waitreason
|
|
waitReasonAddr, err := rdr.AddrForMember("waitreason", initialInstructions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
waitreason, err := thread.readString(uintptr(waitReasonAddr))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Parse gopc
|
|
gopcAddr, err := rdr.AddrForMember("gopc", initialInstructions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gopc, err := thread.readUintRaw(uintptr(gopcAddr), 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, l, fn := thread.dbp.goSymTable.PCToLine(gopc)
|
|
g := &G{
|
|
Id: int(goid),
|
|
GoPC: gopc,
|
|
PC: pc,
|
|
SP: sp,
|
|
File: f,
|
|
Line: l,
|
|
Func: fn,
|
|
WaitReason: waitreason,
|
|
DeferPC: deferPC,
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
// Returns the value of the named symbol.
|
|
func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) {
|
|
pc, err := thread.PC()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reader := thread.dbp.DwarfReader()
|
|
|
|
_, err = reader.SeekToFunction(pc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
varName := name
|
|
memberName := ""
|
|
if strings.Contains(name, ".") {
|
|
idx := strings.Index(name, ".")
|
|
varName = name[:idx]
|
|
memberName = name[idx+1:]
|
|
}
|
|
|
|
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
n, ok := entry.Val(dwarf.AttrName).(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if n == varName {
|
|
if len(memberName) == 0 {
|
|
return thread.extractVariableFromEntry(entry)
|
|
}
|
|
return thread.evaluateStructMember(entry, reader, memberName)
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("could not find symbol value for %s", name)
|
|
}
|
|
|
|
// LocalVariables returns all local variables from the current function scope.
|
|
func (thread *ThreadContext) LocalVariables() ([]*Variable, error) {
|
|
return thread.variablesByTag(dwarf.TagVariable)
|
|
}
|
|
|
|
// FunctionArguments returns the name, value, and type of all current function arguments.
|
|
func (thread *ThreadContext) FunctionArguments() ([]*Variable, error) {
|
|
return thread.variablesByTag(dwarf.TagFormalParameter)
|
|
}
|
|
|
|
// PackageVariables returns the name, value, and type of all package variables in the application.
|
|
func (thread *ThreadContext) PackageVariables() ([]*Variable, error) {
|
|
reader := thread.dbp.DwarfReader()
|
|
|
|
vars := make([]*Variable, 0)
|
|
|
|
for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ignore errors trying to extract values
|
|
val, err := thread.extractVariableFromEntry(entry)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
vars = append(vars, val)
|
|
}
|
|
|
|
return vars, nil
|
|
}
|
|
|
|
func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader.Reader, memberName string) (*Variable, error) {
|
|
parentAddr, err := thread.extractVariableDataAddress(parentEntry, rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get parent variable name
|
|
parentName, ok := parentEntry.Val(dwarf.AttrName).(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to retrive variable name")
|
|
}
|
|
|
|
// Seek reader to the type information so members can be iterated
|
|
_, err = rdr.SeekToType(parentEntry, true, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Iterate to find member by name
|
|
for memberEntry, err := rdr.NextMemberVariable(); memberEntry != nil; memberEntry, err = rdr.NextMemberVariable() {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name, ok := memberEntry.Val(dwarf.AttrName).(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if name == memberName {
|
|
// Nil ptr, wait until here to throw a nil pointer error to prioritize no such member error
|
|
if parentAddr == 0 {
|
|
return nil, fmt.Errorf("%s is nil", parentName)
|
|
}
|
|
|
|
memberInstr, err := rdr.InstructionsForEntry(memberEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
offset, ok := memberEntry.Val(dwarf.AttrType).(dwarf.Offset)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type assertion failed")
|
|
}
|
|
|
|
data := thread.dbp.dwarf
|
|
t, err := data.Type(offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
baseAddr := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(baseAddr, uint64(parentAddr))
|
|
|
|
parentInstructions := append([]byte{op.DW_OP_addr}, baseAddr...)
|
|
val, err := thread.extractValue(append(parentInstructions, memberInstr...), 0, t, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Variable{Name: strings.Join([]string{parentName, memberName}, "."), Type: t.String(), Value: val}, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("%s has no member %s", parentName, memberName)
|
|
}
|
|
|
|
// Extracts the name, type, and value of a variable from a dwarf entry
|
|
func (thread *ThreadContext) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) {
|
|
if entry == nil {
|
|
return nil, fmt.Errorf("invalid entry")
|
|
}
|
|
|
|
if entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagVariable {
|
|
return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", entry.Tag.String())
|
|
}
|
|
|
|
n, ok := entry.Val(dwarf.AttrName).(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type assertion failed")
|
|
}
|
|
|
|
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type assertion failed")
|
|
}
|
|
|
|
data := thread.dbp.dwarf
|
|
t, err := data.Type(offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type assertion failed")
|
|
}
|
|
|
|
val, err := thread.extractValue(instructions, 0, t, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Variable{Name: n, Type: t.String(), Value: val}, nil
|
|
}
|
|
|
|
// Execute the stack program taking into account the current stack frame
|
|
func (thread *ThreadContext) executeStackProgram(instructions []byte) (int64, error) {
|
|
regs, err := thread.Registers()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fde, err := thread.dbp.frameEntries.FDEForPC(regs.PC())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fctx := fde.EstablishFrame(regs.PC())
|
|
cfa := fctx.CFAOffset() + int64(regs.SP())
|
|
address, err := op.ExecuteStackProgram(cfa, instructions)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return address, nil
|
|
}
|
|
|
|
// Extracts the address of a variable, dereferencing any pointers
|
|
func (thread *ThreadContext) extractVariableDataAddress(entry *dwarf.Entry, rdr *reader.Reader) (int64, error) {
|
|
instructions, err := rdr.InstructionsForEntry(entry)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
address, err := thread.executeStackProgram(instructions)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Dereference pointers to get down the concrete type
|
|
for typeEntry, err := rdr.SeekToType(entry, true, false); typeEntry != nil; typeEntry, err = rdr.SeekToType(typeEntry, true, false) {
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if typeEntry.Tag != dwarf.TagPointerType {
|
|
break
|
|
}
|
|
|
|
ptraddress := uintptr(address)
|
|
|
|
ptr, err := thread.readMemory(ptraddress, thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
address = int64(binary.LittleEndian.Uint64(ptr))
|
|
}
|
|
|
|
return address, nil
|
|
}
|
|
|
|
// Extracts the value from the instructions given in the DW_AT_location entry.
|
|
// We execute the stack program described in the DW_OP_* instruction stream, and
|
|
// then grab the value from the other processes memory.
|
|
func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}, printStructName bool) (string, error) {
|
|
return thread.extractValueInternal(instructions, addr, typ, printStructName, 0)
|
|
}
|
|
|
|
func (thread *ThreadContext) extractValueInternal(instructions []byte, addr int64, typ interface{}, printStructName bool, recurseLevel int) (string, error) {
|
|
var err error
|
|
|
|
if addr == 0 {
|
|
addr, err = thread.executeStackProgram(instructions)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
// If we have a user defined type, find the
|
|
// underlying concrete type and use that.
|
|
for {
|
|
if tt, ok := typ.(*dwarf.TypedefType); ok {
|
|
typ = tt.Type
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
ptraddress := uintptr(addr)
|
|
switch t := typ.(type) {
|
|
case *dwarf.PtrType:
|
|
ptr, err := thread.readMemory(ptraddress, thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
intaddr := int64(binary.LittleEndian.Uint64(ptr))
|
|
if intaddr == 0 {
|
|
return fmt.Sprintf("%s nil", t.String()), nil
|
|
}
|
|
|
|
// Don't increase the recursion level when dereferencing pointers
|
|
val, err := thread.extractValueInternal(nil, intaddr, t.Type, printStructName, recurseLevel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("*%s", val), nil
|
|
case *dwarf.StructType:
|
|
switch {
|
|
case t.StructName == "string":
|
|
return thread.readString(ptraddress)
|
|
case strings.HasPrefix(t.StructName, "[]"):
|
|
return thread.readSlice(ptraddress, t)
|
|
default:
|
|
// Recursively call extractValue to grab
|
|
// the value of all the members of the struct.
|
|
if recurseLevel <= maxVariableRecurse {
|
|
fields := make([]string, 0, len(t.Field))
|
|
for _, field := range t.Field {
|
|
val, err := thread.extractValueInternal(nil, field.ByteOffset+addr, field.Type, printStructName, recurseLevel+1)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
fields = append(fields, fmt.Sprintf("%s: %s", field.Name, val))
|
|
}
|
|
if printStructName {
|
|
return fmt.Sprintf("%s {%s}", t.StructName, strings.Join(fields, ", ")), nil
|
|
}
|
|
return fmt.Sprintf("{%s}", strings.Join(fields, ", ")), nil
|
|
}
|
|
// no fields
|
|
if printStructName {
|
|
return fmt.Sprintf("%s {...}", t.StructName), nil
|
|
}
|
|
return "{...}", nil
|
|
}
|
|
case *dwarf.ArrayType:
|
|
return thread.readArray(ptraddress, t)
|
|
case *dwarf.IntType:
|
|
return thread.readInt(ptraddress, t.ByteSize)
|
|
case *dwarf.UintType:
|
|
return thread.readUint(ptraddress, t.ByteSize)
|
|
case *dwarf.FloatType:
|
|
return thread.readFloat(ptraddress, t.ByteSize)
|
|
case *dwarf.BoolType:
|
|
return thread.readBool(ptraddress)
|
|
case *dwarf.FuncType:
|
|
return thread.readFunctionPtr(ptraddress)
|
|
case *dwarf.VoidType:
|
|
return "(void)", nil
|
|
case *dwarf.UnspecifiedType:
|
|
return "(unknown)", nil
|
|
default:
|
|
fmt.Printf("Unknown type: %T\n", t)
|
|
}
|
|
|
|
return "", fmt.Errorf("could not find value for type %s", typ)
|
|
}
|
|
|
|
func (thread *ThreadContext) readString(addr uintptr) (string, error) {
|
|
// string data structure is always two ptrs in size. Addr, followed by len
|
|
// http://research.swtch.com/godata
|
|
|
|
// read len
|
|
val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
strlen := int(binary.LittleEndian.Uint64(val))
|
|
|
|
// read addr
|
|
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
addr = uintptr(binary.LittleEndian.Uint64(val))
|
|
if addr == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
val, err = thread.readMemory(addr, strlen)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return *(*string)(unsafe.Pointer(&val)), nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readSlice(addr uintptr, t *dwarf.StructType) (string, error) {
|
|
var sliceLen, sliceCap int64
|
|
var arrayAddr uintptr
|
|
var arrayType dwarf.Type
|
|
for _, f := range t.Field {
|
|
switch f.Name {
|
|
case "array":
|
|
val, err := thread.readMemory(addr+uintptr(f.ByteOffset), thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
arrayAddr = uintptr(binary.LittleEndian.Uint64(val))
|
|
// Dereference array type to get value type
|
|
ptrType, ok := f.Type.(*dwarf.PtrType)
|
|
if !ok {
|
|
return "", fmt.Errorf("Invalid type %s in slice array", f.Type)
|
|
}
|
|
arrayType = ptrType.Type
|
|
case "len":
|
|
lstr, err := thread.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sliceLen, err = strconv.ParseInt(lstr, 10, 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
case "cap":
|
|
cstr, err := thread.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sliceCap, err = strconv.ParseInt(cstr, 10, 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
|
|
stride := arrayType.Size()
|
|
if _, ok := arrayType.(*dwarf.PtrType); ok {
|
|
stride = int64(thread.dbp.arch.PtrSize())
|
|
}
|
|
vals, err := thread.readArrayValues(arrayAddr, sliceLen, stride, arrayType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("[]%s len: %d, cap: %d, [%s]", arrayType, sliceLen, sliceCap, strings.Join(vals, ",")), nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readArray(addr uintptr, t *dwarf.ArrayType) (string, error) {
|
|
if t.Count > 0 {
|
|
vals, err := thread.readArrayValues(addr, t.Count, t.ByteSize/t.Count, t.Type)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%s [%s]", t, strings.Join(vals, ",")), nil
|
|
}
|
|
// because you can declare a zero-size array
|
|
return fmt.Sprintf("%s []", t), nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readArrayValues(addr uintptr, count int64, stride int64, t dwarf.Type) ([]string, error) {
|
|
vals := make([]string, 0)
|
|
|
|
for i := int64(0); i < count; i++ {
|
|
// Cap number of elements
|
|
if i >= maxArrayValues {
|
|
vals = append(vals, fmt.Sprintf("...+%d more", count-maxArrayValues))
|
|
break
|
|
}
|
|
|
|
val, err := thread.extractValue(nil, int64(addr+uintptr(i*stride)), t, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vals = append(vals, val)
|
|
}
|
|
return vals, nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readInt(addr uintptr, size int64) (string, error) {
|
|
n, err := thread.readIntRaw(addr, size)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strconv.FormatInt(n, 10), nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readIntRaw(addr uintptr, size int64) (int64, error) {
|
|
var n int64
|
|
|
|
val, err := thread.readMemory(addr, int(size))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch size {
|
|
case 1:
|
|
n = int64(val[0])
|
|
case 2:
|
|
n = int64(binary.LittleEndian.Uint16(val))
|
|
case 4:
|
|
n = int64(binary.LittleEndian.Uint32(val))
|
|
case 8:
|
|
n = int64(binary.LittleEndian.Uint64(val))
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readUint(addr uintptr, size int64) (string, error) {
|
|
n, err := thread.readUintRaw(addr, size)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strconv.FormatUint(n, 10), nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readUintRaw(addr uintptr, size int64) (uint64, error) {
|
|
var n uint64
|
|
|
|
val, err := thread.readMemory(addr, int(size))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
switch size {
|
|
case 1:
|
|
n = uint64(val[0])
|
|
case 2:
|
|
n = uint64(binary.LittleEndian.Uint16(val))
|
|
case 4:
|
|
n = uint64(binary.LittleEndian.Uint32(val))
|
|
case 8:
|
|
n = uint64(binary.LittleEndian.Uint64(val))
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readFloat(addr uintptr, size int64) (string, error) {
|
|
val, err := thread.readMemory(addr, int(size))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
buf := bytes.NewBuffer(val)
|
|
|
|
switch size {
|
|
case 4:
|
|
n := float32(0)
|
|
binary.Read(buf, binary.LittleEndian, &n)
|
|
return strconv.FormatFloat(float64(n), 'f', -1, int(size)*8), nil
|
|
case 8:
|
|
n := float64(0)
|
|
binary.Read(buf, binary.LittleEndian, &n)
|
|
return strconv.FormatFloat(n, 'f', -1, int(size)*8), nil
|
|
}
|
|
|
|
return "", fmt.Errorf("could not read float")
|
|
}
|
|
|
|
func (thread *ThreadContext) readBool(addr uintptr) (string, error) {
|
|
val, err := thread.readMemory(addr, 1)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if val[0] == 0 {
|
|
return "false", nil
|
|
}
|
|
|
|
return "true", nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readFunctionPtr(addr uintptr) (string, error) {
|
|
val, err := thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// dereference pointer to find function pc
|
|
addr = uintptr(binary.LittleEndian.Uint64(val))
|
|
if addr == 0 {
|
|
return "nil", nil
|
|
}
|
|
|
|
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
funcAddr := binary.LittleEndian.Uint64(val)
|
|
fn := thread.dbp.goSymTable.PCToFunc(uint64(funcAddr))
|
|
if fn == nil {
|
|
return "", fmt.Errorf("could not find function for %#v", funcAddr)
|
|
}
|
|
|
|
return fn.Name, nil
|
|
}
|
|
|
|
func (thread *ThreadContext) readMemory(addr uintptr, size int) ([]byte, error) {
|
|
if size == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
buf := make([]byte, size)
|
|
_, err := readMemory(thread, addr, buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
// Fetches all variables of a specific type in the current function scope
|
|
func (thread *ThreadContext) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
|
pc, err := thread.PC()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reader := thread.dbp.DwarfReader()
|
|
|
|
_, err = reader.SeekToFunction(pc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vars := make([]*Variable, 0)
|
|
|
|
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if entry.Tag == tag {
|
|
val, err := thread.extractVariableFromEntry(entry)
|
|
if err != nil {
|
|
// skip variables that we can't parse yet
|
|
continue
|
|
}
|
|
|
|
vars = append(vars, val)
|
|
}
|
|
}
|
|
|
|
return vars, nil
|
|
}
|