go-ps/go-ps.go

231 lines
5.7 KiB
Go
Raw Normal View History

2024-10-21 13:40:27 +02:00
//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
}