proc/variables: prefetch of target process memory
Prefetch the entire memory of structs and arrays and cache it instead of issuing readMemory calls only when we get down to primitive types. This reduces the number of system calls to ptrace that variables makes. Improves performance in general, greatly improving it in some particular cases (involving large structs). Benchmarks without prefetching: BenchmarkArray-4 10 132189944 ns/op 0.06 MB/s BenchmarkArrayPointer-4 5 202538503 ns/op 0.04 MB/s BenchmarkMap-4 500 3804336 ns/op 0.27 MB/s BenchmarkGoroutinesInfo-4 10 126397104 ns/op BenchmarkLocalVariables-4 500 2494846 ns/op Benchmarks with prefetching: BenchmarkArray-4 200 10719087 ns/op 0.76 MB/s BenchmarkArrayPointer-4 100 11931326 ns/op 0.73 MB/s BenchmarkMap-4 1000 1466479 ns/op 0.70 MB/s BenchmarkGoroutinesInfo-4 10 103407004 ns/op BenchmarkLocalVariables-4 1000 1530395 ns/op Improvement factors: BenchmarkArray 12.33x BenchmarkArrayPointer 16.97x BenchmarkMap 2.59x BenchmarkGoroutinesInfo 1.22x BenchmarkLocalVariables 1.63x
This commit is contained in:
parent
32a0c752eb
commit
b839eda2a9
@ -48,6 +48,11 @@ type dstruct struct {
|
||||
|
||||
type maptype map[string]interface{}
|
||||
|
||||
type benchstruct struct {
|
||||
a [64]byte
|
||||
b [64]byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
i1 := 1
|
||||
i2 := 2
|
||||
@ -138,6 +143,12 @@ func main() {
|
||||
var iface2fn2 interface{} = afunc2
|
||||
var mapinf maptype = map[string]interface{}{}
|
||||
mapinf["inf"] = mapinf
|
||||
var bencharr [64]benchstruct
|
||||
var benchparr [64]*benchstruct
|
||||
|
||||
for i := range benchparr {
|
||||
benchparr[i] = &benchstruct{}
|
||||
}
|
||||
|
||||
var amb1 = 1
|
||||
runtime.Breakpoint()
|
||||
@ -145,5 +156,5 @@ func main() {
|
||||
fmt.Println(amb1)
|
||||
}
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, mapinf)
|
||||
fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf)
|
||||
}
|
||||
|
44
proc/eval.go
44
proc/eval.go
@ -135,7 +135,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
|
||||
converr := fmt.Errorf("can not convert %q to %s", exprToString(node.Args[0]), typ.String())
|
||||
|
||||
v := newVariable("", 0, styp, scope.Thread)
|
||||
v := newVariable("", 0, styp, scope.Thread.dbp, scope.Thread)
|
||||
v.loaded = true
|
||||
|
||||
switch ttyp := typ.(type) {
|
||||
@ -154,7 +154,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
|
||||
|
||||
n, _ := constant.Int64Val(argv.Value)
|
||||
|
||||
v.Children = []Variable{*newVariable("", uintptr(n), ttyp.Type, scope.Thread)}
|
||||
v.Children = []Variable{*(scope.newVariable("", uintptr(n), ttyp.Type))}
|
||||
return v, nil
|
||||
|
||||
case *dwarf.UintType:
|
||||
@ -274,18 +274,18 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
||||
case reflect.Slice:
|
||||
return newConstant(constant.MakeInt64(arg.Cap), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(arg.Cap), arg.mem), nil
|
||||
case reflect.Chan:
|
||||
arg.loadValue()
|
||||
if arg.Unreadable != nil {
|
||||
return nil, arg.Unreadable
|
||||
}
|
||||
if arg.base == 0 {
|
||||
return newConstant(constant.MakeInt64(0), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
||||
}
|
||||
return newConstant(arg.Children[1].Value, arg.thread), nil
|
||||
return newConstant(arg.Children[1].Value, arg.mem), nil
|
||||
default:
|
||||
return nil, invalidArgErr
|
||||
}
|
||||
@ -309,25 +309,25 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
if arg.Unreadable != nil {
|
||||
return nil, arg.Unreadable
|
||||
}
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
||||
case reflect.Chan:
|
||||
arg.loadValue()
|
||||
if arg.Unreadable != nil {
|
||||
return nil, arg.Unreadable
|
||||
}
|
||||
if arg.base == 0 {
|
||||
return newConstant(constant.MakeInt64(0), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
||||
}
|
||||
return newConstant(arg.Children[0].Value, arg.thread), 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.thread), nil
|
||||
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
||||
}
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.thread), nil
|
||||
return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
|
||||
default:
|
||||
return nil, invalidArgErr
|
||||
}
|
||||
@ -377,7 +377,7 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
|
||||
typ := &dwarf.ComplexType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: int64(sz / 8), Name: fmt.Sprintf("complex%d", sz)}, BitSize: sz, BitOffset: 0}}
|
||||
|
||||
r := newVariable("", 0, typ, realev.thread)
|
||||
r := realev.newVariable("", 0, typ)
|
||||
r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value))
|
||||
return r, nil
|
||||
}
|
||||
@ -398,7 +398,7 @@ func imagBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
return nil, fmt.Errorf("invalid argument %s (type %s) to imag", exprToString(nodeargs[0]), arg.TypeString())
|
||||
}
|
||||
|
||||
return newConstant(constant.Imag(arg.Value), arg.thread), nil
|
||||
return newConstant(constant.Imag(arg.Value), arg.mem), nil
|
||||
}
|
||||
|
||||
func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
@ -417,7 +417,7 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
return nil, fmt.Errorf("invalid argument %s (type %s) to real", exprToString(nodeargs[0]), arg.TypeString())
|
||||
}
|
||||
|
||||
return newConstant(constant.Real(arg.Value), arg.thread), nil
|
||||
return newConstant(constant.Real(arg.Value), arg.mem), nil
|
||||
}
|
||||
|
||||
// Evaluates identifier expressions
|
||||
@ -625,7 +625,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) {
|
||||
xev.OnlyAddr = true
|
||||
|
||||
typename := "*" + xev.DwarfType.String()
|
||||
rv := newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType}, scope.Thread)
|
||||
rv := scope.newVariable("", 0, &dwarf.PtrType{CommonType: dwarf.CommonType{ByteSize: int64(scope.Thread.dbp.arch.PtrSize()), Name: typename}, Type: xev.DwarfType})
|
||||
rv.Children = []Variable{*xev}
|
||||
rv.loaded = true
|
||||
|
||||
@ -687,11 +687,11 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
|
||||
return nil, err
|
||||
}
|
||||
if xv.DwarfType != nil {
|
||||
r := newVariable("", 0, xv.DwarfType, xv.thread)
|
||||
r := xv.newVariable("", 0, xv.DwarfType)
|
||||
r.Value = rc
|
||||
return r, nil
|
||||
}
|
||||
return newConstant(rc, xv.thread), nil
|
||||
return newConstant(rc, xv.mem), nil
|
||||
}
|
||||
|
||||
func negotiateType(op token.Token, xv, yv *Variable) (dwarf.Type, error) {
|
||||
@ -806,7 +806,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newConstant(constant.MakeBool(v), xv.thread), nil
|
||||
return newConstant(constant.MakeBool(v), xv.mem), nil
|
||||
|
||||
default:
|
||||
if xv.Value == nil {
|
||||
@ -823,10 +823,10 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
|
||||
}
|
||||
|
||||
if typ == nil {
|
||||
return newConstant(rc, xv.thread), nil
|
||||
return newConstant(rc, xv.mem), nil
|
||||
}
|
||||
|
||||
r := newVariable("", 0, typ, xv.thread)
|
||||
r := xv.newVariable("", 0, typ)
|
||||
r.Value = rc
|
||||
return r, nil
|
||||
}
|
||||
@ -1039,7 +1039,7 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
||||
if idx < 0 || int64(idx) >= v.Len {
|
||||
return nil, fmt.Errorf("index out of bounds")
|
||||
}
|
||||
return newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType, v.thread), nil
|
||||
return v.newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType), nil
|
||||
}
|
||||
|
||||
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
||||
@ -1101,7 +1101,7 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||
}
|
||||
}
|
||||
|
||||
r := newVariable("", 0, typ, v.thread)
|
||||
r := v.newVariable("", 0, typ)
|
||||
r.Cap = len
|
||||
r.Len = len
|
||||
r.base = base
|
||||
|
54
proc/mem.go
Normal file
54
proc/mem.go
Normal file
@ -0,0 +1,54 @@
|
||||
package proc
|
||||
|
||||
const cacheEnabled = true
|
||||
|
||||
type memoryReadWriter interface {
|
||||
readMemory(addr uintptr, size int) (data []byte, err error)
|
||||
writeMemory(addr uintptr, data []byte) (written int, err error)
|
||||
}
|
||||
|
||||
type memCache struct {
|
||||
cacheAddr uintptr
|
||||
cache []byte
|
||||
mem memoryReadWriter
|
||||
}
|
||||
|
||||
func (m *memCache) contains(addr uintptr, size int) bool {
|
||||
return addr >= m.cacheAddr && (addr+uintptr(size)) <= (m.cacheAddr+uintptr(len(m.cache)))
|
||||
}
|
||||
|
||||
func (m *memCache) readMemory(addr uintptr, size int) (data []byte, err error) {
|
||||
if m.contains(addr, size) {
|
||||
d := make([]byte, size)
|
||||
copy(d, m.cache[addr-m.cacheAddr:])
|
||||
return d, nil
|
||||
}
|
||||
|
||||
return m.mem.readMemory(addr, size)
|
||||
}
|
||||
|
||||
func (m *memCache) writeMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
return m.mem.writeMemory(addr, data)
|
||||
}
|
||||
|
||||
func cacheMemory(mem memoryReadWriter, addr uintptr, size int) memoryReadWriter {
|
||||
if !cacheEnabled {
|
||||
return mem
|
||||
}
|
||||
if cacheMem, isCache := mem.(*memCache); isCache {
|
||||
if cacheMem.contains(addr, size) {
|
||||
return mem
|
||||
} else {
|
||||
cache, err := cacheMem.mem.readMemory(addr, size)
|
||||
if err != nil {
|
||||
return mem
|
||||
}
|
||||
return &memCache{addr, cache, mem}
|
||||
}
|
||||
}
|
||||
cache, err := mem.readMemory(addr, size)
|
||||
if err != nil {
|
||||
return mem
|
||||
}
|
||||
return &memCache{addr, cache, mem}
|
||||
}
|
@ -26,7 +26,7 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(protest.RunTestsWithFixtures(m))
|
||||
}
|
||||
|
||||
func withTestProcess(name string, t *testing.T, fn func(p *Process, fixture protest.Fixture)) {
|
||||
func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture protest.Fixture)) {
|
||||
fixture := protest.BuildFixture(name)
|
||||
p, err := Launch([]string{fixture.Path})
|
||||
if err != nil {
|
||||
@ -54,7 +54,7 @@ func dataAtAddr(thread *Thread, addr uint64) ([]byte, error) {
|
||||
return thread.readMemory(uintptr(addr), 1)
|
||||
}
|
||||
|
||||
func assertNoError(err error, t *testing.T, s string) {
|
||||
func assertNoError(err error, t testing.TB, s string) {
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fname := filepath.Base(file)
|
||||
@ -1181,6 +1181,18 @@ func TestBreakpointCounts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkArray(b *testing.B) {
|
||||
// each bencharr struct is 128 bytes, bencharr is 64 elements long
|
||||
b.SetBytes(int64(64 * 128))
|
||||
withTestProcess("testvariables3", b, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), b, "Continue()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := evalVariable(p, "bencharr")
|
||||
assertNoError(err, b, "EvalVariable()")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const doTestBreakpointCountsWithDetection = false
|
||||
|
||||
func TestBreakpointCountsWithDetection(t *testing.T) {
|
||||
@ -1245,6 +1257,44 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkArrayPointer(b *testing.B) {
|
||||
// each bencharr struct is 128 bytes, benchparr is an array of 64 pointers to bencharr
|
||||
// each read will read 64 bencharr structs plus the 64 pointers of benchparr
|
||||
b.SetBytes(int64(64*128 + 64*8))
|
||||
withTestProcess("testvariables3", b, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), b, "Continue()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := evalVariable(p, "bencharr")
|
||||
assertNoError(err, b, "EvalVariable()")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMap(b *testing.B) {
|
||||
// m1 contains 41 entries, each one has a value that's 2 int values (2* 8 bytes) and a string key
|
||||
// each string key has an average of 9 character
|
||||
// reading strings and the map structure imposes a overhead that we ignore here
|
||||
b.SetBytes(int64(41 * (2*8 + 9)))
|
||||
withTestProcess("testvariables3", b, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), b, "Continue()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := evalVariable(p, "m1")
|
||||
assertNoError(err, b, "EvalVariable()")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkGoroutinesInfo(b *testing.B) {
|
||||
withTestProcess("testvariables3", b, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), b, "Continue()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.allGCache = nil
|
||||
_, err := p.GoroutinesInfo()
|
||||
assertNoError(err, b, "GoroutinesInfo")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIssue262(t *testing.T) {
|
||||
// Continue does not work when the current breakpoint is set on a NOP instruction
|
||||
withTestProcess("issue262", t, func(p *Process, fixture protest.Fixture) {
|
||||
@ -1275,3 +1325,15 @@ func TestIssue341(t *testing.T) {
|
||||
t.Logf("mapinf: %v\n", mapinf)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLocalVariables(b *testing.B) {
|
||||
withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), b, "Continue() returned an error")
|
||||
scope, err := p.CurrentThread.Scope()
|
||||
assertNoError(err, b, "Scope()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := scope.LocalVariables()
|
||||
assertNoError(err, b, "LocalVariables()")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ const (
|
||||
maxVariableRecurse = 1 // How far to recurse when evaluating nested types.
|
||||
maxArrayValues = 64 // Max value for reading large arrays.
|
||||
maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs
|
||||
|
||||
maxArrayStridePrefetch = 1024 // Maximum size of array stride for which we will prefetch the array contents
|
||||
|
||||
chanRecv = "chan receive"
|
||||
chanSend = "chan send"
|
||||
@ -39,7 +41,8 @@ type Variable struct {
|
||||
DwarfType dwarf.Type
|
||||
RealType dwarf.Type
|
||||
Kind reflect.Kind
|
||||
thread *Thread
|
||||
mem memoryReadWriter
|
||||
dbp *Process
|
||||
|
||||
Value constant.Value
|
||||
|
||||
@ -136,12 +139,21 @@ func ptrTypeKind(t *dwarf.PtrType) reflect.Kind {
|
||||
return reflect.Ptr
|
||||
}
|
||||
|
||||
func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) *Variable {
|
||||
func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable {
|
||||
return newVariable(name, addr, dwarfType, scope.Thread.dbp, scope.Thread)
|
||||
}
|
||||
|
||||
func (v *Variable) newVariable(name string, addr uintptr, dwarfType dwarf.Type) *Variable {
|
||||
return newVariable(name, addr, dwarfType, v.dbp, v.mem)
|
||||
}
|
||||
|
||||
func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process, mem memoryReadWriter) *Variable {
|
||||
v := &Variable{
|
||||
Name: name,
|
||||
Addr: addr,
|
||||
DwarfType: dwarfType,
|
||||
thread: thread,
|
||||
mem: mem,
|
||||
dbp: dbp,
|
||||
}
|
||||
|
||||
v.RealType = resolveTypedef(v.DwarfType)
|
||||
@ -156,7 +168,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
|
||||
v.stride = 1
|
||||
v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}}
|
||||
if v.Addr != 0 {
|
||||
v.base, v.Len, v.Unreadable = v.thread.readStringInfo(v.Addr)
|
||||
v.base, v.Len, v.Unreadable = readStringInfo(v.mem, v.dbp.arch, v.Addr)
|
||||
}
|
||||
case t.StructName == "runtime.iface" || t.StructName == "runtime.eface":
|
||||
v.Kind = reflect.Interface
|
||||
@ -222,8 +234,8 @@ func resolveTypedef(typ dwarf.Type) dwarf.Type {
|
||||
}
|
||||
}
|
||||
|
||||
func newConstant(val constant.Value, thread *Thread) *Variable {
|
||||
v := &Variable{Value: val, thread: thread, loaded: true}
|
||||
func newConstant(val constant.Value, mem memoryReadWriter) *Variable {
|
||||
v := &Variable{Value: val, mem: mem, loaded: true}
|
||||
switch val.Kind() {
|
||||
case constant.Int:
|
||||
v.Kind = reflect.Int
|
||||
@ -281,7 +293,7 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
|
||||
name = fmt.Sprintf("%s.%s", v.Name, field.Name)
|
||||
}
|
||||
}
|
||||
return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread), nil
|
||||
return v.newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type), nil
|
||||
}
|
||||
|
||||
// DwarfReader returns the DwarfReader containing the
|
||||
@ -349,6 +361,11 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mem memoryReadWriter = thread
|
||||
if gtype, err := thread.dbp.dwarf.Type(entry.Offset); err == nil {
|
||||
mem = cacheMemory(thread, uintptr(gaddr), int(gtype.Size()))
|
||||
}
|
||||
|
||||
// Parse defer
|
||||
deferAddr, err := rdr.AddrForMember("_defer", initialInstructions)
|
||||
if err != nil {
|
||||
@ -356,7 +373,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
}
|
||||
var deferPC uint64
|
||||
// Dereference *defer pointer
|
||||
deferAddrBytes, err := thread.readMemory(uintptr(deferAddr), thread.dbp.arch.PtrSize())
|
||||
deferAddrBytes, err := mem.readMemory(uintptr(deferAddr), thread.dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error derefing defer %s", err)
|
||||
}
|
||||
@ -367,11 +384,11 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
return nil, err
|
||||
}
|
||||
deferPCAddr, err := rdr.AddrForMember("fn", initialDeferInstructions)
|
||||
deferPC, err = thread.readUintRaw(uintptr(deferPCAddr), 8)
|
||||
deferPC, err = readUintRaw(mem, uintptr(deferPCAddr), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deferPC, err = thread.readUintRaw(uintptr(deferPC), 8)
|
||||
deferPC, err = readUintRaw(mem, uintptr(deferPC), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -391,11 +408,11 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
return nil, err
|
||||
}
|
||||
// From sched, let's parse PC and SP.
|
||||
sp, err := thread.readUintRaw(uintptr(schedAddr), 8)
|
||||
sp, err := readUintRaw(mem, uintptr(schedAddr), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := thread.readUintRaw(uintptr(schedAddr+uint64(thread.dbp.arch.PtrSize())), 8)
|
||||
pc, err := readUintRaw(mem, uintptr(schedAddr+uint64(thread.dbp.arch.PtrSize())), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -404,13 +421,13 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
atomicStatus, err := thread.readUintRaw(uintptr(atomicStatusAddr), 4)
|
||||
atomicStatus, err := readUintRaw(mem, uintptr(atomicStatusAddr), 4)
|
||||
// Parse goid
|
||||
goidAddr, err := rdr.AddrForMember("goid", initialInstructions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
goid, err := thread.readIntRaw(uintptr(goidAddr), 8)
|
||||
goid, err := readIntRaw(mem, uintptr(goidAddr), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -419,7 +436,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
waitreason, _, err := thread.readString(uintptr(waitReasonAddr))
|
||||
waitreason, _, err := readString(mem, thread.dbp.arch, uintptr(waitReasonAddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -428,7 +445,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gopc, err := thread.readUintRaw(uintptr(gopcAddr), 8)
|
||||
gopc, err := readUintRaw(mem, uintptr(gopcAddr), 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -725,7 +742,7 @@ func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry, rdr *reader.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newVariable(n, uintptr(addr), t, scope.Thread), nil
|
||||
return scope.newVariable(n, uintptr(addr), t), nil
|
||||
}
|
||||
|
||||
// If v is a pointer a new variable is returned containing the value pointed by v.
|
||||
@ -736,8 +753,8 @@ func (v *Variable) maybeDereference() *Variable {
|
||||
|
||||
switch t := v.RealType.(type) {
|
||||
case *dwarf.PtrType:
|
||||
ptrval, err := v.thread.readUintRaw(uintptr(v.Addr), int64(v.thread.dbp.arch.PtrSize()))
|
||||
r := newVariable("", uintptr(ptrval), t.Type, v.thread)
|
||||
ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize)
|
||||
r := v.newVariable("", uintptr(ptrval), t.Type)
|
||||
if err != nil {
|
||||
r.Unreadable = err
|
||||
}
|
||||
@ -757,6 +774,7 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||
if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.base == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
v.loaded = true
|
||||
switch v.Kind {
|
||||
case reflect.Ptr, reflect.UnsafePointer:
|
||||
@ -777,13 +795,14 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||
|
||||
case reflect.String:
|
||||
var val string
|
||||
val, v.Unreadable = v.thread.readStringValue(v.base, v.Len)
|
||||
val, v.Unreadable = readStringValue(v.mem, v.base, v.Len)
|
||||
v.Value = constant.MakeString(val)
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
v.loadArrayValues(recurseLevel)
|
||||
|
||||
case reflect.Struct:
|
||||
v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
|
||||
t := v.RealType.(*dwarf.StructType)
|
||||
v.Len = int64(len(t.Field))
|
||||
// Recursively call extractValue to grab
|
||||
@ -805,15 +824,15 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||
v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var val int64
|
||||
val, v.Unreadable = v.thread.readIntRaw(v.Addr, v.RealType.(*dwarf.IntType).ByteSize)
|
||||
val, v.Unreadable = readIntRaw(v.mem, v.Addr, v.RealType.(*dwarf.IntType).ByteSize)
|
||||
v.Value = constant.MakeInt64(val)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
var val uint64
|
||||
val, v.Unreadable = v.thread.readUintRaw(v.Addr, v.RealType.(*dwarf.UintType).ByteSize)
|
||||
val, v.Unreadable = readUintRaw(v.mem, v.Addr, v.RealType.(*dwarf.UintType).ByteSize)
|
||||
v.Value = constant.MakeUint64(val)
|
||||
|
||||
case reflect.Bool:
|
||||
val, err := v.thread.readMemory(v.Addr, 1)
|
||||
val, err := v.mem.readMemory(v.Addr, 1)
|
||||
v.Unreadable = err
|
||||
if err == nil {
|
||||
v.Value = constant.MakeBool(val[0] != 0)
|
||||
@ -849,8 +868,8 @@ func (v *Variable) setValue(y *Variable) error {
|
||||
err = v.writeComplex(real, imag, v.RealType.Size())
|
||||
default:
|
||||
fmt.Printf("default\n")
|
||||
if _, isptr := v.RealType.(*dwarf.PtrType); isptr {
|
||||
err = v.writeUint(uint64(y.Children[0].Addr), int64(v.thread.dbp.arch.PtrSize()))
|
||||
if t, isptr := v.RealType.(*dwarf.PtrType); isptr {
|
||||
err = v.writeUint(uint64(y.Children[0].Addr), int64(t.ByteSize))
|
||||
} else {
|
||||
return fmt.Errorf("can not set variables of type %s (not implemented)", v.Kind.String())
|
||||
}
|
||||
@ -859,12 +878,14 @@ func (v *Variable) setValue(y *Variable) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (thread *Thread) readStringInfo(addr uintptr) (uintptr, int64, error) {
|
||||
func readStringInfo(mem memoryReadWriter, arch Arch, addr uintptr) (uintptr, int64, error) {
|
||||
// string data structure is always two ptrs in size. Addr, followed by len
|
||||
// http://research.swtch.com/godata
|
||||
|
||||
mem = cacheMemory(mem, addr, arch.PtrSize()*2)
|
||||
|
||||
// read len
|
||||
val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize())
|
||||
val, err := mem.readMemory(addr+uintptr(arch.PtrSize()), arch.PtrSize())
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("could not read string len %s", err)
|
||||
}
|
||||
@ -874,7 +895,7 @@ func (thread *Thread) readStringInfo(addr uintptr) (uintptr, int64, error) {
|
||||
}
|
||||
|
||||
// read addr
|
||||
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
||||
val, err = mem.readMemory(addr, arch.PtrSize())
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("could not read string pointer %s", err)
|
||||
}
|
||||
@ -886,13 +907,13 @@ func (thread *Thread) readStringInfo(addr uintptr) (uintptr, int64, error) {
|
||||
return addr, strlen, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readStringValue(addr uintptr, strlen int64) (string, error) {
|
||||
func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64) (string, error) {
|
||||
count := strlen
|
||||
if count > maxArrayValues {
|
||||
count = maxArrayValues
|
||||
}
|
||||
|
||||
val, err := thread.readMemory(addr, int(count))
|
||||
val, err := mem.readMemory(addr, int(count))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read string at %#v due to %s", addr, err)
|
||||
}
|
||||
@ -902,23 +923,25 @@ func (thread *Thread) readStringValue(addr uintptr, strlen int64) (string, error
|
||||
return retstr, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readString(addr uintptr) (string, int64, error) {
|
||||
addr, strlen, err := thread.readStringInfo(addr)
|
||||
func readString(mem memoryReadWriter, arch Arch, addr uintptr) (string, int64, error) {
|
||||
addr, strlen, err := readStringInfo(mem, arch, addr)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
retstr, err := thread.readStringValue(addr, strlen)
|
||||
retstr, err := readStringValue(mem, addr, strlen)
|
||||
return retstr, strlen, err
|
||||
}
|
||||
|
||||
func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
|
||||
v.mem = cacheMemory(v.mem, v.Addr, int(t.Size()))
|
||||
|
||||
var err error
|
||||
for _, f := range t.Field {
|
||||
switch f.Name {
|
||||
case "array":
|
||||
var base uint64
|
||||
base, err = v.thread.readUintRaw(uintptr(int64(v.Addr)+f.ByteOffset), int64(v.thread.dbp.arch.PtrSize()))
|
||||
base, err = readUintRaw(v.mem, uintptr(int64(v.Addr)+f.ByteOffset), f.Type.Size())
|
||||
if err == nil {
|
||||
v.base = uintptr(base)
|
||||
// Dereference array type to get value type
|
||||
@ -951,8 +974,8 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
|
||||
}
|
||||
|
||||
v.stride = v.fieldType.Size()
|
||||
if _, ok := v.fieldType.(*dwarf.PtrType); ok {
|
||||
v.stride = int64(v.thread.dbp.arch.PtrSize())
|
||||
if t, ok := v.fieldType.(*dwarf.PtrType); ok {
|
||||
v.stride = t.ByteSize
|
||||
}
|
||||
|
||||
return
|
||||
@ -963,15 +986,20 @@ func (v *Variable) loadArrayValues(recurseLevel int) {
|
||||
return
|
||||
}
|
||||
|
||||
count := v.Len
|
||||
// Cap number of elements
|
||||
if count > maxArrayValues {
|
||||
count = maxArrayValues
|
||||
}
|
||||
|
||||
if v.stride < maxArrayStridePrefetch {
|
||||
v.mem = cacheMemory(v.mem, v.base, int(v.stride*count))
|
||||
}
|
||||
|
||||
errcount := 0
|
||||
|
||||
for i := int64(0); i < v.Len; i++ {
|
||||
// Cap number of elements
|
||||
if i >= maxArrayValues {
|
||||
break
|
||||
}
|
||||
|
||||
fieldvar := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
|
||||
for i := int64(0); i < count; i++ {
|
||||
fieldvar := v.newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType)
|
||||
fieldvar.loadValueInternal(recurseLevel + 1)
|
||||
|
||||
if fieldvar.Unreadable != nil {
|
||||
@ -999,8 +1027,8 @@ func (v *Variable) readComplex(size int64) {
|
||||
|
||||
ftyp := &dwarf.FloatType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: fs, Name: fmt.Sprintf("float%d", fs)}, BitSize: fs * 8, BitOffset: 0}}
|
||||
|
||||
realvar := newVariable("real", v.Addr, ftyp, v.thread)
|
||||
imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread)
|
||||
realvar := v.newVariable("real", v.Addr, ftyp)
|
||||
imagvar := v.newVariable("imaginary", v.Addr+uintptr(fs), ftyp)
|
||||
realvar.loadValue()
|
||||
imagvar.loadValue()
|
||||
v.Value = constant.BinaryOp(realvar.Value, token.ADD, constant.MakeImag(imagvar.Value))
|
||||
@ -1016,10 +1044,10 @@ func (v *Variable) writeComplex(real, imag float64, size int64) error {
|
||||
return imagaddr.writeFloatRaw(imag, int64(size/2))
|
||||
}
|
||||
|
||||
func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) {
|
||||
func readIntRaw(mem memoryReadWriter, addr uintptr, size int64) (int64, error) {
|
||||
var n int64
|
||||
|
||||
val, err := thread.readMemory(addr, int(size))
|
||||
val, err := mem.readMemory(addr, int(size))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -1052,14 +1080,14 @@ func (v *Variable) writeUint(value uint64, size int64) error {
|
||||
binary.LittleEndian.PutUint64(val, uint64(value))
|
||||
}
|
||||
|
||||
_, err := v.thread.writeMemory(v.Addr, val)
|
||||
_, err := v.mem.writeMemory(v.Addr, val)
|
||||
return err
|
||||
}
|
||||
|
||||
func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) {
|
||||
func readUintRaw(mem memoryReadWriter, addr uintptr, size int64) (uint64, error) {
|
||||
var n uint64
|
||||
|
||||
val, err := thread.readMemory(addr, int(size))
|
||||
val, err := mem.readMemory(addr, int(size))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -1079,7 +1107,7 @@ func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) {
|
||||
}
|
||||
|
||||
func (v *Variable) readFloatRaw(size int64) (float64, error) {
|
||||
val, err := v.thread.readMemory(v.Addr, int(size))
|
||||
val, err := v.mem.readMemory(v.Addr, int(size))
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
@ -1111,19 +1139,19 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error {
|
||||
binary.Write(buf, binary.LittleEndian, n)
|
||||
}
|
||||
|
||||
_, err := v.thread.writeMemory(v.Addr, buf.Bytes())
|
||||
_, err := v.mem.writeMemory(v.Addr, buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Variable) writeBool(value bool) error {
|
||||
val := []byte{0}
|
||||
val[0] = *(*byte)(unsafe.Pointer(&value))
|
||||
_, err := v.thread.writeMemory(v.Addr, val)
|
||||
_, err := v.mem.writeMemory(v.Addr, val)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Variable) readFunctionPtr() {
|
||||
val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize())
|
||||
val, err := v.mem.readMemory(v.Addr, v.dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
v.Unreadable = err
|
||||
return
|
||||
@ -1137,14 +1165,14 @@ func (v *Variable) readFunctionPtr() {
|
||||
return
|
||||
}
|
||||
|
||||
val, err = v.thread.readMemory(fnaddr, v.thread.dbp.arch.PtrSize())
|
||||
val, err = v.mem.readMemory(fnaddr, v.dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
v.Unreadable = err
|
||||
return
|
||||
}
|
||||
|
||||
v.base = uintptr(binary.LittleEndian.Uint64(val))
|
||||
fn := v.thread.dbp.goSymTable.PCToFunc(uint64(v.base))
|
||||
fn := v.dbp.goSymTable.PCToFunc(uint64(v.base))
|
||||
if fn == nil {
|
||||
v.Unreadable = fmt.Errorf("could not find function for %#v", v.base)
|
||||
return
|
||||
@ -1225,6 +1253,8 @@ func (v *Variable) mapIterator() *mapIterator {
|
||||
return it
|
||||
}
|
||||
|
||||
v.mem = cacheMemory(v.mem, v.base, int(v.RealType.Size()))
|
||||
|
||||
for _, f := range maptype.Field {
|
||||
var err error
|
||||
field, _ := sv.toField(f)
|
||||
@ -1301,6 +1331,8 @@ func (it *mapIterator) nextBucket() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
it.b.mem = cacheMemory(it.b.mem, it.b.Addr, int(it.b.RealType.Size()))
|
||||
|
||||
it.tophashes = nil
|
||||
it.keys = nil
|
||||
it.values = nil
|
||||
@ -1403,6 +1435,8 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
|
||||
var typestring, data *Variable
|
||||
isnil := false
|
||||
|
||||
v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
|
||||
|
||||
for _, f := range v.RealType.(*dwarf.StructType).Field {
|
||||
switch f.Name {
|
||||
case "tab": // for runtime.iface
|
||||
@ -1464,7 +1498,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
|
||||
return
|
||||
}
|
||||
|
||||
typ, err := v.thread.dbp.findTypeExpr(t)
|
||||
typ, err := v.dbp.findTypeExpr(t)
|
||||
if err != nil {
|
||||
v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), data.Addr, err)
|
||||
return
|
||||
@ -1473,10 +1507,10 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
|
||||
realtyp := resolveTypedef(typ)
|
||||
if _, isptr := realtyp.(*dwarf.PtrType); !isptr {
|
||||
// interface to non-pointer types are pointers even if the type says otherwise
|
||||
typ = v.thread.dbp.pointerTo(typ)
|
||||
typ = v.dbp.pointerTo(typ)
|
||||
}
|
||||
|
||||
data = newVariable("data", data.Addr, typ, data.thread)
|
||||
data = data.newVariable("data", data.Addr, typ)
|
||||
|
||||
v.Children = []Variable{*data}
|
||||
if loadData {
|
||||
|
Loading…
Reference in New Issue
Block a user