Variable evaluation on arbitrary (goroutine, frame) pair.

This commit is contained in:
aarzilli 2015-08-28 22:06:29 +02:00 committed by Hashrocket Workstation
parent 7206267772
commit c6ebd80905
18 changed files with 641 additions and 311 deletions

@ -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

@ -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
}