提交 e4fc5e32 编写于 作者: D Derek Parker

Refactor: Use thread-locked goroutine for ptrace ops

Previously either the terminal client or the debugger service would
either lock main goroutine to a thread or provide a locked goroutine to
run _all_ DebuggedProcess functions in. This is unnecessary because only
ptrace functions need to be run from the same thread that originated the
PT_ATTACH request.

Here we use a specific thread-locked goroutine to service any ptrace
request. That goroutine is also responsible for the initial spawning /
attaching of the process, since it must be responsible for the PT_ATTACH
request.
上级 fe230360
...@@ -30,7 +30,7 @@ func (bp *Breakpoint) String() string { ...@@ -30,7 +30,7 @@ func (bp *Breakpoint) String() string {
// hardware or software breakpoint. // hardware or software breakpoint.
func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) { func (bp *Breakpoint) Clear(thread *Thread) (*Breakpoint, error) {
if bp.hardware { if bp.hardware {
if err := clearHardwareBreakpoint(bp.reg, thread.Id); err != nil { if err := thread.dbp.clearHardwareBreakpoint(bp.reg, thread.Id); err != nil {
return nil, err return nil, err
} }
return bp, nil return bp, nil
...@@ -121,7 +121,7 @@ func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64, temp bool) (*Bre ...@@ -121,7 +121,7 @@ func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64, temp bool) (*Bre
} }
if v == nil { if v == nil {
for t, _ := range dbp.Threads { for t, _ := range dbp.Threads {
if err := setHardwareBreakpoint(i, t, addr); err != nil { if err := dbp.setHardwareBreakpoint(i, t, addr); err != nil {
return nil, fmt.Errorf("could not set hardware breakpoint on thread %d: %s", t, err) return nil, fmt.Errorf("could not set hardware breakpoint on thread %d: %s", t, err)
} }
} }
......
...@@ -3,11 +3,11 @@ package proc ...@@ -3,11 +3,11 @@ package proc
import "fmt" import "fmt"
// TODO(darwin) // TODO(darwin)
func setHardwareBreakpoint(reg, tid int, addr uint64) error { func (dbp *DebuggedProcess) setHardwareBreakpoint(reg, tid int, addr uint64) error {
return fmt.Errorf("not implemented on darwin") return fmt.Errorf("not implemented on darwin")
} }
// TODO(darwin) // TODO(darwin)
func clearHardwareBreakpoint(reg, tid int) error { func (dbp *DebuggedProcess) clearHardwareBreakpoint(reg, tid int) error {
return fmt.Errorf("not implemented on darwin") return fmt.Errorf("not implemented on darwin")
} }
...@@ -21,7 +21,7 @@ import "fmt" ...@@ -21,7 +21,7 @@ import "fmt"
// debug register `reg` with the address of the instruction // debug register `reg` with the address of the instruction
// that we want to break at. There are only 4 debug registers // that we want to break at. There are only 4 debug registers
// DR0-DR3. Debug register 7 is the control register. // DR0-DR3. Debug register 7 is the control register.
func setHardwareBreakpoint(reg, tid int, addr uint64) error { func (dbp *DebuggedProcess) setHardwareBreakpoint(reg, tid int, addr uint64) error {
if reg < 0 || reg > 3 { if reg < 0 || reg > 3 {
return fmt.Errorf("invalid debug register value") return fmt.Errorf("invalid debug register value")
} }
...@@ -32,10 +32,12 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error { ...@@ -32,10 +32,12 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error {
drxmask = uintptr((((1 << C.DR_CONTROL_SIZE) - 1) << uintptr(reg*C.DR_CONTROL_SIZE)) | (((1 << C.DR_ENABLE_SIZE) - 1) << uintptr(reg*C.DR_ENABLE_SIZE))) drxmask = uintptr((((1 << C.DR_CONTROL_SIZE) - 1) << uintptr(reg*C.DR_CONTROL_SIZE)) | (((1 << C.DR_ENABLE_SIZE) - 1) << uintptr(reg*C.DR_ENABLE_SIZE)))
drxenable = uintptr(0x1) << uintptr(reg*C.DR_ENABLE_SIZE) drxenable = uintptr(0x1) << uintptr(reg*C.DR_ENABLE_SIZE)
drxctl = uintptr(C.DR_RW_EXECUTE|C.DR_LEN_1) << uintptr(reg*C.DR_CONTROL_SIZE) drxctl = uintptr(C.DR_RW_EXECUTE|C.DR_LEN_1) << uintptr(reg*C.DR_CONTROL_SIZE)
dr7 uintptr
) )
// Get current state // Get current state
dr7, err := PtracePeekUser(tid, dr7off) var err error
dbp.execPtraceFunc(func() { dr7, err = PtracePeekUser(tid, dr7off) })
if err != nil { if err != nil {
return err return err
} }
...@@ -43,12 +45,14 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error { ...@@ -43,12 +45,14 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error {
// If addr == 0 we are expected to disable the breakpoint // If addr == 0 we are expected to disable the breakpoint
if addr == 0 { if addr == 0 {
dr7 &= ^drxmask dr7 &= ^drxmask
return PtracePokeUser(tid, dr7off, dr7) dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, dr7off, dr7) })
return err
} }
// Set the debug register `reg` with the address of the // Set the debug register `reg` with the address of the
// instruction we want to trigger a debug exception. // instruction we want to trigger a debug exception.
if err := PtracePokeUser(tid, drxoff, uintptr(addr)); err != nil { dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, drxoff, uintptr(addr)) })
if err != nil {
return err return err
} }
...@@ -61,12 +65,13 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error { ...@@ -61,12 +65,13 @@ func setHardwareBreakpoint(reg, tid int, addr uint64) error {
// instructs the cpu to raise a debug // instructs the cpu to raise a debug
// exception when hitting the address of // exception when hitting the address of
// an instruction stored in dr0-dr3. // an instruction stored in dr0-dr3.
return PtracePokeUser(tid, dr7off, dr7) dbp.execPtraceFunc(func() { err = PtracePokeUser(tid, dr7off, dr7) })
return err
} }
// Clears a hardware breakpoint. Essentially sets // Clears a hardware breakpoint. Essentially sets
// the debug reg to 0 and clears the control register // the debug reg to 0 and clears the control register
// flags for that reg. // flags for that reg.
func clearHardwareBreakpoint(reg, tid int) error { func (dbp *DebuggedProcess) clearHardwareBreakpoint(reg, tid int) error {
return setHardwareBreakpoint(reg, tid, 0) return dbp.setHardwareBreakpoint(reg, tid, 0)
} }
...@@ -49,6 +49,23 @@ type DebuggedProcess struct { ...@@ -49,6 +49,23 @@ type DebuggedProcess struct {
running bool running bool
halt bool halt bool
exited bool exited bool
ptraceChan chan func()
ptraceDoneChan chan interface{}
}
func New(pid int) *DebuggedProcess {
dbp := &DebuggedProcess{
Pid: pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
firstStart: true,
os: new(OSProcessDetails),
ast: source.New(),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
}
go dbp.handlePtraceFuncs()
return dbp
} }
// A ManualStopError happens when the user triggers a // A ManualStopError happens when the user triggers a
...@@ -72,22 +89,33 @@ func (pe ProcessExitedError) Error() string { ...@@ -72,22 +89,33 @@ func (pe ProcessExitedError) Error() string {
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int) (*DebuggedProcess, error) { func Attach(pid int) (*DebuggedProcess, error) {
dbp := &DebuggedProcess{ dbp, err := initializeDebugProcess(New(pid), "", true)
Pid: pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
os: new(OSProcessDetails),
ast: source.New(),
}
dbp, err := initializeDebugProcess(dbp, "", true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dbp, nil return dbp, nil
} }
func (dbp *DebuggedProcess) Detach() error { func (dbp *DebuggedProcess) Detach(kill bool) (err error) {
return PtraceDetach(dbp.Pid, int(sys.SIGINT)) // Clean up any breakpoints we've set.
for _, bp := range dbp.arch.HardwareBreakpoints() {
if bp != nil {
dbp.Clear(bp.Addr)
}
}
for _, bp := range dbp.Breakpoints {
if bp != nil {
dbp.Clear(bp.Addr)
}
}
dbp.execPtraceFunc(func() {
var sig int
if kill {
sig = int(sys.SIGINT)
}
err = PtraceDetach(dbp.Pid, sig)
})
return
} }
// Returns whether or not Delve thinks the debugged // Returns whether or not Delve thinks the debugged
...@@ -497,6 +525,21 @@ func (dbp *DebuggedProcess) PCToLine(pc uint64) (string, int, *gosym.Func) { ...@@ -497,6 +525,21 @@ func (dbp *DebuggedProcess) PCToLine(pc uint64) (string, int, *gosym.Func) {
return dbp.goSymTable.PCToLine(pc) return dbp.goSymTable.PCToLine(pc)
} }
// Finds the breakpoint for the given ID.
func (dbp *DebuggedProcess) FindBreakpointByID(id int) (*Breakpoint, bool) {
for _, bp := range dbp.arch.HardwareBreakpoints() {
if bp != nil && bp.ID == id {
return bp, true
}
}
for _, bp := range dbp.Breakpoints {
if bp.ID == id {
return bp, true
}
}
return nil, false
}
// Finds the breakpoint for the given pc. // Finds the breakpoint for the given pc.
func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*Breakpoint, bool) { func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
for _, bp := range dbp.arch.HardwareBreakpoints() { for _, bp := range dbp.arch.HardwareBreakpoints() {
...@@ -513,7 +556,8 @@ func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*Breakpoint, bool) { ...@@ -513,7 +556,8 @@ func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
// Returns a new DebuggedProcess struct. // Returns a new DebuggedProcess struct.
func initializeDebugProcess(dbp *DebuggedProcess, path string, attach bool) (*DebuggedProcess, error) { func initializeDebugProcess(dbp *DebuggedProcess, path string, attach bool) (*DebuggedProcess, error) {
if attach { if attach {
err := sys.PtraceAttach(dbp.Pid) var err error
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(dbp.Pid) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -623,3 +667,20 @@ func (dbp *DebuggedProcess) run(fn func() error) error { ...@@ -623,3 +667,20 @@ func (dbp *DebuggedProcess) run(fn func() error) error {
} }
return nil return nil
} }
func (dbp *DebuggedProcess) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// the execution of dbg. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
func (dbp *DebuggedProcess) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
"github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/source"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
) )
...@@ -44,16 +43,12 @@ func Launch(cmd []string) (*DebuggedProcess, error) { ...@@ -44,16 +43,12 @@ func Launch(cmd []string) (*DebuggedProcess, error) {
var argv **C.char var argv **C.char
argv = &argvSlice[0] argv = &argvSlice[0]
dbp := &DebuggedProcess{ dbp := New(0)
Threads: make(map[int]*Thread), var pid int
Breakpoints: make(map[uint64]*Breakpoint), dbp.execPtraceFunc(func() {
firstStart: true, ret := C.fork_exec(argv0, argv, &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort)
os: new(OSProcessDetails), pid = int(ret)
ast: source.New(), })
}
ret := C.fork_exec(argv0, argv, &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort)
pid := int(ret)
if pid <= 0 { if pid <= 0 {
return nil, fmt.Errorf("could not fork/exec") return nil, fmt.Errorf("could not fork/exec")
} }
......
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
"github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/source"
) )
const ( const (
...@@ -31,27 +30,27 @@ type OSProcessDetails interface{} ...@@ -31,27 +30,27 @@ type OSProcessDetails interface{}
// `cmd` is the program to run, and then rest are the arguments // `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process. // to be supplied to that process.
func Launch(cmd []string) (*DebuggedProcess, error) { func Launch(cmd []string) (*DebuggedProcess, error) {
proc := exec.Command(cmd[0]) var (
proc.Args = cmd proc *exec.Cmd
proc.Stdout = os.Stdout err error
proc.Stderr = os.Stderr )
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} dbp := New(0)
dbp.execPtraceFunc(func() {
if err := proc.Start(); err != nil { proc = exec.Command(cmd[0])
proc.Args = cmd
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
err = proc.Start()
})
if err != nil {
return nil, err return nil, err
} }
_, _, err := wait(proc.Process.Pid, 0) dbp.Pid = proc.Process.Pid
_, _, err = wait(proc.Process.Pid, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
dbp := &DebuggedProcess{
Pid: proc.Process.Pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
os: new(OSProcessDetails),
ast: source.New(),
}
return initializeDebugProcess(dbp, proc.Path, false) return initializeDebugProcess(dbp, proc.Path, false)
} }
...@@ -66,8 +65,9 @@ func (dbp *DebuggedProcess) addThread(tid int, attach bool) (*Thread, error) { ...@@ -66,8 +65,9 @@ func (dbp *DebuggedProcess) addThread(tid int, attach bool) (*Thread, error) {
return thread, nil return thread, nil
} }
var err error
if attach { if attach {
err := sys.PtraceAttach(tid) dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
if err != nil && err != sys.EPERM { if err != nil && err != sys.EPERM {
// Do not return err if err == EPERM, // Do not return err if err == EPERM,
// we may already be tracing this thread due to // we may already be tracing this thread due to
...@@ -86,14 +86,14 @@ func (dbp *DebuggedProcess) addThread(tid int, attach bool) (*Thread, error) { ...@@ -86,14 +86,14 @@ func (dbp *DebuggedProcess) addThread(tid int, attach bool) (*Thread, error) {
} }
} }
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err == syscall.ESRCH { if err == syscall.ESRCH {
_, _, err = wait(tid, 0) _, _, err = wait(tid, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
} }
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err != nil { if err != nil {
return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err) return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
} }
...@@ -244,7 +244,8 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*Thread, error) { ...@@ -244,7 +244,8 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*Thread, error) {
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
// A traced thread has cloned a new thread, grab the pid and // A traced thread has cloned a new thread, grab the pid and
// add it to our list of traced threads. // add it to our list of traced threads.
cloned, err := sys.PtraceGetEventMsg(wpid) var cloned uint
dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get event message: %s", err) return nil, fmt.Errorf("could not get event message: %s", err)
} }
...@@ -256,7 +257,7 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*Thread, error) { ...@@ -256,7 +257,7 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*Thread, error) {
if bp == nil { if bp == nil {
continue continue
} }
if err = setHardwareBreakpoint(reg, th.Id, bp.Addr); err != nil { if err = dbp.setHardwareBreakpoint(reg, th.Id, bp.Addr); err != nil {
return nil, err return nil, err
} }
} }
......
...@@ -10,20 +10,22 @@ import ( ...@@ -10,20 +10,22 @@ import (
protest "github.com/derekparker/delve/proc/test" protest "github.com/derekparker/delve/proc/test"
) )
func init() {
runtime.GOMAXPROCS(2)
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
protest.RunTestsWithFixtures(m) protest.RunTestsWithFixtures(m)
} }
func withTestProcess(name string, t *testing.T, fn func(p *DebuggedProcess, fixture protest.Fixture)) { func withTestProcess(name string, t *testing.T, fn func(p *DebuggedProcess, fixture protest.Fixture)) {
runtime.LockOSThread()
fixture := protest.BuildFixture(name) fixture := protest.BuildFixture(name)
p, err := Launch([]string{fixture.Path}) p, err := Launch([]string{fixture.Path})
if err != nil { if err != nil {
t.Fatal("Launch():", err) t.Fatal("Launch():", err)
} }
defer p.Detach() defer p.Detach(true)
fn(p, fixture) fn(p, fixture)
} }
...@@ -88,6 +90,7 @@ func TestExit(t *testing.T) { ...@@ -88,6 +90,7 @@ func TestExit(t *testing.T) {
} }
func TestHalt(t *testing.T) { func TestHalt(t *testing.T) {
runtime.GOMAXPROCS(2)
withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
go func() { go func() {
for { for {
......
...@@ -18,14 +18,18 @@ func (r *Regs) CX() uint64 { ...@@ -18,14 +18,18 @@ func (r *Regs) CX() uint64 {
return r.regs.Rcx return r.regs.Rcx
} }
func (r *Regs) SetPC(thread *Thread, pc uint64) error { func (r *Regs) SetPC(thread *Thread, pc uint64) (err error) {
r.regs.SetPC(pc) r.regs.SetPC(pc)
return sys.PtraceSetRegs(thread.Id, r.regs) thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, r.regs) })
return
} }
func registers(thread *Thread) (Registers, error) { func registers(thread *Thread) (Registers, error) {
var regs sys.PtraceRegs var (
err := sys.PtraceGetRegs(thread.Id, &regs) regs sys.PtraceRegs
err error
)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.Id, &regs) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -28,7 +28,7 @@ func BuildFixture(name string) Fixture { ...@@ -28,7 +28,7 @@ func BuildFixture(name string) Fixture {
return f return f
} }
parent := ".." parent := ".."
fixturesDir := filepath.Join(parent, "_fixtures") fixturesDir := "_fixtures"
for depth := 0; depth < 10; depth++ { for depth := 0; depth < 10; depth++ {
if _, err := os.Stat(fixturesDir); err == nil { if _, err := os.Stat(fixturesDir); err == nil {
break break
......
...@@ -36,7 +36,9 @@ func (t *Thread) singleStep() error { ...@@ -36,7 +36,9 @@ func (t *Thread) singleStep() error {
func (t *Thread) resume() error { func (t *Thread) resume() error {
// TODO(dp) set flag for ptrace stops // TODO(dp) set flag for ptrace stops
if PtraceCont(t.dbp.Pid, 0) == nil { var err error
t.dbp.execPtraceFunc(func() { err = PtraceCont(t.dbp.Pid, 0) })
if err == nil {
return nil return nil
} }
kret := C.resume_thread(t.os.thread_act) kret := C.resume_thread(t.os.thread_act)
......
...@@ -27,12 +27,13 @@ func (t *Thread) Halt() error { ...@@ -27,12 +27,13 @@ func (t *Thread) Halt() error {
return nil return nil
} }
func (t *Thread) resume() error { func (t *Thread) resume() (err error) {
return PtraceCont(t.Id, 0) t.dbp.execPtraceFunc(func() { err = PtraceCont(t.Id, 0) })
return
} }
func (t *Thread) singleStep() error { func (t *Thread) singleStep() (err error) {
err := sys.PtraceSingleStep(t.Id) t.dbp.execPtraceFunc(func() { err = sys.PtraceSingleStep(t.Id) })
if err != nil { if err != nil {
return err return err
} }
...@@ -51,26 +52,31 @@ func (t *Thread) blocked() bool { ...@@ -51,26 +52,31 @@ func (t *Thread) blocked() bool {
} }
func (thread *Thread) saveRegisters() (Registers, error) { func (thread *Thread) saveRegisters() (Registers, error) {
if err := sys.PtraceGetRegs(thread.Id, &thread.os.registers); err != nil { var err error
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.Id, &thread.os.registers) })
if err != nil {
return nil, fmt.Errorf("could not save register contents") return nil, fmt.Errorf("could not save register contents")
} }
return &Regs{&thread.os.registers}, nil return &Regs{&thread.os.registers}, nil
} }
func (thread *Thread) restoreRegisters() error { func (thread *Thread) restoreRegisters() (err error) {
return sys.PtraceSetRegs(thread.Id, &thread.os.registers) thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.Id, &thread.os.registers) })
return
} }
func writeMemory(thread *Thread, addr uintptr, data []byte) (int, error) { func writeMemory(thread *Thread, addr uintptr, data []byte) (written int, err error) {
if len(data) == 0 { if len(data) == 0 {
return 0, nil return
} }
return sys.PtracePokeData(thread.Id, addr, data) thread.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(thread.Id, addr, data) })
return
} }
func readMemory(thread *Thread, addr uintptr, data []byte) (int, error) { func readMemory(thread *Thread, addr uintptr, data []byte) (read int, err error) {
if len(data) == 0 { if len(data) == 0 {
return 0, nil return
} }
return sys.PtracePeekData(thread.Id, addr, data) thread.dbp.execPtraceFunc(func() { read, err = sys.PtracePeekData(thread.Id, addr, data) })
return
} }
package api
import "github.com/derekparker/delve/proc"
// convertBreakpoint converts an internal breakpoint to an API Breakpoint.
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
return &Breakpoint{
ID: bp.ID,
FunctionName: bp.FunctionName,
File: bp.File,
Line: bp.Line,
Addr: bp.Addr,
}
}
// convertThread converts an internal thread to an API Thread.
func ConvertThread(th *proc.Thread) *Thread {
var (
function *Function
file string
line int
pc uint64
)
loc, err := th.Location()
if err == nil {
pc = loc.PC
file = loc.File
line = loc.Line
if loc.Fn != nil {
function = &Function{
Name: loc.Fn.Name,
Type: loc.Fn.Type,
Value: loc.Fn.Value,
GoType: loc.Fn.GoType,
}
}
}
return &Thread{
ID: th.Id,
PC: pc,
File: file,
Line: line,
Function: function,
}
}
// convertVar converts an internal variable to an API Variable.
func ConvertVar(v *proc.Variable) Variable {
return Variable{
Name: v.Name,
Value: v.Value,
Type: v.Type,
}
}
// convertGoroutine converts an internal Goroutine to an API Goroutine.
func ConvertGoroutine(g *proc.G) *Goroutine {
var function *Function
if g.Func != nil {
function = &Function{
Name: g.Func.Name,
Type: g.Func.Type,
Value: g.Func.Value,
GoType: g.Func.GoType,
}
}
return &Goroutine{
ID: g.Id,
PC: g.PC,
File: g.File,
Line: g.Line,
Function: function,
}
}
...@@ -4,20 +4,16 @@ import ( ...@@ -4,20 +4,16 @@ import (
"fmt" "fmt"
"log" "log"
"regexp" "regexp"
"runtime"
"github.com/derekparker/delve/proc" "github.com/derekparker/delve/proc"
"github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/api"
) )
// Debugger provides a thread-safe DebuggedProcess service. Instances of // Debugger provides a conveniant way to convert from internal
// Debugger can be exposed by other services. // structure representations to API representations.
type Debugger struct { type Debugger struct {
config *Config config *Config
process *proc.DebuggedProcess process *proc.DebuggedProcess
processOps chan func(*proc.DebuggedProcess)
stop chan stopSignal
running bool
} }
// Config provides the configuration to start a Debugger. // Config provides the configuration to start a Debugger.
...@@ -33,222 +29,107 @@ type Config struct { ...@@ -33,222 +29,107 @@ type Config struct {
AttachPid int AttachPid int
} }
// stopSignal is used to stop the debugger.
type stopSignal struct {
// KillProcess indicates whether to kill the debugee following detachment.
KillProcess bool
}
// New creates a new Debugger. // New creates a new Debugger.
func New(config *Config) *Debugger { func New(config *Config) (*Debugger, error) {
debugger := &Debugger{ d := &Debugger{
processOps: make(chan func(*proc.DebuggedProcess)), config: config,
config: config,
stop: make(chan stopSignal),
}
return debugger
}
// withProcess facilitates thread-safe access to the DebuggedProcess. Most
// interaction with DebuggedProcess should occur via calls to withProcess[1],
// and the functions placed on the processOps channel should be consumed and
// executed from the same thread as the DebuggedProcess.
//
// This is convenient because it allows things like HTTP handlers in
// goroutines to work with the DebuggedProcess with synchronous semantics.
//
// [1] There are some exceptional cases where direct access is okay; for
// instance, when performing an operation like halt which merely sends a
// signal to the process rather than performing something like a ptrace
// operation.
func (d *Debugger) withProcess(f func(*proc.DebuggedProcess) error) error {
if !d.running {
return fmt.Errorf("debugger isn't running")
} }
result := make(chan error)
d.processOps <- func(proc *proc.DebuggedProcess) {
result <- f(proc)
}
return <-result
}
// Run starts debugging a process until Detach is called.
func (d *Debugger) Run() error {
// We must ensure here that we are running on the same thread during
// the execution of dbg. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
d.running = true
defer func() { d.running = false }()
// Create the process by either attaching or launching. // Create the process by either attaching or launching.
if d.config.AttachPid > 0 { if d.config.AttachPid > 0 {
log.Printf("attaching to pid %d", d.config.AttachPid) log.Printf("attaching to pid %d", d.config.AttachPid)
p, err := proc.Attach(d.config.AttachPid) p, err := proc.Attach(d.config.AttachPid)
if err != nil { if err != nil {
return fmt.Errorf("couldn't attach to pid %d: %s", d.config.AttachPid, err) return nil, fmt.Errorf("couldn't attach to pid %d: %s", d.config.AttachPid, err)
} }
d.process = p d.process = p
} else { } else {
log.Printf("launching process with args: %v", d.config.ProcessArgs) log.Printf("launching process with args: %v", d.config.ProcessArgs)
p, err := proc.Launch(d.config.ProcessArgs) p, err := proc.Launch(d.config.ProcessArgs)
if err != nil { if err != nil {
return fmt.Errorf("couldn't launch process: %s", err) return nil, fmt.Errorf("couldn't launch process: %s", err)
} }
d.process = p d.process = p
} }
return d, nil
// Handle access to the process from the current thread.
log.Print("debugger started")
for {
select {
case f := <-d.processOps:
// Execute the function
f(d.process)
case s := <-d.stop:
// Handle shutdown
log.Print("debugger is stopping")
// Clear breakpoints
bps := []*proc.Breakpoint{}
for _, bp := range d.process.Breakpoints {
if bp != nil {
bps = append(bps, bp)
}
}
for _, bp := range d.process.HardwareBreakpoints() {
if bp != nil {
bps = append(bps, bp)
}
}
for _, bp := range bps {
_, err := d.process.Clear(bp.Addr)
if err != nil {
log.Printf("warning: couldn't clear breakpoint @ %#v: %s", bp.Addr, err)
} else {
log.Printf("cleared breakpoint @ %#v", bp.Addr)
}
}
// Kill the process if requested
if s.KillProcess {
if err := d.process.Detach(); err == nil {
log.Print("killed process")
} else {
log.Printf("couldn't kill process: %s", err)
}
} else {
// Detach
if !d.process.Exited() {
if err := proc.PtraceDetach(d.process.Pid, 0); err == nil {
log.Print("detached from process")
} else {
log.Printf("couldn't detach from process: %s", err)
}
}
}
return nil
}
}
} }
// Detach stops the debugger.
func (d *Debugger) Detach(kill bool) error { func (d *Debugger) Detach(kill bool) error {
if !d.running { return d.process.Detach(kill)
return fmt.Errorf("debugger isn't running")
}
d.stop <- stopSignal{KillProcess: kill}
return nil
} }
func (d *Debugger) State() (*api.DebuggerState, error) { func (d *Debugger) State() (*api.DebuggerState, error) {
var state *api.DebuggerState var (
state *api.DebuggerState
err := d.withProcess(func(p *proc.DebuggedProcess) error { thread *api.Thread
var thread *api.Thread )
th := p.CurrentThread th := d.process.CurrentThread
if th != nil { if th != nil {
thread = convertThread(th) thread = api.ConvertThread(th)
} }
var breakpoint *api.Breakpoint var breakpoint *api.Breakpoint
bp := p.CurrentBreakpoint() bp := d.process.CurrentBreakpoint()
if bp != nil { if bp != nil {
breakpoint = convertBreakpoint(bp) breakpoint = api.ConvertBreakpoint(bp)
} }
state = &api.DebuggerState{ state = &api.DebuggerState{
Breakpoint: breakpoint, Breakpoint: breakpoint,
CurrentThread: thread, CurrentThread: thread,
Exited: p.Exited(), Exited: d.process.Exited(),
} }
return nil
})
return state, err return state, nil
} }
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) { func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
var createdBp *api.Breakpoint var createdBp *api.Breakpoint
err := d.withProcess(func(p *proc.DebuggedProcess) error { var loc string
var loc string switch {
switch { case len(requestedBp.File) > 0:
case len(requestedBp.File) > 0: loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line)
loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line) case len(requestedBp.FunctionName) > 0:
case len(requestedBp.FunctionName) > 0: loc = requestedBp.FunctionName
loc = requestedBp.FunctionName default:
default: return nil, fmt.Errorf("no file or function name specified")
return fmt.Errorf("no file or function name specified") }
}
bp, breakError := p.BreakByLocation(loc) bp, err := d.process.BreakByLocation(loc)
if breakError != nil { if err != nil {
return breakError return nil, err
} }
createdBp = convertBreakpoint(bp) createdBp = api.ConvertBreakpoint(bp)
log.Printf("created breakpoint: %#v", createdBp) log.Printf("created breakpoint: %#v", createdBp)
return nil return createdBp, nil
})
return createdBp, err
} }
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) { func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
var clearedBp *api.Breakpoint var clearedBp *api.Breakpoint
err := d.withProcess(func(p *proc.DebuggedProcess) error { bp, err := d.process.Clear(requestedBp.Addr)
bp, err := p.Clear(requestedBp.Addr) if err != nil {
if err != nil { return nil, fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err)
return fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err) }
} clearedBp = api.ConvertBreakpoint(bp)
clearedBp = convertBreakpoint(bp) log.Printf("cleared breakpoint: %#v", clearedBp)
log.Printf("cleared breakpoint: %#v", clearedBp)
return nil
})
return clearedBp, err return clearedBp, err
} }
func (d *Debugger) Breakpoints() []*api.Breakpoint { func (d *Debugger) Breakpoints() []*api.Breakpoint {
bps := []*api.Breakpoint{} bps := []*api.Breakpoint{}
d.withProcess(func(p *proc.DebuggedProcess) error { for _, bp := range d.process.HardwareBreakpoints() {
for _, bp := range p.HardwareBreakpoints() { if bp == nil {
if bp == nil { continue
continue
}
bps = append(bps, convertBreakpoint(bp))
} }
bps = append(bps, api.ConvertBreakpoint(bp))
}
for _, bp := range p.Breakpoints { for _, bp := range d.process.Breakpoints {
if bp.Temp { if bp.Temp {
continue continue
}
bps = append(bps, convertBreakpoint(bp))
} }
return nil bps = append(bps, api.ConvertBreakpoint(bp))
}) }
return bps return bps
} }
...@@ -263,12 +144,9 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint { ...@@ -263,12 +144,9 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
func (d *Debugger) Threads() []*api.Thread { func (d *Debugger) Threads() []*api.Thread {
threads := []*api.Thread{} threads := []*api.Thread{}
d.withProcess(func(p *proc.DebuggedProcess) error { for _, th := range d.process.Threads {
for _, th := range p.Threads { threads = append(threads, api.ConvertThread(th))
threads = append(threads, convertThread(th)) }
}
return nil
})
return threads return threads
} }
...@@ -291,26 +169,17 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er ...@@ -291,26 +169,17 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
var err error var err error
switch command.Name { switch command.Name {
case api.Continue: case api.Continue:
err = d.withProcess(func(p *proc.DebuggedProcess) error { log.Print("continuing")
log.Print("continuing") err = d.process.Continue()
e := p.Continue()
return e
})
case api.Next: case api.Next:
err = d.withProcess(func(p *proc.DebuggedProcess) error { log.Print("nexting")
log.Print("nexting") err = d.process.Next()
return p.Next()
})
case api.Step: case api.Step:
err = d.withProcess(func(p *proc.DebuggedProcess) error { log.Print("stepping")
log.Print("stepping") err = d.process.Step()
return p.Step()
})
case api.SwitchThread: case api.SwitchThread:
err = d.withProcess(func(p *proc.DebuggedProcess) error { log.Printf("switching to thread %d", command.ThreadID)
log.Printf("switching to thread %d", command.ThreadID) err = d.process.SwitchThread(command.ThreadID)
return p.SwitchThread(command.ThreadID)
})
case api.Halt: case api.Halt:
// RequestManualStop does not invoke any ptrace syscalls, so it's safe to // RequestManualStop does not invoke any ptrace syscalls, so it's safe to
// access the process directly. // access the process directly.
...@@ -333,14 +202,11 @@ func (d *Debugger) Sources(filter string) ([]string, error) { ...@@ -333,14 +202,11 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
} }
files := []string{} files := []string{}
d.withProcess(func(p *proc.DebuggedProcess) error { for f := range d.process.Sources() {
for f := range p.Sources() { if regex.Match([]byte(f)) {
if regex.Match([]byte(f)) { files = append(files, f)
files = append(files, f)
}
} }
return nil }
})
return files, nil return files, nil
} }
...@@ -351,14 +217,11 @@ func (d *Debugger) Functions(filter string) ([]string, error) { ...@@ -351,14 +217,11 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
} }
funcs := []string{} funcs := []string{}
d.withProcess(func(p *proc.DebuggedProcess) error { for _, f := range d.process.Funcs() {
for _, f := range p.Funcs() { if f.Sym != nil && regex.Match([]byte(f.Name)) {
if f.Sym != nil && regex.Match([]byte(f.Name)) { funcs = append(funcs, f.Name)
funcs = append(funcs, f.Name)
}
} }
return nil }
})
return funcs, nil return funcs, nil
} }
...@@ -369,166 +232,75 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable ...@@ -369,166 +232,75 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
} }
vars := []api.Variable{} vars := []api.Variable{}
err = d.withProcess(func(p *proc.DebuggedProcess) error { thread, found := d.process.Threads[threadID]
thread, found := p.Threads[threadID] if !found {
if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID)
return fmt.Errorf("couldn't find thread %d", threadID) }
} pv, err := thread.PackageVariables()
pv, err := thread.PackageVariables() if err != nil {
if err != nil { return nil, err
return err }
} for _, v := range pv {
for _, v := range pv { if regex.Match([]byte(v.Name)) {
if regex.Match([]byte(v.Name)) { vars = append(vars, api.ConvertVar(v))
vars = append(vars, convertVar(v))
}
} }
return nil }
})
return vars, err return vars, err
} }
func (d *Debugger) LocalVariables(threadID int) ([]api.Variable, error) { func (d *Debugger) LocalVariables(threadID int) ([]api.Variable, error) {
vars := []api.Variable{} vars := []api.Variable{}
err := d.withProcess(func(p *proc.DebuggedProcess) error { thread, found := d.process.Threads[threadID]
thread, found := p.Threads[threadID] if !found {
if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID)
return fmt.Errorf("couldn't find thread %d", threadID) }
} pv, err := thread.LocalVariables()
pv, err := thread.LocalVariables() if err != nil {
if err != nil { return nil, err
return err }
} for _, v := range pv {
for _, v := range pv { vars = append(vars, api.ConvertVar(v))
vars = append(vars, convertVar(v)) }
}
return nil
})
return vars, err return vars, err
} }
func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) { func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) {
vars := []api.Variable{} vars := []api.Variable{}
err := d.withProcess(func(p *proc.DebuggedProcess) error { thread, found := d.process.Threads[threadID]
thread, found := p.Threads[threadID] if !found {
if !found { return nil, fmt.Errorf("couldn't find thread %d", threadID)
return fmt.Errorf("couldn't find thread %d", threadID)
}
pv, err := thread.FunctionArguments()
if err != nil {
return err
}
for _, v := range pv {
vars = append(vars, convertVar(v))
}
return nil
})
return vars, err
}
func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {
var variable *api.Variable
err := d.withProcess(func(p *proc.DebuggedProcess) error {
thread, found := p.Threads[threadID]
if !found {
return fmt.Errorf("couldn't find thread %d", threadID)
}
v, err := thread.EvalVariable(symbol)
if err != nil {
return err
}
converted := convertVar(v)
variable = &converted
return nil
})
return variable, err
}
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
goroutines := []*api.Goroutine{}
err := d.withProcess(func(p *proc.DebuggedProcess) error {
gs, err := p.GoroutinesInfo()
if err != nil {
return err
}
for _, g := range gs {
goroutines = append(goroutines, convertGoroutine(g))
}
return nil
})
return goroutines, err
}
// convertBreakpoint converts an internal breakpoint to an API Breakpoint.
func convertBreakpoint(bp *proc.Breakpoint) *api.Breakpoint {
return &api.Breakpoint{
ID: bp.ID,
FunctionName: bp.FunctionName,
File: bp.File,
Line: bp.Line,
Addr: bp.Addr,
} }
} pv, err := thread.FunctionArguments()
if err != nil {
// convertThread converts an internal thread to an API Thread. return nil, err
func convertThread(th *proc.Thread) *api.Thread {
var (
function *api.Function
file string
line int
pc uint64
)
loc, err := th.Location()
if err == nil {
pc = loc.PC
file = loc.File
line = loc.Line
if loc.Fn != nil {
function = &api.Function{
Name: loc.Fn.Name,
Type: loc.Fn.Type,
Value: loc.Fn.Value,
GoType: loc.Fn.GoType,
}
}
} }
for _, v := range pv {
return &api.Thread{ vars = append(vars, api.ConvertVar(v))
ID: th.Id,
PC: pc,
File: file,
Line: line,
Function: function,
} }
return vars, err
} }
// convertVar converts an internal variable to an API Variable. func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {
func convertVar(v *proc.Variable) api.Variable { thread, found := d.process.Threads[threadID]
return api.Variable{ if !found {
Name: v.Name, return nil, fmt.Errorf("couldn't find thread %d", threadID)
Value: v.Value, }
Type: v.Type, v, err := thread.EvalVariable(symbol)
if err != nil {
return nil, err
} }
converted := api.ConvertVar(v)
return &converted, err
} }
// convertGoroutine converts an internal Goroutine to an API Goroutine. func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
func convertGoroutine(g *proc.G) *api.Goroutine { goroutines := []*api.Goroutine{}
var function *api.Function gs, err := d.process.GoroutinesInfo()
if g.Func != nil { if err != nil {
function = &api.Function{ return nil, err
Name: g.Func.Name,
Type: g.Func.Type,
Value: g.Func.Value,
GoType: g.Func.GoType,
}
} }
for _, g := range gs {
return &api.Goroutine{ goroutines = append(goroutines, api.ConvertGoroutine(g))
ID: g.Id,
PC: g.PC,
File: g.File,
Line: g.Line,
Function: function,
} }
return goroutines, err
} }
...@@ -2,7 +2,9 @@ package rest ...@@ -2,7 +2,9 @@ package rest
import ( import (
"net" "net"
"os"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
protest "github.com/derekparker/delve/proc/test" protest "github.com/derekparker/delve/proc/test"
...@@ -10,6 +12,10 @@ import ( ...@@ -10,6 +12,10 @@ import (
"github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/api"
) )
func init() {
runtime.GOMAXPROCS(2)
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
protest.RunTestsWithFixtures(m) protest.RunTestsWithFixtures(m)
} }
...@@ -272,10 +278,16 @@ func TestClientServer_switchThread(t *testing.T) { ...@@ -272,10 +278,16 @@ func TestClientServer_switchThread(t *testing.T) {
func TestClientServer_infoLocals(t *testing.T) { func TestClientServer_infoLocals(t *testing.T) {
withTestClient("testnextprog", t, func(c service.Client) { withTestClient("testnextprog", t, func(c service.Client) {
fp, err := filepath.Abs("../../_fixtures/testnextprog.go") fp, err := filepath.Abs("_fixtures/testnextprog.go")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(fp); err != nil {
fp, err = filepath.Abs("../../_fixtures/testnextprog.go")
if err != nil {
t.Fatal(err)
}
}
_, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23}) _, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
...@@ -296,10 +308,16 @@ func TestClientServer_infoLocals(t *testing.T) { ...@@ -296,10 +308,16 @@ func TestClientServer_infoLocals(t *testing.T) {
func TestClientServer_infoArgs(t *testing.T) { func TestClientServer_infoArgs(t *testing.T) {
withTestClient("testnextprog", t, func(c service.Client) { withTestClient("testnextprog", t, func(c service.Client) {
fp, err := filepath.Abs("../../_fixtures/testnextprog.go") fp, err := filepath.Abs("_fixtures/testnextprog.go")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(fp); err != nil {
fp, err = filepath.Abs("../../_fixtures/testnextprog.go")
if err != nil {
t.Fatal(err)
}
}
_, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47}) _, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
......
...@@ -21,8 +21,6 @@ type RESTServer struct { ...@@ -21,8 +21,6 @@ type RESTServer struct {
listener net.Listener listener net.Listener
// debugger is a debugger service. // debugger is a debugger service.
debugger *debugger.Debugger debugger *debugger.Debugger
// debuggerStopped is used to detect shutdown of the debugger service.
debuggerStopped chan error
} }
// Config provides the configuration to start a Debugger and expose it with a // Config provides the configuration to start a Debugger and expose it with a
...@@ -49,9 +47,8 @@ func NewServer(config *Config, logEnabled bool) *RESTServer { ...@@ -49,9 +47,8 @@ func NewServer(config *Config, logEnabled bool) *RESTServer {
} }
return &RESTServer{ return &RESTServer{
config: config, config: config,
listener: config.Listener, listener: config.Listener,
debuggerStopped: make(chan error),
} }
} }
...@@ -59,22 +56,17 @@ func NewServer(config *Config, logEnabled bool) *RESTServer { ...@@ -59,22 +56,17 @@ func NewServer(config *Config, logEnabled bool) *RESTServer {
// itself can be stopped with the `detach` API. Run blocks until the HTTP // itself can be stopped with the `detach` API. Run blocks until the HTTP
// server stops. // server stops.
func (s *RESTServer) Run() error { func (s *RESTServer) Run() error {
var err error
// Create and start the debugger // Create and start the debugger
s.debugger = debugger.New(&debugger.Config{ if s.debugger, err = debugger.New(&debugger.Config{
ProcessArgs: s.config.ProcessArgs, ProcessArgs: s.config.ProcessArgs,
AttachPid: s.config.AttachPid, AttachPid: s.config.AttachPid,
}) }); err != nil {
go func() { return err
err := s.debugger.Run() }
if err != nil {
log.Printf("debugger stopped with error: %s", err)
}
s.debuggerStopped <- err
}()
// Set up the HTTP server // Set up the HTTP server
container := restful.NewContainer() container := restful.NewContainer()
ws := new(restful.WebService) ws := new(restful.WebService)
ws. ws.
Path(""). Path("").
...@@ -108,11 +100,7 @@ func (s *RESTServer) Run() error { ...@@ -108,11 +100,7 @@ func (s *RESTServer) Run() error {
// Stop detaches from the debugger and waits for it to stop. // Stop detaches from the debugger and waits for it to stop.
func (s *RESTServer) Stop(kill bool) error { func (s *RESTServer) Stop(kill bool) error {
err := s.debugger.Detach(kill) return s.debugger.Detach(kill)
if err != nil {
return err
}
return <-s.debuggerStopped
} }
// writeError writes a simple error response. // writeError writes a simple error response.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册