From f09ef23f9ec650f18f2530d7164229ea4a119700 Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Thu, 4 Aug 2016 14:44:34 +1000 Subject: [PATCH] proc: implement attach on windows Updates #363 --- proc/proc.go | 10 ++--- proc/proc_windows.go | 80 ++++++++++++++++++++++++++++------------ proc/syscall_windows.go | 5 +++ proc/zsyscall_windows.go | 52 ++++++++++++++++++++------ 4 files changed, 106 insertions(+), 41 deletions(-) diff --git a/proc/proc.go b/proc/proc.go index 8b623284..081ac921 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -79,6 +79,11 @@ func New(pid int) *Process { ptraceChan: make(chan func()), ptraceDoneChan: make(chan interface{}), } + // TODO: find better way to determine proc arch (perhaps use executable file info) + switch runtime.GOARCH { + case "amd64": + dbp.arch = AMD64Arch() + } go dbp.handlePtraceFuncs() return dbp } @@ -704,11 +709,6 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e return nil, err } - switch runtime.GOARCH { - case "amd64": - dbp.arch = AMD64Arch() - } - if err := dbp.updateThreadList(); err != nil { return nil, err } diff --git a/proc/proc_windows.go b/proc/proc_windows.go index 46f39a0f..41948daa 100644 --- a/proc/proc_windows.go +++ b/proc/proc_windows.go @@ -8,21 +8,15 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "sync" "syscall" "unsafe" sys "golang.org/x/sys/windows" - "golang.org/x/debug/dwarf" "github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/line" -) - -const ( - // DEBUGONLYTHISPROCESS tracks https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx - DEBUGONLYTHISPROCESS = 0x00000002 + "golang.org/x/debug/dwarf" ) // OSProcessDetails holds Windows specific information. @@ -97,24 +91,25 @@ func Launch(cmd []string) (*Process, error) { si.StdOutput = sys.Handle(fd[1]) si.StdErr = sys.Handle(fd[2]) pi := new(sys.ProcessInformation) - err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, DEBUGONLYTHISPROCESS, nil, nil, si, pi) + err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi) if err != nil { return nil, err } sys.CloseHandle(sys.Handle(pi.Process)) sys.CloseHandle(sys.Handle(pi.Thread)) - dbp := New(int(pi.ProcessId)) + return newDebugProcess(int(pi.ProcessId), argv0Go) +} - switch runtime.GOARCH { - case "amd64": - dbp.arch = AMD64Arch() - } - - // Note - it should not actually be possible for the +// newDebugProcess prepares process pid for debugging. +func newDebugProcess(pid int, exepath string) (*Process, error) { + dbp := New(pid) + // It should not actually be possible for the // call to waitForDebugEvent to fail, since Windows - // will always fire a CreateProcess event immediately - // after launching under DEBUGONLYTHISPROCESS. + // 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() @@ -126,13 +121,55 @@ func Launch(cmd []string) (*Process, error) { dbp.postExit() return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode} } + return initializeDebugProcess(dbp, exepath, false) +} - return initializeDebugProcess(dbp, argv0Go, 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) { - return nil, fmt.Errorf("not implemented: Attach") + // 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(pid, exepath) } // Kill kills the process. @@ -316,11 +353,6 @@ func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) { var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") func (dbp *Process) findExecutable(path string) (*pe.File, error) { - if path == "" { - // TODO: Find executable path from PID/handle on Windows: - // https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx - return nil, fmt.Errorf("not yet implemented") - } peFile, err := openExecutablePath(path) if err != nil { return nil, err diff --git a/proc/syscall_windows.go b/proc/syscall_windows.go index 039d2c0b..848a3951 100644 --- a/proc/syscall_windows.go +++ b/proc/syscall_windows.go @@ -69,6 +69,9 @@ const ( _UNLOAD_DLL_DEBUG_EVENT = 7 _OUTPUT_DEBUG_STRING_EVENT = 8 _RIP_EVENT = 9 + + // DEBUG_ONLY_THIS_PROCESS tracks https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx + _DEBUG_ONLY_THIS_PROCESS = 0x00000002 ) func _NT_SUCCESS(x _NTSTATUS) bool { @@ -85,3 +88,5 @@ func _NT_SUCCESS(x _NTSTATUS) bool { //sys _ReadProcessMemory(process syscall.Handle, baseaddr uintptr, buffer *byte, size uintptr, bytesread *uintptr) (err error) = kernel32.ReadProcessMemory //sys _DebugBreakProcess(process syscall.Handle) (err error) = kernel32.DebugBreakProcess //sys _WaitForDebugEvent(debugevent *_DEBUG_EVENT, milliseconds uint32) (err error) = kernel32.WaitForDebugEvent +//sys _DebugActiveProcess(processid uint32) (err error) = kernel32.DebugActiveProcess +//sys _QueryFullProcessImageName(process syscall.Handle, flags uint32, exename *uint16, size *uint32) (err error) = kernel32.QueryFullProcessImageNameW diff --git a/proc/zsyscall_windows.go b/proc/zsyscall_windows.go index fe8ba258..8ab0aba3 100644 --- a/proc/zsyscall_windows.go +++ b/proc/zsyscall_windows.go @@ -2,8 +2,10 @@ package proc -import "unsafe" -import "syscall" +import ( + "syscall" + "unsafe" +) var _ unsafe.Pointer @@ -11,16 +13,18 @@ var ( modntdll = syscall.NewLazyDLL("ntdll.dll") modkernel32 = syscall.NewLazyDLL("kernel32.dll") - procNtQueryInformationThread = modntdll.NewProc("NtQueryInformationThread") - procGetThreadContext = modkernel32.NewProc("GetThreadContext") - procSetThreadContext = modkernel32.NewProc("SetThreadContext") - procSuspendThread = modkernel32.NewProc("SuspendThread") - procResumeThread = modkernel32.NewProc("ResumeThread") - procContinueDebugEvent = modkernel32.NewProc("ContinueDebugEvent") - procWriteProcessMemory = modkernel32.NewProc("WriteProcessMemory") - procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") - procDebugBreakProcess = modkernel32.NewProc("DebugBreakProcess") - procWaitForDebugEvent = modkernel32.NewProc("WaitForDebugEvent") + procNtQueryInformationThread = modntdll.NewProc("NtQueryInformationThread") + procGetThreadContext = modkernel32.NewProc("GetThreadContext") + procSetThreadContext = modkernel32.NewProc("SetThreadContext") + procSuspendThread = modkernel32.NewProc("SuspendThread") + procResumeThread = modkernel32.NewProc("ResumeThread") + procContinueDebugEvent = modkernel32.NewProc("ContinueDebugEvent") + procWriteProcessMemory = modkernel32.NewProc("WriteProcessMemory") + procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") + procDebugBreakProcess = modkernel32.NewProc("DebugBreakProcess") + procWaitForDebugEvent = modkernel32.NewProc("WaitForDebugEvent") + procDebugActiveProcess = modkernel32.NewProc("DebugActiveProcess") + procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") ) func _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) { @@ -138,3 +142,27 @@ func _WaitForDebugEvent(debugevent *_DEBUG_EVENT, milliseconds uint32) (err erro } return } + +func _DebugActiveProcess(processid uint32) (err error) { + r1, _, e1 := syscall.Syscall(procDebugActiveProcess.Addr(), 1, uintptr(processid), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _QueryFullProcessImageName(process syscall.Handle, flags uint32, exename *uint16, size *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procQueryFullProcessImageNameW.Addr(), 4, uintptr(process), uintptr(flags), uintptr(unsafe.Pointer(exename)), uintptr(unsafe.Pointer(size)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +}