Variable evaluation on arbitrary (goroutine, frame) pair.
This commit is contained in:
parent
7206267772
commit
c6ebd80905
@ -4,7 +4,7 @@ import "runtime"
|
||||
|
||||
const N = 10
|
||||
|
||||
func agoroutine(done chan<- struct{}) {
|
||||
func agoroutine(done chan<- struct{}, i int) {
|
||||
done <- struct{}{}
|
||||
}
|
||||
|
||||
@ -15,11 +15,25 @@ func stacktraceme() {
|
||||
func main() {
|
||||
done := make(chan struct{})
|
||||
for i := 0; i < N; i++ {
|
||||
go agoroutine(done)
|
||||
go agoroutine(done, i)
|
||||
}
|
||||
runtime.Gosched()
|
||||
stacktraceme()
|
||||
for i := 0; i < N; i++ {
|
||||
<-done
|
||||
}
|
||||
n := 0
|
||||
func1(n + 1)
|
||||
}
|
||||
|
||||
func func1(n int) {
|
||||
func2(n + 1)
|
||||
}
|
||||
|
||||
func func2(n int) {
|
||||
func3(n + 1)
|
||||
}
|
||||
|
||||
func func3(n int) {
|
||||
stacktraceme()
|
||||
}
|
||||
|
@ -52,9 +52,9 @@ func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
|
||||
}
|
||||
|
||||
// Return the offset from the current SP that the return address is stored at.
|
||||
func (fde *FrameDescriptionEntry) ReturnAddressOffset(pc uint64) int64 {
|
||||
func (fde *FrameDescriptionEntry) ReturnAddressOffset(pc uint64) (frameOffset, returnAddressOffset int64) {
|
||||
frame := fde.EstablishFrame(pc)
|
||||
return frame.cfa.offset + frame.regs[fde.CIE.ReturnAddressRegister].offset
|
||||
return frame.cfa.offset, frame.regs[fde.CIE.ReturnAddressRegister].offset
|
||||
}
|
||||
|
||||
type FrameDescriptionEntries []*FrameDescriptionEntry
|
||||
|
103
proc/proc.go
103
proc/proc.go
@ -32,9 +32,13 @@ type Process struct {
|
||||
// List of threads mapped as such: pid -> *Thread
|
||||
Threads map[int]*Thread
|
||||
|
||||
// Active thread. This is the default thread used for setting breakpoints, evaluating variables, etc..
|
||||
// Active thread
|
||||
CurrentThread *Thread
|
||||
|
||||
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
|
||||
// Normally SelectedGoroutine is CurrentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
|
||||
SelectedGoroutine *G
|
||||
|
||||
dwarf *dwarf.Data
|
||||
goSymTable *gosym.Table
|
||||
frameEntries frame.FrameDescriptionEntries
|
||||
@ -317,10 +321,8 @@ func (dbp *Process) next() (err error) {
|
||||
if tg.Id == g.Id || goroutineExiting {
|
||||
// Check to see if the goroutine has switched to another
|
||||
// thread, if so make it the current thread.
|
||||
if dbp.CurrentThread.Id != th.Id {
|
||||
if err = dbp.SwitchThread(th.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dbp.SwitchThread(th.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -373,9 +375,7 @@ func (dbp *Process) Continue() error {
|
||||
if err := dbp.Halt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dbp.CurrentThread != thread {
|
||||
dbp.SwitchThread(thread.Id)
|
||||
}
|
||||
dbp.SwitchThread(thread.Id)
|
||||
loc, err := thread.Location()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -414,11 +414,30 @@ func (dbp *Process) Step() (err error) {
|
||||
func (dbp *Process) SwitchThread(tid int) error {
|
||||
if th, ok := dbp.Threads[tid]; ok {
|
||||
dbp.CurrentThread = th
|
||||
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("thread %d does not exist", tid)
|
||||
}
|
||||
|
||||
// Change from current thread to the thread running the specified goroutine
|
||||
func (dbp *Process) SwitchGoroutine(gid int) error {
|
||||
gs, err := dbp.GoroutinesInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, g := range gs {
|
||||
if g.Id == gid {
|
||||
if g.thread != nil {
|
||||
return dbp.SwitchThread(g.thread.Id)
|
||||
}
|
||||
dbp.SelectedGoroutine = g
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not find goroutine %d", gid)
|
||||
}
|
||||
|
||||
// Returns an array of G structures representing the information
|
||||
// Delve cares about from the internal runtime G structure.
|
||||
func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
||||
@ -503,11 +522,6 @@ func (dbp *Process) CurrentBreakpoint() *Breakpoint {
|
||||
return dbp.CurrentThread.CurrentBreakpoint
|
||||
}
|
||||
|
||||
// Returns the value of the named symbol.
|
||||
func (dbp *Process) EvalVariable(name string) (*Variable, error) {
|
||||
return dbp.CurrentThread.EvalVariable(name)
|
||||
}
|
||||
|
||||
// Returns a reader for the dwarf data
|
||||
func (dbp *Process) DwarfReader() *reader.Reader {
|
||||
return reader.New(dbp.dwarf)
|
||||
@ -580,21 +594,26 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dbp.updateThreadList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
dbp.arch = AMD64Arch()
|
||||
}
|
||||
|
||||
if err := dbp.updateThreadList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ver, isextld, err := dbp.getGoInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.arch.SetGStructOffset(ver, isextld)
|
||||
// SelectedGoroutine can not be set correctly by the call to updateThreadList
|
||||
// because without calling SetGStructOffset we can not read the G struct of CurrentThread
|
||||
// but without calling updateThreadList we can not examine memory to determine
|
||||
// the offset of g struct inside TLS
|
||||
dbp.SelectedGoroutine, _ = dbp.CurrentThread.GetG()
|
||||
|
||||
return dbp, nil
|
||||
}
|
||||
@ -674,7 +693,7 @@ func (dbp *Process) execPtraceFunc(fn func()) {
|
||||
}
|
||||
|
||||
func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) {
|
||||
vv, err := dbp.CurrentThread.EvalPackageVariable("runtime.buildVersion")
|
||||
vv, err := dbp.EvalPackageVariable("runtime.buildVersion")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not determine version number: %v\n", err)
|
||||
return
|
||||
@ -699,3 +718,51 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dbp *Process) FindGoroutine(gid int) (*G, error) {
|
||||
if gid == -1 {
|
||||
return dbp.SelectedGoroutine, nil
|
||||
}
|
||||
|
||||
gs, err := dbp.GoroutinesInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range gs {
|
||||
if gs[i].Id == gid {
|
||||
return gs[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
||||
}
|
||||
|
||||
func (dbp *Process) ConvertEvalScope(gid, frame int) (*EvalScope, error) {
|
||||
g, err := dbp.FindGoroutine(gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if g == nil {
|
||||
return dbp.CurrentThread.Scope()
|
||||
}
|
||||
|
||||
var out EvalScope
|
||||
|
||||
if g.thread == nil {
|
||||
out.Thread = dbp.CurrentThread
|
||||
} else {
|
||||
out.Thread = g.thread
|
||||
}
|
||||
|
||||
locs, err := dbp.GoroutineStacktrace(g, frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if frame >= len(locs) {
|
||||
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
||||
}
|
||||
|
||||
out.PC, out.CFA = locs[frame].PC, locs[frame].CFA
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
|
||||
dbp.Threads[port] = thread
|
||||
thread.os.thread_act = C.thread_act_t(port)
|
||||
if dbp.CurrentThread == nil {
|
||||
dbp.CurrentThread = thread
|
||||
dbp.SwitchThread(thread.Id)
|
||||
}
|
||||
return thread, nil
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
|
||||
os: new(OSSpecificDetails),
|
||||
}
|
||||
if dbp.CurrentThread == nil {
|
||||
dbp.CurrentThread = dbp.Threads[tid]
|
||||
dbp.SwitchThread(tid)
|
||||
}
|
||||
return dbp.Threads[tid], nil
|
||||
}
|
||||
|
@ -310,9 +310,14 @@ func TestNextConcurrent(t *testing.T) {
|
||||
assertNoError(err, t, "SetBreakpoint")
|
||||
assertNoError(p.Continue(), t, "Continue")
|
||||
f, ln := currentLineNumber(p, t)
|
||||
initV, err := p.EvalVariable("n")
|
||||
initV, err := evalVariable(p, "n")
|
||||
assertNoError(err, t, "EvalVariable")
|
||||
for _, tc := range testcases {
|
||||
g, err := p.CurrentThread.GetG()
|
||||
assertNoError(err, t, "GetG()")
|
||||
if p.SelectedGoroutine.Id != g.Id {
|
||||
t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.Id, p.SelectedGoroutine.Id)
|
||||
}
|
||||
if ln != tc.begin {
|
||||
t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
|
||||
}
|
||||
@ -321,7 +326,7 @@ func TestNextConcurrent(t *testing.T) {
|
||||
if ln != tc.end {
|
||||
t.Fatalf("Program did not continue to correct next location expected %d was %s:%d", tc.end, filepath.Base(f), ln)
|
||||
}
|
||||
v, err := p.EvalVariable("n")
|
||||
v, err := evalVariable(p, "n")
|
||||
assertNoError(err, t, "EvalVariable")
|
||||
if v.Value != initV.Value {
|
||||
t.Fatal("Did not end up on same goroutine")
|
||||
@ -527,7 +532,7 @@ type loc struct {
|
||||
fn string
|
||||
}
|
||||
|
||||
func (l1 *loc) match(l2 Location) bool {
|
||||
func (l1 *loc) match(l2 Stackframe) bool {
|
||||
if l1.line >= 0 {
|
||||
if l1.line != l2.Line-1 {
|
||||
return false
|
||||
@ -555,6 +560,8 @@ func TestStacktrace(t *testing.T) {
|
||||
t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2)
|
||||
}
|
||||
|
||||
t.Logf("Stacktrace %d: %v\n", i, locations)
|
||||
|
||||
for j := range stacks[i] {
|
||||
if !stacks[i][j].match(locations[j]) {
|
||||
t.Fatalf("Wrong stack trace pos %d\n", j)
|
||||
@ -567,7 +574,7 @@ func TestStacktrace(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func stackMatch(stack []loc, locations []Location) bool {
|
||||
func stackMatch(stack []loc, locations []Stackframe) bool {
|
||||
if len(stack) > len(locations) {
|
||||
return false
|
||||
}
|
||||
|
@ -13,6 +13,12 @@ func (nra NoReturnAddr) Error() string {
|
||||
return fmt.Sprintf("could not find return address for %s", nra.fn)
|
||||
}
|
||||
|
||||
type Stackframe struct {
|
||||
Location
|
||||
CFA int64
|
||||
Ret uint64
|
||||
}
|
||||
|
||||
// Takes an offset from RSP and returns the address of the
|
||||
// instruction the current function is going to return to.
|
||||
func (thread *Thread) ReturnAddress() (uint64, error) {
|
||||
@ -28,7 +34,7 @@ func (thread *Thread) ReturnAddress() (uint64, error) {
|
||||
|
||||
// Returns the stack trace for thread.
|
||||
// Note the locations in the array are return addresses not call addresses.
|
||||
func (thread *Thread) Stacktrace(depth int) ([]Location, error) {
|
||||
func (thread *Thread) Stacktrace(depth int) ([]Stackframe, error) {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -38,7 +44,7 @@ func (thread *Thread) Stacktrace(depth int) ([]Location, error) {
|
||||
|
||||
// Returns the stack trace for a goroutine.
|
||||
// Note the locations in the array are return addresses not call addresses.
|
||||
func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Location, error) {
|
||||
func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Stackframe, error) {
|
||||
if g.thread != nil {
|
||||
return g.thread.Stacktrace(depth)
|
||||
}
|
||||
@ -57,42 +63,48 @@ func (n NullAddrError) Error() string {
|
||||
return "NULL address"
|
||||
}
|
||||
|
||||
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Location, error) {
|
||||
var (
|
||||
ret = pc
|
||||
btoffset int64
|
||||
locations []Location
|
||||
retaddr uintptr
|
||||
)
|
||||
func (dbp *Process) frameInfo(pc, sp uint64) (Stackframe, error) {
|
||||
f, l, fn := dbp.PCToLine(pc)
|
||||
locations = append(locations, Location{PC: pc, File: f, Line: l, Fn: fn})
|
||||
for i := 0; i < depth; i++ {
|
||||
fde, err := dbp.frameEntries.FDEForPC(ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
btoffset += fde.ReturnAddressOffset(ret)
|
||||
retaddr = uintptr(int64(sp) + btoffset + int64(i*dbp.arch.PtrSize()))
|
||||
if retaddr == 0 {
|
||||
return nil, NullAddrError{}
|
||||
}
|
||||
data, err := dbp.CurrentThread.readMemory(retaddr, dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = binary.LittleEndian.Uint64(data)
|
||||
if ret <= 0 {
|
||||
break
|
||||
}
|
||||
f, l, fn = dbp.goSymTable.PCToLine(ret)
|
||||
if fn == nil {
|
||||
break
|
||||
}
|
||||
locations = append(locations, Location{PC: ret, File: f, Line: l, Fn: fn})
|
||||
// Look for "top of stack" functions.
|
||||
if fn.Name == "runtime.rt0_go" {
|
||||
break
|
||||
}
|
||||
fde, err := dbp.frameEntries.FDEForPC(pc)
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
}
|
||||
return locations, nil
|
||||
spoffset, retoffset := fde.ReturnAddressOffset(pc)
|
||||
cfa := int64(sp) + spoffset
|
||||
|
||||
retaddr := uintptr(cfa + retoffset)
|
||||
if retaddr == 0 {
|
||||
return Stackframe{}, NullAddrError{}
|
||||
}
|
||||
data, err := dbp.CurrentThread.readMemory(retaddr, dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return Stackframe{}, err
|
||||
}
|
||||
return Stackframe{Location: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, Ret: binary.LittleEndian.Uint64(data)}, nil
|
||||
}
|
||||
|
||||
func (dbp *Process) stacktrace(pc, sp uint64, depth int) ([]Stackframe, error) {
|
||||
frames := make([]Stackframe, 0, depth+1)
|
||||
|
||||
for i := 0; i < depth+1; i++ {
|
||||
frame, err := dbp.frameInfo(pc, sp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if frame.Fn == nil {
|
||||
break
|
||||
}
|
||||
frames = append(frames, frame)
|
||||
if frame.Ret <= 0 {
|
||||
break
|
||||
}
|
||||
// Look for "top of stack" functions.
|
||||
if frame.Fn.Name == "runtime.goexit" || frame.Fn.Name == "runtime.rt0_go" {
|
||||
break
|
||||
}
|
||||
|
||||
pc = frame.Ret
|
||||
sp = uint64(frame.CFA)
|
||||
}
|
||||
return frames, nil
|
||||
}
|
||||
|
@ -280,6 +280,12 @@ func (thread *Thread) GetG() (g *G, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if thread.dbp.arch.GStructOffset() == 0 {
|
||||
// GetG was called through SwitchThread / updateThreadList during initialization
|
||||
// thread.dbp.arch isn't setup yet (it needs a CurrentThread to read global variables from)
|
||||
return nil, fmt.Errorf("g struct offset not initialized")
|
||||
}
|
||||
|
||||
gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -315,3 +321,11 @@ func (thread *Thread) Halt() (err error) {
|
||||
err = thread.halt()
|
||||
return
|
||||
}
|
||||
|
||||
func (thread *Thread) Scope() (*EvalScope, error) {
|
||||
locations, err := thread.Stacktrace(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EvalScope{Thread: thread, PC: locations[0].PC, CFA: locations[0].CFA}, nil
|
||||
}
|
||||
|
@ -58,6 +58,25 @@ type G struct {
|
||||
thread *Thread
|
||||
}
|
||||
|
||||
// Scope for variable evaluation
|
||||
type EvalScope struct {
|
||||
Thread *Thread
|
||||
PC uint64
|
||||
CFA int64
|
||||
}
|
||||
|
||||
func (scope *EvalScope) DwarfReader() *reader.Reader {
|
||||
return scope.Thread.dbp.DwarfReader()
|
||||
}
|
||||
|
||||
func (scope *EvalScope) Type(offset dwarf.Offset) (dwarf.Type, error) {
|
||||
return scope.Thread.dbp.dwarf.Type(offset)
|
||||
}
|
||||
|
||||
func (scope *EvalScope) PtrSize() int {
|
||||
return scope.Thread.dbp.arch.PtrSize()
|
||||
}
|
||||
|
||||
// Returns whether the goroutine is blocked on
|
||||
// a channel read operation.
|
||||
func (g *G) ChanRecvBlocked() bool {
|
||||
@ -201,15 +220,10 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
||||
}
|
||||
|
||||
// Returns the value of the named variable.
|
||||
func (thread *Thread) EvalVariable(name string) (*Variable, error) {
|
||||
pc, err := thread.PC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (scope *EvalScope) EvalVariable(name string) (*Variable, error) {
|
||||
reader := scope.DwarfReader()
|
||||
|
||||
reader := thread.dbp.DwarfReader()
|
||||
|
||||
_, err = reader.SeekToFunction(pc)
|
||||
_, err := reader.SeekToFunction(scope.PC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -234,19 +248,19 @@ func (thread *Thread) EvalVariable(name string) (*Variable, error) {
|
||||
|
||||
if n == varName {
|
||||
if len(memberName) == 0 {
|
||||
return thread.extractVariableFromEntry(entry)
|
||||
return scope.extractVariableFromEntry(entry)
|
||||
}
|
||||
return thread.evaluateStructMember(entry, reader, memberName)
|
||||
return scope.evaluateStructMember(entry, reader, memberName)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to evaluate name as a package variable.
|
||||
if memberName != "" {
|
||||
return thread.EvalPackageVariable(name)
|
||||
return scope.Thread.dbp.EvalPackageVariable(name)
|
||||
} else {
|
||||
loc, err := thread.Location()
|
||||
if err == nil && loc.Fn != nil {
|
||||
v, err := thread.EvalPackageVariable(loc.Fn.PackageName() + "." + name)
|
||||
_, _, fn := scope.Thread.dbp.PCToLine(scope.PC)
|
||||
if fn != nil {
|
||||
v, err := scope.Thread.dbp.EvalPackageVariable(fn.PackageName() + "." + name)
|
||||
if err == nil {
|
||||
v.Name = name
|
||||
return v, nil
|
||||
@ -258,18 +272,18 @@ func (thread *Thread) EvalVariable(name string) (*Variable, error) {
|
||||
}
|
||||
|
||||
// LocalVariables returns all local variables from the current function scope.
|
||||
func (thread *Thread) LocalVariables() ([]*Variable, error) {
|
||||
return thread.variablesByTag(dwarf.TagVariable)
|
||||
func (scope *EvalScope) LocalVariables() ([]*Variable, error) {
|
||||
return scope.variablesByTag(dwarf.TagVariable)
|
||||
}
|
||||
|
||||
// FunctionArguments returns the name, value, and type of all current function arguments.
|
||||
func (thread *Thread) FunctionArguments() ([]*Variable, error) {
|
||||
return thread.variablesByTag(dwarf.TagFormalParameter)
|
||||
func (scope *EvalScope) FunctionArguments() ([]*Variable, error) {
|
||||
return scope.variablesByTag(dwarf.TagFormalParameter)
|
||||
}
|
||||
|
||||
// PackageVariables returns the name, value, and type of all package variables in the application.
|
||||
func (thread *Thread) PackageVariables() ([]*Variable, error) {
|
||||
reader := thread.dbp.DwarfReader()
|
||||
func (scope *EvalScope) PackageVariables() ([]*Variable, error) {
|
||||
reader := scope.DwarfReader()
|
||||
|
||||
vars := make([]*Variable, 0)
|
||||
|
||||
@ -279,7 +293,7 @@ func (thread *Thread) PackageVariables() ([]*Variable, error) {
|
||||
}
|
||||
|
||||
// Ignore errors trying to extract values
|
||||
val, err := thread.extractVariableFromEntry(entry)
|
||||
val, err := scope.extractVariableFromEntry(entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -289,8 +303,9 @@ func (thread *Thread) PackageVariables() ([]*Variable, error) {
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
func (thread *Thread) EvalPackageVariable(name string) (*Variable, error) {
|
||||
reader := thread.dbp.DwarfReader()
|
||||
func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) {
|
||||
reader := dbp.DwarfReader()
|
||||
scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0}
|
||||
|
||||
for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() {
|
||||
if err != nil {
|
||||
@ -303,15 +318,15 @@ func (thread *Thread) EvalPackageVariable(name string) (*Variable, error) {
|
||||
}
|
||||
|
||||
if n == name {
|
||||
return thread.extractVariableFromEntry(entry)
|
||||
return scope.extractVariableFromEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", name)
|
||||
}
|
||||
|
||||
func (thread *Thread) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader.Reader, memberName string) (*Variable, error) {
|
||||
parentAddr, err := thread.extractVariableDataAddress(parentEntry, rdr)
|
||||
func (scope *EvalScope) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader.Reader, memberName string) (*Variable, error) {
|
||||
parentAddr, err := scope.extractVariableDataAddress(parentEntry, rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -355,8 +370,7 @@ func (thread *Thread) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
|
||||
data := thread.dbp.dwarf
|
||||
t, err := data.Type(offset)
|
||||
t, err := scope.Type(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -365,7 +379,7 @@ func (thread *Thread) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader
|
||||
binary.LittleEndian.PutUint64(baseAddr, uint64(parentAddr))
|
||||
|
||||
parentInstructions := append([]byte{op.DW_OP_addr}, baseAddr...)
|
||||
val, err := thread.extractValue(append(parentInstructions, memberInstr...), 0, t, true)
|
||||
val, err := scope.extractValue(append(parentInstructions, memberInstr...), 0, t, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -377,7 +391,7 @@ func (thread *Thread) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader
|
||||
}
|
||||
|
||||
// Extracts the name, type, and value of a variable from a dwarf entry
|
||||
func (thread *Thread) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) {
|
||||
func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) {
|
||||
if entry == nil {
|
||||
return nil, fmt.Errorf("invalid entry")
|
||||
}
|
||||
@ -396,8 +410,7 @@ func (thread *Thread) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, e
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
|
||||
data := thread.dbp.dwarf
|
||||
t, err := data.Type(offset)
|
||||
t, err := scope.Type(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -407,7 +420,7 @@ func (thread *Thread) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, e
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
|
||||
val, err := thread.extractValue(instructions, 0, t, true)
|
||||
val, err := scope.extractValue(instructions, 0, t, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -415,35 +428,14 @@ func (thread *Thread) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, e
|
||||
return &Variable{Name: n, Type: t.String(), Value: val}, nil
|
||||
}
|
||||
|
||||
// Execute the stack program taking into account the current stack frame
|
||||
func (thread *Thread) executeStackProgram(instructions []byte) (int64, error) {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var cfa int64 = 0
|
||||
|
||||
fde, err := thread.dbp.frameEntries.FDEForPC(regs.PC())
|
||||
if err == nil {
|
||||
fctx := fde.EstablishFrame(regs.PC())
|
||||
cfa = fctx.CFAOffset() + int64(regs.SP())
|
||||
}
|
||||
address, err := op.ExecuteStackProgram(cfa, instructions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// Extracts the address of a variable, dereferencing any pointers
|
||||
func (thread *Thread) extractVariableDataAddress(entry *dwarf.Entry, rdr *reader.Reader) (int64, error) {
|
||||
func (scope *EvalScope) extractVariableDataAddress(entry *dwarf.Entry, rdr *reader.Reader) (int64, error) {
|
||||
instructions, err := rdr.InstructionsForEntry(entry)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
address, err := thread.executeStackProgram(instructions)
|
||||
address, err := op.ExecuteStackProgram(scope.CFA, instructions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -460,7 +452,7 @@ func (thread *Thread) extractVariableDataAddress(entry *dwarf.Entry, rdr *reader
|
||||
|
||||
ptraddress := uintptr(address)
|
||||
|
||||
ptr, err := thread.readMemory(ptraddress, thread.dbp.arch.PtrSize())
|
||||
ptr, err := scope.Thread.readMemory(ptraddress, scope.PtrSize())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -473,15 +465,15 @@ func (thread *Thread) extractVariableDataAddress(entry *dwarf.Entry, rdr *reader
|
||||
// Extracts the value from the instructions given in the DW_AT_location entry.
|
||||
// We execute the stack program described in the DW_OP_* instruction stream, and
|
||||
// then grab the value from the other processes memory.
|
||||
func (thread *Thread) extractValue(instructions []byte, addr int64, typ interface{}, printStructName bool) (string, error) {
|
||||
return thread.extractValueInternal(instructions, addr, typ, printStructName, 0)
|
||||
func (scope *EvalScope) extractValue(instructions []byte, addr int64, typ interface{}, printStructName bool) (string, error) {
|
||||
return scope.extractValueInternal(instructions, addr, typ, printStructName, 0)
|
||||
}
|
||||
|
||||
func (thread *Thread) extractValueInternal(instructions []byte, addr int64, typ interface{}, printStructName bool, recurseLevel int) (string, error) {
|
||||
func (scope *EvalScope) extractValueInternal(instructions []byte, addr int64, typ interface{}, printStructName bool, recurseLevel int) (string, error) {
|
||||
var err error
|
||||
|
||||
if addr == 0 {
|
||||
addr, err = thread.executeStackProgram(instructions)
|
||||
addr, err = op.ExecuteStackProgram(scope.CFA, instructions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -500,7 +492,7 @@ func (thread *Thread) extractValueInternal(instructions []byte, addr int64, typ
|
||||
ptraddress := uintptr(addr)
|
||||
switch t := typ.(type) {
|
||||
case *dwarf.PtrType:
|
||||
ptr, err := thread.readMemory(ptraddress, thread.dbp.arch.PtrSize())
|
||||
ptr, err := scope.Thread.readMemory(ptraddress, scope.PtrSize())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -511,7 +503,7 @@ func (thread *Thread) extractValueInternal(instructions []byte, addr int64, typ
|
||||
}
|
||||
|
||||
// Don't increase the recursion level when dereferencing pointers
|
||||
val, err := thread.extractValueInternal(nil, intaddr, t.Type, printStructName, recurseLevel)
|
||||
val, err := scope.extractValueInternal(nil, intaddr, t.Type, printStructName, recurseLevel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -520,16 +512,16 @@ func (thread *Thread) extractValueInternal(instructions []byte, addr int64, typ
|
||||
case *dwarf.StructType:
|
||||
switch {
|
||||
case t.StructName == "string":
|
||||
return thread.readString(ptraddress)
|
||||
return scope.Thread.readString(ptraddress)
|
||||
case strings.HasPrefix(t.StructName, "[]"):
|
||||
return thread.readSlice(ptraddress, t, recurseLevel)
|
||||
return scope.readSlice(ptraddress, t, recurseLevel)
|
||||
default:
|
||||
// Recursively call extractValue to grab
|
||||
// the value of all the members of the struct.
|
||||
if recurseLevel <= maxVariableRecurse {
|
||||
fields := make([]string, 0, len(t.Field))
|
||||
for _, field := range t.Field {
|
||||
val, err := thread.extractValueInternal(nil, field.ByteOffset+addr, field.Type, printStructName, recurseLevel+1)
|
||||
val, err := scope.extractValueInternal(nil, field.ByteOffset+addr, field.Type, printStructName, recurseLevel+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -548,19 +540,19 @@ func (thread *Thread) extractValueInternal(instructions []byte, addr int64, typ
|
||||
return "{...}", nil
|
||||
}
|
||||
case *dwarf.ArrayType:
|
||||
return thread.readArray(ptraddress, t, recurseLevel)
|
||||
return scope.readArray(ptraddress, t, recurseLevel)
|
||||
case *dwarf.ComplexType:
|
||||
return thread.readComplex(ptraddress, t.ByteSize)
|
||||
return scope.Thread.readComplex(ptraddress, t.ByteSize)
|
||||
case *dwarf.IntType:
|
||||
return thread.readInt(ptraddress, t.ByteSize)
|
||||
return scope.Thread.readInt(ptraddress, t.ByteSize)
|
||||
case *dwarf.UintType:
|
||||
return thread.readUint(ptraddress, t.ByteSize)
|
||||
return scope.Thread.readUint(ptraddress, t.ByteSize)
|
||||
case *dwarf.FloatType:
|
||||
return thread.readFloat(ptraddress, t.ByteSize)
|
||||
return scope.Thread.readFloat(ptraddress, t.ByteSize)
|
||||
case *dwarf.BoolType:
|
||||
return thread.readBool(ptraddress)
|
||||
return scope.Thread.readBool(ptraddress)
|
||||
case *dwarf.FuncType:
|
||||
return thread.readFunctionPtr(ptraddress)
|
||||
return scope.Thread.readFunctionPtr(ptraddress)
|
||||
case *dwarf.VoidType:
|
||||
return "(void)", nil
|
||||
case *dwarf.UnspecifiedType:
|
||||
@ -601,14 +593,14 @@ func (thread *Thread) readString(addr uintptr) (string, error) {
|
||||
return *(*string)(unsafe.Pointer(&val)), nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel int) (string, error) {
|
||||
func (scope *EvalScope) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel int) (string, error) {
|
||||
var sliceLen, sliceCap int64
|
||||
var arrayAddr uintptr
|
||||
var arrayType dwarf.Type
|
||||
for _, f := range t.Field {
|
||||
switch f.Name {
|
||||
case "array":
|
||||
val, err := thread.readMemory(addr+uintptr(f.ByteOffset), thread.dbp.arch.PtrSize())
|
||||
val, err := scope.Thread.readMemory(addr+uintptr(f.ByteOffset), scope.PtrSize())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -620,7 +612,7 @@ func (thread *Thread) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel
|
||||
}
|
||||
arrayType = ptrType.Type
|
||||
case "len":
|
||||
lstr, err := thread.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
||||
lstr, err := scope.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -629,7 +621,7 @@ func (thread *Thread) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel
|
||||
return "", err
|
||||
}
|
||||
case "cap":
|
||||
cstr, err := thread.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
||||
cstr, err := scope.extractValue(nil, int64(addr+uintptr(f.ByteOffset)), f.Type, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -642,9 +634,9 @@ func (thread *Thread) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel
|
||||
|
||||
stride := arrayType.Size()
|
||||
if _, ok := arrayType.(*dwarf.PtrType); ok {
|
||||
stride = int64(thread.dbp.arch.PtrSize())
|
||||
stride = int64(scope.PtrSize())
|
||||
}
|
||||
vals, err := thread.readArrayValues(arrayAddr, sliceLen, stride, arrayType, recurseLevel)
|
||||
vals, err := scope.readArrayValues(arrayAddr, sliceLen, stride, arrayType, recurseLevel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -652,9 +644,9 @@ func (thread *Thread) readSlice(addr uintptr, t *dwarf.StructType, recurseLevel
|
||||
return fmt.Sprintf("[]%s len: %d, cap: %d, [%s]", arrayType, sliceLen, sliceCap, strings.Join(vals, ",")), nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readArray(addr uintptr, t *dwarf.ArrayType, recurseLevel int) (string, error) {
|
||||
func (scope *EvalScope) readArray(addr uintptr, t *dwarf.ArrayType, recurseLevel int) (string, error) {
|
||||
if t.Count > 0 {
|
||||
vals, err := thread.readArrayValues(addr, t.Count, t.ByteSize/t.Count, t.Type, recurseLevel)
|
||||
vals, err := scope.readArrayValues(addr, t.Count, t.ByteSize/t.Count, t.Type, recurseLevel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -664,7 +656,7 @@ func (thread *Thread) readArray(addr uintptr, t *dwarf.ArrayType, recurseLevel i
|
||||
return fmt.Sprintf("%s []", t), nil
|
||||
}
|
||||
|
||||
func (thread *Thread) readArrayValues(addr uintptr, count int64, stride int64, t dwarf.Type, recurseLevel int) ([]string, error) {
|
||||
func (scope *EvalScope) readArrayValues(addr uintptr, count int64, stride int64, t dwarf.Type, recurseLevel int) ([]string, error) {
|
||||
vals := make([]string, 0)
|
||||
|
||||
for i := int64(0); i < count; i++ {
|
||||
@ -674,7 +666,7 @@ func (thread *Thread) readArrayValues(addr uintptr, count int64, stride int64, t
|
||||
break
|
||||
}
|
||||
|
||||
val, err := thread.extractValueInternal(nil, int64(addr+uintptr(i*stride)), t, false, recurseLevel+1)
|
||||
val, err := scope.extractValueInternal(nil, int64(addr+uintptr(i*stride)), t, false, recurseLevel+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -825,15 +817,10 @@ func (thread *Thread) readFunctionPtr(addr uintptr) (string, error) {
|
||||
}
|
||||
|
||||
// Fetches all variables of a specific type in the current function scope
|
||||
func (thread *Thread) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
||||
pc, err := thread.PC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
||||
reader := scope.DwarfReader()
|
||||
|
||||
reader := thread.dbp.DwarfReader()
|
||||
|
||||
_, err = reader.SeekToFunction(pc)
|
||||
_, err := reader.SeekToFunction(scope.PC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -846,7 +833,7 @@ func (thread *Thread) variablesByTag(tag dwarf.Tag) ([]*Variable, error) {
|
||||
}
|
||||
|
||||
if entry.Tag == tag {
|
||||
val, err := thread.extractVariableFromEntry(entry)
|
||||
val, err := scope.extractVariableFromEntry(entry)
|
||||
if err != nil {
|
||||
// skip variables that we can't parse yet
|
||||
continue
|
||||
|
@ -3,6 +3,7 @@ package proc
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
protest "github.com/derekparker/delve/proc/test"
|
||||
@ -29,6 +30,14 @@ func assertVariable(t *testing.T, variable *Variable, expected varTest) {
|
||||
}
|
||||
}
|
||||
|
||||
func evalVariable(p *Process, symbol string) (*Variable, error) {
|
||||
scope, err := p.CurrentThread.Scope()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return scope.EvalVariable(symbol)
|
||||
}
|
||||
|
||||
const varTestBreakpointLineNumber = 59
|
||||
|
||||
func TestVariableEvaluation(t *testing.T) {
|
||||
@ -83,7 +92,7 @@ func TestVariableEvaluation(t *testing.T) {
|
||||
assertNoError(err, t, "Continue() returned an error")
|
||||
|
||||
for _, tc := range testcases {
|
||||
variable, err := p.EvalVariable(tc.name)
|
||||
variable, err := evalVariable(p, tc.name)
|
||||
if tc.err == nil {
|
||||
assertNoError(err, t, "EvalVariable() returned an error")
|
||||
assertVariable(t, variable, tc)
|
||||
@ -107,10 +116,10 @@ func TestVariableFunctionScoping(t *testing.T) {
|
||||
assertNoError(err, t, "Continue() returned an error")
|
||||
p.ClearBreakpoint(pc)
|
||||
|
||||
_, err = p.EvalVariable("a1")
|
||||
_, err = evalVariable(p, "a1")
|
||||
assertNoError(err, t, "Unable to find variable a1")
|
||||
|
||||
_, err = p.EvalVariable("a2")
|
||||
_, err = evalVariable(p, "a2")
|
||||
assertNoError(err, t, "Unable to find variable a1")
|
||||
|
||||
// Move scopes, a1 exists here by a2 does not
|
||||
@ -122,10 +131,10 @@ func TestVariableFunctionScoping(t *testing.T) {
|
||||
err = p.Continue()
|
||||
assertNoError(err, t, "Continue() returned an error")
|
||||
|
||||
_, err = p.EvalVariable("a1")
|
||||
_, err = evalVariable(p, "a1")
|
||||
assertNoError(err, t, "Unable to find variable a1")
|
||||
|
||||
_, err = p.EvalVariable("a2")
|
||||
_, err = evalVariable(p, "a2")
|
||||
if err == nil {
|
||||
t.Fatalf("Can eval out of scope variable a2")
|
||||
}
|
||||
@ -151,10 +160,10 @@ func (s varArray) Less(i, j int) bool {
|
||||
|
||||
func TestLocalVariables(t *testing.T) {
|
||||
testcases := []struct {
|
||||
fn func(*Thread) ([]*Variable, error)
|
||||
fn func(*EvalScope) ([]*Variable, error)
|
||||
output []varTest
|
||||
}{
|
||||
{(*Thread).LocalVariables,
|
||||
{(*EvalScope).LocalVariables,
|
||||
[]varTest{
|
||||
{"a1", "foofoofoofoofoofoo", "struct string", nil},
|
||||
{"a10", "ofo", "struct string", nil},
|
||||
@ -185,7 +194,7 @@ func TestLocalVariables(t *testing.T) {
|
||||
{"u64", "18446744073709551615", "uint64", nil},
|
||||
{"u8", "255", "uint8", nil},
|
||||
{"up", "5", "uintptr", nil}}},
|
||||
{(*Thread).FunctionArguments,
|
||||
{(*EvalScope).FunctionArguments,
|
||||
[]varTest{
|
||||
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil},
|
||||
{"baz", "bazburzum", "struct string", nil}}},
|
||||
@ -201,7 +210,9 @@ func TestLocalVariables(t *testing.T) {
|
||||
assertNoError(err, t, "Continue() returned an error")
|
||||
|
||||
for _, tc := range testcases {
|
||||
vars, err := tc.fn(p.CurrentThread)
|
||||
scope, err := p.CurrentThread.Scope()
|
||||
assertNoError(err, t, "AsScope()")
|
||||
vars, err := tc.fn(scope)
|
||||
assertNoError(err, t, "LocalVariables() returned an error")
|
||||
|
||||
sort.Sort(varArray(vars))
|
||||
@ -220,8 +231,74 @@ func TestLocalVariables(t *testing.T) {
|
||||
func TestRecursiveStructure(t *testing.T) {
|
||||
withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
v, err := p.EvalVariable("aas")
|
||||
v, err := evalVariable(p, "aas")
|
||||
assertNoError(err, t, "EvalVariable()")
|
||||
t.Logf("v: %v\n", v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFrameEvaluation(t *testing.T) {
|
||||
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
|
||||
_, err := setFunctionBreakpoint(p, "main.stacktraceme")
|
||||
assertNoError(err, t, "setFunctionBreakpoint")
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
|
||||
/**** Testing evaluation on goroutines ****/
|
||||
gs, err := p.GoroutinesInfo()
|
||||
assertNoError(err, t, "GoroutinesInfo")
|
||||
found := make([]bool, 10)
|
||||
for _, g := range gs {
|
||||
frame := -1
|
||||
frames, err := p.GoroutineStacktrace(g, 10)
|
||||
assertNoError(err, t, "GoroutineStacktrace()")
|
||||
for i := range frames {
|
||||
if frames[i].Fn != nil && frames[i].Fn.Name == "main.agoroutine" {
|
||||
frame = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if frame < 0 {
|
||||
t.Logf("Goroutine %d: could not find correct frame", g.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
scope, err := p.ConvertEvalScope(g.Id, frame)
|
||||
assertNoError(err, t, "ConvertEvalScope()")
|
||||
t.Logf("scope = %v", scope)
|
||||
v, err := scope.EvalVariable("i")
|
||||
t.Logf("v = %v", v)
|
||||
if err != nil {
|
||||
t.Logf("Goroutine %d: %v\n", g.Id, err)
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(v.Value)
|
||||
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s)", v.Value))
|
||||
found[i] = true
|
||||
}
|
||||
|
||||
for i := range found {
|
||||
if !found[i] {
|
||||
t.Fatalf("Goroutine %d not found\n", i)
|
||||
}
|
||||
}
|
||||
|
||||
/**** Testing evaluation on frames ****/
|
||||
assertNoError(p.Continue(), t, "Continue() 2")
|
||||
g, err := p.CurrentThread.GetG()
|
||||
assertNoError(err, t, "GetG()")
|
||||
|
||||
for i := 0; i <= 3; i++ {
|
||||
scope, err := p.ConvertEvalScope(g.Id, i+1)
|
||||
assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1))
|
||||
v, err := scope.EvalVariable("n")
|
||||
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
|
||||
n, err := strconv.Atoi(v.Value)
|
||||
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s) on frame %d", v.Value, i+1))
|
||||
t.Logf("frame %d n %d\n", i+1, n)
|
||||
if n != 3-i {
|
||||
t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ type DebuggerState struct {
|
||||
Breakpoint *Breakpoint `json:"breakPoint,omitempty"`
|
||||
// CurrentThread is the currently selected debugger thread.
|
||||
CurrentThread *Thread `json:"currentThread,omitempty"`
|
||||
// SelectedGoroutine is the currently selected goroutine
|
||||
SelectedGoroutine *Goroutine `json:"currentGoroutine,omitempty"`
|
||||
// Information requested by the current breakpoint
|
||||
BreakpointInfo *BreakpointInfo `json:"breakPointInfo,omitrempty"`
|
||||
// Exited indicates whether the debugged process has exited.
|
||||
@ -105,6 +107,9 @@ type DebuggerCommand struct {
|
||||
// ThreadID is used to specify which thread to use with the SwitchThread
|
||||
// command.
|
||||
ThreadID int `json:"threadID,omitempty"`
|
||||
// GoroutineID is used to specify which thread to use with the SwitchGoroutine
|
||||
// command.
|
||||
GoroutineID int `json:"goroutineID,omitempty"`
|
||||
}
|
||||
|
||||
// Informations about the current breakpoint
|
||||
@ -115,6 +120,11 @@ type BreakpointInfo struct {
|
||||
Arguments []Variable `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type EvalScope struct {
|
||||
GoroutineID int
|
||||
Frame int
|
||||
}
|
||||
|
||||
const (
|
||||
// Continue resumes process execution.
|
||||
Continue = "continue"
|
||||
@ -124,6 +134,8 @@ const (
|
||||
Next = "next"
|
||||
// SwitchThread switches the debugger's current thread context.
|
||||
SwitchThread = "switchThread"
|
||||
// SwitchGoroutine switches the debugger's current thread context to the thread running the specified goroutine
|
||||
SwitchGoroutine = "switchGoroutine"
|
||||
// Halt suspends the process.
|
||||
Halt = "halt"
|
||||
)
|
||||
|
@ -27,6 +27,8 @@ type Client interface {
|
||||
Step() (*api.DebuggerState, error)
|
||||
// SwitchThread switches the current thread context.
|
||||
SwitchThread(threadID int) (*api.DebuggerState, error)
|
||||
// SwitchGoroutine switches the current goroutine (and the current thread as well)
|
||||
SwitchGoroutine(goroutineID int) (*api.DebuggerState, error)
|
||||
// Halt suspends the process.
|
||||
Halt() (*api.DebuggerState, error)
|
||||
|
||||
@ -47,20 +49,18 @@ type Client interface {
|
||||
// ListPackageVariables lists all package variables in the context of the current thread.
|
||||
ListPackageVariables(filter string) ([]api.Variable, error)
|
||||
// EvalVariable returns a variable in the context of the current thread.
|
||||
EvalVariable(symbol string) (*api.Variable, error)
|
||||
EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error)
|
||||
// ListPackageVariablesFor lists all package variables in the context of a thread.
|
||||
ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error)
|
||||
// EvalVariableFor returns a variable in the context of the specified thread.
|
||||
EvalVariableFor(threadID int, symbol string) (*api.Variable, error)
|
||||
|
||||
// ListSources lists all source files in the process matching filter.
|
||||
ListSources(filter string) ([]string, error)
|
||||
// ListFunctions lists all functions in the process matching filter.
|
||||
ListFunctions(filter string) ([]string, error)
|
||||
// ListLocals lists all local variables in scope.
|
||||
ListLocalVariables() ([]api.Variable, error)
|
||||
ListLocalVariables(scope api.EvalScope) ([]api.Variable, error)
|
||||
// ListFunctionArgs lists all arguments to the current function.
|
||||
ListFunctionArgs() ([]api.Variable, error)
|
||||
ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error)
|
||||
// ListRegisters lists registers and their values.
|
||||
ListRegisters() (string, error)
|
||||
|
||||
@ -84,5 +84,5 @@ type Client interface {
|
||||
// * <line> returns a location for a line in the current file
|
||||
// * *<address> returns the location corresponding to the specified address
|
||||
// NOTE: this function does not actually set breakpoints.
|
||||
FindLocation(loc string) ([]api.Location, error)
|
||||
FindLocation(scope api.EvalScope, loc string) ([]api.Location, error)
|
||||
}
|
||||
|
@ -106,12 +106,17 @@ func (d *Debugger) Restart() error {
|
||||
|
||||
func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
var (
|
||||
state *api.DebuggerState
|
||||
thread *api.Thread
|
||||
state *api.DebuggerState
|
||||
thread *api.Thread
|
||||
goroutine *api.Goroutine
|
||||
)
|
||||
th := d.process.CurrentThread
|
||||
if th != nil {
|
||||
thread = api.ConvertThread(th)
|
||||
|
||||
if d.process.CurrentThread != nil {
|
||||
thread = api.ConvertThread(d.process.CurrentThread)
|
||||
}
|
||||
|
||||
if d.process.SelectedGoroutine != nil {
|
||||
goroutine = api.ConvertGoroutine(d.process.SelectedGoroutine)
|
||||
}
|
||||
|
||||
var breakpoint *api.Breakpoint
|
||||
@ -121,9 +126,10 @@ func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
}
|
||||
|
||||
state = &api.DebuggerState{
|
||||
Breakpoint: breakpoint,
|
||||
CurrentThread: thread,
|
||||
Exited: d.process.Exited(),
|
||||
Breakpoint: breakpoint,
|
||||
CurrentThread: thread,
|
||||
SelectedGoroutine: goroutine,
|
||||
Exited: d.process.Exited(),
|
||||
}
|
||||
|
||||
return state, nil
|
||||
@ -245,6 +251,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
case api.SwitchThread:
|
||||
log.Printf("switching to thread %d", command.ThreadID)
|
||||
err = d.process.SwitchThread(command.ThreadID)
|
||||
case api.SwitchGoroutine:
|
||||
log.Printf("switching to goroutine %d", command.GoroutineID)
|
||||
err = d.process.SwitchGoroutine(command.GoroutineID)
|
||||
case api.Halt:
|
||||
// RequestManualStop does not invoke any ptrace syscalls, so it's safe to
|
||||
// access the process directly.
|
||||
@ -282,21 +291,25 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
|
||||
bpi.Stacktrace = convertStacktrace(rawlocs)
|
||||
}
|
||||
|
||||
s, err := d.process.CurrentThread.Scope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bp.Variables) > 0 {
|
||||
bpi.Variables = make([]api.Variable, len(bp.Variables))
|
||||
}
|
||||
for i := range bp.Variables {
|
||||
v, err := d.process.CurrentThread.EvalVariable(bp.Variables[i])
|
||||
v, err := s.EvalVariable(bp.Variables[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bpi.Variables[i] = api.ConvertVar(v)
|
||||
}
|
||||
vars, err := d.FunctionArguments(d.process.CurrentThread.Id)
|
||||
vars, err := functionArguments(s)
|
||||
if err == nil {
|
||||
bpi.Arguments = vars
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -345,7 +358,11 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.PackageVariables()
|
||||
scope, err := thread.Scope()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pv, err := scope.PackageVariables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -369,44 +386,48 @@ func (d *Debugger) Registers(threadID int) (string, error) {
|
||||
return regs.String(), err
|
||||
}
|
||||
|
||||
func (d *Debugger) LocalVariables(threadID int) ([]api.Variable, error) {
|
||||
vars := []api.Variable{}
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.LocalVariables()
|
||||
func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pv, err := s.LocalVariables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars := make([]api.Variable, 0, len(pv))
|
||||
for _, v := range pv {
|
||||
vars = append(vars, api.ConvertVar(v))
|
||||
}
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) {
|
||||
vars := []api.Variable{}
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.FunctionArguments()
|
||||
func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return functionArguments(s)
|
||||
}
|
||||
|
||||
func functionArguments(s *proc.EvalScope) ([]api.Variable, error) {
|
||||
pv, err := s.FunctionArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars := make([]api.Variable, 0, len(pv))
|
||||
for _, v := range pv {
|
||||
vars = append(vars, api.ConvertVar(v))
|
||||
}
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := thread.EvalVariable(symbol)
|
||||
v, err := s.EvalVariable(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -427,13 +448,20 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
||||
}
|
||||
|
||||
func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
||||
var rawlocs []proc.Location
|
||||
var rawlocs []proc.Stackframe
|
||||
var err error
|
||||
|
||||
if goroutineId < 0 {
|
||||
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if d.process.SelectedGoroutine != nil {
|
||||
rawlocs, err = d.process.GoroutineStacktrace(d.process.SelectedGoroutine, depth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gs, err := d.process.GoroutinesInfo()
|
||||
@ -458,23 +486,28 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
||||
return convertStacktrace(rawlocs), nil
|
||||
}
|
||||
|
||||
func convertStacktrace(rawlocs []proc.Location) []api.Location {
|
||||
func convertStacktrace(rawlocs []proc.Stackframe) []api.Location {
|
||||
locations := make([]api.Location, 0, len(rawlocs))
|
||||
for i := range rawlocs {
|
||||
rawlocs[i].Line--
|
||||
locations = append(locations, api.ConvertLocation(rawlocs[i]))
|
||||
locations = append(locations, api.ConvertLocation(rawlocs[i].Location))
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
func (d *Debugger) FindLocation(locStr string) ([]api.Location, error) {
|
||||
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
|
||||
loc, err := parseLocationSpec(locStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
locs, err := loc.Find(d, locStr)
|
||||
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
locs, err := loc.Find(d, s, locStr)
|
||||
for i := range locs {
|
||||
file, line, fn := d.process.PCToLine(locs[i].PC)
|
||||
locs[i].File = file
|
||||
|
@ -7,13 +7,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/proc"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
)
|
||||
|
||||
const maxFindLocationCandidates = 5
|
||||
|
||||
type LocationSpec interface {
|
||||
Find(d *Debugger, locStr string) ([]api.Location, error)
|
||||
Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error)
|
||||
}
|
||||
|
||||
type NormalLocationSpec struct {
|
||||
@ -204,7 +205,7 @@ func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (loc *RegexLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
|
||||
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
funcs := d.process.Funcs()
|
||||
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
|
||||
if err != nil {
|
||||
@ -220,7 +221,7 @@ func (loc *RegexLocationSpec) Find(d *Debugger, locStr string) ([]api.Location,
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (loc *AddrLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
|
||||
func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
return []api.Location{{PC: loc.Addr}}, nil
|
||||
}
|
||||
|
||||
@ -247,7 +248,7 @@ func (ale AmbiguousLocationError) Error() string {
|
||||
return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
|
||||
}
|
||||
|
||||
func (loc *NormalLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
|
||||
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
funcs := d.process.Funcs()
|
||||
files := d.process.Sources()
|
||||
|
||||
@ -303,20 +304,26 @@ func (loc *NormalLocationSpec) Find(d *Debugger, locStr string) ([]api.Location,
|
||||
}
|
||||
}
|
||||
|
||||
func (loc *OffsetLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
|
||||
cur, err := d.process.CurrentThread.Location()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
if scope == nil {
|
||||
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
||||
}
|
||||
addr, err := d.process.FindFileLocation(cur.File, cur.Line+loc.Offset)
|
||||
file, line, fn := d.process.PCToLine(scope.PC)
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
addr, err := d.process.FindFileLocation(file, line+loc.Offset)
|
||||
return []api.Location{{PC: addr}}, err
|
||||
}
|
||||
|
||||
func (loc *LineLocationSpec) Find(d *Debugger, locStr string) ([]api.Location, error) {
|
||||
cur, err := d.process.CurrentThread.Location()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
if scope == nil {
|
||||
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
||||
}
|
||||
addr, err := d.process.FindFileLocation(cur.File, loc.Line)
|
||||
file, _, fn := d.process.PCToLine(scope.PC)
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
addr, err := d.process.FindFileLocation(file, loc.Line)
|
||||
return []api.Location{{PC: addr}}, err
|
||||
}
|
||||
|
@ -97,6 +97,16 @@ func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
|
||||
return state, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) SwitchGoroutine(goroutineID int) (*api.DebuggerState, error) {
|
||||
state := new(api.DebuggerState)
|
||||
cmd := &api.DebuggerCommand{
|
||||
Name: api.SwitchGoroutine,
|
||||
GoroutineID: goroutineID,
|
||||
}
|
||||
err := c.call("Command", cmd, state)
|
||||
return state, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Halt() (*api.DebuggerState, error) {
|
||||
state := new(api.DebuggerState)
|
||||
err := c.call("Command", &api.DebuggerCommand{Name: api.Halt}, state)
|
||||
@ -139,15 +149,9 @@ func (c *RPCClient) GetThread(id int) (*api.Thread, error) {
|
||||
return thread, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) EvalVariable(symbol string) (*api.Variable, error) {
|
||||
func (c *RPCClient) EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error) {
|
||||
v := new(api.Variable)
|
||||
err := c.call("EvalSymbol", symbol, v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) EvalVariableFor(threadID int, symbol string) (*api.Variable, error) {
|
||||
v := new(api.Variable)
|
||||
err := c.call("EvalThreadSymbol", ThreadSymbolArgs{threadID, symbol}, v)
|
||||
err := c.call("EvalSymbol", EvalSymbolArgs{scope, symbol}, v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
@ -175,9 +179,9 @@ func (c *RPCClient) ListPackageVariablesFor(threadID int, filter string) ([]api.
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListLocalVariables() ([]api.Variable, error) {
|
||||
func (c *RPCClient) ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) {
|
||||
var vars []api.Variable
|
||||
err := c.call("ListLocalVars", nil, &vars)
|
||||
err := c.call("ListLocalVars", scope, &vars)
|
||||
return vars, err
|
||||
}
|
||||
|
||||
@ -187,9 +191,9 @@ func (c *RPCClient) ListRegisters() (string, error) {
|
||||
return regs, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListFunctionArgs() ([]api.Variable, error) {
|
||||
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) {
|
||||
var vars []api.Variable
|
||||
err := c.call("ListFunctionArgs", nil, &vars)
|
||||
err := c.call("ListFunctionArgs", scope, &vars)
|
||||
return vars, err
|
||||
}
|
||||
|
||||
@ -211,9 +215,9 @@ func (c *RPCClient) AttachedToExistingProcess() bool {
|
||||
return answer
|
||||
}
|
||||
|
||||
func (c *RPCClient) FindLocation(loc string) ([]api.Location, error) {
|
||||
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string) ([]api.Location, error) {
|
||||
var answer []api.Location
|
||||
err := c.call("FindLocation", loc, &answer)
|
||||
err := c.call("FindLocation", FindLocationArgs{scope, loc}, &answer)
|
||||
return answer, err
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,8 @@ func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||
}
|
||||
|
||||
type StacktraceGoroutineArgs struct {
|
||||
Id, Depth int
|
||||
Id int
|
||||
Depth int
|
||||
}
|
||||
|
||||
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Location) error {
|
||||
@ -215,13 +216,8 @@ func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) ListLocalVars(arg interface{}, variables *[]api.Variable) error {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vars, err := s.debugger.LocalVariables(state.CurrentThread.ID)
|
||||
func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable) error {
|
||||
vars, err := s.debugger.LocalVariables(scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -229,13 +225,8 @@ func (s *RPCServer) ListLocalVars(arg interface{}, variables *[]api.Variable) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) ListFunctionArgs(arg interface{}, variables *[]api.Variable) error {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vars, err := s.debugger.FunctionArguments(state.CurrentThread.ID)
|
||||
func (s *RPCServer) ListFunctionArgs(scope api.EvalScope, variables *[]api.Variable) error {
|
||||
vars, err := s.debugger.FunctionArguments(scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -243,32 +234,13 @@ func (s *RPCServer) ListFunctionArgs(arg interface{}, variables *[]api.Variable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) EvalSymbol(symbol string, variable *api.Variable) error {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
current := state.CurrentThread
|
||||
if current == nil {
|
||||
return errors.New("no current thread")
|
||||
}
|
||||
|
||||
v, err := s.debugger.EvalVariableInThread(current.ID, symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*variable = *v
|
||||
return nil
|
||||
}
|
||||
|
||||
type ThreadSymbolArgs struct {
|
||||
Id int
|
||||
type EvalSymbolArgs struct {
|
||||
Scope api.EvalScope
|
||||
Symbol string
|
||||
}
|
||||
|
||||
func (s *RPCServer) EvalThreadSymbol(args *ThreadSymbolArgs, variable *api.Variable) error {
|
||||
v, err := s.debugger.EvalVariableInThread(args.Id, args.Symbol)
|
||||
func (s *RPCServer) EvalSymbol(args EvalSymbolArgs, variable *api.Variable) error {
|
||||
v, err := s.debugger.EvalVariableInScope(args.Scope, args.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -310,8 +282,13 @@ func (c *RPCServer) AttachedToExistingProcess(arg interface{}, answer *bool) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RPCServer) FindLocation(loc string, answer *[]api.Location) error {
|
||||
type FindLocationArgs struct {
|
||||
Scope api.EvalScope
|
||||
Loc string
|
||||
}
|
||||
|
||||
func (c *RPCServer) FindLocation(args FindLocationArgs, answer *[]api.Location) error {
|
||||
var err error
|
||||
*answer, err = c.debugger.FindLocation(loc)
|
||||
*answer, err = c.debugger.FindLocation(args.Scope, args.Loc)
|
||||
return err
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ func TestClientServer_infoLocals(t *testing.T) {
|
||||
if state.Err != nil {
|
||||
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||
}
|
||||
locals, err := c.ListLocalVariables()
|
||||
locals, err := c.ListLocalVariables(api.EvalScope{-1, 0})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
@ -417,7 +417,7 @@ func TestClientServer_infoArgs(t *testing.T) {
|
||||
if regs == "" {
|
||||
t.Fatal("Expected string showing registers values, got empty string")
|
||||
}
|
||||
locals, err := c.ListFunctionArgs()
|
||||
locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
@ -524,7 +524,7 @@ func TestClientServer_traceContinue2(t *testing.T) {
|
||||
}
|
||||
|
||||
func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
|
||||
locs, err := c.FindLocation(loc)
|
||||
locs, err := c.FindLocation(api.EvalScope{-1, 0}, loc)
|
||||
t.Logf("FindLocation(\"%s\") → %v\n", loc, locs)
|
||||
|
||||
if shouldErr {
|
||||
@ -602,7 +602,7 @@ func TestClientServer_FindLocations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServer_EvalVariableFor(t *testing.T) {
|
||||
func TestClientServer_EvalVariable(t *testing.T) {
|
||||
withTestClient("testvariables", t, func(c service.Client) {
|
||||
fp := testProgPath(t, "testvariables")
|
||||
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59})
|
||||
@ -616,7 +616,7 @@ func TestClientServer_EvalVariableFor(t *testing.T) {
|
||||
t.Fatalf("Continue(): %v\n", state.Err)
|
||||
}
|
||||
|
||||
var1, err := c.EvalVariable("a1")
|
||||
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
|
||||
if err != nil {
|
||||
t.Fatalf("EvalVariable(): %v", err)
|
||||
}
|
||||
@ -626,16 +626,5 @@ func TestClientServer_EvalVariableFor(t *testing.T) {
|
||||
if var1.Value != "foofoofoofoofoofoo" {
|
||||
t.Fatalf("Wrong variable value (EvalVariable)", var1.Value)
|
||||
}
|
||||
|
||||
var2, err := c.EvalVariableFor(state.CurrentThread.ID, "a1")
|
||||
if err != nil {
|
||||
t.Fatalf("EvalVariableFor(): %v", err)
|
||||
}
|
||||
|
||||
t.Logf("var2: <%s>", var2.Value)
|
||||
|
||||
if var2.Value != var1.Value {
|
||||
t.Fatalf("Wrong variable value (EvalVariableFor)", var2.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ import (
|
||||
)
|
||||
|
||||
type cmdfunc func(client service.Client, args ...string) error
|
||||
type scopedCmdfunc func(client service.Client, scope api.EvalScope, args ...string) error
|
||||
|
||||
type filteringFunc func(client service.Client, filter string) ([]string, error)
|
||||
type scopedFilteringFunc func(client service.Client, scope api.EvalScope, filter string) ([]string, error)
|
||||
|
||||
type command struct {
|
||||
aliases []string
|
||||
@ -59,17 +63,19 @@ func DebugCommands(client service.Client) *Commands {
|
||||
{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
|
||||
{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "Deletes all breakpoints."},
|
||||
{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "Print out info for every goroutine."},
|
||||
{aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."},
|
||||
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
|
||||
{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."},
|
||||
{aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."},
|
||||
{aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."},
|
||||
{aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."},
|
||||
{aliases: []string{"args"}, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."},
|
||||
{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."},
|
||||
{aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."},
|
||||
{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(g0f0filter(locals)), helpMsg: "Print function locals, optionally filtered by a regexp."},
|
||||
{aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."},
|
||||
{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
|
||||
{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
|
||||
{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth> [<goroutine id>]]. Prints stack."},
|
||||
{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>. Show source around current point or provided linespec."},
|
||||
{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
|
||||
}
|
||||
|
||||
return c
|
||||
@ -166,7 +172,7 @@ func threads(client service.Client, args ...string) error {
|
||||
prefix, th.ID, th.PC, shortenFilePath(th.File),
|
||||
th.Line, th.Function.Name)
|
||||
} else {
|
||||
fmt.Printf("%sThread %d at %s:%d\n", prefix, th.ID, shortenFilePath(th.File), th.Line)
|
||||
fmt.Printf("%sThread %s\n", prefix, formatThread(th))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -202,18 +208,130 @@ func thread(client service.Client, args ...string) error {
|
||||
}
|
||||
|
||||
func goroutines(client service.Client, args ...string) error {
|
||||
state, err := client.GetState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gs, err := client.ListGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[%d goroutines]\n", len(gs))
|
||||
for _, g := range gs {
|
||||
fmt.Printf("Goroutine %s\n", formatGoroutine(g))
|
||||
prefix := " "
|
||||
if g.ID == state.SelectedGoroutine.ID {
|
||||
prefix = "* "
|
||||
}
|
||||
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func goroutine(client service.Client, args ...string) error {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return printscope(client)
|
||||
|
||||
case 1:
|
||||
gid, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldState, err := client.GetState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newState, err := client.SwitchGoroutine(gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return scopePrefix(client, "goroutine", args...)
|
||||
}
|
||||
}
|
||||
|
||||
func frame(client service.Client, args ...string) error {
|
||||
return scopePrefix(client, "frame", args...)
|
||||
}
|
||||
|
||||
func scopePrefix(client service.Client, cmdname string, pargs ...string) error {
|
||||
fullargs := make([]string, 0, len(pargs)+1)
|
||||
fullargs = append(fullargs, cmdname)
|
||||
fullargs = append(fullargs, pargs...)
|
||||
|
||||
scope := api.EvalScope{-1, 0}
|
||||
lastcmd := ""
|
||||
|
||||
callFilterSortAndOutput := func(fn scopedFilteringFunc, fnargs []string) error {
|
||||
outfn := filterSortAndOutput(func(client service.Client, filter string) ([]string, error) {
|
||||
return fn(client, scope, filter)
|
||||
})
|
||||
return outfn(client, fnargs...)
|
||||
}
|
||||
|
||||
for i := 0; i < len(fullargs); i++ {
|
||||
lastcmd = fullargs[i]
|
||||
switch fullargs[i] {
|
||||
case "goroutine":
|
||||
if i+1 >= len(fullargs) {
|
||||
return fmt.Errorf("goroutine command needs an argument")
|
||||
}
|
||||
n, err := strconv.Atoi(fullargs[i+1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid argument to goroutine, expected integer")
|
||||
}
|
||||
scope.GoroutineID = int(n)
|
||||
i++
|
||||
case "frame":
|
||||
if i+1 >= len(fullargs) {
|
||||
return fmt.Errorf("frame command needs an argument")
|
||||
}
|
||||
n, err := strconv.Atoi(fullargs[i+1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid argument to frame, expected integer")
|
||||
}
|
||||
scope.Frame = int(n)
|
||||
i++
|
||||
case "locals":
|
||||
return callFilterSortAndOutput(locals, fullargs[i+1:])
|
||||
case "args":
|
||||
return callFilterSortAndOutput(args, fullargs[i+1:])
|
||||
case "print", "p":
|
||||
return printVar(client, scope, fullargs[i+1:]...)
|
||||
default:
|
||||
return fmt.Errorf("unknown command %s", fullargs[i])
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("no command passed to %s", lastcmd)
|
||||
}
|
||||
|
||||
func printscope(client service.Client) error {
|
||||
state, err := client.GetState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Thread %s\nGoroutine %s\n", formatThread(state.CurrentThread), formatGoroutine(state.SelectedGoroutine))
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatThread(th *api.Thread) string {
|
||||
if th == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("%d at %s:%d", th.ID, shortenFilePath(th.File), th.Line)
|
||||
}
|
||||
|
||||
func formatGoroutine(g *api.Goroutine) string {
|
||||
if g == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
fname := ""
|
||||
if g.Function != nil {
|
||||
fname = g.Function.Name
|
||||
@ -349,7 +467,7 @@ func setBreakpoint(client service.Client, tracepoint bool, args ...string) error
|
||||
}
|
||||
|
||||
requestedBp.Tracepoint = tracepoint
|
||||
locs, err := client.FindLocation(args[0])
|
||||
locs, err := client.FindLocation(api.EvalScope{-1, 0}, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -378,11 +496,23 @@ func tracepoint(client service.Client, args ...string) error {
|
||||
return setBreakpoint(client, true, args...)
|
||||
}
|
||||
|
||||
func printVar(client service.Client, args ...string) error {
|
||||
func g0f0(fn scopedCmdfunc) cmdfunc {
|
||||
return func(client service.Client, args ...string) error {
|
||||
return fn(client, api.EvalScope{-1, 0}, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func g0f0filter(fn scopedFilteringFunc) filteringFunc {
|
||||
return func(client service.Client, filter string) ([]string, error) {
|
||||
return fn(client, api.EvalScope{-1, 0}, filter)
|
||||
}
|
||||
}
|
||||
|
||||
func printVar(client service.Client, scope api.EvalScope, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
val, err := client.EvalVariable(args[0])
|
||||
val, err := client.EvalVariable(scope, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -413,16 +543,16 @@ func funcs(client service.Client, filter string) ([]string, error) {
|
||||
return client.ListFunctions(filter)
|
||||
}
|
||||
|
||||
func args(client service.Client, filter string) ([]string, error) {
|
||||
vars, err := client.ListFunctionArgs()
|
||||
func args(client service.Client, scope api.EvalScope, filter string) ([]string, error) {
|
||||
vars, err := client.ListFunctionArgs(scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filterVariables(vars, filter), nil
|
||||
}
|
||||
|
||||
func locals(client service.Client, filter string) ([]string, error) {
|
||||
locals, err := client.ListLocalVariables()
|
||||
func locals(client service.Client, scope api.EvalScope, filter string) ([]string, error) {
|
||||
locals, err := client.ListLocalVariables(scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -446,7 +576,7 @@ func regs(client service.Client, args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterSortAndOutput(fn func(client service.Client, filter string) ([]string, error)) cmdfunc {
|
||||
func filterSortAndOutput(fn filteringFunc) cmdfunc {
|
||||
return func(client service.Client, args ...string) error {
|
||||
var filter string
|
||||
if len(args) == 1 {
|
||||
@ -510,7 +640,7 @@ func listCommand(client service.Client, args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
locs, err := client.FindLocation(args[0])
|
||||
locs, err := client.FindLocation(api.EvalScope{-1, 0}, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user