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:
aarzilli 2016-01-10 13:49:03 +01:00
parent 32a0c752eb
commit b839eda2a9
5 changed files with 245 additions and 84 deletions

@ -48,6 +48,11 @@ type dstruct struct {
type maptype map[string]interface{} type maptype map[string]interface{}
type benchstruct struct {
a [64]byte
b [64]byte
}
func main() { func main() {
i1 := 1 i1 := 1
i2 := 2 i2 := 2
@ -138,6 +143,12 @@ func main() {
var iface2fn2 interface{} = afunc2 var iface2fn2 interface{} = afunc2
var mapinf maptype = map[string]interface{}{} var mapinf maptype = map[string]interface{}{}
mapinf["inf"] = mapinf mapinf["inf"] = mapinf
var bencharr [64]benchstruct
var benchparr [64]*benchstruct
for i := range benchparr {
benchparr[i] = &benchstruct{}
}
var amb1 = 1 var amb1 = 1
runtime.Breakpoint() runtime.Breakpoint()
@ -145,5 +156,5 @@ func main() {
fmt.Println(amb1) fmt.Println(amb1)
} }
runtime.Breakpoint() 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)
} }

@ -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()) 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 v.loaded = true
switch ttyp := typ.(type) { switch ttyp := typ.(type) {
@ -154,7 +154,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) {
n, _ := constant.Int64Val(argv.Value) 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 return v, nil
case *dwarf.UintType: case *dwarf.UintType:
@ -274,18 +274,18 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
} }
fallthrough fallthrough
case reflect.Array: case reflect.Array:
return newConstant(constant.MakeInt64(arg.Len), arg.thread), nil return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil
case reflect.Slice: case reflect.Slice:
return newConstant(constant.MakeInt64(arg.Cap), arg.thread), nil return newConstant(constant.MakeInt64(arg.Cap), arg.mem), nil
case reflect.Chan: case reflect.Chan:
arg.loadValue() arg.loadValue()
if arg.Unreadable != nil { if arg.Unreadable != nil {
return nil, arg.Unreadable return nil, arg.Unreadable
} }
if arg.base == 0 { 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: default:
return nil, invalidArgErr return nil, invalidArgErr
} }
@ -309,25 +309,25 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
if arg.Unreadable != nil { if arg.Unreadable != nil {
return nil, arg.Unreadable 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: case reflect.Chan:
arg.loadValue() arg.loadValue()
if arg.Unreadable != nil { if arg.Unreadable != nil {
return nil, arg.Unreadable return nil, arg.Unreadable
} }
if arg.base == 0 { 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: case reflect.Map:
it := arg.mapIterator() it := arg.mapIterator()
if arg.Unreadable != nil { if arg.Unreadable != nil {
return nil, arg.Unreadable return nil, arg.Unreadable
} }
if it == nil { 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: default:
return nil, invalidArgErr 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}} 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)) r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value))
return r, nil 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 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) { 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 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 // Evaluates identifier expressions
@ -625,7 +625,7 @@ func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) {
xev.OnlyAddr = true xev.OnlyAddr = true
typename := "*" + xev.DwarfType.String() 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.Children = []Variable{*xev}
rv.loaded = true rv.loaded = true
@ -687,11 +687,11 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) {
return nil, err return nil, err
} }
if xv.DwarfType != nil { if xv.DwarfType != nil {
r := newVariable("", 0, xv.DwarfType, xv.thread) r := xv.newVariable("", 0, xv.DwarfType)
r.Value = rc r.Value = rc
return r, nil 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) { 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 { if err != nil {
return nil, err return nil, err
} }
return newConstant(constant.MakeBool(v), xv.thread), nil return newConstant(constant.MakeBool(v), xv.mem), nil
default: default:
if xv.Value == nil { if xv.Value == nil {
@ -823,10 +823,10 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) {
} }
if typ == nil { 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 r.Value = rc
return r, nil return r, nil
} }
@ -1039,7 +1039,7 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
if idx < 0 || int64(idx) >= v.Len { if idx < 0 || int64(idx) >= v.Len {
return nil, fmt.Errorf("index out of bounds") 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) { 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.Cap = len
r.Len = len r.Len = len
r.base = base r.base = base

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)) 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) fixture := protest.BuildFixture(name)
p, err := Launch([]string{fixture.Path}) p, err := Launch([]string{fixture.Path})
if err != nil { if err != nil {
@ -54,7 +54,7 @@ func dataAtAddr(thread *Thread, addr uint64) ([]byte, error) {
return thread.readMemory(uintptr(addr), 1) 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 { if err != nil {
_, file, line, _ := runtime.Caller(1) _, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file) 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 const doTestBreakpointCountsWithDetection = false
func TestBreakpointCountsWithDetection(t *testing.T) { 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) { func TestIssue262(t *testing.T) {
// Continue does not work when the current breakpoint is set on a NOP instruction // Continue does not work when the current breakpoint is set on a NOP instruction
withTestProcess("issue262", t, func(p *Process, fixture protest.Fixture) { withTestProcess("issue262", t, func(p *Process, fixture protest.Fixture) {
@ -1275,3 +1325,15 @@ func TestIssue341(t *testing.T) {
t.Logf("mapinf: %v\n", mapinf) 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. maxVariableRecurse = 1 // How far to recurse when evaluating nested types.
maxArrayValues = 64 // Max value for reading large arrays. maxArrayValues = 64 // Max value for reading large arrays.
maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs 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" chanRecv = "chan receive"
chanSend = "chan send" chanSend = "chan send"
@ -39,7 +41,8 @@ type Variable struct {
DwarfType dwarf.Type DwarfType dwarf.Type
RealType dwarf.Type RealType dwarf.Type
Kind reflect.Kind Kind reflect.Kind
thread *Thread mem memoryReadWriter
dbp *Process
Value constant.Value Value constant.Value
@ -136,12 +139,21 @@ func ptrTypeKind(t *dwarf.PtrType) reflect.Kind {
return reflect.Ptr 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{ v := &Variable{
Name: name, Name: name,
Addr: addr, Addr: addr,
DwarfType: dwarfType, DwarfType: dwarfType,
thread: thread, mem: mem,
dbp: dbp,
} }
v.RealType = resolveTypedef(v.DwarfType) v.RealType = resolveTypedef(v.DwarfType)
@ -156,7 +168,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
v.stride = 1 v.stride = 1
v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}} v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}}
if v.Addr != 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": case t.StructName == "runtime.iface" || t.StructName == "runtime.eface":
v.Kind = reflect.Interface v.Kind = reflect.Interface
@ -222,8 +234,8 @@ func resolveTypedef(typ dwarf.Type) dwarf.Type {
} }
} }
func newConstant(val constant.Value, thread *Thread) *Variable { func newConstant(val constant.Value, mem memoryReadWriter) *Variable {
v := &Variable{Value: val, thread: thread, loaded: true} v := &Variable{Value: val, mem: mem, loaded: true}
switch val.Kind() { switch val.Kind() {
case constant.Int: case constant.Int:
v.Kind = reflect.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) 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 // DwarfReader returns the DwarfReader containing the
@ -349,6 +361,11 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
return nil, err 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 // Parse defer
deferAddr, err := rdr.AddrForMember("_defer", initialInstructions) deferAddr, err := rdr.AddrForMember("_defer", initialInstructions)
if err != nil { if err != nil {
@ -356,7 +373,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
} }
var deferPC uint64 var deferPC uint64
// Dereference *defer pointer // 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 { if err != nil {
return nil, fmt.Errorf("error derefing defer %s", err) 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 return nil, err
} }
deferPCAddr, err := rdr.AddrForMember("fn", initialDeferInstructions) deferPCAddr, err := rdr.AddrForMember("fn", initialDeferInstructions)
deferPC, err = thread.readUintRaw(uintptr(deferPCAddr), 8) deferPC, err = readUintRaw(mem, uintptr(deferPCAddr), 8)
if err != nil { if err != nil {
return nil, err return nil, err
} }
deferPC, err = thread.readUintRaw(uintptr(deferPC), 8) deferPC, err = readUintRaw(mem, uintptr(deferPC), 8)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -391,11 +408,11 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
return nil, err return nil, err
} }
// From sched, let's parse PC and SP. // 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -404,13 +421,13 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
atomicStatus, err := thread.readUintRaw(uintptr(atomicStatusAddr), 4) atomicStatus, err := readUintRaw(mem, uintptr(atomicStatusAddr), 4)
// Parse goid // Parse goid
goidAddr, err := rdr.AddrForMember("goid", initialInstructions) goidAddr, err := rdr.AddrForMember("goid", initialInstructions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
goid, err := thread.readIntRaw(uintptr(goidAddr), 8) goid, err := readIntRaw(mem, uintptr(goidAddr), 8)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -419,7 +436,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
waitreason, _, err := thread.readString(uintptr(waitReasonAddr)) waitreason, _, err := readString(mem, thread.dbp.arch, uintptr(waitReasonAddr))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -428,7 +445,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
gopc, err := thread.readUintRaw(uintptr(gopcAddr), 8) gopc, err := readUintRaw(mem, uintptr(gopcAddr), 8)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -725,7 +742,7 @@ func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry, rdr *reader.
return nil, err 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. // 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) { switch t := v.RealType.(type) {
case *dwarf.PtrType: case *dwarf.PtrType:
ptrval, err := v.thread.readUintRaw(uintptr(v.Addr), int64(v.thread.dbp.arch.PtrSize())) ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize)
r := newVariable("", uintptr(ptrval), t.Type, v.thread) r := v.newVariable("", uintptr(ptrval), t.Type)
if err != nil { if err != nil {
r.Unreadable = err 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) { if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.base == 0) {
return return
} }
v.loaded = true v.loaded = true
switch v.Kind { switch v.Kind {
case reflect.Ptr, reflect.UnsafePointer: case reflect.Ptr, reflect.UnsafePointer:
@ -777,13 +795,14 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
case reflect.String: case reflect.String:
var val 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) v.Value = constant.MakeString(val)
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
v.loadArrayValues(recurseLevel) v.loadArrayValues(recurseLevel)
case reflect.Struct: case reflect.Struct:
v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
t := v.RealType.(*dwarf.StructType) t := v.RealType.(*dwarf.StructType)
v.Len = int64(len(t.Field)) v.Len = int64(len(t.Field))
// Recursively call extractValue to grab // Recursively call extractValue to grab
@ -805,15 +824,15 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var val 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) v.Value = constant.MakeInt64(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
var val uint64 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) v.Value = constant.MakeUint64(val)
case reflect.Bool: case reflect.Bool:
val, err := v.thread.readMemory(v.Addr, 1) val, err := v.mem.readMemory(v.Addr, 1)
v.Unreadable = err v.Unreadable = err
if err == nil { if err == nil {
v.Value = constant.MakeBool(val[0] != 0) 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()) err = v.writeComplex(real, imag, v.RealType.Size())
default: default:
fmt.Printf("default\n") fmt.Printf("default\n")
if _, isptr := v.RealType.(*dwarf.PtrType); isptr { if t, isptr := v.RealType.(*dwarf.PtrType); isptr {
err = v.writeUint(uint64(y.Children[0].Addr), int64(v.thread.dbp.arch.PtrSize())) err = v.writeUint(uint64(y.Children[0].Addr), int64(t.ByteSize))
} else { } else {
return fmt.Errorf("can not set variables of type %s (not implemented)", v.Kind.String()) 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 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 // string data structure is always two ptrs in size. Addr, followed by len
// http://research.swtch.com/godata // http://research.swtch.com/godata
mem = cacheMemory(mem, addr, arch.PtrSize()*2)
// read len // 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 { if err != nil {
return 0, 0, fmt.Errorf("could not read string len %s", err) 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 // read addr
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize()) val, err = mem.readMemory(addr, arch.PtrSize())
if err != nil { if err != nil {
return 0, 0, fmt.Errorf("could not read string pointer %s", err) 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 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 count := strlen
if count > maxArrayValues { if count > maxArrayValues {
count = maxArrayValues count = maxArrayValues
} }
val, err := thread.readMemory(addr, int(count)) val, err := mem.readMemory(addr, int(count))
if err != nil { if err != nil {
return "", fmt.Errorf("could not read string at %#v due to %s", addr, err) 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 return retstr, nil
} }
func (thread *Thread) readString(addr uintptr) (string, int64, error) { func readString(mem memoryReadWriter, arch Arch, addr uintptr) (string, int64, error) {
addr, strlen, err := thread.readStringInfo(addr) addr, strlen, err := readStringInfo(mem, arch, addr)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
} }
retstr, err := thread.readStringValue(addr, strlen) retstr, err := readStringValue(mem, addr, strlen)
return retstr, strlen, err return retstr, strlen, err
} }
func (v *Variable) loadSliceInfo(t *dwarf.StructType) { func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
v.mem = cacheMemory(v.mem, v.Addr, int(t.Size()))
var err error var err error
for _, f := range t.Field { for _, f := range t.Field {
switch f.Name { switch f.Name {
case "array": case "array":
var base uint64 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 { if err == nil {
v.base = uintptr(base) v.base = uintptr(base)
// Dereference array type to get value type // Dereference array type to get value type
@ -951,8 +974,8 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
} }
v.stride = v.fieldType.Size() v.stride = v.fieldType.Size()
if _, ok := v.fieldType.(*dwarf.PtrType); ok { if t, ok := v.fieldType.(*dwarf.PtrType); ok {
v.stride = int64(v.thread.dbp.arch.PtrSize()) v.stride = t.ByteSize
} }
return return
@ -963,15 +986,20 @@ func (v *Variable) loadArrayValues(recurseLevel int) {
return 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 errcount := 0
for i := int64(0); i < v.Len; i++ { for i := int64(0); i < count; i++ {
// Cap number of elements fieldvar := v.newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType)
if i >= maxArrayValues {
break
}
fieldvar := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
fieldvar.loadValueInternal(recurseLevel + 1) fieldvar.loadValueInternal(recurseLevel + 1)
if fieldvar.Unreadable != nil { 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}} 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) realvar := v.newVariable("real", v.Addr, ftyp)
imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread) imagvar := v.newVariable("imaginary", v.Addr+uintptr(fs), ftyp)
realvar.loadValue() realvar.loadValue()
imagvar.loadValue() imagvar.loadValue()
v.Value = constant.BinaryOp(realvar.Value, token.ADD, constant.MakeImag(imagvar.Value)) 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)) 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 var n int64
val, err := thread.readMemory(addr, int(size)) val, err := mem.readMemory(addr, int(size))
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -1052,14 +1080,14 @@ func (v *Variable) writeUint(value uint64, size int64) error {
binary.LittleEndian.PutUint64(val, uint64(value)) binary.LittleEndian.PutUint64(val, uint64(value))
} }
_, err := v.thread.writeMemory(v.Addr, val) _, err := v.mem.writeMemory(v.Addr, val)
return err 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 var n uint64
val, err := thread.readMemory(addr, int(size)) val, err := mem.readMemory(addr, int(size))
if err != nil { if err != nil {
return 0, err 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) { 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 { if err != nil {
return 0.0, err return 0.0, err
} }
@ -1111,19 +1139,19 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error {
binary.Write(buf, binary.LittleEndian, n) binary.Write(buf, binary.LittleEndian, n)
} }
_, err := v.thread.writeMemory(v.Addr, buf.Bytes()) _, err := v.mem.writeMemory(v.Addr, buf.Bytes())
return err return err
} }
func (v *Variable) writeBool(value bool) error { func (v *Variable) writeBool(value bool) error {
val := []byte{0} val := []byte{0}
val[0] = *(*byte)(unsafe.Pointer(&value)) val[0] = *(*byte)(unsafe.Pointer(&value))
_, err := v.thread.writeMemory(v.Addr, val) _, err := v.mem.writeMemory(v.Addr, val)
return err return err
} }
func (v *Variable) readFunctionPtr() { 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 { if err != nil {
v.Unreadable = err v.Unreadable = err
return return
@ -1137,14 +1165,14 @@ func (v *Variable) readFunctionPtr() {
return 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 { if err != nil {
v.Unreadable = err v.Unreadable = err
return return
} }
v.base = uintptr(binary.LittleEndian.Uint64(val)) 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 { if fn == nil {
v.Unreadable = fmt.Errorf("could not find function for %#v", v.base) v.Unreadable = fmt.Errorf("could not find function for %#v", v.base)
return return
@ -1225,6 +1253,8 @@ func (v *Variable) mapIterator() *mapIterator {
return it return it
} }
v.mem = cacheMemory(v.mem, v.base, int(v.RealType.Size()))
for _, f := range maptype.Field { for _, f := range maptype.Field {
var err error var err error
field, _ := sv.toField(f) field, _ := sv.toField(f)
@ -1301,6 +1331,8 @@ func (it *mapIterator) nextBucket() bool {
return false return false
} }
it.b.mem = cacheMemory(it.b.mem, it.b.Addr, int(it.b.RealType.Size()))
it.tophashes = nil it.tophashes = nil
it.keys = nil it.keys = nil
it.values = nil it.values = nil
@ -1403,6 +1435,8 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
var typestring, data *Variable var typestring, data *Variable
isnil := false isnil := false
v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
for _, f := range v.RealType.(*dwarf.StructType).Field { for _, f := range v.RealType.(*dwarf.StructType).Field {
switch f.Name { switch f.Name {
case "tab": // for runtime.iface case "tab": // for runtime.iface
@ -1464,7 +1498,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
return return
} }
typ, err := v.thread.dbp.findTypeExpr(t) typ, err := v.dbp.findTypeExpr(t)
if err != nil { if err != nil {
v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), data.Addr, err) v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), data.Addr, err)
return return
@ -1473,10 +1507,10 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) {
realtyp := resolveTypedef(typ) realtyp := resolveTypedef(typ)
if _, isptr := realtyp.(*dwarf.PtrType); !isptr { if _, isptr := realtyp.(*dwarf.PtrType); !isptr {
// interface to non-pointer types are pointers even if the type says otherwise // 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} v.Children = []Variable{*data}
if loadData { if loadData {