mirror of
https://github.com/beego/bee.git
synced 2024-11-14 17:00:54 +00:00
644 lines
17 KiB
Go
644 lines
17 KiB
Go
package proc
|
|
|
|
import (
|
|
"debug/gosym"
|
|
"debug/pe"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
sys "golang.org/x/sys/windows"
|
|
|
|
"github.com/derekparker/delve/dwarf/frame"
|
|
"github.com/derekparker/delve/dwarf/line"
|
|
"golang.org/x/debug/dwarf"
|
|
)
|
|
|
|
// OSProcessDetails holds Windows specific information.
|
|
type OSProcessDetails struct {
|
|
hProcess syscall.Handle
|
|
breakThread int
|
|
}
|
|
|
|
// Launch creates and begins debugging a new process.
|
|
func Launch(cmd []string, wd string) (*Process, error) {
|
|
argv0Go, err := filepath.Abs(cmd[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Make sure the binary exists and is an executable file
|
|
if filepath.Base(cmd[0]) == cmd[0] {
|
|
if _, err := exec.LookPath(cmd[0]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
peFile, err := openExecutablePath(argv0Go)
|
|
if err != nil {
|
|
return nil, NotExecutableErr
|
|
}
|
|
peFile.Close()
|
|
|
|
// Duplicate the stdin/stdout/stderr handles
|
|
files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)}
|
|
p, _ := syscall.GetCurrentProcess()
|
|
fd := make([]syscall.Handle, len(files))
|
|
for i := range files {
|
|
err := syscall.DuplicateHandle(p, syscall.Handle(files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer syscall.CloseHandle(syscall.Handle(fd[i]))
|
|
}
|
|
|
|
argv0, err := syscall.UTF16PtrFromString(argv0Go)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create suitable command line for CreateProcess
|
|
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L326
|
|
// adapted from standard library makeCmdLine
|
|
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L86
|
|
var cmdLineGo string
|
|
if len(cmd) >= 1 {
|
|
for _, v := range cmd {
|
|
if cmdLineGo != "" {
|
|
cmdLineGo += " "
|
|
}
|
|
cmdLineGo += syscall.EscapeArg(v)
|
|
}
|
|
}
|
|
|
|
var cmdLine *uint16
|
|
if cmdLineGo != "" {
|
|
if cmdLine, err = syscall.UTF16PtrFromString(cmdLineGo); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var workingDir *uint16
|
|
if wd != "" {
|
|
if workingDir, err = syscall.UTF16PtrFromString(wd); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Initialize the startup info and create process
|
|
si := new(sys.StartupInfo)
|
|
si.Cb = uint32(unsafe.Sizeof(*si))
|
|
si.Flags = syscall.STARTF_USESTDHANDLES
|
|
si.StdInput = sys.Handle(fd[0])
|
|
si.StdOutput = sys.Handle(fd[1])
|
|
si.StdErr = sys.Handle(fd[2])
|
|
pi := new(sys.ProcessInformation)
|
|
|
|
dbp := New(0)
|
|
dbp.execPtraceFunc(func() {
|
|
if wd == "" {
|
|
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi)
|
|
} else {
|
|
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, workingDir, si, pi)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sys.CloseHandle(sys.Handle(pi.Process))
|
|
sys.CloseHandle(sys.Handle(pi.Thread))
|
|
|
|
dbp.Pid = int(pi.ProcessId)
|
|
|
|
return newDebugProcess(dbp, argv0Go)
|
|
}
|
|
|
|
// newDebugProcess prepares process pid for debugging.
|
|
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
|
|
// It should not actually be possible for the
|
|
// call to waitForDebugEvent to fail, since Windows
|
|
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
|
|
// immediately after launching under DEBUG_ONLY_THIS_PROCESS.
|
|
// Attaching with DebugActiveProcess has similar effect.
|
|
var err error
|
|
var tid, exitCode int
|
|
dbp.execPtraceFunc(func() {
|
|
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tid == 0 {
|
|
dbp.postExit()
|
|
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
|
|
}
|
|
// Suspend all threads so that the call to _ContinueDebugEvent will
|
|
// not resume the target.
|
|
for _, thread := range dbp.Threads {
|
|
_, err := _SuspendThread(thread.os.hThread)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
dbp.execPtraceFunc(func() {
|
|
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return initializeDebugProcess(dbp, exepath, false)
|
|
}
|
|
|
|
// findExePath searches for process pid, and returns its executable path.
|
|
func findExePath(pid int) (string, error) {
|
|
// Original code suggested different approach (see below).
|
|
// Maybe it could be useful in the future.
|
|
//
|
|
// Find executable path from PID/handle on Windows:
|
|
// https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
|
|
|
|
p, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer syscall.CloseHandle(p)
|
|
|
|
n := uint32(128)
|
|
for {
|
|
buf := make([]uint16, int(n))
|
|
err = _QueryFullProcessImageName(p, 0, &buf[0], &n)
|
|
switch err {
|
|
case syscall.ERROR_INSUFFICIENT_BUFFER:
|
|
// try bigger buffer
|
|
n *= 2
|
|
// but stop if it gets too big
|
|
if n > 10000 {
|
|
return "", err
|
|
}
|
|
case nil:
|
|
return syscall.UTF16ToString(buf[:n]), nil
|
|
default:
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attach to an existing process with the given PID.
|
|
func Attach(pid int) (*Process, error) {
|
|
// TODO: Probably should have SeDebugPrivilege before starting here.
|
|
err := _DebugActiveProcess(uint32(pid))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exepath, err := findExePath(pid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newDebugProcess(New(pid), exepath)
|
|
}
|
|
|
|
// Kill kills the process.
|
|
func (dbp *Process) Kill() error {
|
|
if dbp.exited {
|
|
return nil
|
|
}
|
|
if !dbp.Threads[dbp.Pid].Stopped() {
|
|
return errors.New("process must be stopped in order to kill it")
|
|
}
|
|
// TODO: Should not have to ignore failures here,
|
|
// but some tests appear to Kill twice causing
|
|
// this to fail on second attempt.
|
|
_ = syscall.TerminateProcess(dbp.os.hProcess, 1)
|
|
dbp.exited = true
|
|
return nil
|
|
}
|
|
|
|
func (dbp *Process) requestManualStop() error {
|
|
return _DebugBreakProcess(dbp.os.hProcess)
|
|
}
|
|
|
|
func (dbp *Process) updateThreadList() error {
|
|
// We ignore this request since threads are being
|
|
// tracked as they are created/killed in waitForDebugEvent.
|
|
return nil
|
|
}
|
|
|
|
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool) (*Thread, error) {
|
|
if thread, ok := dbp.Threads[threadID]; ok {
|
|
return thread, nil
|
|
}
|
|
thread := &Thread{
|
|
ID: threadID,
|
|
dbp: dbp,
|
|
os: new(OSSpecificDetails),
|
|
}
|
|
thread.os.hThread = hThread
|
|
dbp.Threads[threadID] = thread
|
|
if dbp.CurrentThread == nil {
|
|
dbp.SwitchThread(thread.ID)
|
|
}
|
|
if suspendNewThreads {
|
|
_, err := _SuspendThread(thread.os.hThread)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return thread, nil
|
|
}
|
|
|
|
func (dbp *Process) parseDebugFrame(exe *pe.File, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
debugFrameSec := exe.Section(".debug_frame")
|
|
debugInfoSec := exe.Section(".debug_info")
|
|
|
|
if debugFrameSec != nil && debugInfoSec != nil {
|
|
debugFrame, err := debugFrameSec.Data()
|
|
if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size {
|
|
fmt.Println("could not get .debug_frame section", err)
|
|
os.Exit(1)
|
|
}
|
|
if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size {
|
|
debugFrame = debugFrame[:debugFrameSec.VirtualSize]
|
|
}
|
|
dat, err := debugInfoSec.Data()
|
|
if err != nil {
|
|
fmt.Println("could not get .debug_info section", err)
|
|
os.Exit(1)
|
|
}
|
|
dbp.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat))
|
|
} else {
|
|
fmt.Println("could not find .debug_frame section in binary")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
|
|
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
|
|
for _, s := range f.Symbols {
|
|
if s.Name != name {
|
|
continue
|
|
}
|
|
if s.SectionNumber <= 0 {
|
|
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
|
|
}
|
|
if len(f.Sections) < int(s.SectionNumber) {
|
|
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
|
|
}
|
|
return s, nil
|
|
}
|
|
return nil, fmt.Errorf("no %s symbol found", name)
|
|
}
|
|
|
|
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
|
|
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
|
|
ssym, err := findPESymbol(f, sname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
esym, err := findPESymbol(f, ename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ssym.SectionNumber != esym.SectionNumber {
|
|
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
|
|
}
|
|
sect := f.Sections[ssym.SectionNumber-1]
|
|
data, err := sect.Data()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return data[ssym.Value:esym.Value], nil
|
|
}
|
|
|
|
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
|
|
func pcln(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error) {
|
|
var imageBase uint64
|
|
switch oh := exe.OptionalHeader.(type) {
|
|
case *pe.OptionalHeader32:
|
|
imageBase = uint64(oh.ImageBase)
|
|
case *pe.OptionalHeader64:
|
|
imageBase = oh.ImageBase
|
|
default:
|
|
return 0, nil, nil, fmt.Errorf("pe file format not recognized")
|
|
}
|
|
if sect := exe.Section(".text"); sect != nil {
|
|
textStart = imageBase + uint64(sect.VirtualAddress)
|
|
}
|
|
if pclntab, err = loadPETable(exe, "runtime.pclntab", "runtime.epclntab"); err != nil {
|
|
// We didn't find the symbols, so look for the names used in 1.3 and earlier.
|
|
// TODO: Remove code looking for the old symbols when we no longer care about 1.3.
|
|
var err2 error
|
|
if pclntab, err2 = loadPETable(exe, "pclntab", "epclntab"); err2 != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
}
|
|
if symtab, err = loadPETable(exe, "runtime.symtab", "runtime.esymtab"); err != nil {
|
|
// Same as above.
|
|
var err2 error
|
|
if symtab, err2 = loadPETable(exe, "symtab", "esymtab"); err2 != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
}
|
|
return textStart, symtab, pclntab, nil
|
|
}
|
|
|
|
func (dbp *Process) obtainGoSymbols(exe *pe.File, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
_, symdat, pclndat, err := pcln(exe)
|
|
if err != nil {
|
|
fmt.Println("could not get Go symbols", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset))
|
|
tab, err := gosym.NewTable(symdat, pcln)
|
|
if err != nil {
|
|
fmt.Println("could not get initialize line table", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
dbp.goSymTable = tab
|
|
}
|
|
|
|
func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
if sec := exe.Section(".debug_line"); sec != nil {
|
|
debugLine, err := sec.Data()
|
|
if err != nil && uint32(len(debugLine)) < sec.Size {
|
|
fmt.Println("could not get .debug_line section", err)
|
|
os.Exit(1)
|
|
}
|
|
if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size {
|
|
debugLine = debugLine[:sec.VirtualSize]
|
|
}
|
|
dbp.lineInfo = line.Parse(debugLine)
|
|
} else {
|
|
fmt.Println("could not find .debug_line section in binary")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
|
|
|
|
func (dbp *Process) findExecutable(path string) (*pe.File, string, error) {
|
|
peFile, err := openExecutablePath(path)
|
|
if err != nil {
|
|
return nil, path, err
|
|
}
|
|
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
|
|
return nil, path, UnsupportedArchErr
|
|
}
|
|
dbp.dwarf, err = dwarfFromPE(peFile)
|
|
if err != nil {
|
|
return nil, path, err
|
|
}
|
|
return peFile, path, nil
|
|
}
|
|
|
|
func openExecutablePath(path string) (*pe.File, error) {
|
|
f, err := os.OpenFile(path, 0, os.ModePerm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pe.NewFile(f)
|
|
}
|
|
|
|
// Adapted from src/debug/pe/file.go: pe.(*File).DWARF()
|
|
func dwarfFromPE(f *pe.File) (*dwarf.Data, error) {
|
|
// There are many other DWARF sections, but these
|
|
// are the ones the debug/dwarf package uses.
|
|
// Don't bother loading others.
|
|
var names = [...]string{"abbrev", "info", "line", "str"}
|
|
var dat [len(names)][]byte
|
|
for i, name := range names {
|
|
name = ".debug_" + name
|
|
s := f.Section(name)
|
|
if s == nil {
|
|
continue
|
|
}
|
|
b, err := s.Data()
|
|
if err != nil && uint32(len(b)) < s.Size {
|
|
return nil, err
|
|
}
|
|
if 0 < s.VirtualSize && s.VirtualSize < s.Size {
|
|
b = b[:s.VirtualSize]
|
|
}
|
|
dat[i] = b
|
|
}
|
|
|
|
abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
|
|
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
|
|
}
|
|
|
|
type waitForDebugEventFlags int
|
|
|
|
const (
|
|
waitBlocking waitForDebugEventFlags = 1 << iota
|
|
waitSuspendNewThreads
|
|
)
|
|
|
|
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
|
|
var debugEvent _DEBUG_EVENT
|
|
shouldExit := false
|
|
for {
|
|
continueStatus := uint32(_DBG_CONTINUE)
|
|
var milliseconds uint32 = 0
|
|
if flags&waitBlocking != 0 {
|
|
milliseconds = syscall.INFINITE
|
|
}
|
|
// Wait for a debug event...
|
|
err := _WaitForDebugEvent(&debugEvent, milliseconds)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// ... handle each event kind ...
|
|
unionPtr := unsafe.Pointer(&debugEvent.U[0])
|
|
switch debugEvent.DebugEventCode {
|
|
case _CREATE_PROCESS_DEBUG_EVENT:
|
|
debugInfo := (*_CREATE_PROCESS_DEBUG_INFO)(unionPtr)
|
|
hFile := debugInfo.File
|
|
if hFile != 0 && hFile != syscall.InvalidHandle {
|
|
err = syscall.CloseHandle(hFile)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
dbp.os.hProcess = debugInfo.Process
|
|
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
break
|
|
case _CREATE_THREAD_DEBUG_EVENT:
|
|
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
|
|
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
break
|
|
case _EXIT_THREAD_DEBUG_EVENT:
|
|
delete(dbp.Threads, int(debugEvent.ThreadId))
|
|
break
|
|
case _OUTPUT_DEBUG_STRING_EVENT:
|
|
//TODO: Handle debug output strings
|
|
break
|
|
case _LOAD_DLL_DEBUG_EVENT:
|
|
debugInfo := (*_LOAD_DLL_DEBUG_INFO)(unionPtr)
|
|
hFile := debugInfo.File
|
|
if hFile != 0 && hFile != syscall.InvalidHandle {
|
|
err = syscall.CloseHandle(hFile)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
break
|
|
case _UNLOAD_DLL_DEBUG_EVENT:
|
|
break
|
|
case _RIP_EVENT:
|
|
break
|
|
case _EXCEPTION_DEBUG_EVENT:
|
|
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
|
|
if code := exception.ExceptionRecord.ExceptionCode; code == _EXCEPTION_BREAKPOINT || code == _EXCEPTION_SINGLE_STEP {
|
|
tid := int(debugEvent.ThreadId)
|
|
dbp.os.breakThread = tid
|
|
return tid, 0, nil
|
|
} else {
|
|
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
|
|
}
|
|
case _EXIT_PROCESS_DEBUG_EVENT:
|
|
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
|
|
exitCode = int(debugInfo.ExitCode)
|
|
shouldExit = true
|
|
default:
|
|
return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode)
|
|
}
|
|
|
|
// .. and then continue unless we received an event that indicated we should break into debugger.
|
|
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
if shouldExit {
|
|
return 0, exitCode, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|
var err error
|
|
var tid, exitCode int
|
|
dbp.execPtraceFunc(func() {
|
|
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tid == 0 {
|
|
dbp.postExit()
|
|
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
|
|
}
|
|
th := dbp.Threads[tid]
|
|
return th, nil
|
|
}
|
|
|
|
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
|
wg.Done()
|
|
}
|
|
|
|
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
|
return 0, nil, fmt.Errorf("not implemented: wait")
|
|
}
|
|
|
|
func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
|
|
// While the debug event that stopped the target was being propagated
|
|
// other target threads could generate other debug events.
|
|
// After this function we need to know about all the threads
|
|
// stopped on a breakpoint. To do that we first suspend all target
|
|
// threads and then repeatedly call _ContinueDebugEvent followed by
|
|
// waitForDebugEvent in non-blocking mode.
|
|
// We need to explicitly call SuspendThread because otherwise the
|
|
// call to _ContinueDebugEvent will resume execution of some of the
|
|
// target threads.
|
|
|
|
err := trapthread.SetCurrentBreakpoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, thread := range dbp.Threads {
|
|
thread.running = false
|
|
_, err := _SuspendThread(thread.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for {
|
|
var err error
|
|
var tid int
|
|
dbp.execPtraceFunc(func() {
|
|
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
|
if err == nil {
|
|
tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tid == 0 {
|
|
break
|
|
}
|
|
err = dbp.Threads[tid].SetCurrentBreakpoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dbp *Process) exitGuard(err error) error {
|
|
return err
|
|
}
|
|
|
|
func (dbp *Process) resume() error {
|
|
for _, thread := range dbp.Threads {
|
|
if thread.CurrentBreakpoint != nil {
|
|
if err := thread.StepInstruction(); err != nil {
|
|
return err
|
|
}
|
|
thread.CurrentBreakpoint = nil
|
|
}
|
|
}
|
|
|
|
for _, thread := range dbp.Threads {
|
|
thread.running = true
|
|
_, err := _ResumeThread(thread.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func killProcess(pid int) error {
|
|
p, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return p.Kill()
|
|
}
|