Improve chan / goroutine coordination

* Properly find next source line for goroutines blocked in chanrecv
* Refactor breakpoint clearing
* Refactor temp breakpoint setting
This commit is contained in:
Derek Parker 2015-04-19 17:11:33 -05:00
parent ab5aec4365
commit 58db8322ef
13 changed files with 502 additions and 225 deletions

@ -16,7 +16,7 @@ endif
test:
ifeq "$(UNAME)" "Darwin"
go test $(PREFIX)/command $(PREFIX)/dwarf/frame $(PREFIX)/dwarf/op $(PREFIX)/dwarf/util $(PREFIX)/source $(PREFIX)/dwarf/line
cd proctl && go test -c $(PREFIX)/proctl && codesign -s $(CERT) ./proctl.test && ./proctl.test && rm ./proctl.test
cd proctl && go test -c $(PREFIX)/proctl && codesign -s $(CERT) ./proctl.test && ./proctl.test -test.v && rm ./proctl.test
else
go test ./...
go test -v ./...
endif

@ -1,8 +1,8 @@
// fix lines
package main
import (
"fmt"
"runtime"
"time"
)
@ -35,9 +35,14 @@ func testnext() {
}
func main() {
runtime.LockOSThread()
for {
testnext()
fmt.Println("foo")
}
d := make(chan int)
testnext()
go testgoroutine(9, d)
<-d
fmt.Println("done")
}
// fix line
func testgoroutine(foo int, d chan int) {
d <- foo
}

@ -57,3 +57,9 @@ func endlesslooptest() {
fmt.Println("foo")
}
}
func decltest() {
var foo = "bar"
var baz = 9
fmt.Println(foo, baz)
}

@ -159,7 +159,7 @@ func (frame *FrameContext) ExecuteUntilPC(instructions []byte) {
// We only need to execute the instructions until
// ctx.loc > ctx.addess (which is the address we
// are currently at in the traced process).
for frame.address > frame.loc && frame.buf.Len() > 0 {
for frame.address >= frame.loc && frame.buf.Len() > 0 {
executeDwarfInstruction(frame)
}
}

@ -16,12 +16,27 @@ type BreakPoint struct {
OriginalData []byte
ID int
Temp bool
hardware bool
reg int
}
func (bp *BreakPoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d", bp.ID, bp.Addr, bp.File, bp.Line)
}
func (bp *BreakPoint) Clear(thread *ThreadContext) (*BreakPoint, error) {
if bp.hardware {
if err := clearHardwareBreakpoint(bp.reg, thread.Id); err != nil {
return nil, err
}
return bp, nil
}
if _, err := writeMemory(thread, uintptr(bp.Addr), bp.OriginalData); err != nil {
return nil, fmt.Errorf("could not clear breakpoint %s", err)
}
return bp, nil
}
// Returned when trying to set a breakpoint at
// an address that already has a breakpoint set for it.
type BreakPointExistsError struct {
@ -59,7 +74,7 @@ func (dbp *DebuggedProcess) BreakpointExists(addr uint64) bool {
return ok
}
func (dbp *DebuggedProcess) newBreakpoint(fn, f string, l int, addr uint64, data []byte) *BreakPoint {
func (dbp *DebuggedProcess) newBreakpoint(fn, f string, l int, addr uint64, data []byte, temp bool) *BreakPoint {
dbp.breakpointIDCounter++
return &BreakPoint{
FunctionName: fn,
@ -68,10 +83,18 @@ func (dbp *DebuggedProcess) newBreakpoint(fn, f string, l int, addr uint64, data
Addr: addr,
OriginalData: data,
ID: dbp.breakpointIDCounter,
Temp: temp,
}
}
func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64) (*BreakPoint, error) {
func (dbp *DebuggedProcess) newHardwareBreakpoint(fn, f string, l int, addr uint64, data []byte, temp bool, reg int) *BreakPoint {
bp := dbp.newBreakpoint(fn, f, l, addr, data, temp)
bp.hardware = true
bp.reg = reg
return bp
}
func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64, temp bool) (*BreakPoint, error) {
var f, l, fn = dbp.goSymTable.PCToLine(uint64(addr))
if fn == nil {
return nil, InvalidAddressError{address: addr}
@ -89,7 +112,7 @@ func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64) (*BreakPoint, er
if err := setHardwareBreakpoint(i, tid, addr); err != nil {
return nil, fmt.Errorf("could not set hardware breakpoint: %v", err)
}
dbp.HWBreakPoints[i] = dbp.newBreakpoint(fn.Name, f, l, addr, nil)
dbp.HWBreakPoints[i] = dbp.newHardwareBreakpoint(fn.Name, f, l, addr, nil, temp, i)
return dbp.HWBreakPoints[i], nil
}
}
@ -103,6 +126,33 @@ func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64) (*BreakPoint, er
if _, err := writeMemory(thread, uintptr(addr), []byte{0xCC}); err != nil {
return nil, err
}
dbp.BreakPoints[addr] = dbp.newBreakpoint(fn.Name, f, l, addr, originalData)
dbp.BreakPoints[addr] = dbp.newBreakpoint(fn.Name, f, l, addr, originalData, temp)
return dbp.BreakPoints[addr], nil
}
func (dbp *DebuggedProcess) clearBreakpoint(tid int, addr uint64) (*BreakPoint, error) {
thread := dbp.Threads[tid]
// Check for hardware breakpoint
for i, bp := range dbp.HWBreakPoints {
if bp == nil {
continue
}
if bp.Addr == addr {
_, err := bp.Clear(thread)
if err != nil {
return nil, err
}
dbp.HWBreakPoints[i] = nil
return bp, nil
}
}
// Check for software breakpoint
if bp, ok := dbp.BreakPoints[addr]; ok {
if _, err := bp.Clear(thread); err != nil {
return nil, err
}
delete(dbp.BreakPoints, addr)
return bp, nil
}
return nil, fmt.Errorf("no breakpoint at %#v", addr)
}

@ -205,7 +205,11 @@ func (dbp *DebuggedProcess) RequestManualStop() error {
// will set a hardware breakpoint. Otherwise we fall back to software
// breakpoints, which are a bit more work for us.
func (dbp *DebuggedProcess) Break(addr uint64) (*BreakPoint, error) {
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr)
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, false)
}
func (dbp *DebuggedProcess) TempBreak(addr uint64) (*BreakPoint, error) {
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, true)
}
// Sets a breakpoint by location string (function, file+line, address)
@ -219,30 +223,7 @@ func (dbp *DebuggedProcess) BreakByLocation(loc string) (*BreakPoint, error) {
// Clears a breakpoint in the current thread.
func (dbp *DebuggedProcess) Clear(addr uint64) (*BreakPoint, error) {
tid := dbp.CurrentThread.Id
// Check for hardware breakpoint
for i, bp := range dbp.HWBreakPoints {
if bp == nil {
continue
}
if bp.Addr == addr {
dbp.HWBreakPoints[i] = nil
if err := clearHardwareBreakpoint(i, tid); err != nil {
return nil, err
}
return bp, nil
}
}
// Check for software breakpoint
if bp, ok := dbp.BreakPoints[addr]; ok {
thread := dbp.Threads[tid]
if _, err := writeMemory(thread, uintptr(bp.Addr), bp.OriginalData); err != nil {
return nil, fmt.Errorf("could not clear breakpoint %s", err)
}
delete(dbp.BreakPoints, addr)
return bp, nil
}
return nil, fmt.Errorf("no breakpoint at %#v", addr)
return dbp.clearBreakpoint(dbp.CurrentThread.Id, addr)
}
// Clears a breakpoint by location (function, file+line, address, breakpoint id)
@ -261,15 +242,40 @@ func (dbp *DebuggedProcess) Status() *sys.WaitStatus {
// Step over function calls.
func (dbp *DebuggedProcess) Next() error {
if err := dbp.setChanRecvBreakpoints(); err != nil {
return err
}
return dbp.run(dbp.next)
}
func (dbp *DebuggedProcess) setChanRecvBreakpoints() error {
allg, err := dbp.GoroutinesInfo()
if err != nil {
return err
}
for _, g := range allg {
if g.ChanRecvBlocked() {
ret, err := g.chanRecvReturnAddr(dbp)
if err != nil {
return err
}
if _, err = dbp.TempBreak(ret); err != nil {
return err
}
}
}
return nil
}
func (dbp *DebuggedProcess) next() error {
defer dbp.clearTempBreakpoints()
curg, err := dbp.CurrentThread.curG()
if err != nil {
return err
}
defer dbp.clearTempBreakpoints()
var goroutineExiting bool
for _, th := range dbp.Threads {
if th.blocked() { // Continue threads that aren't running go code.
if err := th.Continue(); err != nil {
@ -277,7 +283,16 @@ func (dbp *DebuggedProcess) next() error {
}
continue
}
if err := th.Next(); err != nil {
if err = th.Next(); err != nil {
if err, ok := err.(GoroutineExitingError); ok {
if err.goid == curg.Id {
goroutineExiting = true
}
if err := th.Continue(); err != nil {
return err
}
continue
}
return err
}
}
@ -287,11 +302,19 @@ func (dbp *DebuggedProcess) next() error {
if err != nil {
return err
}
// Check if we've hit a breakpoint.
if goroutineExiting {
break
}
if dbp.CurrentBreakpoint != nil {
if err = thread.clearTempBreakpoint(dbp.CurrentBreakpoint.Addr); err != nil {
bp, err := dbp.Clear(dbp.CurrentBreakpoint.Addr)
if err != nil {
return err
}
if !bp.hardware {
if err = thread.SetPC(bp.Addr); err != nil {
return err
}
}
}
// Grab the current goroutine for this thread.
tg, err := thread.curG()
@ -398,7 +421,7 @@ func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) {
allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ {
g, err := parseG(dbp, allgptr+(i*uint64(ptrsize)), reader)
g, err := parseG(dbp.CurrentThread, allgptr+(i*uint64(ptrsize)), reader)
if err != nil {
return nil, err
}
@ -432,7 +455,7 @@ func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
return dbp.CurrentThread.EvalSymbol(name)
}
func (dbp *DebuggedProcess) CallFn(name string, fn func(*ThreadContext) error) error {
func (dbp *DebuggedProcess) CallFn(name string, fn func() error) error {
return dbp.CurrentThread.CallFn(name, fn)
}

@ -219,45 +219,18 @@ func TestClearBreakPoint(t *testing.T) {
})
}
func TestNext(t *testing.T) {
var (
err error
executablePath = "../_fixtures/testnextprog"
)
testcases := []struct {
begin, end int
}{
{19, 20},
{20, 23},
{23, 24},
{24, 26},
{26, 31},
{31, 23},
{23, 24},
{24, 26},
{26, 31},
{31, 23},
{23, 24},
{24, 26},
{26, 27},
{27, 34},
{34, 41},
{41, 40},
{40, 41},
}
fp, err := filepath.Abs("../_fixtures/testnextprog.go")
if err != nil {
t.Fatal(err)
}
type nextTest struct {
begin, end int
}
func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
var executablePath = "../_fixtures/testnextprog"
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.goSymTable.LineToPC(fp, testcases[0].begin)
_, err := p.Break(pc)
bp, err := p.BreakByLocation(initialLocation)
assertNoError(err, t, "Break()")
assertNoError(p.Continue(), t, "Continue()")
p.Clear(pc)
p.Clear(bp.Addr)
p.CurrentThread.SetPC(bp.Addr)
f, ln := currentLineNumber(p, t)
for _, tc := range testcases {
@ -273,9 +246,8 @@ func TestNext(t *testing.T) {
}
}
p.Clear(pc)
if len(p.BreakPoints) != 0 {
t.Fatal("Not all breakpoints were cleaned up", len(p.HWBreakPoints))
t.Fatal("Not all breakpoints were cleaned up", len(p.BreakPoints))
}
for _, bp := range p.HWBreakPoints {
if bp != nil {
@ -285,6 +257,43 @@ func TestNext(t *testing.T) {
})
}
func TestNextGeneral(t *testing.T) {
testcases := []nextTest{
{17, 19},
{19, 20},
{20, 23},
{23, 24},
{24, 26},
{26, 31},
{31, 23},
{23, 24},
{24, 26},
{26, 31},
{31, 23},
{23, 24},
{24, 26},
{26, 27},
{27, 34},
}
testnext(testcases, "main.testnext", t)
}
func TestNextGoroutine(t *testing.T) {
testcases := []nextTest{
{46, 47},
{47, 42},
}
testnext(testcases, "main.testgoroutine", t)
}
func TestNextFunctionReturn(t *testing.T) {
testcases := []nextTest{
{13, 14},
{14, 35},
}
testnext(testcases, "main.helloworld", t)
}
func TestFindReturnAddress(t *testing.T) {
var testfile, _ = filepath.Abs("../_fixtures/testnextprog")
@ -331,10 +340,9 @@ func TestFindReturnAddress(t *testing.T) {
readMemory(p.CurrentThread, uintptr(addr), data)
addr = binary.LittleEndian.Uint64(data)
linuxExpected := uint64(0x400fbc)
darwinExpected := uint64(0x23bc)
if addr != linuxExpected && addr != darwinExpected {
t.Fatalf("return address not found correctly, expected (linux) %#v or (darwin) %#v got %#v", linuxExpected, darwinExpected, addr)
_, l, _ := p.goSymTable.PCToLine(addr)
if l != 40 {
t.Fatalf("return address not found correctly, expected line 40")
}
})
}
@ -409,7 +417,8 @@ func TestFunctionCall(t *testing.T) {
if fn.Name != "main.main" {
t.Fatal("Program stopped at incorrect place")
}
if err = p.CallFn("runtime.getg", func(th *ThreadContext) error {
if err = p.CallFn("runtime.getg", func() error {
th := p.CurrentThread
pc, err := th.CurrentPC()
if err != nil {
t.Fatal(err)

53
proctl/stack.go Normal file

@ -0,0 +1,53 @@
package proctl
import (
"debug/gosym"
"encoding/binary"
)
type stackLocation struct {
addr uint64
file string
line int
fn *gosym.Func
}
// Takes an offset from RSP and returns the address of the
// instruction the currect function is going to return to.
func (thread *ThreadContext) ReturnAddress() (uint64, error) {
regs, err := thread.Registers()
if err != nil {
return 0, err
}
locations, err := thread.Process.stacktrace(regs.PC(), regs.SP(), 1)
if err != nil {
return 0, err
}
return locations[0].addr, nil
}
func (dbp *DebuggedProcess) stacktrace(pc, sp uint64, depth int) ([]stackLocation, error) {
var (
ret = pc
data = make([]byte, 8)
btoffset int64
locations []stackLocation
retaddr uintptr
)
for i := int64(0); i < int64(depth); i++ {
fde, err := dbp.frameEntries.FDEForPC(ret)
if err != nil {
return nil, err
}
btoffset += fde.ReturnAddressOffset(ret)
retaddr = uintptr(int64(sp) + btoffset + (i * 8))
_, err = readMemory(dbp.CurrentThread, retaddr, data)
if err != nil {
return nil, err
}
ret = binary.LittleEndian.Uint64(data)
f, l, fn := dbp.goSymTable.PCToLine(ret)
locations = append(locations, stackLocation{addr: ret, file: f, line: l, fn: fn})
}
return locations, nil
}

@ -1,9 +1,10 @@
package proctl
import (
"encoding/binary"
"fmt"
"path/filepath"
"github.com/derekparker/delve/dwarf/frame"
sys "golang.org/x/sys/unix"
)
@ -16,6 +17,7 @@ type ThreadContext struct {
Id int
Process *DebuggedProcess
Status *sys.WaitStatus
running bool
os *OSSpecificDetails
}
@ -65,7 +67,6 @@ func (thread *ThreadContext) Continue() error {
return fmt.Errorf("could not step %s", err)
}
}
return thread.resume()
}
@ -108,14 +109,14 @@ func (thread *ThreadContext) Step() (err error) {
}
// Call a function named `name`. This is currently _NOT_ safe.
func (thread *ThreadContext) CallFn(name string, fn func(*ThreadContext) error) error {
func (thread *ThreadContext) CallFn(name string, fn func() error) error {
f := thread.Process.goSymTable.LookupFunc(name)
if f == nil {
return fmt.Errorf("could not find function %s", name)
}
// Set breakpoint at the end of the function (before it returns).
bp, err := thread.Process.Break(f.End - 2)
bp, err := thread.Break(f.End - 2)
if err != nil {
return err
}
@ -134,7 +135,17 @@ func (thread *ThreadContext) CallFn(name string, fn func(*ThreadContext) error)
if _, err = trapWait(thread.Process, -1); err != nil {
return err
}
return fn(thread)
return fn()
}
// Set breakpoint using this thread.
func (thread *ThreadContext) Break(addr uint64) (*BreakPoint, error) {
return thread.Process.setBreakpoint(thread.Id, addr, false)
}
// Clear breakpoint using this thread.
func (thread *ThreadContext) Clear(addr uint64) (*BreakPoint, error) {
return thread.Process.clearBreakpoint(thread.Id, addr)
}
// Step to next source line.
@ -166,34 +177,88 @@ func (thread *ThreadContext) Next() (err error) {
// Get current file/line.
f, l, _ := thread.Process.goSymTable.PCToLine(curpc)
if filepath.Ext(f) == ".go" {
if err = thread.next(curpc, fde, f, l); err != nil {
return err
}
} else {
if err = thread.cnext(curpc, fde, f, l); err != nil {
return err
}
}
return thread.Continue()
}
// Find any line we could potentially get to.
lines, err := thread.Process.ast.NextLines(f, l)
// Go routine is exiting.
type GoroutineExitingError struct {
goid int
}
func (ge GoroutineExitingError) Error() string {
return fmt.Sprintf("goroutine %d is exiting", ge.goid)
}
// This version of next uses the AST from the current source file to figure out all of the potential source lines
// we could end up at.
func (thread *ThreadContext) next(curpc uint64, fde *frame.FrameDescriptionEntry, file string, line int) error {
lines, err := thread.Process.ast.NextLines(file, line)
if err != nil {
return err
}
// Set a breakpoint at every line reachable from our location.
for _, l := range lines {
pcs := thread.Process.lineInfo.AllPCsForFileLine(f, l)
for _, pc := range pcs {
if pc == curpc {
continue
}
if !fde.Cover(pc) {
pc = thread.ReturnAddressFromOffset(fde.ReturnAddressOffset(curpc))
}
bp, err := thread.Process.Break(pc)
if err != nil {
if err, ok := err.(BreakPointExistsError); !ok {
return err
}
continue
}
bp.Temp = true
ret, err := thread.ReturnAddress()
if err != nil {
return err
}
pcs := make([]uint64, 0, len(lines))
for i := range lines {
pcs = append(pcs, thread.Process.lineInfo.AllPCsForFileLine(file, lines[i])...)
}
var covered bool
for i := range pcs {
if fde.Cover(pcs[i]) {
covered = true
break
}
}
return thread.Continue()
if !covered {
fn := thread.Process.goSymTable.PCToFunc(ret)
if fn != nil && fn.Name == "runtime.goexit" {
g, err := thread.curG()
if err != nil {
return err
}
return GoroutineExitingError{goid: g.Id}
}
pcs = append(pcs, ret)
}
// Set a breakpoint at every line reachable from our location.
for _, pc := range pcs {
// Do not set breakpoint at our current location.
if pc == curpc {
continue
}
// If the PC is not covered by our frame, set breakpoint at return address.
if !fde.Cover(pc) {
pc = ret
}
if _, err := thread.Process.TempBreak(pc); err != nil {
if err, ok := err.(BreakPointExistsError); !ok {
return err
}
}
}
return nil
}
func (thread *ThreadContext) cnext(curpc uint64, fde *frame.FrameDescriptionEntry, file string, line int) error {
// TODO(dp) We are not in a Go source file, we should fall back on DWARF line information
// and use that to set breakpoints.
return nil
}
func (thread *ThreadContext) SetPC(pc uint64) error {
@ -204,47 +269,15 @@ func (thread *ThreadContext) SetPC(pc uint64) error {
return regs.SetPC(thread, pc)
}
// Takes an offset from RSP and returns the address of the
// instruction the currect function is going to return to.
func (thread *ThreadContext) ReturnAddressFromOffset(offset int64) uint64 {
regs, err := thread.Registers()
if err != nil {
panic("Could not obtain register values")
}
retaddr := int64(regs.SP()) + offset
data := make([]byte, 8)
readMemory(thread, uintptr(retaddr), data)
return binary.LittleEndian.Uint64(data)
}
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
clearbp := func(bp *BreakPoint) error {
if _, err := thread.Process.Clear(bp.Addr); err != nil {
return err
}
return thread.SetPC(bp.Addr)
}
for _, bp := range thread.Process.HWBreakPoints {
if bp != nil && bp.Temp && bp.Addr == pc {
return clearbp(bp)
}
}
if bp, ok := thread.Process.BreakPoints[pc]; ok && bp.Temp {
return clearbp(bp)
}
return nil
}
func (thread *ThreadContext) curG() (*G, error) {
var g *G
err := thread.CallFn("runtime.getg", func(t *ThreadContext) error {
regs, err := t.Registers()
err := thread.CallFn("runtime.getg", func() error {
regs, err := thread.Registers()
if err != nil {
return err
}
reader := t.Process.dwarf.Reader()
g, err = parseG(t.Process, regs.SP()+uint64(ptrsize), reader)
reader := thread.Process.dwarf.Reader()
g, err = parseG(thread, regs.SP()+uint64(ptrsize), reader)
return err
})
return g, err

@ -16,7 +16,7 @@ func (t *ThreadContext) Halt() error {
var kret C.kern_return_t
kret = C.thread_suspend(t.os.thread_act)
if kret != C.KERN_SUCCESS {
return fmt.Errorf("could not suspend task %d", t.Id)
return fmt.Errorf("could not suspend thread %d", t.Id)
}
return nil
}
@ -50,7 +50,7 @@ func (t *ThreadContext) blocked() bool {
// TODO(dp) cache the func pc to remove this lookup
pc, _ := t.CurrentPC()
fn := t.Process.goSymTable.PCToFunc(pc)
if fn != nil && ((fn.Name == "runtime.mach_semaphore_wait") || (fn.Name == "runtime.usleep")) {
if fn != nil && (fn.Name == "runtime.mach_semaphore_wait") {
return true
}
return false

@ -17,6 +17,9 @@ import (
const (
maxVariableRecurse = 1
maxArrayValues = 64
ChanRecv = "chan receive"
ChanSend = "chan send"
)
type Variable struct {
@ -33,11 +36,28 @@ type M struct {
}
type G struct {
Id int
PC uint64
File string
Line int
Func *gosym.Func
Id int
PC uint64
SP uint64
GoPC uint64
File string
Line int
Func *gosym.Func
WaitReason string
}
func (g *G) ChanRecvBlocked() bool {
return g.WaitReason == ChanRecv
}
// chanRecvReturnAddr returns the address of the return from a channel read.
func (g *G) chanRecvReturnAddr(dbp *DebuggedProcess) (uint64, error) {
locs, err := dbp.stacktrace(g.PC, g.SP, 4)
if err != nil {
return 0, err
}
topLoc := locs[len(locs)-1]
return topLoc.addr, nil
}
const ptrsize uintptr = unsafe.Sizeof(int(1))
@ -209,40 +229,80 @@ func parseAllMPtr(dbp *DebuggedProcess, reader *dwarf.Reader) (uint64, error) {
return uint64(addr), nil
}
func parseG(dbp *DebuggedProcess, addr uint64, reader *dwarf.Reader) (*G, error) {
gaddrbytes, err := dbp.CurrentThread.readMemory(uintptr(addr), ptrsize)
type NoGError struct {
tid int
}
func (ng NoGError) Error() string {
return fmt.Sprintf("no G executing on thread %d", ng.tid)
}
func parseG(thread *ThreadContext, addr uint64, reader *dwarf.Reader) (*G, error) {
gaddrbytes, err := thread.readMemory(uintptr(addr), ptrsize)
if err != nil {
return nil, fmt.Errorf("error derefing *G %s", err)
}
initialInstructions := append([]byte{op.DW_OP_addr}, gaddrbytes...)
gaddr := binary.LittleEndian.Uint64(gaddrbytes)
if gaddr == 0 {
return nil, NoGError{tid: thread.Id}
}
reader.Seek(0)
goidaddr, err := offsetFor(dbp, "goid", reader, initialInstructions)
goidaddr, err := offsetFor("goid", reader, initialInstructions)
if err != nil {
return nil, err
}
reader.Seek(0)
schedaddr, err := offsetFor(dbp, "sched", reader, initialInstructions)
if err != nil {
return nil, err
}
goidbytes, err := dbp.CurrentThread.readMemory(uintptr(goidaddr), ptrsize)
goidbytes, err := thread.readMemory(uintptr(goidaddr), ptrsize)
if err != nil {
return nil, fmt.Errorf("error reading goid %s", err)
}
schedbytes, err := dbp.CurrentThread.readMemory(uintptr(schedaddr+uint64(ptrsize)), ptrsize)
reader.Seek(0)
gopcaddr, err := offsetFor("gopc", reader, initialInstructions)
if err != nil {
return nil, fmt.Errorf("error reading sched %s", err)
return nil, err
}
gopc := binary.LittleEndian.Uint64(schedbytes)
f, l, fn := dbp.goSymTable.PCToLine(gopc)
gopcbytes, err := thread.readMemory(uintptr(gopcaddr), ptrsize)
if err != nil {
return nil, fmt.Errorf("error reading gopc %s", err)
}
reader.Seek(0)
schedaddr, err := offsetFor("sched", reader, initialInstructions)
if err != nil {
return nil, err
}
reader.Seek(0)
waitreasonaddr, err := offsetFor("waitreason", reader, initialInstructions)
if err != nil {
return nil, err
}
waitreason, err := thread.readString(uintptr(waitreasonaddr))
if err != nil {
return nil, err
}
spbytes, err := thread.readMemory(uintptr(schedaddr), ptrsize)
if err != nil {
return nil, fmt.Errorf("error reading goroutine SP %s", err)
}
gosp := binary.LittleEndian.Uint64(spbytes)
pcbytes, err := thread.readMemory(uintptr(schedaddr+uint64(ptrsize)), ptrsize)
if err != nil {
return nil, fmt.Errorf("error reading goroutine PC %s", err)
}
gopc := binary.LittleEndian.Uint64(pcbytes)
f, l, fn := thread.Process.goSymTable.PCToLine(gopc)
g := &G{
Id: int(binary.LittleEndian.Uint64(goidbytes)),
PC: gopc,
File: f,
Line: l,
Func: fn,
Id: int(binary.LittleEndian.Uint64(goidbytes)),
GoPC: binary.LittleEndian.Uint64(gopcbytes),
PC: gopc,
SP: gosp,
File: f,
Line: l,
Func: fn,
WaitReason: waitreason,
}
return g, nil
}
@ -286,7 +346,7 @@ func addressFor(dbp *DebuggedProcess, name string, reader *dwarf.Reader) (uint64
return uint64(addr), nil
}
func offsetFor(dbp *DebuggedProcess, name string, reader *dwarf.Reader, parentinstr []byte) (uint64, error) {
func offsetFor(name string, reader *dwarf.Reader, parentinstr []byte) (uint64, error) {
entry, err := findDwarfEntry(name, reader, true)
if err != nil {
return 0, err
@ -895,13 +955,15 @@ func (thread *ThreadContext) readFunctionPtr(addr uintptr) (string, error) {
}
func (thread *ThreadContext) readMemory(addr uintptr, size uintptr) ([]byte, error) {
buf := make([]byte, size)
if size == 0 {
return nil, nil
}
buf := make([]byte, size)
_, err := readMemory(thread, addr, buf)
if err != nil {
return nil, err
}
return buf, nil
}

@ -1,6 +1,7 @@
package source
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
@ -33,6 +34,9 @@ func (s *Searcher) FirstNodeAt(fname string, line int) (ast.Node, error) {
}
return true
})
if node == nil {
return nil, fmt.Errorf("could not find node at %s:%d", fname, line)
}
return node, nil
}
@ -83,11 +87,11 @@ func (s *Searcher) NextLines(fname string, line int) (lines []int, err error) {
if x.Else == nil {
// Grab first line after entire 'if' block
rbrace = s.fileset.Position(x.Body.Rbrace).Line
n, err := s.FirstNodeAt(fname, 1)
f, err := s.parse(fname)
if err != nil {
return nil, err
}
ast.Inspect(n, func(n ast.Node) bool {
ast.Inspect(f, func(n ast.Node) bool {
if n == nil {
return true
}
@ -156,9 +160,8 @@ func (s *Searcher) NextLines(fname string, line int) (lines []int, err error) {
// end up at the top of the loop again.
default:
var (
parents []*ast.BlockStmt
parentLines []int
parentLine int
parents []*ast.BlockStmt
parentBlockBeginLine int
)
f, err := s.parse(fname)
if err != nil {
@ -174,54 +177,85 @@ func (s *Searcher) NextLines(fname string, line int) (lines []int, err error) {
if stmt, ok := n.(*ast.ForStmt); ok {
parents = append(parents, stmt.Body)
pos := s.fileset.Position(stmt.Pos())
parentLine = pos.Line
parentLines = append(parentLines, pos.Line)
parentBlockBeginLine = pos.Line
}
if _, ok := n.(*ast.GenDecl); ok {
return true
}
if st, ok := n.(*ast.DeclStmt); ok {
beginpos := s.fileset.Position(st.Pos())
endpos := s.fileset.Position(st.End())
if beginpos.Line < endpos.Line {
return true
}
}
// Check to see if we've found the "next" line.
pos := s.fileset.Position(n.Pos())
if line < pos.Line {
if _, ok := n.(*ast.BlockStmt); ok {
return true
}
for {
if 0 < len(parents) {
parent := parents[len(parents)-1]
endLine := s.fileset.Position(parent.Rbrace).Line
if endLine < line {
if len(parents) == 1 {
parents = []*ast.BlockStmt{}
parentLines = []int{}
parentLine = 0
} else {
parents = parents[0 : len(parents)-1]
parentLines = parentLines[0:len(parents)]
parent = parents[len(parents)-1]
parentLine = s.fileset.Position(parent.Pos()).Line
}
continue
}
if parentLine != 0 {
var endfound bool
ast.Inspect(f, func(n ast.Node) bool {
if n == nil || endfound {
return false
}
if _, ok := n.(*ast.BlockStmt); ok {
return true
}
pos := s.fileset.Position(n.Pos())
if endLine < pos.Line {
endLine = pos.Line
endfound = true
return false
}
return true
})
lines = append(lines, parentLine, endLine)
}
var (
parent *ast.BlockStmt
parentEndLine int
)
for len(parents) > 0 {
parent = parents[len(parents)-1]
// Grab the line number of the right brace of the parent block.
parentEndLine = s.fileset.Position(parent.Rbrace).Line
// Check to see if we're still within the parents block.
// If we are, we're done and that is our parent.
if parentEndLine > line {
parentBlockBeginLine = s.fileset.Position(parent.Pos()).Line
break
}
break
// If we weren't, and there is only 1 parent, we no longer have one.
if len(parents) == 1 {
parent = nil
break
}
// Remove that parent from the stack.
parents = parents[0 : len(parents)-1]
}
if _, ok := n.(*ast.BranchStmt); !ok {
if parent != nil {
var (
endfound bool
beginFound bool
beginLine int
)
ast.Inspect(f, func(n ast.Node) bool {
if n == nil || endfound {
return false
}
if _, ok := n.(*ast.BlockStmt); ok {
return true
}
pos := s.fileset.Position(n.Pos())
if parentBlockBeginLine < pos.Line && !beginFound {
beginFound = true
beginLine = pos.Line
return true
}
if parentEndLine < pos.Line {
if _, ok := n.(*ast.FuncDecl); !ok {
lines = append(lines, beginLine, pos.Line)
}
endfound = true
return false
}
return true
})
lines = append(lines, parentBlockBeginLine)
}
switch n.(type) {
case *ast.BranchStmt, *ast.FuncDecl:
default:
lines = append(lines, pos.Line)
}
found = true

@ -33,11 +33,13 @@ func TestNextLines(t *testing.T) {
{8, []int{9, 10, 13}},
{15, []int{17, 19}},
{25, []int{27}},
{22, []int{6, 25}},
{22, []int{7, 25, 6}},
{33, []int{36}},
{36, []int{37, 40}},
{47, []int{44, 51}},
{57, []int{55, 56}},
{47, []int{45, 51, 44}},
{57, []int{55}},
{30, []int{32}},
{62, []int{63}},
}
for i, c := range cases {
lines, err := v.NextLines(tf, c.line)