mirror of https://github.com/beego/bee.git
498 lines
12 KiB
Go
498 lines
12 KiB
Go
package native
|
|
|
|
import (
|
|
"debug/pe"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
sys "golang.org/x/sys/windows"
|
|
|
|
"github.com/derekparker/delve/pkg/proc"
|
|
)
|
|
|
|
// OSProcessDetails holds Windows specific information.
|
|
type OSProcessDetails struct {
|
|
hProcess syscall.Handle
|
|
breakThread int
|
|
entryPoint uint64
|
|
}
|
|
|
|
func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
|
f, err := os.OpenFile(path, 0, os.ModePerm)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
peFile, err := pe.NewFile(f)
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, nil, err
|
|
}
|
|
return peFile, f, nil
|
|
}
|
|
|
|
// Launch creates and begins debugging a new process.
|
|
func Launch(cmd []string, wd string, foreground bool) (*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
|
|
}
|
|
}
|
|
|
|
_, closer, err := openExecutablePathPE(argv0Go)
|
|
if err != nil {
|
|
return nil, proc.ErrNotExecutable
|
|
}
|
|
closer.Close()
|
|
|
|
var p *os.Process
|
|
dbp := New(0)
|
|
dbp.common = proc.NewCommonProcess(true)
|
|
dbp.execPtraceFunc(func() {
|
|
attr := &os.ProcAttr{
|
|
Dir: wd,
|
|
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
|
|
Sys: &syscall.SysProcAttr{
|
|
CreationFlags: _DEBUG_ONLY_THIS_PROCESS,
|
|
},
|
|
}
|
|
p, err = os.StartProcess(argv0Go, cmd, attr)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer p.Release()
|
|
|
|
dbp.pid = p.Pid
|
|
dbp.childProcess = true
|
|
|
|
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, proc.ErrProcessExited{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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
dbp, err := newDebugProcess(New(pid), exepath)
|
|
if err != nil {
|
|
if dbp != nil {
|
|
dbp.Detach(false)
|
|
}
|
|
return nil, err
|
|
}
|
|
return dbp, nil
|
|
}
|
|
|
|
// kill kills the process.
|
|
func (dbp *Process) kill() error {
|
|
if dbp.exited {
|
|
return nil
|
|
}
|
|
|
|
p, err := os.FindProcess(dbp.pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer p.Release()
|
|
|
|
// 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.execPtraceFunc(func() {
|
|
dbp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions)
|
|
})
|
|
|
|
p.Wait()
|
|
|
|
dbp.postExit()
|
|
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 findExecutable(path string, pid int) string {
|
|
return path
|
|
}
|
|
|
|
type waitForDebugEventFlags int
|
|
|
|
const (
|
|
waitBlocking waitForDebugEventFlags = 1 << iota
|
|
waitSuspendNewThreads
|
|
waitDontHandleExceptions
|
|
)
|
|
|
|
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.entryPoint = uint64(debugInfo.BaseOfImage)
|
|
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:
|
|
if flags&waitDontHandleExceptions != 0 {
|
|
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
|
|
break
|
|
}
|
|
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
|
|
tid := int(debugEvent.ThreadId)
|
|
|
|
switch code := exception.ExceptionRecord.ExceptionCode; code {
|
|
case _EXCEPTION_BREAKPOINT:
|
|
|
|
// check if the exception address really is a breakpoint instruction, if
|
|
// it isn't we already removed that breakpoint and we can't deal with
|
|
// this exception anymore.
|
|
atbp := true
|
|
if thread, found := dbp.threads[tid]; found {
|
|
data := make([]byte, dbp.bi.Arch.BreakpointSize())
|
|
if _, err := thread.ReadMemory(data, exception.ExceptionRecord.ExceptionAddress); err == nil {
|
|
instr := dbp.bi.Arch.BreakpointInstruction()
|
|
for i := range instr {
|
|
if data[i] != instr[i] {
|
|
atbp = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !atbp {
|
|
thread.SetPC(uint64(exception.ExceptionRecord.ExceptionAddress))
|
|
}
|
|
}
|
|
|
|
if atbp {
|
|
dbp.os.breakThread = tid
|
|
return tid, 0, nil
|
|
} else {
|
|
continueStatus = _DBG_CONTINUE
|
|
}
|
|
case _EXCEPTION_SINGLE_STEP:
|
|
dbp.os.breakThread = tid
|
|
return tid, 0, nil
|
|
default:
|
|
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, proc.ErrProcessExited{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) exitGuard(err error) error {
|
|
return err
|
|
}
|
|
|
|
func (dbp *Process) resume() error {
|
|
for _, thread := range dbp.threads {
|
|
if thread.CurrentBreakpoint.Breakpoint != nil {
|
|
if err := thread.StepInstruction(); err != nil {
|
|
return err
|
|
}
|
|
thread.CurrentBreakpoint.Clear()
|
|
}
|
|
}
|
|
|
|
for _, thread := range dbp.threads {
|
|
_, err := _ResumeThread(thread.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// stop stops all running threads threads and sets breakpoints
|
|
func (dbp *Process) stop(trapthread *Thread) (err error) {
|
|
if dbp.exited {
|
|
return &proc.ErrProcessExited{Pid: dbp.Pid()}
|
|
}
|
|
|
|
// 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 {
|
|
_, 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) detach(kill bool) error {
|
|
if !kill {
|
|
for _, thread := range dbp.threads {
|
|
_, err := _ResumeThread(thread.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return _DebugActiveProcessStop(uint32(dbp.pid))
|
|
}
|
|
|
|
func (dbp *Process) entryPoint() (uint64, error) {
|
|
return dbp.os.entryPoint, nil
|
|
}
|
|
|
|
func killProcess(pid int) error {
|
|
p, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer p.Release()
|
|
|
|
return p.Kill()
|
|
}
|