
This commit adds a new mode to call injection. If the runtime.debugPinner function is available in the target executable it obtains a pinner by calling it and then uses it to pin the pointers in the results of call injection. This allows the code for call injection to be refactored to execute the calls in the normal order, since it doesn't need to be concerned with having space on the target's memory to store intermediate values. Updates #3310
3080 lines
84 KiB
Go
3080 lines
84 KiB
Go
package proc
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/dwarf"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/constant"
|
|
"go/parser"
|
|
"go/printer"
|
|
"go/token"
|
|
"reflect"
|
|
"runtime/debug"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
|
"github.com/go-delve/delve/pkg/goversion"
|
|
"github.com/go-delve/delve/pkg/logflags"
|
|
"github.com/go-delve/delve/pkg/proc/evalop"
|
|
)
|
|
|
|
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
|
|
|
|
const (
|
|
goDictionaryName = ".dict"
|
|
goClosurePtr = ".closureptr"
|
|
)
|
|
|
|
// EvalScope is the scope for variable evaluation. Contains the thread,
|
|
// current location (PC), and canonical frame address.
|
|
type EvalScope struct {
|
|
Location
|
|
Regs op.DwarfRegisters
|
|
Mem MemoryReadWriter // Target's memory
|
|
g *G
|
|
threadID int
|
|
BinInfo *BinaryInfo
|
|
target *Target
|
|
loadCfg *LoadConfig
|
|
|
|
frameOffset int64
|
|
|
|
// When the following pointer is not nil this EvalScope was created
|
|
// by EvalExpressionWithCalls and function call injection are allowed.
|
|
// See the top comment in fncall.go for a description of how the call
|
|
// injection protocol is handled.
|
|
callCtx *callContext
|
|
|
|
dictAddr uint64 // dictionary address for instantiated generic functions
|
|
|
|
enclosingRangeScopes []*EvalScope
|
|
rangeFrames []Stackframe
|
|
}
|
|
|
|
type localsFlags uint8
|
|
|
|
const (
|
|
// If localsTrustArgOrder is set function arguments that don't have an
|
|
// address will have one assigned by looking at their position in the argument
|
|
// list.
|
|
localsTrustArgOrder localsFlags = 1 << iota
|
|
|
|
// If localsNoDeclLineCheck the declaration line isn't checked at
|
|
// all to determine if the variable is in scope.
|
|
localsNoDeclLineCheck
|
|
|
|
// If localsOnlyRangeBodyClosures is set simpleLocals only returns
|
|
// variables containing the range body closure.
|
|
localsOnlyRangeBodyClosures
|
|
|
|
// If localsIsRangeBody is set DW_AT_formal_parameter variables will be
|
|
// considered local variables.
|
|
localsIsRangeBody
|
|
)
|
|
|
|
// ConvertEvalScope returns a new EvalScope in the context of the
|
|
// specified goroutine ID and stack frame.
|
|
// If deferCall is > 0 the eval scope will be relative to the specified deferred call.
|
|
func ConvertEvalScope(dbp *Target, gid int64, frame, deferCall int) (*EvalScope, error) {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return nil, err
|
|
}
|
|
ct := dbp.CurrentThread()
|
|
threadID := ct.ThreadID()
|
|
g, err := FindGoroutine(dbp, gid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var opts StacktraceOptions
|
|
if deferCall > 0 {
|
|
opts = StacktraceReadDefers
|
|
}
|
|
|
|
var locs []Stackframe
|
|
if g != nil {
|
|
if g.Thread != nil {
|
|
threadID = g.Thread.ThreadID()
|
|
}
|
|
locs, err = GoroutineStacktrace(dbp, g, frame+1, opts)
|
|
} else {
|
|
locs, err = ThreadStacktrace(dbp, ct, frame+1)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if frame >= len(locs) {
|
|
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
|
}
|
|
|
|
if deferCall > 0 {
|
|
if deferCall-1 >= len(locs[frame].Defers) {
|
|
return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
|
|
}
|
|
|
|
d := locs[frame].Defers[deferCall-1]
|
|
if d.Unreadable != nil {
|
|
return nil, d.Unreadable
|
|
}
|
|
|
|
return d.EvalScope(dbp, ct)
|
|
}
|
|
|
|
return FrameToScope(dbp, dbp.Memory(), g, threadID, locs[frame:]...), nil
|
|
}
|
|
|
|
// FrameToScope returns a new EvalScope for frames[0].
|
|
// If frames has at least two elements all memory between
|
|
// frames[0].Regs.SP() and frames[1].Regs.CFA will be cached.
|
|
// Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA
|
|
// will be cached.
|
|
func FrameToScope(t *Target, thread MemoryReadWriter, g *G, threadID int, frames ...Stackframe) *EvalScope {
|
|
// Creates a cacheMem that will preload the entire stack frame the first
|
|
// time any local variable is read.
|
|
// Remember that the stack grows downward in memory.
|
|
minaddr := frames[0].Regs.SP()
|
|
var maxaddr uint64
|
|
if len(frames) > 1 && frames[0].SystemStack == frames[1].SystemStack {
|
|
maxaddr = uint64(frames[1].Regs.CFA)
|
|
} else {
|
|
maxaddr = uint64(frames[0].Regs.CFA)
|
|
}
|
|
if maxaddr > minaddr && maxaddr-minaddr < maxFramePrefetchSize {
|
|
thread = cacheMemory(thread, minaddr, int(maxaddr-minaddr))
|
|
}
|
|
|
|
s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, g: g, BinInfo: t.BinInfo(), target: t, frameOffset: frames[0].FrameOffset(), threadID: threadID}
|
|
s.PC = frames[0].lastpc
|
|
return s
|
|
}
|
|
|
|
// ThreadScope returns an EvalScope for the given thread.
|
|
func ThreadScope(t *Target, thread Thread) (*EvalScope, error) {
|
|
locations, err := ThreadStacktrace(t, thread, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(locations) < 1 {
|
|
return nil, errors.New("could not decode first frame")
|
|
}
|
|
return FrameToScope(t, thread.ProcessMemory(), nil, thread.ThreadID(), locations...), nil
|
|
}
|
|
|
|
// GoroutineScope returns an EvalScope for the goroutine running on the given thread.
|
|
func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) {
|
|
locations, err := ThreadStacktrace(t, thread, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(locations) < 1 {
|
|
return nil, errors.New("could not decode first frame")
|
|
}
|
|
g, err := GetG(thread)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
threadID := 0
|
|
if g.Thread != nil {
|
|
threadID = g.Thread.ThreadID()
|
|
}
|
|
return FrameToScope(t, thread.ProcessMemory(), g, threadID, locations...), nil
|
|
}
|
|
|
|
func (scope *EvalScope) evalopFlags() evalop.Flags {
|
|
flags := evalop.Flags(0)
|
|
if scope.BinInfo.hasDebugPinner() {
|
|
flags |= evalop.HasDebugPinner
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// EvalExpression returns the value of the given expression.
|
|
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
|
|
ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stack := &evalStack{}
|
|
|
|
scope.loadCfg = &cfg
|
|
stack.eval(scope, ops)
|
|
ev, err := stack.result(&cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ev.loadValue(cfg)
|
|
if ev.Name == "" {
|
|
ev.Name = expr
|
|
}
|
|
return ev, nil
|
|
}
|
|
|
|
type scopeToEvalLookup struct {
|
|
*EvalScope
|
|
}
|
|
|
|
func (s scopeToEvalLookup) FindTypeExpr(expr ast.Expr) (godwarf.Type, error) {
|
|
return s.BinInfo.findTypeExpr(expr)
|
|
}
|
|
|
|
func (scope scopeToEvalLookup) HasBuiltin(name string) bool {
|
|
return supportedBuiltins[name] != nil
|
|
}
|
|
|
|
// ChanGoroutines returns the list of goroutines waiting to receive from or
|
|
// send to the channel.
|
|
func (scope *EvalScope) ChanGoroutines(expr string, start, count int) ([]int64, error) {
|
|
t, err := parser.ParseExpr(expr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v, err := scope.evalAST(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v.Kind != reflect.Chan {
|
|
return nil, nil
|
|
}
|
|
|
|
structMemberMulti := func(v *Variable, names ...string) *Variable {
|
|
for _, name := range names {
|
|
var err error
|
|
v, err = v.structMember(name)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
waitqFirst := func(qname string) *Variable {
|
|
qvar := structMemberMulti(v, qname, "first")
|
|
if qvar == nil {
|
|
return nil
|
|
}
|
|
return qvar.maybeDereference()
|
|
}
|
|
|
|
var goids []int64
|
|
|
|
waitqToGoIDSlice := func(qvar *Variable) error {
|
|
if qvar == nil {
|
|
return nil
|
|
}
|
|
for {
|
|
if qvar.Addr == 0 {
|
|
return nil
|
|
}
|
|
if len(goids) > count {
|
|
return nil
|
|
}
|
|
goidVar := structMemberMulti(qvar, "g", "goid")
|
|
if goidVar == nil {
|
|
return nil
|
|
}
|
|
goidVar.loadValue(loadSingleValue)
|
|
if goidVar.Unreadable != nil {
|
|
return goidVar.Unreadable
|
|
}
|
|
goid, _ := constant.Int64Val(goidVar.Value)
|
|
if start > 0 {
|
|
start--
|
|
} else {
|
|
goids = append(goids, goid)
|
|
}
|
|
|
|
nextVar, err := qvar.structMember("next")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
qvar = nextVar.maybeDereference()
|
|
}
|
|
}
|
|
|
|
recvqVar := waitqFirst("recvq")
|
|
err = waitqToGoIDSlice(recvqVar)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sendqVar := waitqFirst("sendq")
|
|
err = waitqToGoIDSlice(sendqVar)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return goids, nil
|
|
}
|
|
|
|
// Locals returns all variables in 'scope' named wantedName, or all of them
|
|
// if wantedName is "".
|
|
// If scope is the scope for a range-over-func closure body it will merge in
|
|
// the scopes of the enclosing functions.
|
|
func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variable, error) {
|
|
var scopes [][]*Variable
|
|
filter := func(vars []*Variable) []*Variable {
|
|
if wantedName == "" || vars == nil {
|
|
return vars
|
|
}
|
|
vars2 := []*Variable{}
|
|
for _, v := range vars {
|
|
if v.Name == wantedName {
|
|
vars2 = append(vars2, v)
|
|
}
|
|
}
|
|
return vars2
|
|
}
|
|
|
|
rangeBodyFlags := localsFlags(0)
|
|
if scope.Fn != nil && scope.Fn.rangeParentName() != "" {
|
|
rangeBodyFlags = localsFlags(localsIsRangeBody)
|
|
}
|
|
|
|
vars0, err := scope.simpleLocals(flags|rangeBodyFlags, wantedName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vars0 = filter(vars0)
|
|
if scope.Fn.extra(scope.BinInfo).rangeParent == nil || scope.target == nil || scope.g == nil {
|
|
return vars0, nil
|
|
}
|
|
if wantedName != "" && len(vars0) > 0 {
|
|
return vars0, nil
|
|
}
|
|
|
|
scopes = append(scopes, vars0)
|
|
|
|
if scope.rangeFrames == nil {
|
|
err := scope.setupRangeFrames()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
for i, scope2 := range scope.enclosingRangeScopes {
|
|
if scope2 == nil {
|
|
scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[2*i:]...)
|
|
scope.enclosingRangeScopes[i] = scope2
|
|
}
|
|
rangeBodyFlags := localsFlags(localsIsRangeBody)
|
|
if i == len(scope.enclosingRangeScopes)-1 {
|
|
rangeBodyFlags = 0
|
|
}
|
|
vars, err := scope2.simpleLocals(flags|rangeBodyFlags, wantedName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vars = filter(vars)
|
|
scopes = append(scopes, vars)
|
|
if wantedName != "" && len(vars) > 0 {
|
|
return vars, nil
|
|
}
|
|
}
|
|
|
|
vars := []*Variable{}
|
|
for i := len(scopes) - 1; i >= 0; i-- {
|
|
vars = append(vars, scopes[i]...)
|
|
}
|
|
|
|
// Apply shadowning
|
|
lvn := map[string]*Variable{}
|
|
for _, v := range vars {
|
|
if otherv := lvn[v.Name]; otherv != nil {
|
|
otherv.Flags |= VariableShadowed
|
|
}
|
|
lvn[v.Name] = v
|
|
}
|
|
return vars, nil
|
|
}
|
|
|
|
func (scope *EvalScope) setupRangeFrames() error {
|
|
var err error
|
|
scope.rangeFrames, err = rangeFuncStackTrace(scope.target, scope.g)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(scope.rangeFrames) > 0 {
|
|
scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame
|
|
}
|
|
scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)/2)
|
|
return nil
|
|
}
|
|
|
|
// simpleLocals returns all local variables in 'scope'.
|
|
// This function does not try to merge the scopes of range-over-func closure
|
|
// bodies with their enclosing function, for that use (*EvalScope).Locals or
|
|
// (*EvalScope).FindLocal instead.
|
|
// If wantedName is specified only variables called wantedName or "&"+wantedName are returned.
|
|
func (scope *EvalScope) simpleLocals(flags localsFlags, wantedName string) ([]*Variable, error) {
|
|
if scope.Fn == nil {
|
|
return nil, errors.New("unable to find function context")
|
|
}
|
|
|
|
if scope.image().Stripped() {
|
|
return nil, errors.New("unable to find locals: no debug information present in binary")
|
|
}
|
|
|
|
trustArgOrder := (flags&localsTrustArgOrder != 0) && scope.BinInfo.Producer() != "" && goversion.ProducerAfterOrEqual(scope.BinInfo.Producer(), 1, 12) && scope.Fn != nil && (scope.PC == scope.Fn.Entry)
|
|
|
|
dwarfTree, err := scope.image().getDwarfTree(scope.Fn.offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
variablesFlags := reader.VariablesOnlyVisible | reader.VariablesSkipInlinedSubroutines
|
|
if flags&localsNoDeclLineCheck != 0 {
|
|
variablesFlags = reader.VariablesNoDeclLineCheck
|
|
}
|
|
if scope.BinInfo.Producer() != "" && goversion.ProducerAfterOrEqual(scope.BinInfo.Producer(), 1, 15) {
|
|
variablesFlags |= reader.VariablesTrustDeclLine
|
|
}
|
|
|
|
varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags)
|
|
|
|
// look for dictionary entry
|
|
if scope.dictAddr == 0 {
|
|
scope.dictAddr = readLocalPtrVar(dwarfTree, goDictionaryName, scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem)
|
|
}
|
|
|
|
vars := make([]*Variable, 0, len(varEntries))
|
|
depths := make([]int, 0, len(varEntries))
|
|
for _, entry := range varEntries {
|
|
name, _ := entry.Val(dwarf.AttrName).(string)
|
|
switch {
|
|
case wantedName != "":
|
|
if name != wantedName && name != "&"+wantedName {
|
|
continue
|
|
}
|
|
case flags&localsOnlyRangeBodyClosures != 0:
|
|
if !strings.HasPrefix(name, "#yield") && !strings.HasPrefix(name, "&#yield") {
|
|
continue
|
|
}
|
|
default:
|
|
if name == goDictionaryName || name == goClosurePtr || strings.HasPrefix(name, "#yield") || strings.HasPrefix(name, "&#yield") {
|
|
continue
|
|
}
|
|
}
|
|
if scope.Fn.rangeParentName() != "" && (strings.HasPrefix(name, "~") || entry.Val(godwarf.AttrGoClosureOffset) != nil) {
|
|
// Skip unnamed parameters and closure variables for range-over-func closure bodies
|
|
continue
|
|
}
|
|
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, scope.image(), scope.Regs, scope.Mem, entry.Tree, scope.dictAddr)
|
|
if err != nil {
|
|
// skip variables that we can't parse yet
|
|
continue
|
|
}
|
|
if trustArgOrder && ((val.Unreadable != nil && val.Addr == 0) || val.Flags&VariableFakeAddress != 0) && entry.Tag == dwarf.TagFormalParameter {
|
|
addr := afterLastArgAddr(vars)
|
|
if addr == 0 {
|
|
addr = uint64(scope.Regs.CFA)
|
|
}
|
|
addr = uint64(alignAddr(int64(addr), val.DwarfType.Align()))
|
|
val = newVariable(val.Name, addr, val.DwarfType, scope.BinInfo, scope.Mem)
|
|
}
|
|
vars = append(vars, val)
|
|
depth := entry.Depth
|
|
if (flags&localsIsRangeBody == 0) && (entry.Tag == dwarf.TagFormalParameter) {
|
|
if depth <= 1 {
|
|
depth = 0
|
|
}
|
|
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
|
|
if isret {
|
|
val.Flags |= VariableReturnArgument
|
|
} else {
|
|
val.Flags |= VariableArgument
|
|
}
|
|
}
|
|
depths = append(depths, depth)
|
|
}
|
|
|
|
if len(vars) == 0 {
|
|
return vars, nil
|
|
}
|
|
|
|
sort.Stable(&variablesByDepthAndDeclLine{vars, depths})
|
|
|
|
lvn := map[string]*Variable{} // lvn[n] is the last variable we saw named n
|
|
|
|
for i, v := range vars {
|
|
if name := v.Name; len(name) > 1 && name[0] == '&' {
|
|
locationExpr := v.LocationExpr
|
|
declLine := v.DeclLine
|
|
v = v.maybeDereference()
|
|
if v.Addr == 0 && v.Unreadable == nil {
|
|
v.Unreadable = errors.New("no address for escaped variable")
|
|
}
|
|
v.Name = name[1:]
|
|
v.Flags |= VariableEscaped
|
|
// See https://github.com/go-delve/delve/issues/2049 for details
|
|
if locationExpr != nil {
|
|
locationExpr.isEscaped = true
|
|
v.LocationExpr = locationExpr
|
|
}
|
|
v.DeclLine = declLine
|
|
vars[i] = v
|
|
}
|
|
if otherv := lvn[v.Name]; otherv != nil {
|
|
otherv.Flags |= VariableShadowed
|
|
}
|
|
lvn[v.Name] = v
|
|
}
|
|
|
|
return vars, nil
|
|
}
|
|
|
|
func afterLastArgAddr(vars []*Variable) uint64 {
|
|
for i := len(vars) - 1; i >= 0; i-- {
|
|
v := vars[i]
|
|
if (v.Flags&VariableArgument != 0) || (v.Flags&VariableReturnArgument != 0) {
|
|
return v.Addr + uint64(v.DwarfType.Size())
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// readLocalPtrVar reads the value of the local pointer variable vname. This
|
|
// is a low level helper function, it does not support nested scopes, range
|
|
// resolution across range bodies, type parameters, &c...
|
|
func readLocalPtrVar(dwarfTree *godwarf.Tree, vname string, tgt *Target, bi *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter) uint64 {
|
|
for _, entry := range dwarfTree.Children {
|
|
name, _ := entry.Val(dwarf.AttrName).(string)
|
|
if name == vname {
|
|
v, err := extractVarInfoFromEntry(tgt, bi, image, regs, mem, entry, 0)
|
|
if err != nil {
|
|
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err)
|
|
} else if v.Unreadable != nil {
|
|
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, v.Unreadable)
|
|
} else {
|
|
r, err := readUintRaw(v.mem, v.Addr, int64(bi.Arch.PtrSize()))
|
|
if err != nil {
|
|
logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err)
|
|
}
|
|
return r
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// setValue writes the value of srcv to dstv.
|
|
// - If srcv is a numerical literal constant and srcv is of a compatible type
|
|
// the necessary type conversion is performed.
|
|
// - If srcv is nil and dstv is of a nil'able type then dstv is nilled.
|
|
// - If srcv is the empty string and dstv is a string then dstv is set to the
|
|
// empty string.
|
|
// - If dstv is an "interface {}" and srcv is either an interface (possibly
|
|
// non-empty) or a pointer shaped type (map, channel, pointer or struct
|
|
// containing a single pointer field) the type conversion to "interface {}"
|
|
// is performed.
|
|
// - If srcv and dstv have the same type and are both addressable then the
|
|
// contents of srcv are copied byte-by-byte into dstv
|
|
func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {
|
|
srcv.loadValue(loadSingleValue)
|
|
|
|
typerr := srcv.isType(dstv.RealType, dstv.Kind)
|
|
if _, isTypeConvErr := typerr.(*typeConvErr); isTypeConvErr {
|
|
// attempt iface -> eface and ptr-shaped -> eface conversions.
|
|
return convertToEface(srcv, dstv)
|
|
}
|
|
if typerr != nil {
|
|
return typerr
|
|
}
|
|
|
|
if srcv.Unreadable != nil {
|
|
//lint:ignore ST1005 backwards compatibility
|
|
return fmt.Errorf("Expression %q is unreadable: %v", srcExpr, srcv.Unreadable)
|
|
}
|
|
|
|
// Numerical types
|
|
switch dstv.Kind {
|
|
case reflect.Float32, reflect.Float64:
|
|
f, _ := constant.Float64Val(srcv.Value)
|
|
return dstv.writeFloatRaw(f, dstv.RealType.Size())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, _ := constant.Int64Val(srcv.Value)
|
|
return dstv.writeUint(uint64(n), dstv.RealType.Size())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
n, _ := constant.Uint64Val(srcv.Value)
|
|
return dstv.writeUint(n, dstv.RealType.Size())
|
|
case reflect.Bool:
|
|
return dstv.writeBool(constant.BoolVal(srcv.Value))
|
|
case reflect.Complex64, reflect.Complex128:
|
|
real, _ := constant.Float64Val(constant.Real(srcv.Value))
|
|
imag, _ := constant.Float64Val(constant.Imag(srcv.Value))
|
|
return dstv.writeComplex(real, imag, dstv.RealType.Size())
|
|
case reflect.Func:
|
|
if dstv.RealType.Size() == 0 {
|
|
if dstv.Name != "" {
|
|
return fmt.Errorf("can not assign to %s", dstv.Name)
|
|
}
|
|
return errors.New("can not assign to function expression")
|
|
}
|
|
}
|
|
|
|
// nilling nillable variables
|
|
if srcv == nilVariable {
|
|
return dstv.writeZero()
|
|
}
|
|
|
|
if srcv.Kind == reflect.String {
|
|
if srcv.Base == 0 && srcv.Len > 0 && srcv.Flags&VariableConstant != 0 {
|
|
return errFuncCallNotAllowedStrAlloc
|
|
}
|
|
return dstv.writeString(uint64(srcv.Len), srcv.Base)
|
|
}
|
|
|
|
// slice assignment (this is not handled by the writeCopy below so that
|
|
// results of a reslice operation can be used here).
|
|
if srcv.Kind == reflect.Slice {
|
|
return dstv.writeSlice(srcv.Len, srcv.Cap, srcv.Base)
|
|
}
|
|
|
|
// allow any integer to be converted to any pointer
|
|
if t, isptr := dstv.RealType.(*godwarf.PtrType); isptr {
|
|
return dstv.writeUint(srcv.Children[0].Addr, t.ByteSize)
|
|
}
|
|
|
|
// byte-by-byte copying for everything else, but the source must be addressable
|
|
if srcv.Addr != 0 {
|
|
return dstv.writeCopy(srcv)
|
|
}
|
|
|
|
return fmt.Errorf("can not set variables of type %s (not implemented)", dstv.Kind.String())
|
|
}
|
|
|
|
// SetVariable sets the value of the named variable
|
|
func (scope *EvalScope) SetVariable(name, value string) error {
|
|
ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value, scope.evalopFlags())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stack := &evalStack{}
|
|
stack.eval(scope, ops)
|
|
_, err = stack.result(nil)
|
|
return err
|
|
}
|
|
|
|
// LocalVariables returns all local variables from the current function scope.
|
|
func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
|
|
vars, err := scope.Locals(0, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vars = filterVariables(vars, func(v *Variable) bool {
|
|
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
|
|
})
|
|
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
|
loadValues(vars, cfg)
|
|
return vars, nil
|
|
}
|
|
|
|
// FunctionArguments returns the name, value, and type of all current function arguments.
|
|
func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
|
|
vars, err := scope.Locals(0, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vars = filterVariables(vars, func(v *Variable) bool {
|
|
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
|
|
})
|
|
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
|
loadValues(vars, cfg)
|
|
return vars, nil
|
|
}
|
|
|
|
func filterVariables(vars []*Variable, pred func(v *Variable) bool) []*Variable {
|
|
r := make([]*Variable, 0, len(vars))
|
|
for i := range vars {
|
|
if pred(vars[i]) {
|
|
r = append(r, vars[i])
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func regsReplaceStaticBase(regs op.DwarfRegisters, image *Image) op.DwarfRegisters {
|
|
regs.StaticBase = image.StaticBase
|
|
return regs
|
|
}
|
|
|
|
// PackageVariables returns the name, value, and type of all package variables in the application.
|
|
func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
|
|
pkgvars := make([]packageVar, len(scope.BinInfo.packageVars))
|
|
copy(pkgvars, scope.BinInfo.packageVars)
|
|
sort.Slice(pkgvars, func(i, j int) bool {
|
|
if pkgvars[i].cu.image.addr == pkgvars[j].cu.image.addr {
|
|
return pkgvars[i].offset < pkgvars[j].offset
|
|
}
|
|
return pkgvars[i].cu.image.addr < pkgvars[j].cu.image.addr
|
|
})
|
|
vars := make([]*Variable, 0, len(scope.BinInfo.packageVars))
|
|
for _, pkgvar := range pkgvars {
|
|
reader := pkgvar.cu.image.dwarfReader
|
|
reader.Seek(pkgvar.offset)
|
|
entry, err := reader.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ignore errors trying to extract values
|
|
val, err := extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0)
|
|
if val != nil && val.Kind == reflect.Invalid {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
continue
|
|
}
|
|
val.loadValue(cfg)
|
|
vars = append(vars, val)
|
|
}
|
|
|
|
return vars, nil
|
|
}
|
|
|
|
func (scope *EvalScope) findGlobal(pkgName, varName string) (*Variable, error) {
|
|
for _, pkgPath := range scope.BinInfo.PackageMap[pkgName] {
|
|
v, err := scope.findGlobalInternal(pkgPath + "." + varName)
|
|
if err != nil || v != nil {
|
|
return v, err
|
|
}
|
|
}
|
|
v, err := scope.findGlobalInternal(pkgName + "." + varName)
|
|
if err != nil || v != nil {
|
|
return v, err
|
|
}
|
|
return nil, &errCouldNotFindSymbol{fmt.Sprintf("%s.%s", pkgName, varName)}
|
|
}
|
|
|
|
type errCouldNotFindSymbol struct {
|
|
name string
|
|
}
|
|
|
|
func (e *errCouldNotFindSymbol) Error() string {
|
|
return fmt.Sprintf("could not find symbol %s", e.name)
|
|
}
|
|
|
|
func isSymbolNotFound(e error) bool {
|
|
var e2 *errCouldNotFindSymbol
|
|
return errors.As(e, &e2)
|
|
}
|
|
|
|
func (scope *EvalScope) findGlobalInternal(name string) (*Variable, error) {
|
|
for _, pkgvar := range scope.BinInfo.packageVars {
|
|
if pkgvar.name == name || strings.HasSuffix(pkgvar.name, "/"+name) {
|
|
reader := pkgvar.cu.image.dwarfReader
|
|
reader.Seek(pkgvar.offset)
|
|
entry, err := reader.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return extractVarInfoFromEntry(scope.target, scope.BinInfo, pkgvar.cu.image, regsReplaceStaticBase(scope.Regs, pkgvar.cu.image), scope.Mem, godwarf.EntryToTree(entry), 0)
|
|
}
|
|
}
|
|
for _, fn := range scope.BinInfo.Functions {
|
|
if fn.Name == name || strings.HasSuffix(fn.Name, "/"+name) {
|
|
//TODO(aarzilli): convert function entry into a function type?
|
|
r := newVariable(fn.Name, fn.Entry, &godwarf.FuncType{}, scope.BinInfo, scope.Mem)
|
|
r.Value = constant.MakeString(fn.Name)
|
|
r.Base = fn.Entry
|
|
r.loaded = true
|
|
if fn.Entry == 0 {
|
|
r.Unreadable = fmt.Errorf("function %s is inlined", fn.Name)
|
|
}
|
|
return r, nil
|
|
}
|
|
}
|
|
for dwref, ctyp := range scope.BinInfo.consts {
|
|
for _, cval := range ctyp.values {
|
|
if cval.fullName == name || strings.HasSuffix(cval.fullName, "/"+name) {
|
|
t, err := scope.BinInfo.Images[dwref.imageIndex].Type(dwref.offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := newVariable(name, 0x0, t, scope.BinInfo, scope.Mem)
|
|
switch v.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
v.Value = constant.MakeInt64(cval.value)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
v.Value = constant.MakeUint64(uint64(cval.value))
|
|
default:
|
|
return nil, fmt.Errorf("unsupported constant kind %v", v.Kind)
|
|
}
|
|
v.Flags |= VariableConstant
|
|
v.loaded = true
|
|
return v, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// image returns the image containing the current function.
|
|
func (scope *EvalScope) image() *Image {
|
|
return scope.BinInfo.funcToImage(scope.Fn)
|
|
}
|
|
|
|
// evalStack stores the stack machine used to evaluate a program made of
|
|
// evalop.Ops.
|
|
// When an opcode sets callInjectionContinue execution of the program will be suspended
|
|
// and the call injection protocol will be executed instead.
|
|
type evalStack struct {
|
|
stack []*Variable // current stack of Variable values
|
|
fncalls []*functionCallState // stack of call injections currently being executed
|
|
ops []evalop.Op // program being executed
|
|
opidx int // program counter for the stack program
|
|
callInjectionContinue bool // when set program execution suspends and the call injection protocol is executed instead
|
|
err error
|
|
|
|
spoff, bpoff, fboff int64
|
|
scope *EvalScope
|
|
curthread Thread
|
|
lastRetiredFncall *functionCallState
|
|
debugPinner *Variable
|
|
}
|
|
|
|
func (s *evalStack) push(v *Variable) {
|
|
if v == nil {
|
|
panic(errors.New("internal debugger error, nil pushed onto variables stack"))
|
|
}
|
|
s.stack = append(s.stack, v)
|
|
}
|
|
|
|
func (s *evalStack) pop() *Variable {
|
|
v := s.stack[len(s.stack)-1]
|
|
s.stack = s.stack[:len(s.stack)-1]
|
|
return v
|
|
}
|
|
|
|
func (s *evalStack) peek() *Variable {
|
|
return s.stack[len(s.stack)-1]
|
|
}
|
|
|
|
func (s *evalStack) fncallPush(fncall *functionCallState) {
|
|
s.fncalls = append(s.fncalls, fncall)
|
|
}
|
|
|
|
func (s *evalStack) fncallPop() *functionCallState {
|
|
fncall := s.fncalls[len(s.fncalls)-1]
|
|
s.fncalls = s.fncalls[:len(s.fncalls)-1]
|
|
return fncall
|
|
}
|
|
|
|
func (s *evalStack) fncallPeek() *functionCallState {
|
|
return s.fncalls[len(s.fncalls)-1]
|
|
}
|
|
|
|
func (s *evalStack) pushErr(v *Variable, err error) {
|
|
s.err = err
|
|
s.stack = append(s.stack, v)
|
|
}
|
|
|
|
// eval evaluates ops. When it returns if callInjectionContinue is set the
|
|
// target program should be resumed to execute the call injection protocol.
|
|
// Otherwise the result of the evaluation can be retrieved using
|
|
// stack.result.
|
|
func (stack *evalStack) eval(scope *EvalScope, ops []evalop.Op) {
|
|
if logflags.FnCall() {
|
|
fncallLog("eval program:\n%s", evalop.Listing(nil, ops))
|
|
}
|
|
|
|
stack.ops = ops
|
|
stack.scope = scope
|
|
|
|
if scope.g != nil {
|
|
stack.spoff = int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi)
|
|
stack.bpoff = int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(scope.g.stack.hi)
|
|
stack.fboff = scope.Regs.FrameBase - int64(scope.g.stack.hi)
|
|
}
|
|
|
|
if scope.g != nil && scope.g.Thread != nil {
|
|
stack.curthread = scope.g.Thread
|
|
}
|
|
|
|
stack.run()
|
|
}
|
|
|
|
// resume resumes evaluation of stack.ops. When it returns if
|
|
// callInjectionContinue is set the target program should be resumed to
|
|
// execute the call injection protocol. Otherwise the result of the
|
|
// evaluation can be retrieved using stack.result.
|
|
func (stack *evalStack) resume(g *G) {
|
|
stack.callInjectionContinue = false
|
|
scope := stack.scope
|
|
// Go 1.15 will move call injection execution to a different goroutine,
|
|
// but we want to keep evaluation on the original goroutine.
|
|
if g.ID == scope.g.ID {
|
|
scope.g = g
|
|
} else {
|
|
// We are in Go 1.15 and we switched to a new goroutine, the original
|
|
// goroutine is now parked and therefore does not have a thread
|
|
// associated.
|
|
scope.g.Thread = nil
|
|
scope.g.Status = Gwaiting
|
|
scope.callCtx.injectionThread = g.Thread
|
|
}
|
|
|
|
// adjust the value of registers inside scope
|
|
pcreg, bpreg, spreg := scope.Regs.Reg(scope.Regs.PCRegNum), scope.Regs.Reg(scope.Regs.BPRegNum), scope.Regs.Reg(scope.Regs.SPRegNum)
|
|
scope.Regs.ClearRegisters()
|
|
scope.Regs.AddReg(scope.Regs.PCRegNum, pcreg)
|
|
scope.Regs.AddReg(scope.Regs.BPRegNum, bpreg)
|
|
scope.Regs.AddReg(scope.Regs.SPRegNum, spreg)
|
|
scope.Regs.Reg(scope.Regs.SPRegNum).Uint64Val = uint64(stack.spoff + int64(scope.g.stack.hi))
|
|
scope.Regs.Reg(scope.Regs.BPRegNum).Uint64Val = uint64(stack.bpoff + int64(scope.g.stack.hi))
|
|
scope.Regs.FrameBase = stack.fboff + int64(scope.g.stack.hi)
|
|
scope.Regs.CFA = scope.frameOffset + int64(scope.g.stack.hi)
|
|
stack.curthread = g.Thread
|
|
scope.rangeFrames = nil
|
|
scope.enclosingRangeScopes = nil
|
|
|
|
finished := funcCallStep(scope, stack, g.Thread)
|
|
if finished {
|
|
funcCallFinish(scope, stack)
|
|
}
|
|
|
|
if stack.callInjectionContinue {
|
|
// not done with call injection, stay in this mode
|
|
stack.scope.callCtx.injectionThread = nil
|
|
return
|
|
}
|
|
|
|
// call injection protocol suspended or concluded, resume normal opcode execution
|
|
stack.run()
|
|
}
|
|
|
|
func (stack *evalStack) run() {
|
|
scope, curthread := stack.scope, stack.curthread
|
|
for stack.opidx < len(stack.ops) && stack.err == nil {
|
|
stack.callInjectionContinue = false
|
|
stack.executeOp()
|
|
// If the instruction we just executed requests the call injection
|
|
// protocol by setting callInjectionContinue we switch to it.
|
|
if stack.callInjectionContinue && stack.err == nil {
|
|
scope.callCtx.injectionThread = nil
|
|
return
|
|
}
|
|
}
|
|
|
|
if stack.err == nil && len(stack.fncalls) > 0 {
|
|
stack.err = fmt.Errorf("internal debugger error: eval program finished without error but %d call injections still active", len(stack.fncalls))
|
|
return
|
|
}
|
|
|
|
// If there is an error we must undo all currently executing call
|
|
// injections before returning.
|
|
|
|
if len(stack.fncalls) > 0 {
|
|
fncallLog("undoing calls (%v)", stack.err)
|
|
fncall := stack.fncallPeek()
|
|
if fncall == stack.lastRetiredFncall {
|
|
stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err)
|
|
return
|
|
}
|
|
if fncall.undoInjection != nil {
|
|
if fncall.undoInjection.doComplete2 {
|
|
// doComplete2 is set if CallInjectionComplete{DoPinning: true} has been
|
|
// executed but CallInjectionComplete2 hasn't.
|
|
regs, err := curthread.Registers()
|
|
if err == nil {
|
|
callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
|
|
}
|
|
} else {
|
|
// undoInjection is set if evalop.CallInjectionSetTarget has been
|
|
// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
|
|
// call in evalop.CallInjectionSetTarget before continuing.
|
|
switch scope.BinInfo.Arch.Name {
|
|
case "amd64":
|
|
regs, _ := curthread.Registers()
|
|
setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
|
|
setPC(curthread, fncall.undoInjection.oldpc)
|
|
case "arm64", "ppc64le":
|
|
setLR(curthread, fncall.undoInjection.oldlr)
|
|
setPC(curthread, fncall.undoInjection.oldpc)
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
}
|
|
}
|
|
stack.lastRetiredFncall = fncall
|
|
// Resume target to undo one call
|
|
stack.callInjectionContinue = true
|
|
scope.callCtx.injectionThread = nil
|
|
return
|
|
}
|
|
}
|
|
|
|
func (stack *evalStack) result(cfg *LoadConfig) (*Variable, error) {
|
|
var r *Variable
|
|
switch len(stack.stack) {
|
|
case 0:
|
|
// ok
|
|
case 1:
|
|
r = stack.peek()
|
|
default:
|
|
if stack.err == nil {
|
|
stack.err = fmt.Errorf("internal debugger error: wrong stack size at end %d", len(stack.stack))
|
|
}
|
|
}
|
|
if r != nil && cfg != nil && stack.err == nil {
|
|
r.loadValue(*cfg)
|
|
}
|
|
return r, stack.err
|
|
}
|
|
|
|
// executeOp executes the opcode at stack.ops[stack.opidx] and increments stack.opidx.
|
|
func (stack *evalStack) executeOp() {
|
|
scope, ops, curthread := stack.scope, stack.ops, stack.curthread
|
|
defer func() {
|
|
err := recover()
|
|
if err != nil {
|
|
stack.err = fmt.Errorf("internal debugger error: %v (recovered)\n%s", err, string(debug.Stack()))
|
|
}
|
|
}()
|
|
switch op := ops[stack.opidx].(type) {
|
|
case *evalop.PushCurg:
|
|
if scope.g != nil {
|
|
stack.push(scope.g.variable.clone())
|
|
} else {
|
|
typ, err := scope.BinInfo.findType("runtime.g")
|
|
if err != nil {
|
|
stack.err = fmt.Errorf("could not find runtime.g: %v", err)
|
|
return
|
|
}
|
|
gvar := newVariable("curg", fakeAddressUnresolv, typ, scope.BinInfo, scope.Mem)
|
|
gvar.loaded = true
|
|
gvar.Flags = VariableFakeAddress
|
|
gvar.Children = append(gvar.Children, *newConstant(constant.MakeInt64(0), scope.Mem))
|
|
gvar.Children[0].Name = "goid"
|
|
stack.push(gvar)
|
|
}
|
|
|
|
case *evalop.PushFrameoff:
|
|
stack.push(newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem))
|
|
|
|
case *evalop.PushRangeParentOffset:
|
|
if scope.rangeFrames == nil {
|
|
stack.err = scope.setupRangeFrames()
|
|
}
|
|
if len(scope.rangeFrames) > 0 {
|
|
stack.push(newConstant(constant.MakeInt64(scope.rangeFrames[len(scope.rangeFrames)-2].FrameOffset()), scope.Mem))
|
|
} else {
|
|
stack.push(newConstant(constant.MakeInt64(0), scope.Mem))
|
|
}
|
|
|
|
case *evalop.PushThreadID:
|
|
stack.push(newConstant(constant.MakeInt64(int64(scope.threadID)), scope.Mem))
|
|
|
|
case *evalop.PushConst:
|
|
stack.push(newConstant(op.Value, scope.Mem))
|
|
|
|
case *evalop.PushLocal:
|
|
found := stack.pushLocal(scope, op.Name, op.Frame)
|
|
if !found {
|
|
stack.err = fmt.Errorf("could not find symbol value for %s", op.Name)
|
|
}
|
|
|
|
case *evalop.PushNil:
|
|
stack.push(nilVariable)
|
|
|
|
case *evalop.PushPackageVarOrSelect:
|
|
v, err := scope.findGlobal(op.Name, op.Sel)
|
|
if err != nil && !isSymbolNotFound(err) {
|
|
stack.err = err
|
|
return
|
|
}
|
|
if v != nil {
|
|
stack.push(v)
|
|
} else {
|
|
if op.NameIsString {
|
|
stack.err = fmt.Errorf("%q (type string) is not a struct", op.Name)
|
|
return
|
|
}
|
|
found := stack.pushIdent(scope, op.Name)
|
|
if stack.err != nil {
|
|
return
|
|
}
|
|
if found {
|
|
scope.evalStructSelector(&evalop.Select{Name: op.Sel}, stack)
|
|
} else {
|
|
stack.err = fmt.Errorf("could not find symbol value for %s", op.Name)
|
|
}
|
|
}
|
|
|
|
case *evalop.PushIdent:
|
|
found := stack.pushIdent(scope, op.Name)
|
|
if !found {
|
|
stack.err = fmt.Errorf("could not find symbol value for %s", op.Name)
|
|
}
|
|
|
|
case *evalop.PushLen:
|
|
v := stack.peek()
|
|
stack.push(newConstant(constant.MakeInt64(v.Len), scope.Mem))
|
|
|
|
case *evalop.Select:
|
|
scope.evalStructSelector(op, stack)
|
|
|
|
case *evalop.TypeAssert:
|
|
scope.evalTypeAssert(op, stack)
|
|
|
|
case *evalop.PointerDeref:
|
|
scope.evalPointerDeref(op, stack)
|
|
|
|
case *evalop.Unary:
|
|
scope.evalUnary(op, stack)
|
|
|
|
case *evalop.AddrOf:
|
|
scope.evalAddrOf(op, stack)
|
|
|
|
case *evalop.TypeCast:
|
|
scope.evalTypeCast(op, stack)
|
|
|
|
case *evalop.Reslice:
|
|
scope.evalReslice(op, stack)
|
|
|
|
case *evalop.Index:
|
|
scope.evalIndex(op, stack)
|
|
|
|
case *evalop.Jump:
|
|
scope.evalJump(op, stack)
|
|
|
|
case *evalop.Binary:
|
|
scope.evalBinary(op, stack)
|
|
|
|
case *evalop.BoolToConst:
|
|
x := stack.pop()
|
|
if x.Kind != reflect.Bool {
|
|
stack.err = errors.New("internal debugger error: expected boolean")
|
|
return
|
|
}
|
|
x.loadValue(loadFullValue)
|
|
stack.push(newConstant(x.Value, scope.Mem))
|
|
|
|
case *evalop.Pop:
|
|
stack.pop()
|
|
|
|
case *evalop.Roll:
|
|
rolled := stack.stack[len(stack.stack)-op.N-1]
|
|
copy(stack.stack[len(stack.stack)-op.N-1:], stack.stack[len(stack.stack)-op.N:])
|
|
stack.stack[len(stack.stack)-1] = rolled
|
|
|
|
case *evalop.BuiltinCall:
|
|
vars := make([]*Variable, len(op.Args))
|
|
for i := len(op.Args) - 1; i >= 0; i-- {
|
|
vars[i] = stack.pop()
|
|
}
|
|
stack.pushErr(supportedBuiltins[op.Name](vars, op.Args))
|
|
|
|
case *evalop.CallInjectionStart:
|
|
scope.evalCallInjectionStart(op, stack)
|
|
|
|
case *evalop.CallInjectionSetTarget:
|
|
scope.evalCallInjectionSetTarget(op, stack, curthread)
|
|
|
|
case *evalop.CallInjectionCopyArg:
|
|
fncall := stack.fncallPeek()
|
|
actualArg := stack.pop()
|
|
if actualArg.Name == "" {
|
|
actualArg.Name = exprToString(op.ArgExpr)
|
|
}
|
|
stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)
|
|
|
|
case *evalop.CallInjectionComplete:
|
|
fncall := stack.fncallPeek()
|
|
fncall.doPinning = op.DoPinning
|
|
if op.DoPinning {
|
|
fncall.undoInjection.doComplete2 = true
|
|
} else {
|
|
fncall.undoInjection = nil
|
|
}
|
|
stack.callInjectionContinue = true
|
|
|
|
case *evalop.CallInjectionComplete2:
|
|
fncall := stack.fncallPeek()
|
|
if len(fncall.addrsToPin) != 0 {
|
|
stack.err = fmt.Errorf("internal debugger error: CallInjectionComplete2 called when there still are addresses to pin")
|
|
}
|
|
fncall.undoInjection = nil
|
|
regs, err := curthread.Registers()
|
|
if err == nil {
|
|
callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
|
|
stack.callInjectionContinue = true
|
|
} else {
|
|
stack.err = err
|
|
}
|
|
|
|
case *evalop.CallInjectionStartSpecial:
|
|
stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)
|
|
|
|
case *evalop.ConvertAllocToString:
|
|
scope.convertAllocToString(stack)
|
|
|
|
case *evalop.SetValue:
|
|
lhv := stack.pop()
|
|
rhv := stack.pop()
|
|
stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe))
|
|
|
|
case *evalop.PushPinAddress:
|
|
debugPinCount++
|
|
fncall := stack.fncallPeek()
|
|
addrToPin := fncall.addrsToPin[len(fncall.addrsToPin)-1]
|
|
fncall.addrsToPin = fncall.addrsToPin[:len(fncall.addrsToPin)-1]
|
|
typ, err := scope.BinInfo.findType("unsafe.Pointer")
|
|
if ptyp, ok := typ.(*godwarf.PtrType); err == nil && ok {
|
|
v := newVariable("", 0, typ, scope.BinInfo, scope.Mem)
|
|
v.Children = []Variable{*(newVariable("", uint64(addrToPin), ptyp.Type, scope.BinInfo, scope.Mem))}
|
|
stack.push(v)
|
|
} else {
|
|
stack.err = fmt.Errorf("can not pin address: %v", err)
|
|
}
|
|
|
|
case *evalop.SetDebugPinner:
|
|
stack.debugPinner = stack.pop()
|
|
|
|
case *evalop.PushDebugPinner:
|
|
stack.push(stack.debugPinner)
|
|
|
|
default:
|
|
stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op)
|
|
}
|
|
|
|
stack.opidx++
|
|
}
|
|
|
|
func (stack *evalStack) pushLocal(scope *EvalScope, name string, frame int64) (found bool) {
|
|
var vars []*Variable
|
|
var err error
|
|
if frame != 0 {
|
|
frameScope, err2 := ConvertEvalScope(scope.target, -1, int(frame), 0)
|
|
if err2 != nil {
|
|
stack.err = err2
|
|
return
|
|
}
|
|
vars, err = frameScope.Locals(0, name)
|
|
} else {
|
|
vars, err = scope.Locals(0, name)
|
|
}
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
found = false
|
|
for i := range vars {
|
|
if vars[i].Name == name && vars[i].Flags&VariableShadowed == 0 {
|
|
stack.push(vars[i])
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
func (stack *evalStack) pushIdent(scope *EvalScope, name string) (found bool) {
|
|
found = stack.pushLocal(scope, name, 0)
|
|
if found || stack.err != nil {
|
|
return found
|
|
}
|
|
v, err := scope.findGlobal(scope.Fn.PackageName(), name)
|
|
if err != nil && !isSymbolNotFound(err) {
|
|
stack.err = err
|
|
return false
|
|
}
|
|
if v != nil {
|
|
v.Name = name
|
|
stack.push(v)
|
|
return true
|
|
}
|
|
|
|
switch name {
|
|
case "true", "false":
|
|
stack.push(newConstant(constant.MakeBool(name == "true"), scope.Mem))
|
|
return true
|
|
case "nil":
|
|
stack.push(nilVariable)
|
|
return true
|
|
}
|
|
|
|
regname := validRegisterName(name)
|
|
if regname == "" {
|
|
return false
|
|
}
|
|
regnum, ok := scope.BinInfo.Arch.RegisterNameToDwarf(regname)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
reg := scope.Regs.Reg(uint64(regnum))
|
|
if reg == nil {
|
|
return
|
|
}
|
|
reg.FillBytes()
|
|
|
|
var typ godwarf.Type
|
|
if len(reg.Bytes) <= 8 {
|
|
typ = godwarf.FakeBasicType("uint", 64)
|
|
} else {
|
|
var err error
|
|
typ, err = scope.BinInfo.findType("string")
|
|
if err != nil {
|
|
stack.err = err
|
|
return false
|
|
}
|
|
}
|
|
|
|
v = newVariable(regname, 0, typ, scope.BinInfo, scope.Mem)
|
|
if v.Kind == reflect.String {
|
|
v.Len = int64(len(reg.Bytes) * 2)
|
|
v.Base = fakeAddressUnresolv
|
|
}
|
|
v.Addr = fakeAddressUnresolv
|
|
v.Flags = VariableCPURegister
|
|
v.reg = reg
|
|
stack.push(v)
|
|
return true
|
|
}
|
|
|
|
func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
|
|
ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t, scope.evalopFlags())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stack := &evalStack{}
|
|
stack.eval(scope, ops)
|
|
return stack.result(nil)
|
|
}
|
|
|
|
func exprToString(t ast.Expr) string {
|
|
var buf bytes.Buffer
|
|
printer.Fprint(&buf, token.NewFileSet(), t)
|
|
return buf.String()
|
|
}
|
|
|
|
func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
|
|
var x *Variable
|
|
|
|
switch op.When {
|
|
case evalop.JumpIfTrue, evalop.JumpIfFalse, evalop.JumpIfAllocStringChecksFail:
|
|
x = stack.peek()
|
|
if op.Pop {
|
|
stack.pop()
|
|
}
|
|
}
|
|
|
|
var v bool
|
|
switch op.When {
|
|
case evalop.JumpIfTrue:
|
|
v = true
|
|
case evalop.JumpIfFalse:
|
|
v = false
|
|
case evalop.JumpIfAllocStringChecksFail:
|
|
stringChecksFailed := x.Kind != reflect.String || x.Addr != 0 || x.Flags&VariableConstant == 0 || x.Len <= 0
|
|
nilCallCtx := scope.callCtx == nil // do not complain here, setValue will if no other errors happen
|
|
if stringChecksFailed || nilCallCtx {
|
|
stack.opidx = op.Target - 1
|
|
return
|
|
}
|
|
return
|
|
case evalop.JumpAlways:
|
|
stack.opidx = op.Target - 1
|
|
return
|
|
case evalop.JumpIfPinningDone:
|
|
fncall := stack.fncallPeek()
|
|
if len(fncall.addrsToPin) == 0 {
|
|
stack.opidx = op.Target - 1
|
|
}
|
|
return
|
|
default:
|
|
panic("internal error, bad jump condition")
|
|
}
|
|
|
|
if x.Kind != reflect.Bool {
|
|
if op.Node != nil {
|
|
stack.err = fmt.Errorf("expression %q should be boolean not %s", exprToString(op.Node), x.Kind)
|
|
} else {
|
|
stack.err = errors.New("internal debugger error: expected boolean")
|
|
}
|
|
return
|
|
}
|
|
x.loadValue(loadFullValue)
|
|
if x.Unreadable != nil {
|
|
stack.err = x.Unreadable
|
|
return
|
|
}
|
|
if constant.BoolVal(x.Value) == v {
|
|
stack.opidx = op.Target - 1
|
|
}
|
|
}
|
|
|
|
// Eval type cast expressions
|
|
func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) {
|
|
argv := stack.pop()
|
|
|
|
typ := resolveTypedef(op.DwarfType)
|
|
|
|
converr := fmt.Errorf("can not convert %q to %s", exprToString(op.Node.Args[0]), typ.String())
|
|
|
|
// compatible underlying types
|
|
if typeCastCompatibleTypes(argv.RealType, typ) {
|
|
if ptyp, isptr := typ.(*godwarf.PtrType); argv.Kind == reflect.Ptr && argv.loaded && len(argv.Children) > 0 && isptr {
|
|
cv := argv.Children[0]
|
|
argv.Children[0] = *newVariable(cv.Name, cv.Addr, ptyp.Type, cv.bi, cv.mem)
|
|
argv.Children[0].OnlyAddr = true
|
|
}
|
|
argv.RealType = typ
|
|
argv.DwarfType = op.DwarfType
|
|
stack.push(argv)
|
|
return
|
|
}
|
|
|
|
v := newVariable("", 0, op.DwarfType, scope.BinInfo, scope.Mem)
|
|
v.loaded = true
|
|
|
|
switch ttyp := typ.(type) {
|
|
case *godwarf.PtrType:
|
|
switch argv.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
// ok
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
// ok
|
|
default:
|
|
stack.err = converr
|
|
return
|
|
}
|
|
|
|
argv.loadValue(loadSingleValue)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
|
|
n, _ := constant.Int64Val(argv.Value)
|
|
|
|
mem := scope.Mem
|
|
if scope.target != nil {
|
|
if mem2 := scope.target.findFakeMemory(uint64(n)); mem2 != nil {
|
|
mem = mem2
|
|
}
|
|
}
|
|
|
|
v.Children = []Variable{*(newVariable("", uint64(n), ttyp.Type, scope.BinInfo, mem))}
|
|
v.Children[0].OnlyAddr = true
|
|
stack.push(v)
|
|
return
|
|
|
|
case *godwarf.UintType:
|
|
argv.loadValue(loadSingleValue)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
switch argv.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, _ := constant.Int64Val(argv.Value)
|
|
v.Value = constant.MakeUint64(convertInt(uint64(n), false, ttyp.Size()))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
n, _ := constant.Uint64Val(argv.Value)
|
|
v.Value = constant.MakeUint64(convertInt(n, false, ttyp.Size()))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Float32, reflect.Float64:
|
|
x, _ := constant.Float64Val(argv.Value)
|
|
v.Value = constant.MakeUint64(uint64(x))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Ptr:
|
|
v.Value = constant.MakeUint64(argv.Children[0].Addr)
|
|
stack.push(v)
|
|
return
|
|
}
|
|
case *godwarf.IntType:
|
|
argv.loadValue(loadSingleValue)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
switch argv.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, _ := constant.Int64Val(argv.Value)
|
|
v.Value = constant.MakeInt64(int64(convertInt(uint64(n), true, ttyp.Size())))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
n, _ := constant.Uint64Val(argv.Value)
|
|
v.Value = constant.MakeInt64(int64(convertInt(n, true, ttyp.Size())))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Float32, reflect.Float64:
|
|
x, _ := constant.Float64Val(argv.Value)
|
|
v.Value = constant.MakeInt64(int64(x))
|
|
stack.push(v)
|
|
return
|
|
}
|
|
case *godwarf.FloatType:
|
|
argv.loadValue(loadSingleValue)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
switch argv.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
fallthrough
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
fallthrough
|
|
case reflect.Float32, reflect.Float64:
|
|
v.Value = argv.Value
|
|
stack.push(v)
|
|
return
|
|
}
|
|
case *godwarf.ComplexType:
|
|
argv.loadValue(loadSingleValue)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
switch argv.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
fallthrough
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
fallthrough
|
|
case reflect.Float32, reflect.Float64:
|
|
v.Value = argv.Value
|
|
stack.push(v)
|
|
return
|
|
}
|
|
}
|
|
|
|
cfg := loadFullValue
|
|
if scope.loadCfg != nil {
|
|
cfg = *scope.loadCfg
|
|
}
|
|
|
|
switch ttyp := typ.(type) {
|
|
case *godwarf.SliceType:
|
|
switch ttyp.ElemType.Common().ReflectKind {
|
|
case reflect.Uint8:
|
|
// string -> []uint8
|
|
if argv.Kind != reflect.String {
|
|
stack.err = converr
|
|
return
|
|
}
|
|
cfg.MaxStringLen = cfg.MaxArrayValues
|
|
argv.loadValue(cfg)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
for i, ch := range []byte(constant.StringVal(argv.Value)) {
|
|
e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem)
|
|
e.loaded = true
|
|
e.Value = constant.MakeInt64(int64(ch))
|
|
v.Children = append(v.Children, *e)
|
|
}
|
|
v.Len = argv.Len
|
|
v.Cap = v.Len
|
|
stack.push(v)
|
|
return
|
|
|
|
case reflect.Int32:
|
|
// string -> []rune
|
|
if argv.Kind != reflect.String {
|
|
stack.err = converr
|
|
return
|
|
}
|
|
argv.loadValue(cfg)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
for i, ch := range constant.StringVal(argv.Value) {
|
|
e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem)
|
|
e.loaded = true
|
|
e.Value = constant.MakeInt64(int64(ch))
|
|
v.Children = append(v.Children, *e)
|
|
}
|
|
v.Len = int64(len(v.Children))
|
|
v.Cap = v.Len
|
|
stack.push(v)
|
|
return
|
|
}
|
|
|
|
case *godwarf.StringType:
|
|
switch argv.Kind {
|
|
case reflect.String:
|
|
// string -> string
|
|
argv.DwarfType = v.DwarfType
|
|
argv.RealType = v.RealType
|
|
stack.push(argv)
|
|
return
|
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
|
|
// integer -> string
|
|
argv.loadValue(cfg)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
b, _ := constant.Int64Val(argv.Value)
|
|
s := string(rune(b))
|
|
v.Value = constant.MakeString(s)
|
|
v.Len = int64(len(s))
|
|
stack.push(v)
|
|
return
|
|
case reflect.Slice, reflect.Array:
|
|
var elem godwarf.Type
|
|
if argv.Kind == reflect.Slice {
|
|
elem = argv.RealType.(*godwarf.SliceType).ElemType
|
|
} else {
|
|
elem = argv.RealType.(*godwarf.ArrayType).Type
|
|
}
|
|
switch elemType := elem.(type) {
|
|
case *godwarf.UintType:
|
|
// []uint8 -> string
|
|
if elemType.Name != "uint8" && elemType.Name != "byte" {
|
|
stack.err = converr
|
|
return
|
|
}
|
|
cfg.MaxArrayValues = cfg.MaxStringLen
|
|
argv.loadValue(cfg)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
bytes := make([]byte, len(argv.Children))
|
|
for i := range argv.Children {
|
|
n, _ := constant.Int64Val(argv.Children[i].Value)
|
|
bytes[i] = byte(n)
|
|
}
|
|
v.Value = constant.MakeString(string(bytes))
|
|
v.Len = argv.Len
|
|
|
|
case *godwarf.IntType:
|
|
// []rune -> string
|
|
if elemType.Name != "int32" && elemType.Name != "rune" {
|
|
stack.err = converr
|
|
return
|
|
}
|
|
cfg.MaxArrayValues = cfg.MaxStringLen
|
|
argv.loadValue(cfg)
|
|
if argv.Unreadable != nil {
|
|
stack.err = argv.Unreadable
|
|
return
|
|
}
|
|
runes := make([]rune, len(argv.Children))
|
|
for i := range argv.Children {
|
|
n, _ := constant.Int64Val(argv.Children[i].Value)
|
|
runes[i] = rune(n)
|
|
}
|
|
v.Value = constant.MakeString(string(runes))
|
|
// The following line is wrong but the only way to get the correct value
|
|
// would be to decode the entire slice.
|
|
v.Len = int64(len(constant.StringVal(v.Value)))
|
|
|
|
default:
|
|
stack.err = converr
|
|
return
|
|
}
|
|
stack.push(v)
|
|
return
|
|
}
|
|
}
|
|
|
|
stack.err = converr
|
|
}
|
|
|
|
// typeCastCompatibleTypes returns true if typ1 and typ2 are compatible for
|
|
// a type cast where only the type of the variable is changed.
|
|
func typeCastCompatibleTypes(typ1, typ2 godwarf.Type) bool {
|
|
if typ1 == nil || typ2 == nil || typ1.Common().Size() != typ2.Common().Size() || typ1.Common().Align() != typ2.Common().Align() {
|
|
return false
|
|
}
|
|
|
|
if typ1.String() == typ2.String() {
|
|
return true
|
|
}
|
|
|
|
switch ttyp1 := typ1.(type) {
|
|
case *godwarf.PtrType:
|
|
if ttyp2, ok := typ2.(*godwarf.PtrType); ok {
|
|
_, isvoid1 := ttyp1.Type.(*godwarf.VoidType)
|
|
_, isvoid2 := ttyp2.Type.(*godwarf.VoidType)
|
|
if isvoid1 || isvoid2 {
|
|
return true
|
|
}
|
|
// pointer types are compatible if their element types are compatible
|
|
return typeCastCompatibleTypes(resolveTypedef(ttyp1.Type), resolveTypedef(ttyp2.Type))
|
|
}
|
|
case *godwarf.StringType:
|
|
if _, ok := typ2.(*godwarf.StringType); ok {
|
|
return true
|
|
}
|
|
case *godwarf.StructType:
|
|
if ttyp2, ok := typ2.(*godwarf.StructType); ok {
|
|
// struct types are compatible if they have the same fields
|
|
if len(ttyp1.Field) != len(ttyp2.Field) {
|
|
return false
|
|
}
|
|
for i := range ttyp1.Field {
|
|
if *ttyp1.Field[i] != *ttyp2.Field[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
case *godwarf.ComplexType:
|
|
if _, ok := typ2.(*godwarf.ComplexType); ok {
|
|
// size and alignment already checked above
|
|
return true
|
|
}
|
|
case *godwarf.FloatType:
|
|
if _, ok := typ2.(*godwarf.FloatType); ok {
|
|
// size and alignment already checked above
|
|
return true
|
|
}
|
|
case *godwarf.IntType:
|
|
if _, ok := typ2.(*godwarf.IntType); ok {
|
|
// size and alignment already checked above
|
|
return true
|
|
}
|
|
case *godwarf.UintType:
|
|
if _, ok := typ2.(*godwarf.UintType); ok {
|
|
// size and alignment already checked above
|
|
return true
|
|
}
|
|
case *godwarf.BoolType:
|
|
if _, ok := typ2.(*godwarf.BoolType); ok {
|
|
// size and alignment already checked above
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func convertInt(n uint64, signed bool, size int64) uint64 {
|
|
bits := uint64(size) * 8
|
|
mask := uint64((1 << bits) - 1)
|
|
r := n & mask
|
|
if signed && (r>>(bits-1)) != 0 {
|
|
// sign extension
|
|
r |= ^uint64(0) &^ mask
|
|
}
|
|
return r
|
|
}
|
|
|
|
var supportedBuiltins = map[string]func([]*Variable, []ast.Expr) (*Variable, error){
|
|
"cap": capBuiltin,
|
|
"len": lenBuiltin,
|
|
"complex": complexBuiltin,
|
|
"imag": imagBuiltin,
|
|
"real": realBuiltin,
|
|
"min": minBuiltin,
|
|
"max": maxBuiltin,
|
|
}
|
|
|
|
func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("wrong number of arguments to cap: %d", len(args))
|
|
}
|
|
|
|
arg := args[0]
|
|
invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for cap", exprToString(nodeargs[0]), arg.TypeString())
|
|
|
|
switch arg.Kind {
|
|
case reflect.Ptr:
|
|
arg = arg.maybeDereference()
|
|
if arg.Kind != reflect.Array {
|
|
return nil, invalidArgErr
|
|
}
|
|
fallthrough
|
|
case reflect.Array:
|
|
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
|
case reflect.Slice:
|
|
return newConstant(constant.MakeInt64(arg.Cap), arg.mem), nil
|
|
case reflect.Chan:
|
|
arg.loadValue(loadFullValue)
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
if arg.Base == 0 {
|
|
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
|
}
|
|
return newConstant(arg.Children[1].Value, arg.mem), nil
|
|
default:
|
|
return nil, invalidArgErr
|
|
}
|
|
}
|
|
|
|
func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("wrong number of arguments to len: %d", len(args))
|
|
}
|
|
arg := args[0]
|
|
invalidArgErr := fmt.Errorf("invalid argument %s (type %s) for len", exprToString(nodeargs[0]), arg.TypeString())
|
|
|
|
switch arg.Kind {
|
|
case reflect.Ptr:
|
|
arg = arg.maybeDereference()
|
|
if arg.Kind != reflect.Array {
|
|
return nil, invalidArgErr
|
|
}
|
|
fallthrough
|
|
case reflect.Array, reflect.Slice, reflect.String:
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
|
case reflect.Chan:
|
|
arg.loadValue(loadFullValue)
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
if arg.Base == 0 {
|
|
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
|
}
|
|
return newConstant(arg.Children[0].Value, arg.mem), nil
|
|
case reflect.Map:
|
|
it := arg.mapIterator()
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
if it == nil {
|
|
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
|
}
|
|
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
|
default:
|
|
return nil, invalidArgErr
|
|
}
|
|
}
|
|
|
|
func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
if len(args) != 2 {
|
|
return nil, fmt.Errorf("wrong number of arguments to complex: %d", len(args))
|
|
}
|
|
|
|
realev := args[0]
|
|
imagev := args[1]
|
|
|
|
realev.loadValue(loadSingleValue)
|
|
imagev.loadValue(loadSingleValue)
|
|
|
|
if realev.Unreadable != nil {
|
|
return nil, realev.Unreadable
|
|
}
|
|
|
|
if imagev.Unreadable != nil {
|
|
return nil, imagev.Unreadable
|
|
}
|
|
|
|
if realev.Value == nil || ((realev.Value.Kind() != constant.Int) && (realev.Value.Kind() != constant.Float)) {
|
|
return nil, fmt.Errorf("invalid argument 1 %s (type %s) to complex", exprToString(nodeargs[0]), realev.TypeString())
|
|
}
|
|
|
|
if imagev.Value == nil || ((imagev.Value.Kind() != constant.Int) && (imagev.Value.Kind() != constant.Float)) {
|
|
return nil, fmt.Errorf("invalid argument 2 %s (type %s) to complex", exprToString(nodeargs[1]), imagev.TypeString())
|
|
}
|
|
|
|
sz := int64(0)
|
|
if realev.RealType != nil {
|
|
sz = realev.RealType.(*godwarf.FloatType).Size()
|
|
}
|
|
if imagev.RealType != nil {
|
|
isz := imagev.RealType.(*godwarf.FloatType).Size()
|
|
if isz > sz {
|
|
sz = isz
|
|
}
|
|
}
|
|
|
|
if sz == 0 {
|
|
sz = 128
|
|
}
|
|
|
|
typ := godwarf.FakeBasicType("complex", int(sz))
|
|
|
|
r := realev.newVariable("", 0, typ, nil)
|
|
r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value))
|
|
return r, nil
|
|
}
|
|
|
|
func imagBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("wrong number of arguments to imag: %d", len(args))
|
|
}
|
|
|
|
arg := args[0]
|
|
arg.loadValue(loadSingleValue)
|
|
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
|
|
if arg.Kind != reflect.Complex64 && arg.Kind != reflect.Complex128 {
|
|
return nil, fmt.Errorf("invalid argument %s (type %s) to imag", exprToString(nodeargs[0]), arg.TypeString())
|
|
}
|
|
|
|
return newConstant(constant.Imag(arg.Value), arg.mem), nil
|
|
}
|
|
|
|
func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("wrong number of arguments to real: %d", len(args))
|
|
}
|
|
|
|
arg := args[0]
|
|
arg.loadValue(loadSingleValue)
|
|
|
|
if arg.Unreadable != nil {
|
|
return nil, arg.Unreadable
|
|
}
|
|
|
|
if arg.Value == nil || ((arg.Value.Kind() != constant.Int) && (arg.Value.Kind() != constant.Float) && (arg.Value.Kind() != constant.Complex)) {
|
|
return nil, fmt.Errorf("invalid argument %s (type %s) to real", exprToString(nodeargs[0]), arg.TypeString())
|
|
}
|
|
|
|
return newConstant(constant.Real(arg.Value), arg.mem), nil
|
|
}
|
|
|
|
func minBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
return minmaxBuiltin("min", token.LSS, args, nodeargs)
|
|
}
|
|
|
|
func maxBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
return minmaxBuiltin("max", token.GTR, args, nodeargs)
|
|
}
|
|
|
|
func minmaxBuiltin(name string, op token.Token, args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
|
var best *Variable
|
|
|
|
for i := range args {
|
|
if args[i].Kind == reflect.String {
|
|
args[i].loadValue(loadFullValueLongerStrings)
|
|
} else {
|
|
args[i].loadValue(loadFullValue)
|
|
}
|
|
|
|
if args[i].Unreadable != nil {
|
|
return nil, fmt.Errorf("could not load %q: %v", exprToString(nodeargs[i]), args[i].Unreadable)
|
|
}
|
|
if args[i].FloatSpecial != 0 {
|
|
return nil, errOperationOnSpecialFloat
|
|
}
|
|
|
|
if best == nil {
|
|
best = args[i]
|
|
continue
|
|
}
|
|
|
|
_, err := negotiateType(op, args[i], best)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v, err := compareOp(op, args[i], best)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v {
|
|
best = args[i]
|
|
}
|
|
}
|
|
|
|
if best == nil {
|
|
return nil, fmt.Errorf("not enough arguments to %s", name)
|
|
}
|
|
return best, nil
|
|
}
|
|
|
|
// Evaluates expressions <subexpr>.<field name> where subexpr is not a package name
|
|
func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack) {
|
|
xv := stack.pop()
|
|
// Prevent abuse, attempting to call "nil.member" directly.
|
|
if xv.Addr == 0 && xv.Name == "nil" {
|
|
stack.err = fmt.Errorf("%s (type %s) is not a struct", xv.Name, xv.TypeString())
|
|
return
|
|
}
|
|
// Prevent abuse, attempting to call "\"fake\".member" directly.
|
|
if xv.Addr == 0 && xv.Name == "" && xv.DwarfType == nil && xv.RealType == nil {
|
|
stack.err = fmt.Errorf("%s (type %s) is not a struct", xv.Value, xv.TypeString())
|
|
return
|
|
}
|
|
// Special type conversions for CPU register variables (REGNAME.int8, etc)
|
|
if xv.Flags&VariableCPURegister != 0 && !xv.loaded {
|
|
stack.pushErr(xv.registerVariableTypeConv(op.Name))
|
|
return
|
|
}
|
|
|
|
rv, err := xv.findMethod(op.Name)
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
if rv != nil {
|
|
stack.push(rv)
|
|
return
|
|
}
|
|
stack.pushErr(xv.structMember(op.Name))
|
|
}
|
|
|
|
// Evaluates expressions <subexpr>.(<type>)
|
|
func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack) {
|
|
xv := stack.pop()
|
|
if xv.Kind != reflect.Interface {
|
|
stack.err = fmt.Errorf("expression %q not an interface", exprToString(op.Node.X))
|
|
return
|
|
}
|
|
xv.loadInterface(0, false, loadFullValue)
|
|
if xv.Unreadable != nil {
|
|
stack.err = xv.Unreadable
|
|
return
|
|
}
|
|
if xv.Children[0].Unreadable != nil {
|
|
stack.err = xv.Children[0].Unreadable
|
|
return
|
|
}
|
|
if xv.Children[0].Addr == 0 {
|
|
stack.err = fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(op.Node.Type))
|
|
return
|
|
}
|
|
typ := op.DwarfType
|
|
if typ != nil && xv.Children[0].DwarfType.Common().Name != typ.Common().Name {
|
|
stack.err = fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name)
|
|
return
|
|
}
|
|
// loadInterface will set OnlyAddr for the data member since here we are
|
|
// passing false to loadData, however returning the variable with OnlyAddr
|
|
// set here would be wrong since, once the expression evaluation
|
|
// terminates, the value of this variable will be loaded.
|
|
xv.Children[0].OnlyAddr = false
|
|
stack.push(&xv.Children[0])
|
|
}
|
|
|
|
// Evaluates expressions <subexpr>[<subexpr>] (subscript access to arrays, slices and maps)
|
|
func (scope *EvalScope) evalIndex(op *evalop.Index, stack *evalStack) {
|
|
idxev := stack.pop()
|
|
xev := stack.pop()
|
|
if xev.Unreadable != nil {
|
|
stack.err = xev.Unreadable
|
|
return
|
|
}
|
|
|
|
if xev.Flags&VariableCPtr == 0 {
|
|
xev = xev.maybeDereference()
|
|
}
|
|
|
|
cantindex := fmt.Errorf("expression %q (%s) does not support indexing", exprToString(op.Node.X), xev.TypeString())
|
|
|
|
switch xev.Kind {
|
|
case reflect.Ptr:
|
|
if xev == nilVariable {
|
|
stack.err = cantindex
|
|
return
|
|
}
|
|
if xev.Flags&VariableCPtr == 0 {
|
|
_, isarrptr := xev.RealType.(*godwarf.PtrType).Type.(*godwarf.ArrayType)
|
|
if !isarrptr {
|
|
stack.err = cantindex
|
|
return
|
|
}
|
|
xev = xev.maybeDereference()
|
|
}
|
|
fallthrough
|
|
|
|
case reflect.Slice, reflect.Array, reflect.String:
|
|
if xev.Base == 0 {
|
|
stack.err = fmt.Errorf("can not index %q", exprToString(op.Node.X))
|
|
return
|
|
}
|
|
n, err := idxev.asInt()
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
stack.pushErr(xev.sliceAccess(int(n)))
|
|
return
|
|
|
|
case reflect.Map:
|
|
idxev.loadValue(loadFullValue)
|
|
if idxev.Unreadable != nil {
|
|
stack.err = idxev.Unreadable
|
|
return
|
|
}
|
|
stack.pushErr(xev.mapAccess(idxev))
|
|
return
|
|
default:
|
|
stack.err = cantindex
|
|
return
|
|
}
|
|
}
|
|
|
|
// Evaluates expressions <subexpr>[<subexpr>:<subexpr>]
|
|
// HACK: slicing a map expression with [0:0] will return the whole map
|
|
func (scope *EvalScope) evalReslice(op *evalop.Reslice, stack *evalStack) {
|
|
low, err := stack.pop().asInt()
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
var high int64
|
|
if op.HasHigh {
|
|
high, err = stack.pop().asInt()
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
}
|
|
xev := stack.pop()
|
|
if xev.Unreadable != nil {
|
|
stack.err = xev.Unreadable
|
|
return
|
|
}
|
|
if !op.HasHigh {
|
|
high = xev.Len
|
|
}
|
|
|
|
switch xev.Kind {
|
|
case reflect.Slice, reflect.Array, reflect.String:
|
|
if xev.Base == 0 {
|
|
stack.err = fmt.Errorf("can not slice %q", exprToString(op.Node.X))
|
|
return
|
|
}
|
|
stack.pushErr(xev.reslice(low, high, op.TrustLen))
|
|
return
|
|
case reflect.Map:
|
|
if op.Node.High != nil {
|
|
stack.err = errors.New("second slice argument must be empty for maps")
|
|
return
|
|
}
|
|
xev.mapSkip += int(low)
|
|
xev.mapIterator() // reads map length
|
|
if int64(xev.mapSkip) >= xev.Len {
|
|
stack.err = errors.New("map index out of bounds")
|
|
return
|
|
}
|
|
stack.push(xev)
|
|
return
|
|
case reflect.Ptr:
|
|
if xev.Flags&VariableCPtr != 0 {
|
|
stack.pushErr(xev.reslice(low, high, op.TrustLen))
|
|
return
|
|
}
|
|
fallthrough
|
|
default:
|
|
stack.err = fmt.Errorf("can not slice %q (type %s)", exprToString(op.Node.X), xev.TypeString())
|
|
return
|
|
}
|
|
}
|
|
|
|
// Evaluates a pointer dereference expression: *<subexpr>
|
|
func (scope *EvalScope) evalPointerDeref(op *evalop.PointerDeref, stack *evalStack) {
|
|
xev := stack.pop()
|
|
|
|
if xev.Kind != reflect.Ptr {
|
|
stack.err = fmt.Errorf("expression %q (%s) can not be dereferenced", exprToString(op.Node.X), xev.TypeString())
|
|
return
|
|
}
|
|
|
|
if xev == nilVariable {
|
|
stack.err = errors.New("nil can not be dereferenced")
|
|
return
|
|
}
|
|
|
|
if len(xev.Children) == 1 {
|
|
// this branch is here to support pointers constructed with typecasts from ints
|
|
xev.Children[0].OnlyAddr = false
|
|
stack.push(&(xev.Children[0]))
|
|
return
|
|
}
|
|
xev.loadPtr()
|
|
if xev.Unreadable != nil {
|
|
val, ok := constant.Uint64Val(xev.Value)
|
|
if ok && val == 0 {
|
|
stack.err = fmt.Errorf("couldn't read pointer: %w", xev.Unreadable)
|
|
return
|
|
}
|
|
}
|
|
rv := &xev.Children[0]
|
|
if rv.Addr == 0 {
|
|
stack.err = errors.New("nil pointer dereference")
|
|
return
|
|
}
|
|
stack.push(rv)
|
|
}
|
|
|
|
// Evaluates expressions &<subexpr>
|
|
func (scope *EvalScope) evalAddrOf(op *evalop.AddrOf, stack *evalStack) {
|
|
xev := stack.pop()
|
|
if xev.Addr == 0 || xev.DwarfType == nil {
|
|
stack.err = fmt.Errorf("can not take address of %q", exprToString(op.Node.X))
|
|
return
|
|
}
|
|
|
|
stack.push(xev.pointerToVariable())
|
|
}
|
|
|
|
func (v *Variable) pointerToVariable() *Variable {
|
|
v.OnlyAddr = true
|
|
|
|
typename := "*" + v.DwarfType.Common().Name
|
|
rv := v.newVariable("", 0, &godwarf.PtrType{CommonType: godwarf.CommonType{ByteSize: int64(v.bi.Arch.PtrSize()), Name: typename}, Type: v.DwarfType}, v.mem)
|
|
rv.Children = []Variable{*v}
|
|
rv.loaded = true
|
|
|
|
return rv
|
|
}
|
|
|
|
func constantUnaryOp(op token.Token, y constant.Value) (r constant.Value, err error) {
|
|
defer func() {
|
|
if ierr := recover(); ierr != nil {
|
|
err = fmt.Errorf("%v", ierr)
|
|
}
|
|
}()
|
|
r = constant.UnaryOp(op, y, 0)
|
|
return
|
|
}
|
|
|
|
func constantBinaryOp(op token.Token, x, y constant.Value) (r constant.Value, err error) {
|
|
defer func() {
|
|
if ierr := recover(); ierr != nil {
|
|
err = fmt.Errorf("%v", ierr)
|
|
}
|
|
}()
|
|
switch op {
|
|
case token.SHL, token.SHR:
|
|
n, _ := constant.Uint64Val(y)
|
|
r = constant.Shift(x, op, uint(n))
|
|
default:
|
|
r = constant.BinaryOp(x, op, y)
|
|
}
|
|
return
|
|
}
|
|
|
|
func constantCompare(op token.Token, x, y constant.Value) (r bool, err error) {
|
|
defer func() {
|
|
if ierr := recover(); ierr != nil {
|
|
err = fmt.Errorf("%v", ierr)
|
|
}
|
|
}()
|
|
r = constant.Compare(x, op, y)
|
|
return
|
|
}
|
|
|
|
// Evaluates expressions: -<subexpr> and +<subexpr>
|
|
func (scope *EvalScope) evalUnary(op *evalop.Unary, stack *evalStack) {
|
|
xv := stack.pop()
|
|
|
|
xv.loadValue(loadSingleValue)
|
|
if xv.Unreadable != nil {
|
|
stack.err = xv.Unreadable
|
|
return
|
|
}
|
|
if xv.FloatSpecial != 0 {
|
|
stack.err = errOperationOnSpecialFloat
|
|
return
|
|
}
|
|
if xv.Value == nil {
|
|
stack.err = fmt.Errorf("operator %s can not be applied to %q", op.Node.Op.String(), exprToString(op.Node.X))
|
|
return
|
|
}
|
|
rc, err := constantUnaryOp(op.Node.Op, xv.Value)
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
if xv.DwarfType != nil {
|
|
r := xv.newVariable("", 0, xv.DwarfType, scope.Mem)
|
|
r.Value = rc
|
|
stack.push(r)
|
|
return
|
|
}
|
|
stack.push(newConstant(rc, xv.mem))
|
|
}
|
|
|
|
func negotiateType(op token.Token, xv, yv *Variable) (godwarf.Type, error) {
|
|
if xv == nilVariable {
|
|
return nil, negotiateTypeNil(op, yv)
|
|
}
|
|
|
|
if yv == nilVariable {
|
|
return nil, negotiateTypeNil(op, xv)
|
|
}
|
|
|
|
if op == token.SHR || op == token.SHL {
|
|
if xv.Value == nil || xv.Value.Kind() != constant.Int {
|
|
return nil, fmt.Errorf("shift of type %s", xv.Kind)
|
|
}
|
|
|
|
switch yv.Kind {
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
// ok
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if constant.Sign(yv.Value) < 0 {
|
|
return nil, errors.New("shift count must not be negative")
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("shift count type %s, must be unsigned integer", yv.Kind.String())
|
|
}
|
|
|
|
return xv.DwarfType, nil
|
|
}
|
|
|
|
if xv.DwarfType == nil && yv.DwarfType == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if xv.DwarfType != nil && yv.DwarfType != nil {
|
|
if xv.DwarfType.String() != yv.DwarfType.String() {
|
|
return nil, fmt.Errorf("mismatched types %q and %q", xv.DwarfType.String(), yv.DwarfType.String())
|
|
}
|
|
return xv.DwarfType, nil
|
|
} else if xv.DwarfType != nil && yv.DwarfType == nil {
|
|
if err := yv.isType(xv.DwarfType, xv.Kind); err != nil {
|
|
return nil, err
|
|
}
|
|
return xv.DwarfType, nil
|
|
} else if xv.DwarfType == nil && yv.DwarfType != nil {
|
|
if err := xv.isType(yv.DwarfType, yv.Kind); err != nil {
|
|
return nil, err
|
|
}
|
|
return yv.DwarfType, nil
|
|
}
|
|
|
|
panic("unreachable")
|
|
}
|
|
|
|
func negotiateTypeNil(op token.Token, v *Variable) error {
|
|
if op != token.EQL && op != token.NEQ {
|
|
return fmt.Errorf("operator %s can not be applied to \"nil\"", op.String())
|
|
}
|
|
switch v.Kind {
|
|
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan, reflect.Map, reflect.Interface, reflect.Slice, reflect.Func:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("can not compare %s to nil", v.Kind.String())
|
|
}
|
|
}
|
|
|
|
func (scope *EvalScope) evalBinary(binop *evalop.Binary, stack *evalStack) {
|
|
node := binop.Node
|
|
|
|
yv := stack.pop()
|
|
xv := stack.pop()
|
|
|
|
if xv.Kind != reflect.String { // delay loading strings until we use them
|
|
xv.loadValue(loadFullValue)
|
|
}
|
|
if xv.Unreadable != nil {
|
|
stack.err = xv.Unreadable
|
|
return
|
|
}
|
|
if yv.Kind != reflect.String { // delay loading strings until we use them
|
|
yv.loadValue(loadFullValue)
|
|
}
|
|
if yv.Unreadable != nil {
|
|
stack.err = yv.Unreadable
|
|
return
|
|
}
|
|
|
|
if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 {
|
|
stack.err = errOperationOnSpecialFloat
|
|
return
|
|
}
|
|
|
|
typ, err := negotiateType(node.Op, xv, yv)
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
|
|
op := node.Op
|
|
if op == token.QUO {
|
|
if typ != nil {
|
|
_, isint := typ.(*godwarf.IntType)
|
|
_, isuint := typ.(*godwarf.UintType)
|
|
if isint || isuint {
|
|
// forces integer division if the result type is integer
|
|
op = token.QUO_ASSIGN
|
|
}
|
|
} else {
|
|
if xv.Value != nil && yv.Value != nil && xv.Value.Kind() == constant.Int && yv.Value.Kind() == constant.Int {
|
|
// See issue #3793 and the specification at https://go.dev/ref/spec#Constant_expressions
|
|
// in particular:
|
|
//
|
|
// "If the untyped operands of a binary operation (other than a shift)
|
|
// are of different kinds, the result is of the operand's kind that
|
|
// appears later in this list: integer, rune, floating-point, complex"
|
|
//
|
|
// However the go/constant package says that to get an integer result
|
|
// from a division token.QUO_ASSIGN must be used.
|
|
op = token.QUO_ASSIGN
|
|
}
|
|
}
|
|
}
|
|
|
|
switch op {
|
|
case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
|
|
v, err := compareOp(op, xv, yv)
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
stack.push(newConstant(constant.MakeBool(v), xv.mem))
|
|
|
|
default:
|
|
if xv.Kind == reflect.String {
|
|
xv.loadValue(loadFullValueLongerStrings)
|
|
}
|
|
if yv.Kind == reflect.String {
|
|
yv.loadValue(loadFullValueLongerStrings)
|
|
}
|
|
if xv.Value == nil {
|
|
stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.X))
|
|
return
|
|
}
|
|
|
|
if yv.Value == nil {
|
|
stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.Y))
|
|
return
|
|
}
|
|
|
|
rc, err := constantBinaryOp(op, xv.Value, yv.Value)
|
|
if err != nil {
|
|
stack.err = err
|
|
return
|
|
}
|
|
|
|
if typ == nil {
|
|
stack.push(newConstant(rc, xv.mem))
|
|
return
|
|
}
|
|
|
|
r := xv.newVariable("", 0, typ, scope.Mem)
|
|
r.Value = rc
|
|
switch r.Kind {
|
|
case reflect.String:
|
|
r.Len = xv.Len + yv.Len
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, _ := constant.Int64Val(r.Value)
|
|
r.Value = constant.MakeInt64(int64(convertInt(uint64(n), true, typ.Size())))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
n, _ := constant.Uint64Val(r.Value)
|
|
r.Value = constant.MakeUint64(convertInt(n, false, typ.Size()))
|
|
}
|
|
stack.push(r)
|
|
}
|
|
}
|
|
|
|
// Compares xv to yv using operator op
|
|
// Both xv and yv must be loaded and have a compatible type (as determined by negotiateType)
|
|
func compareOp(op token.Token, xv *Variable, yv *Variable) (bool, error) {
|
|
switch xv.Kind {
|
|
case reflect.Bool:
|
|
fallthrough
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
fallthrough
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
fallthrough
|
|
case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
|
return constantCompare(op, xv.Value, yv.Value)
|
|
case reflect.String:
|
|
if xv.Len != yv.Len {
|
|
switch op {
|
|
case token.EQL:
|
|
return false, nil
|
|
case token.NEQ:
|
|
return true, nil
|
|
}
|
|
}
|
|
if xv.Kind == reflect.String {
|
|
xv.loadValue(loadFullValueLongerStrings)
|
|
}
|
|
if yv.Kind == reflect.String {
|
|
yv.loadValue(loadFullValueLongerStrings)
|
|
}
|
|
if int64(len(constant.StringVal(xv.Value))) != xv.Len || int64(len(constant.StringVal(yv.Value))) != yv.Len {
|
|
return false, errors.New("string too long for comparison")
|
|
}
|
|
return constantCompare(op, xv.Value, yv.Value)
|
|
}
|
|
|
|
if op != token.EQL && op != token.NEQ {
|
|
return false, fmt.Errorf("operator %s not defined on %s", op.String(), xv.Kind.String())
|
|
}
|
|
|
|
var eql bool
|
|
var err error
|
|
|
|
if xv == nilVariable {
|
|
switch op {
|
|
case token.EQL:
|
|
return yv.isNil(), nil
|
|
case token.NEQ:
|
|
return !yv.isNil(), nil
|
|
}
|
|
}
|
|
|
|
if yv == nilVariable {
|
|
switch op {
|
|
case token.EQL:
|
|
return xv.isNil(), nil
|
|
case token.NEQ:
|
|
return !xv.isNil(), nil
|
|
}
|
|
}
|
|
|
|
switch xv.Kind {
|
|
case reflect.Ptr:
|
|
eql = xv.Children[0].Addr == yv.Children[0].Addr
|
|
case reflect.Array:
|
|
if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len {
|
|
return false, errors.New("array too long for comparison")
|
|
}
|
|
eql, err = equalChildren(xv, yv, true)
|
|
case reflect.Struct:
|
|
if len(xv.Children) != len(yv.Children) {
|
|
return false, nil
|
|
}
|
|
if int64(len(xv.Children)) != xv.Len || int64(len(yv.Children)) != yv.Len {
|
|
return false, errors.New("structure too deep for comparison")
|
|
}
|
|
eql, err = equalChildren(xv, yv, false)
|
|
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
|
return false, fmt.Errorf("can not compare %s variables", xv.Kind.String())
|
|
case reflect.Interface:
|
|
if xv.Children[0].RealType.String() != yv.Children[0].RealType.String() {
|
|
eql = false
|
|
} else {
|
|
eql, err = compareOp(token.EQL, &xv.Children[0], &yv.Children[0])
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("unimplemented comparison of %s variables", xv.Kind.String())
|
|
}
|
|
|
|
if op == token.NEQ {
|
|
return !eql, err
|
|
}
|
|
return eql, err
|
|
}
|
|
|
|
func (v *Variable) isNil() bool {
|
|
switch v.Kind {
|
|
case reflect.Ptr:
|
|
return v.Children[0].Addr == 0
|
|
case reflect.Interface:
|
|
return v.Children[0].Addr == 0 && v.Children[0].Kind == reflect.Invalid
|
|
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
|
return v.Base == 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) {
|
|
r := true
|
|
for i := range xv.Children {
|
|
eql, err := compareOp(token.EQL, &xv.Children[i], &yv.Children[i])
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
r = r && eql
|
|
if !r && shortcircuit {
|
|
return false, nil
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (v *Variable) asInt() (int64, error) {
|
|
if v.DwarfType == nil {
|
|
if v.Value.Kind() != constant.Int {
|
|
return 0, fmt.Errorf("can not convert constant %s to int", v.Value)
|
|
}
|
|
} else {
|
|
v.loadValue(loadSingleValue)
|
|
if v.Unreadable != nil {
|
|
return 0, v.Unreadable
|
|
}
|
|
if _, ok := v.DwarfType.(*godwarf.IntType); !ok {
|
|
return 0, fmt.Errorf("can not convert value of type %s to int", v.DwarfType.String())
|
|
}
|
|
}
|
|
n, _ := constant.Int64Val(v.Value)
|
|
return n, nil
|
|
}
|
|
|
|
func (v *Variable) asUint() (uint64, error) {
|
|
if v.DwarfType == nil {
|
|
if v.Value.Kind() != constant.Int {
|
|
return 0, fmt.Errorf("can not convert constant %s to uint", v.Value)
|
|
}
|
|
} else {
|
|
v.loadValue(loadSingleValue)
|
|
if v.Unreadable != nil {
|
|
return 0, v.Unreadable
|
|
}
|
|
if _, ok := v.DwarfType.(*godwarf.UintType); !ok {
|
|
return 0, fmt.Errorf("can not convert value of type %s to uint", v.DwarfType.String())
|
|
}
|
|
}
|
|
n, _ := constant.Uint64Val(v.Value)
|
|
return n, nil
|
|
}
|
|
|
|
type typeConvErr struct {
|
|
srcType, dstType godwarf.Type
|
|
}
|
|
|
|
func (err *typeConvErr) Error() string {
|
|
return fmt.Sprintf("can not convert value of type %s to %s", err.srcType.String(), err.dstType.String())
|
|
}
|
|
|
|
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
|
if v.DwarfType != nil {
|
|
if typ == nil || !sameType(typ, v.RealType) {
|
|
return &typeConvErr{v.DwarfType, typ}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if typ == nil {
|
|
return nil
|
|
}
|
|
|
|
if v == nilVariable {
|
|
switch kind {
|
|
case reflect.Slice, reflect.Map, reflect.Func, reflect.Ptr, reflect.Chan, reflect.Interface:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("mismatched types nil and %s", typ.String())
|
|
}
|
|
}
|
|
|
|
converr := fmt.Errorf("can not convert %s constant to %s", v.Value, typ.String())
|
|
|
|
if v.Value == nil {
|
|
return converr
|
|
}
|
|
|
|
switch typ.(type) {
|
|
case *godwarf.IntType:
|
|
if v.Value.Kind() != constant.Int {
|
|
return converr
|
|
}
|
|
case *godwarf.UintType:
|
|
if v.Value.Kind() != constant.Int {
|
|
return converr
|
|
}
|
|
case *godwarf.FloatType:
|
|
if (v.Value.Kind() != constant.Int) && (v.Value.Kind() != constant.Float) {
|
|
return converr
|
|
}
|
|
case *godwarf.BoolType:
|
|
if v.Value.Kind() != constant.Bool {
|
|
return converr
|
|
}
|
|
case *godwarf.StringType:
|
|
if v.Value.Kind() != constant.String {
|
|
return converr
|
|
}
|
|
case *godwarf.ComplexType:
|
|
if v.Value.Kind() != constant.Complex && v.Value.Kind() != constant.Float && v.Value.Kind() != constant.Int {
|
|
return converr
|
|
}
|
|
default:
|
|
return converr
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func sameType(t1, t2 godwarf.Type) bool {
|
|
// Because of a bug in the go linker a type that refers to another type
|
|
// (for example a pointer type) will usually use the typedef but rarely use
|
|
// the non-typedef entry directly.
|
|
// For types that we read directly from go this is fine because it's
|
|
// consistent, however we also synthesize some types ourselves
|
|
// (specifically pointers and slices) and we always use a reference through
|
|
// a typedef.
|
|
t1 = resolveTypedef(t1)
|
|
t2 = resolveTypedef(t2)
|
|
|
|
if tt1, isptr1 := t1.(*godwarf.PtrType); isptr1 {
|
|
tt2, isptr2 := t2.(*godwarf.PtrType)
|
|
if !isptr2 {
|
|
return false
|
|
}
|
|
return sameType(tt1.Type, tt2.Type)
|
|
}
|
|
if tt1, isslice1 := t1.(*godwarf.SliceType); isslice1 {
|
|
tt2, isslice2 := t2.(*godwarf.SliceType)
|
|
if !isslice2 {
|
|
return false
|
|
}
|
|
return sameType(tt1.ElemType, tt2.ElemType)
|
|
}
|
|
return t1.String() == t2.String()
|
|
}
|
|
|
|
func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
|
wrong := false
|
|
if v.Flags&VariableCPtr == 0 {
|
|
wrong = idx < 0 || int64(idx) >= v.Len
|
|
} else {
|
|
wrong = idx < 0
|
|
}
|
|
if wrong {
|
|
return nil, errors.New("index out of bounds")
|
|
}
|
|
if v.loaded {
|
|
if v.Kind == reflect.String {
|
|
s := constant.StringVal(v.Value)
|
|
if idx >= len(s) {
|
|
return nil, errors.New("index out of bounds")
|
|
}
|
|
r := v.newVariable("", v.Base+uint64(int64(idx)*v.stride), v.fieldType, v.mem)
|
|
r.loaded = true
|
|
r.Value = constant.MakeInt64(int64(s[idx]))
|
|
return r, nil
|
|
} else {
|
|
if idx >= len(v.Children) {
|
|
return nil, errors.New("index out of bounds")
|
|
}
|
|
return &v.Children[idx], nil
|
|
}
|
|
}
|
|
mem := v.mem
|
|
if v.Kind != reflect.Array {
|
|
mem = DereferenceMemory(mem)
|
|
}
|
|
return v.newVariable("", v.Base+uint64(int64(idx)*v.stride), v.fieldType, mem), nil
|
|
}
|
|
|
|
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
|
it := v.mapIterator()
|
|
if it == nil {
|
|
return nil, fmt.Errorf("can not access unreadable map: %v", v.Unreadable)
|
|
}
|
|
|
|
lcfg := loadFullValue
|
|
if idx.Kind == reflect.String && int64(len(constant.StringVal(idx.Value))) == idx.Len && idx.Len > int64(lcfg.MaxStringLen) {
|
|
// If the index is a string load as much of the keys to at least match the length of the index.
|
|
//TODO(aarzilli): when struct literals are implemented this needs to be
|
|
//done recursively for literal struct fields.
|
|
lcfg.MaxStringLen = int(idx.Len)
|
|
}
|
|
|
|
first := true
|
|
for it.next() {
|
|
key := it.key()
|
|
key.loadValue(lcfg)
|
|
if key.Unreadable != nil {
|
|
return nil, fmt.Errorf("can not access unreadable map: %v", key.Unreadable)
|
|
}
|
|
if first {
|
|
first = false
|
|
if err := idx.isType(key.RealType, key.Kind); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
eql, err := compareOp(token.EQL, key, idx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if eql {
|
|
return it.value(), nil
|
|
}
|
|
}
|
|
if v.Unreadable != nil {
|
|
return nil, v.Unreadable
|
|
}
|
|
// go would return zero for the map value type here, we do not have the ability to create zeroes
|
|
return nil, errors.New("key not found")
|
|
}
|
|
|
|
// LoadResliced returns a new array, slice or map that starts at index start and contains
|
|
// up to cfg.MaxArrayValues children.
|
|
func (v *Variable) LoadResliced(start int, cfg LoadConfig) (newV *Variable, err error) {
|
|
switch v.Kind {
|
|
case reflect.Array, reflect.Slice:
|
|
low, high := int64(start), int64(start+cfg.MaxArrayValues)
|
|
if high > v.Len {
|
|
high = v.Len
|
|
}
|
|
newV, err = v.reslice(low, high, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case reflect.Map:
|
|
newV = v.clone()
|
|
newV.Children = nil
|
|
newV.loaded = false
|
|
newV.mapSkip = start
|
|
default:
|
|
return nil, errors.New("variable to reslice is not an array, slice, or map")
|
|
}
|
|
newV.loadValue(cfg)
|
|
return newV, nil
|
|
}
|
|
|
|
func (v *Variable) reslice(low int64, high int64, trustLen bool) (*Variable, error) {
|
|
wrong := false
|
|
cptrNeedsFakeSlice := false
|
|
if v.Flags&VariableCPtr == 0 {
|
|
if v.Kind == reflect.Slice {
|
|
wrong = low < 0 || low > v.Cap || high < 0 || high > v.Cap
|
|
} else {
|
|
wrong = low < 0 || low > v.Len || high < 0 || high > v.Len
|
|
}
|
|
} else {
|
|
wrong = low < 0 || high < 0
|
|
if high == 0 {
|
|
high = low
|
|
}
|
|
cptrNeedsFakeSlice = v.Kind != reflect.String
|
|
}
|
|
if wrong {
|
|
return nil, errors.New("index out of bounds")
|
|
}
|
|
|
|
base := v.Base + uint64(low*v.stride)
|
|
len := high - low
|
|
|
|
if high-low < 0 {
|
|
return nil, errors.New("index out of bounds")
|
|
}
|
|
|
|
typ := v.DwarfType
|
|
if _, isarr := v.DwarfType.(*godwarf.ArrayType); isarr || cptrNeedsFakeSlice {
|
|
typ = godwarf.FakeSliceType(v.fieldType)
|
|
}
|
|
|
|
mem := v.mem
|
|
if v.Kind != reflect.Array {
|
|
mem = DereferenceMemory(mem)
|
|
}
|
|
|
|
r := v.newVariable("", 0, typ, mem)
|
|
if v.Flags&VariableCPtr == 0 {
|
|
r.Cap = v.Cap - low
|
|
} else {
|
|
r.Cap = len
|
|
}
|
|
r.Len = len
|
|
r.Base = base
|
|
r.stride = v.stride
|
|
r.fieldType = v.fieldType
|
|
r.Flags = v.Flags
|
|
if trustLen {
|
|
r.Flags |= variableTrustLen
|
|
}
|
|
r.reg = v.reg
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// findMethod finds method mname in the type of variable v
|
|
func (v *Variable) findMethod(mname string) (*Variable, error) {
|
|
if _, isiface := v.RealType.(*godwarf.InterfaceType); isiface {
|
|
v.loadInterface(0, false, loadFullValue)
|
|
if v.Unreadable != nil {
|
|
return nil, v.Unreadable
|
|
}
|
|
return v.Children[0].findMethod(mname)
|
|
}
|
|
|
|
queue := []*Variable{v}
|
|
seen := map[string]struct{}{}
|
|
|
|
for len(queue) > 0 {
|
|
v := queue[0]
|
|
queue = append(queue[:0], queue[1:]...)
|
|
if _, isseen := seen[v.RealType.String()]; isseen {
|
|
continue
|
|
}
|
|
seen[v.RealType.String()] = struct{}{}
|
|
|
|
typ := v.DwarfType
|
|
ptyp, isptr := typ.(*godwarf.PtrType)
|
|
if isptr {
|
|
typ = ptyp.Type
|
|
}
|
|
|
|
typePath := typ.Common().Name
|
|
dot := strings.LastIndex(typePath, ".")
|
|
if dot < 0 {
|
|
// probably just a C type
|
|
continue
|
|
}
|
|
|
|
pkg := typePath[:dot]
|
|
receiver := typePath[dot+1:]
|
|
|
|
//TODO(aarzilli): support generic functions?
|
|
|
|
if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.%s.%s", pkg, receiver, mname)]; len(fns) == 1 {
|
|
r, err := functionToVariable(fns[0], v.bi, v.mem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isptr {
|
|
r.Children = append(r.Children, *(v.maybeDereference()))
|
|
} else {
|
|
r.Children = append(r.Children, *v)
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
if fns := v.bi.LookupFunc()[fmt.Sprintf("%s.(*%s).%s", pkg, receiver, mname)]; len(fns) == 1 {
|
|
r, err := functionToVariable(fns[0], v.bi, v.mem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isptr {
|
|
r.Children = append(r.Children, *v)
|
|
} else {
|
|
r.Children = append(r.Children, *(v.pointerToVariable()))
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// queue embedded fields for search
|
|
structVar := v.maybeDereference()
|
|
structVar.Name = v.Name
|
|
if structVar.Unreadable != nil {
|
|
return structVar, nil
|
|
}
|
|
switch t := structVar.RealType.(type) {
|
|
case *godwarf.StructType:
|
|
for _, field := range t.Field {
|
|
if field.Embedded {
|
|
embeddedVar, err := structVar.toField(field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queue = append(queue, embeddedVar)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func functionToVariable(fn *Function, bi *BinaryInfo, mem MemoryReadWriter) (*Variable, error) {
|
|
typ, err := fn.fakeType(bi, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := newVariable(fn.Name, 0, typ, bi, mem)
|
|
v.Value = constant.MakeString(fn.Name)
|
|
v.loaded = true
|
|
v.Base = fn.Entry
|
|
return v, nil
|
|
}
|
|
|
|
func fakeArrayType(n uint64, fieldType godwarf.Type) godwarf.Type {
|
|
stride := alignAddr(fieldType.Common().ByteSize, fieldType.Align())
|
|
return &godwarf.ArrayType{
|
|
CommonType: godwarf.CommonType{
|
|
ReflectKind: reflect.Array,
|
|
ByteSize: int64(n) * stride,
|
|
Name: fmt.Sprintf("[%d]%s", n, fieldType.String())},
|
|
Type: fieldType,
|
|
StrideBitSize: stride * 8,
|
|
Count: int64(n)}
|
|
}
|
|
|
|
var errMethodEvalUnsupported = errors.New("evaluating methods not supported on this version of Go")
|
|
|
|
func (fn *Function) fakeType(bi *BinaryInfo, removeReceiver bool) (*godwarf.FuncType, error) {
|
|
if producer := bi.Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 10) {
|
|
// versions of Go prior to 1.10 do not distinguish between parameters and
|
|
// return values, therefore we can't use a subprogram DIE to derive a
|
|
// function type.
|
|
return nil, errMethodEvalUnsupported
|
|
}
|
|
_, formalArgs, err := funcCallArgs(fn, bi, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Only try and remove the receiver if it is actually being passed in as a formal argument.
|
|
// In the case of:
|
|
//
|
|
// func (_ X) Method() { ... }
|
|
//
|
|
// that would not be true, the receiver is not used and thus
|
|
// not being passed in as a formal argument.
|
|
//
|
|
// TODO(derekparker) This, I think, creates a new bug where
|
|
// if the receiver is not passed in as a formal argument but
|
|
// there are other arguments, such as:
|
|
//
|
|
// func (_ X) Method(i int) { ... }
|
|
//
|
|
// The first argument 'i int' will be removed. We must actually detect
|
|
// here if the receiver is being used. While this is a bug, it's not a
|
|
// functional bug, it only affects the string representation of the fake
|
|
// function type we create. It's not really easy to tell here if we use
|
|
// the receiver or not. Perhaps we should not perform this manipulation at all?
|
|
if removeReceiver && len(formalArgs) > 0 {
|
|
formalArgs = formalArgs[1:]
|
|
}
|
|
|
|
args := make([]string, 0, len(formalArgs))
|
|
rets := make([]string, 0, len(formalArgs))
|
|
|
|
for _, formalArg := range formalArgs {
|
|
var s string
|
|
if strings.HasPrefix(formalArg.name, "~") {
|
|
s = formalArg.typ.String()
|
|
} else {
|
|
s = fmt.Sprintf("%s %s", formalArg.name, formalArg.typ.String())
|
|
}
|
|
if formalArg.isret {
|
|
rets = append(rets, s)
|
|
} else {
|
|
args = append(args, s)
|
|
}
|
|
}
|
|
|
|
argstr := strings.Join(args, ", ")
|
|
var retstr string
|
|
switch len(rets) {
|
|
case 0:
|
|
retstr = ""
|
|
case 1:
|
|
retstr = " " + rets[0]
|
|
default:
|
|
retstr = " (" + strings.Join(rets, ", ") + ")"
|
|
}
|
|
return &godwarf.FuncType{
|
|
CommonType: godwarf.CommonType{
|
|
Name: "func(" + argstr + ")" + retstr,
|
|
ReflectKind: reflect.Func,
|
|
},
|
|
//TODO(aarzilli): at the moment we aren't using the ParamType and
|
|
// ReturnType fields of FuncType anywhere (when this is returned to the
|
|
// client it's first converted to a string and the function calling code
|
|
// reads the subroutine entry because it needs to know the stack offsets).
|
|
// If we start using them they should be filled here.
|
|
}, nil
|
|
}
|
|
|
|
func validRegisterName(s string) string {
|
|
for len(s) > 0 && s[0] == '_' {
|
|
s = s[1:]
|
|
}
|
|
for i := range s {
|
|
if (s[i] < '0' || s[i] > '9') && (s[i] < 'A' || s[i] > 'Z') {
|
|
return ""
|
|
}
|
|
}
|
|
return s
|
|
}
|