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:
parent
ab5aec4365
commit
58db8322ef
4
Makefile
4
Makefile
@ -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
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
|
||||
}
|
||||
|
||||
|
||||
124
source/source.go
124
source/source.go
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user