diff --git a/sigar_darwin.go b/sigar_darwin.go index 90be0f227..487aa2a03 100644 --- a/sigar_darwin.go +++ b/sigar_darwin.go @@ -23,6 +23,8 @@ import ( "syscall" "time" "unsafe" + + "golang.org/x/sys/unix" ) func (self *LoadAverage) Get() error { //nolint:staticcheck @@ -417,21 +419,23 @@ func vm_info(vmstat *C.vm_statistics_data_t) error { // generic Sysctl buffer unmarshalling func sysctlbyname(name string, data interface{}) (err error) { - val, err := syscall.Sysctl(name) - if err != nil { - return err - } - - buf := []byte(val) - switch v := data.(type) { case *uint64: - *v = *(*uint64)(unsafe.Pointer(&buf[0])) - return - } + res, err := unix.SysctlUint64(name) + if err != nil { + return err + } + *v = res + return nil + default: + val, err := syscall.Sysctl(name) + if err != nil { + return err + } - bbuf := bytes.NewBuffer([]byte(val)) - return binary.Read(bbuf, binary.LittleEndian, data) + bbuf := bytes.NewBuffer([]byte(val)) + return binary.Read(bbuf, binary.LittleEndian, data) + } } // syscall.Getfsstat() wrapper is broken, roll our own to workaround. diff --git a/sigar_windows.go b/sigar_windows.go index c22c5eb7e..4406cb1e2 100644 --- a/sigar_windows.go +++ b/sigar_windows.go @@ -186,7 +186,26 @@ func (self *ProcTime) Get(pid int) error { //nolint:staticcheck } func (self *ProcArgs) Get(pid int) error { //nolint:staticcheck - return ErrNotImplemented + handle, err := syscall.OpenProcess(processQueryLimitedInfoAccess|windows.PROCESS_VM_READ, false, uint32(pid)) + if err != nil { + return errors.Wrapf(err, "OpenProcess failed for pid=%v", pid) + } + defer func() { _ = syscall.CloseHandle(handle) }() + pbi, err := windows.NtQueryProcessBasicInformation(handle) + if err != nil { + return errors.Wrapf(err, "NtQueryProcessBasicInformation failed for pid=%v", pid) + } + userProcParams, err := windows.GetUserProcessParams(handle, pbi) + if err != nil { + return nil + } + if argsW, err := windows.ReadProcessUnicodeString(handle, &userProcParams.CommandLine); err == nil { + self.List, err = windows.ByteSliceToStringSlice(argsW) + if err != nil { + return err + } + } + return nil } func (self *ProcExe) Get(pid int) error { //nolint:staticcheck diff --git a/sys/windows/syscall_windows.go b/sys/windows/syscall_windows.go index 7f74945a8..53d586fb2 100644 --- a/sys/windows/syscall_windows.go +++ b/sys/windows/syscall_windows.go @@ -361,6 +361,99 @@ func Process32Next(handle syscall.Handle) (ProcessEntry32, error) { return processEntry32, nil } +type UnicodeString struct { + Length uint16 + MaximumLength uint16 + Buffer uintptr +} + +type RtlUserProcessParameters struct { + Reserved1 [16]byte + Reserved2 [10]uintptr + ImagePathName UnicodeString + CommandLine UnicodeString +} + +func GetUserProcessParams(handle syscall.Handle, pbi ProcessBasicInformation) (*RtlUserProcessParameters, error) { + const pebUserProcessParametersOffset = 0x20 + userProcParamsAddr := pbi.PebBaseAddress + pebUserProcessParametersOffset + + var userProcParamsPtr uintptr + err := ReadProcessMemory(handle, userProcParamsAddr, (*byte)(unsafe.Pointer(&userProcParamsPtr)), unsafe.Sizeof(userProcParamsPtr)) + if err != nil { + return nil, errors.Wrap(err, "ReadProcessMemory failed for user process parameters pointer") + } + + userProcParams := &RtlUserProcessParameters{} + err = ReadProcessMemory(handle, userProcParamsPtr, (*byte)(unsafe.Pointer(userProcParams)), unsafe.Sizeof(*userProcParams)) + if err != nil { + return nil, errors.Wrap(err, "ReadProcessMemory failed for user process parameters") + } + + return userProcParams, nil +} + +func ReadProcessUnicodeString(handle syscall.Handle, s *UnicodeString) ([]byte, error) { + if s.Length == 0 || s.Buffer == 0 { + return nil, errors.New("UnicodeString is empty") + } + + buf := make([]byte, s.Length) + err := ReadProcessMemory(handle, s.Buffer, &buf[0], uintptr(s.Length)) + if err != nil { + return nil, errors.Wrap(err, "ReadProcessMemory failed for UnicodeString") + } + + return buf, nil +} + +func ByteSliceToStringSlice(buf []byte) ([]string, error) { + if len(buf) == 0 { + return nil, errors.New("empty buffer") + } + + words := make([]uint16, len(buf)/2) + for i := range words { + words[i] = uint16(buf[i*2]) | uint16(buf[i*2+1])<<8 + } + + var args []string + var start int + for i, w := range words { + if w == 0 { + if i > start { + args = append(args, syscall.UTF16ToString(words[start:i])) + } + start = i + 1 + } + } + if start < len(words) { + args = append(args, syscall.UTF16ToString(words[start:])) + } + + return args, nil +} + +func ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, dest *byte, size uintptr) error { + var numRead uintptr + err := _ReadProcessMemory(handle, baseAddress, dest, size, &numRead) + if err != nil { + return errors.Wrap(err, "ReadProcessMemory failed") + } + if numRead != size { + return errors.Errorf("ReadProcessMemory read %d bytes, expected %d", numRead, size) + } + return nil +} + +func GetTickCount64() (uint64, error) { + ticks, err := _GetTickCount64() + if err != nil { + return 0, errors.Wrap(err, "GetTickCount64 failed") + } + return ticks, nil +} + // Use "GOOS=windows go generate -v -x ." to generate the source. // Add -trace to enable debug prints around syscalls. @@ -383,3 +476,5 @@ func Process32Next(handle syscall.Handle) (ProcessEntry32, error) { //sys _LookupPrivilegeName(systemName string, luid *int64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW //sys _LookupPrivilegeValue(systemName string, name string, luid *int64) (err error) = advapi32.LookupPrivilegeValueW //sys _AdjustTokenPrivileges(token syscall.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges +//sys _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer *byte, size uintptr, numRead *uintptr) (err error) = kernel32.ReadProcessMemory +//sys _GetTickCount64() (milliseconds uint64, err error) = kernel32.GetTickCount64 diff --git a/sys/windows/zsyscall_windows.go b/sys/windows/zsyscall_windows.go index fb07cbf1d..c3d2885c8 100644 --- a/sys/windows/zsyscall_windows.go +++ b/sys/windows/zsyscall_windows.go @@ -31,6 +31,8 @@ var ( procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") + procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") + procGetTickCount64 = modkernel32.NewProc("GetTickCount64") ) func _GlobalMemoryStatusEx(buffer *MemoryStatusEx) (err error) { @@ -201,7 +203,7 @@ func _LookupPrivilegeName(systemName string, luid *int64, buffer *uint16, size * if err != nil { return } - return __LookupPrivilegeName(_p0, luid, buffer, size) + return } func __LookupPrivilegeName(systemName *uint16, luid *int64, buffer *uint16, size *uint32) (err error) { @@ -260,3 +262,29 @@ func _AdjustTokenPrivileges(token syscall.Token, releaseAll bool, input *byte, o } return } + +func _ReadProcessMemory(handle syscall.Handle, baseAddress uintptr, buffer *byte, size uintptr, numRead *uintptr) (err error) { + r1, _, e1 := syscall.SyscallN(procReadProcessMemory.Addr(), uintptr(handle), uintptr(baseAddress), uintptr(unsafe.Pointer(buffer)), uintptr(size), uintptr(unsafe.Pointer(numRead))) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func _GetTickCount64() (milliseconds uint64, err error) { + r0, _, e1 := syscall.SyscallN(procGetTickCount64.Addr()) + milliseconds = uint64(r0) + if milliseconds == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} +