//go:build windows // +build windows // https://github.com/mitchellh/go-ps/blob/master/process_windows.go // With addition from me on querying using NTDLL and not just kernel32 package ps import ( "fmt" "syscall" "unsafe" ) // Windows API functions var ( modKernel32 = syscall.NewLazyDLL("kernel32.dll") procCloseHandle = modKernel32.NewProc("CloseHandle") procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot") procProcess32First = modKernel32.NewProc("Process32FirstW") procProcess32Next = modKernel32.NewProc("Process32NextW") // NTDLL modNtDll = windows.NewLazySystemDLL("ntdll.dll") procNtQuerySystemInformation = modNtDll.NewProc("NtQuerySystemInformation") ) // Some constants from the Windows API const ( ERROR_NO_MORE_FILES = 0x12 MAX_PATH = 260 SystemProcessInformation = 5 ) // PROCESSENTRY32 is the Windows API structure that contains a process's // information. type PROCESSENTRY32 struct { Size uint32 CntUsage uint32 ProcessID uint32 DefaultHeapID uintptr ModuleID uint32 CntThreads uint32 ParentProcessID uint32 PriorityClassBase int32 Flags uint32 ExeFile [MAX_PATH]uint16 } // WindowsProcess is an implementation of Process for Windows. type WindowsProcess struct { pid int ppid int exe string } type SYSTEM_PROCESS_INFORMATION struct { NextEntryOffset uint32 NumberOfThreads uint32 Reserved1 [3]uint64 CreateTime int64 UserTime int64 KernelTime int64 ImageName windows.NTUnicodeString BasePriority int32 UniqueProcessID uintptr InheritedFromUniqueProcessID uintptr HandleCount uint32 SessionID uint32 PageDirectoryBase uintptr PeakVirtualSize uint64 VirtualSize uint64 PageFaultCount uint32 PeakWorkingSetSize uint64 WorkingSetSize uint64 QuotaPeakPagedPoolUsage uint64 QuotaPagedPoolUsage uint64 QuotaPeakNonPagedPoolUsage uint64 QuotaNonPagedPoolUsage uint64 PagefileUsage uint64 PeakPagefileUsage uint64 PrivatePageCount uint64 Reserved2 [6]uint64 } // ps provides an API for finding and listing processes in a platform-agnostic // way. // // NOTE: If you're reading these docs online via GoDocs or some other system, // you might only see the Unix docs. This project makes heavy use of // platform-specific implementations. We recommend reading the source if you // are interested. // Process is the generic interface that is implemented on every platform // and provides common operations for processes. type Process interface { // Pid is the process ID for this process. Pid() int // PPid is the parent process ID for this process. PPid() int // Executable name running this process. This is not a path to the // executable. Executable() string } // Processes returns all processes. // // This of course will be a point-in-time snapshot of when this method was // called. Some operating systems don't provide snapshot capability of the // process table, in which case the process table returned might contain // ephemeral entities that happened to be running when this was called. func Processes(method string) ([]Process, error) { if method == "k32" { return processes32() } else if method == "ntdll" { return processesNTDLL() } } // FindProcess looks up a single process by pid. // // Process will be nil and error will be nil if a matching process is // not found. func FindProcess(pid int) (Process, error) { return findProcess(pid) } func (p *WindowsProcess) Pid() int { return p.pid } func (p *WindowsProcess) PPid() int { return p.ppid } func (p *WindowsProcess) Executable() string { return p.exe } func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess { // Find when the string ends for decoding end := 0 for { if e.ExeFile[end] == 0 { break } end++ } return &WindowsProcess{ pid: int(e.ProcessID), ppid: int(e.ParentProcessID), exe: syscall.UTF16ToString(e.ExeFile[:end]), } } func findProcess(pid int) (Process, error) { ps, err := processes() if err != nil { return nil, err } for _, p := range ps { if p.Pid() == pid { return p, nil } } return nil, nil } func processesk32() ([]Process, error) { handle, _, _ := procCreateToolhelp32Snapshot.Call( 0x00000002, 0) if handle < 0 { return nil, syscall.GetLastError() } defer procCloseHandle.Call(handle) var entry PROCESSENTRY32 entry.Size = uint32(unsafe.Sizeof(entry)) ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry))) if ret == 0 { return nil, fmt.Errorf("Error retrieving process info.") } results := make([]Process, 0, 50) for { results = append(results, newWindowsProcess(&entry)) ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) if ret == 0 { break } } return results, nil } func processesNTDLL() ([]SYSTEM_PROCESS_INFORMATION, error) { var buffer [1024 * 1024]byte var returnLength uint32 r1, _, err := procNtQuerySystemInformation.Call( uintptr(SystemProcessInformation), uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(&returnLength)), ) if r1 != 0 { return nil, err } var processes []SYSTEM_PROCESS_INFORMATION offset := 0 for { spi := (*SYSTEM_PROCESS_INFORMATION)(unsafe.Pointer(&buffer[offset])) processes = append(processes, *spi) if spi.NextEntryOffset == 0 { break } offset += int(spi.NextEntryOffset) } return processes, nil }