github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/process/process_windows.go (about) 1 //go:build windows 2 3 package process 4 5 import ( 6 "bufio" 7 "context" 8 "errors" 9 "fmt" 10 "github.com/isyscore/isc-gobase/system/common" 11 "github.com/isyscore/isc-gobase/system/cpu" 12 "github.com/isyscore/isc-gobase/system/net" 13 "golang.org/x/sys/windows" 14 "io" 15 "os" 16 "reflect" 17 "strings" 18 "syscall" 19 "time" 20 "unicode/utf16" 21 "unsafe" 22 ) 23 24 var ( 25 modntdll = windows.NewLazySystemDLL("ntdll.dll") 26 procNtResumeProcess = modntdll.NewProc("NtResumeProcess") 27 procNtSuspendProcess = modntdll.NewProc("NtSuspendProcess") 28 29 modpsapi = windows.NewLazySystemDLL("psapi.dll") 30 procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") 31 procGetProcessImageFileNameW = modpsapi.NewProc("GetProcessImageFileNameW") 32 33 advapi32 = windows.NewLazySystemDLL("advapi32.dll") 34 procLookupPrivilegeValue = advapi32.NewProc("LookupPrivilegeValueW") 35 procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges") 36 37 procQueryFullProcessImageNameW = common.Modkernel32.NewProc("QueryFullProcessImageNameW") 38 procGetPriorityClass = common.Modkernel32.NewProc("GetPriorityClass") 39 procGetProcessIoCounters = common.Modkernel32.NewProc("GetProcessIoCounters") 40 procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") 41 42 processorArchitecture uint 43 ) 44 45 const processQueryInformation = windows.PROCESS_QUERY_LIMITED_INFORMATION 46 47 type SystemProcessInformation struct { 48 NextEntryOffset uint64 49 NumberOfThreads uint64 50 Reserved1 [48]byte 51 Reserved2 [3]byte 52 UniqueProcessID uintptr 53 Reserved3 uintptr 54 HandleCount uint64 55 Reserved4 [4]byte 56 Reserved5 [11]byte 57 PeakPagefileUsage uint64 58 PrivatePageCount uint64 59 Reserved6 [6]uint64 60 } 61 62 type systemProcessorInformation struct { 63 ProcessorArchitecture uint16 64 ProcessorLevel uint16 65 ProcessorRevision uint16 66 Reserved uint16 67 ProcessorFeatureBits uint16 68 } 69 70 type systemInfo struct { 71 wProcessorArchitecture uint16 72 wReserved uint16 73 dwPageSize uint32 74 lpMinimumApplicationAddress uintptr 75 lpMaximumApplicationAddress uintptr 76 dwActiveProcessorMask uintptr 77 dwNumberOfProcessors uint32 78 dwProcessorType uint32 79 dwAllocationGranularity uint32 80 wProcessorLevel uint16 81 wProcessorRevision uint16 82 } 83 84 // MemoryInfoExStat Memory_info_ex is different between OSes 85 type MemoryInfoExStat struct { 86 } 87 88 type MemoryMapsStat struct { 89 } 90 91 // ioCounters is an equivalent representation of IO_COUNTERS in the Windows API. 92 // https://docs.microsoft.com/windows/win32/api/winnt/ns-winnt-io_counters 93 type ioCounters struct { 94 ReadOperationCount uint64 95 WriteOperationCount uint64 96 OtherOperationCount uint64 97 ReadTransferCount uint64 98 WriteTransferCount uint64 99 OtherTransferCount uint64 100 } 101 102 type processBasicInformation32 struct { 103 Reserved1 uint32 104 PebBaseAddress uint32 105 Reserved2 uint32 106 Reserved3 uint32 107 UniqueProcessId uint32 108 Reserved4 uint32 109 } 110 111 type processBasicInformation64 struct { 112 Reserved1 uint64 113 PebBaseAddress uint64 114 Reserved2 uint64 115 Reserved3 uint64 116 UniqueProcessId uint64 117 Reserved4 uint64 118 } 119 120 type processEnvironmentBlock32 struct { 121 Reserved1 [2]uint8 122 BeingDebugged uint8 123 Reserved2 uint8 124 Reserved3 [2]uint32 125 Ldr uint32 126 ProcessParameters uint32 127 // More fields which we don't use so far 128 } 129 130 type processEnvironmentBlock64 struct { 131 Reserved1 [2]uint8 132 BeingDebugged uint8 133 Reserved2 uint8 134 _ [4]uint8 // padding, since we are 64 bit, the next pointer is 64 bit aligned (when compiling for 32 bit, this is not the case without manual padding) 135 Reserved3 [2]uint64 136 Ldr uint64 137 ProcessParameters uint64 138 // More fields which we don't use so far 139 } 140 141 type rtlUserProcessParameters32 struct { 142 Reserved1 [16]uint8 143 ConsoleHandle uint32 144 ConsoleFlags uint32 145 StdInputHandle uint32 146 StdOutputHandle uint32 147 StdErrorHandle uint32 148 CurrentDirectoryPathNameLength uint16 149 _ uint16 // Max Length 150 CurrentDirectoryPathAddress uint32 151 CurrentDirectoryHandle uint32 152 DllPathNameLength uint16 153 _ uint16 // Max Length 154 DllPathAddress uint32 155 ImagePathNameLength uint16 156 _ uint16 // Max Length 157 ImagePathAddress uint32 158 CommandLineLength uint16 159 _ uint16 // Max Length 160 CommandLineAddress uint32 161 EnvironmentAddress uint32 162 // More fields which we don't use so far 163 } 164 165 type rtlUserProcessParameters64 struct { 166 Reserved1 [16]uint8 167 ConsoleHandle uint64 168 ConsoleFlags uint64 169 StdInputHandle uint64 170 StdOutputHandle uint64 171 StdErrorHandle uint64 172 CurrentDirectoryPathNameLength uint16 173 _ uint16 // Max Length 174 _ uint32 // Padding 175 CurrentDirectoryPathAddress uint64 176 CurrentDirectoryHandle uint64 177 DllPathNameLength uint16 178 _ uint16 // Max Length 179 _ uint32 // Padding 180 DllPathAddress uint64 181 ImagePathNameLength uint16 182 _ uint16 // Max Length 183 _ uint32 // Padding 184 ImagePathAddress uint64 185 CommandLineLength uint16 186 _ uint16 // Max Length 187 _ uint32 // Padding 188 CommandLineAddress uint64 189 EnvironmentAddress uint64 190 // More fields which we don't use so far 191 } 192 193 type winLUID struct { 194 LowPart winDWord 195 HighPart winLong 196 } 197 198 // LUID_AND_ATTRIBUTES 199 type winLUIDAndAttributes struct { 200 Luid winLUID 201 Attributes winDWord 202 } 203 204 // TOKEN_PRIVILEGES 205 type winTokenPriviledges struct { 206 PrivilegeCount winDWord 207 Privileges [1]winLUIDAndAttributes 208 } 209 210 type winLong int32 211 type winDWord uint32 212 213 func init() { 214 var systemInfo systemInfo 215 216 _, _, _ = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo))) 217 processorArchitecture = uint(systemInfo.wProcessorArchitecture) 218 219 // enable SeDebugPrivilege https://github.com/midstar/proci/blob/6ec79f57b90ba3d9efa2a7b16ef9c9369d4be875/proci_windows.go#L80-L119 220 handle, err := syscall.GetCurrentProcess() 221 if err != nil { 222 return 223 } 224 225 var token syscall.Token 226 err = syscall.OpenProcessToken(handle, 0x0028, &token) 227 if err != nil { 228 return 229 } 230 defer func(token syscall.Token) { 231 _ = token.Close() 232 }(token) 233 234 tokenPriviledges := winTokenPriviledges{PrivilegeCount: 1} 235 lpName := syscall.StringToUTF16("SeDebugPrivilege") 236 ret, _, _ := procLookupPrivilegeValue.Call( 237 0, 238 uintptr(unsafe.Pointer(&lpName[0])), 239 uintptr(unsafe.Pointer(&tokenPriviledges.Privileges[0].Luid))) 240 if ret == 0 { 241 return 242 } 243 244 tokenPriviledges.Privileges[0].Attributes = 0x00000002 // SE_PRIVILEGE_ENABLED 245 246 _, _, _ = procAdjustTokenPrivileges.Call( 247 uintptr(token), 248 0, 249 uintptr(unsafe.Pointer(&tokenPriviledges)), 250 unsafe.Sizeof(tokenPriviledges), 251 0, 252 0) 253 } 254 255 func pidsWithContext(ctx context.Context) ([]int32, error) { 256 // inspired by https://gist.github.com/henkman/3083408 257 // and https://github.com/giampaolo/psutil/blob/1c3a15f637521ba5c0031283da39c733fda53e4c/psutil/arch/windows/process_info.c#L315-L329 258 var ret []int32 259 var read uint32 = 0 260 var psSize uint32 = 1024 261 const dwordSize uint32 = 4 262 263 for { 264 ps := make([]uint32, psSize) 265 if err := windows.EnumProcesses(ps, &read); err != nil { 266 return nil, err 267 } 268 if uint32(len(ps)) == read { // ps buffer was too small to host every results, retry with a bigger one 269 psSize += 1024 270 continue 271 } 272 for _, pid := range ps[:read/dwordSize] { 273 ret = append(ret, int32(pid)) 274 } 275 return ret, nil 276 277 } 278 279 } 280 281 func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { 282 if pid == 0 { // special case for pid 0 System Idle Process 283 return true, nil 284 } 285 if pid < 0 { 286 return false, fmt.Errorf("invalid pid %v", pid) 287 } 288 if pid%4 != 0 { 289 // OpenProcess will succeed even on non-existing pid here https://devblogs.microsoft.com/oldnewthing/20080606-00/?p=22043 290 // so we list every pid just to be sure and be future-proof 291 pids, err := PidsWithContext(ctx) 292 if err != nil { 293 return false, err 294 } 295 for _, i := range pids { 296 if i == pid { 297 return true, err 298 } 299 } 300 return false, err 301 } 302 const STILL_ACTIVE = 259 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess 303 h, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) 304 if err == windows.ERROR_ACCESS_DENIED { 305 return true, nil 306 } 307 if err == windows.ERROR_INVALID_PARAMETER { 308 return false, nil 309 } 310 if err != nil { 311 return false, err 312 } 313 defer func(handle syscall.Handle) { 314 _ = syscall.CloseHandle(handle) 315 }(syscall.Handle(h)) 316 var exitCode uint32 317 err = windows.GetExitCodeProcess(h, &exitCode) 318 return exitCode == STILL_ACTIVE, err 319 } 320 321 func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { 322 // if cached already, return from cache 323 cachedPpid := p.getPpid() 324 if cachedPpid != 0 { 325 return cachedPpid, nil 326 } 327 328 ppid, _, _, err := getFromSnapProcess(p.Pid) 329 if err != nil { 330 return 0, err 331 } 332 333 // no errors and not cached already, so cache it 334 p.setPpid(ppid) 335 336 return ppid, nil 337 } 338 339 func (p *Process) NameWithContext(ctx context.Context) (string, error) { 340 ppid, _, name, err := getFromSnapProcess(p.Pid) 341 if err != nil { 342 return "", fmt.Errorf("could not get Name: %s", err) 343 } 344 345 // if no errors and not cached already, cache ppid 346 p.parent = ppid 347 if 0 == p.getPpid() { 348 p.setPpid(ppid) 349 } 350 351 return name, nil 352 } 353 354 func (p *Process) TgidWithContext(ctx context.Context) (int32, error) { 355 return 0, common.ErrNotImplementedError 356 } 357 358 func (p *Process) ExeWithContext(ctx context.Context) (string, error) { 359 c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid)) 360 if err != nil { 361 return "", err 362 } 363 defer func(handle windows.Handle) { 364 _ = windows.CloseHandle(handle) 365 }(c) 366 buf := make([]uint16, syscall.MAX_LONG_PATH) 367 size := uint32(syscall.MAX_LONG_PATH) 368 if err := procQueryFullProcessImageNameW.Find(); err == nil { // Vista+ 369 ret, _, err := procQueryFullProcessImageNameW.Call( 370 uintptr(c), 371 uintptr(0), 372 uintptr(unsafe.Pointer(&buf[0])), 373 uintptr(unsafe.Pointer(&size))) 374 if ret == 0 { 375 return "", err 376 } 377 return windows.UTF16ToString(buf[:]), nil 378 } 379 // XP fallback 380 ret, _, err := procGetProcessImageFileNameW.Call(uintptr(c), uintptr(unsafe.Pointer(&buf[0])), uintptr(size)) 381 if ret == 0 { 382 return "", err 383 } 384 return common.ConvertDOSPath(windows.UTF16ToString(buf[:])), nil 385 } 386 387 func (p *Process) CmdlineWithContext(_ context.Context) (string, error) { 388 cmdline, err := getProcessCommandLine(p.Pid) 389 if err != nil { 390 return "", fmt.Errorf("could not get CommandLine: %s", err) 391 } 392 return cmdline, nil 393 } 394 395 func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { 396 cmdline, err := p.CmdlineWithContext(ctx) 397 if err != nil { 398 return nil, err 399 } 400 return strings.Split(cmdline, " "), nil 401 } 402 403 func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { 404 ru, err := getRusage(p.Pid) 405 if err != nil { 406 return 0, fmt.Errorf("could not get CreationDate: %s", err) 407 } 408 409 return ru.CreationTime.Nanoseconds() / 1000000, nil 410 } 411 412 func (p *Process) CwdWithContext(_ context.Context) (string, error) { 413 h, err := windows.OpenProcess(processQueryInformation|windows.PROCESS_VM_READ, false, uint32(p.Pid)) 414 if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER { 415 return "", nil 416 } 417 if err != nil { 418 return "", err 419 } 420 defer func(handle syscall.Handle) { 421 _ = syscall.CloseHandle(handle) 422 }(syscall.Handle(h)) 423 424 procIs32Bits := is32BitProcess(h) 425 426 if procIs32Bits { 427 userProcParams, err := getUserProcessParams32(h) 428 if err != nil { 429 return "", err 430 } 431 if userProcParams.CurrentDirectoryPathNameLength > 0 { 432 cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CurrentDirectoryPathAddress), uint(userProcParams.CurrentDirectoryPathNameLength)) 433 if len(cwd) != int(userProcParams.CurrentDirectoryPathAddress) { 434 return "", errors.New("cannot read current working directory") 435 } 436 437 return convertUTF16ToString(cwd), nil 438 } 439 } else { 440 userProcParams, err := getUserProcessParams64(h) 441 if err != nil { 442 return "", err 443 } 444 if userProcParams.CurrentDirectoryPathNameLength > 0 { 445 cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CurrentDirectoryPathAddress, uint(userProcParams.CurrentDirectoryPathNameLength)) 446 if len(cwd) != int(userProcParams.CurrentDirectoryPathNameLength) { 447 return "", errors.New("cannot read current working directory") 448 } 449 450 return convertUTF16ToString(cwd), nil 451 } 452 } 453 454 //if we reach here, we have no cwd 455 return "", nil 456 } 457 458 func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { 459 ppid, err := p.PpidWithContext(ctx) 460 if err != nil { 461 return nil, fmt.Errorf("could not get ParentProcessID: %s", err) 462 } 463 464 return NewProcessWithContext(ctx, ppid) 465 } 466 467 func (p *Process) StatusWithContext(ctx context.Context) (string, error) { 468 return "", common.ErrNotImplementedError 469 } 470 471 func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) { 472 return false, common.ErrNotImplementedError 473 } 474 475 func (p *Process) UsernameWithContext(ctx context.Context) (string, error) { 476 pid := p.Pid 477 c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) 478 if err != nil { 479 return "", err 480 } 481 defer func(handle windows.Handle) { 482 _ = windows.CloseHandle(handle) 483 }(c) 484 485 var token syscall.Token 486 err = syscall.OpenProcessToken(syscall.Handle(c), syscall.TOKEN_QUERY, &token) 487 if err != nil { 488 return "", err 489 } 490 defer func(token syscall.Token) { 491 _ = token.Close() 492 }(token) 493 tokenUser, err := token.GetTokenUser() 494 if err != nil { 495 return "", err 496 } 497 498 user, domain, _, err := tokenUser.User.Sid.LookupAccount("") 499 return domain + "\\" + user, err 500 } 501 502 func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) { 503 return nil, common.ErrNotImplementedError 504 } 505 506 func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) { 507 return nil, common.ErrNotImplementedError 508 } 509 510 func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) { 511 return nil, common.ErrNotImplementedError 512 } 513 514 func (p *Process) TerminalWithContext(ctx context.Context) (string, error) { 515 return "", common.ErrNotImplementedError 516 } 517 518 // priorityClasses maps a win32 priority class to its WMI equivalent Win32_Process.Priority 519 // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass 520 // https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process 521 var priorityClasses = map[int]int32{ 522 0x00008000: 10, // ABOVE_NORMAL_PRIORITY_CLASS 523 0x00004000: 6, // BELOW_NORMAL_PRIORITY_CLASS 524 0x00000080: 13, // HIGH_PRIORITY_CLASS 525 0x00000040: 4, // IDLE_PRIORITY_CLASS 526 0x00000020: 8, // NORMAL_PRIORITY_CLASS 527 0x00000100: 24, // REALTIME_PRIORITY_CLASS 528 } 529 530 func (p *Process) NiceWithContext(ctx context.Context) (int32, error) { 531 c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid)) 532 if err != nil { 533 return 0, err 534 } 535 defer func(handle windows.Handle) { 536 _ = windows.CloseHandle(handle) 537 }(c) 538 ret, _, err := procGetPriorityClass.Call(uintptr(c)) 539 if ret == 0 { 540 return 0, err 541 } 542 priority, ok := priorityClasses[int(ret)] 543 if !ok { 544 return 0, fmt.Errorf("unknown priority class %v", ret) 545 } 546 return priority, nil 547 } 548 549 func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) { 550 return 0, common.ErrNotImplementedError 551 } 552 553 func (p *Process) RlimitWithContext(ctx context.Context) ([]RlimitStat, error) { 554 return nil, common.ErrNotImplementedError 555 } 556 557 func (p *Process) RlimitUsageWithContext(ctx context.Context, gatherUsed bool) ([]RlimitStat, error) { 558 return nil, common.ErrNotImplementedError 559 } 560 561 func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { 562 c, err := windows.OpenProcess(processQueryInformation, false, uint32(p.Pid)) 563 if err != nil { 564 return nil, err 565 } 566 defer func(handle windows.Handle) { 567 _ = windows.CloseHandle(handle) 568 }(c) 569 var ioCounters ioCounters 570 ret, _, err := procGetProcessIoCounters.Call(uintptr(c), uintptr(unsafe.Pointer(&ioCounters))) 571 if ret == 0 { 572 return nil, err 573 } 574 stats := &IOCountersStat{ 575 ReadCount: ioCounters.ReadOperationCount, 576 ReadBytes: ioCounters.ReadTransferCount, 577 WriteCount: ioCounters.WriteOperationCount, 578 WriteBytes: ioCounters.WriteTransferCount, 579 } 580 581 return stats, nil 582 } 583 584 func (p *Process) NumCtxSwitchesWithContext(ctx context.Context) (*NumCtxSwitchesStat, error) { 585 return nil, common.ErrNotImplementedError 586 } 587 588 func (p *Process) NumFDsWithContext(ctx context.Context) (int32, error) { 589 return 0, common.ErrNotImplementedError 590 } 591 592 func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { 593 ppid, ret, _, err := getFromSnapProcess(p.Pid) 594 if err != nil { 595 return 0, err 596 } 597 598 // if no errors and not cached already, cache ppid 599 p.parent = ppid 600 if 0 == p.getPpid() { 601 p.setPpid(ppid) 602 } 603 604 return ret, nil 605 } 606 607 func (p *Process) ThreadsWithContext(ctx context.Context) (map[int32]*cpu.TimesStat, error) { 608 return nil, common.ErrNotImplementedError 609 } 610 611 func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { 612 sysTimes, err := getProcessCPUTimes(p.Pid) 613 if err != nil { 614 return nil, err 615 } 616 617 // User and kernel times are represented as a FILETIME structure 618 // which contains a 64-bit value representing the number of 619 // 100-nanosecond intervals since January 1, 1601 (UTC): 620 // http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx 621 // To convert it into a float representing the seconds that the 622 // process has executed in user/kernel mode I borrowed the code 623 // below from psutil's _psutil_windows.c, and in turn from Python's 624 // Modules/posixmodule.c 625 626 user := float64(sysTimes.UserTime.HighDateTime)*429.4967296 + float64(sysTimes.UserTime.LowDateTime)*1e-7 627 kernel := float64(sysTimes.KernelTime.HighDateTime)*429.4967296 + float64(sysTimes.KernelTime.LowDateTime)*1e-7 628 629 return &cpu.TimesStat{ 630 User: user, 631 System: kernel, 632 }, nil 633 } 634 635 func (p *Process) CPUAffinityWithContext(ctx context.Context) ([]int32, error) { 636 return nil, common.ErrNotImplementedError 637 } 638 639 func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { 640 mem, err := getMemoryInfo(p.Pid) 641 if err != nil { 642 return nil, err 643 } 644 645 ret := &MemoryInfoStat{ 646 RSS: mem.WorkingSetSize, 647 VMS: mem.PagefileUsage, 648 } 649 650 return ret, nil 651 } 652 653 func (p *Process) MemoryInfoExWithContext(ctx context.Context) (*MemoryInfoExStat, error) { 654 return nil, common.ErrNotImplementedError 655 } 656 657 func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, error) { 658 return nil, common.ErrNotImplementedError 659 } 660 661 func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { 662 var out []*Process 663 snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(0)) 664 if err != nil { 665 return out, err 666 } 667 defer func(handle windows.Handle) { 668 _ = windows.CloseHandle(handle) 669 }(snap) 670 var pe32 windows.ProcessEntry32 671 pe32.Size = uint32(unsafe.Sizeof(pe32)) 672 if err := windows.Process32First(snap, &pe32); err != nil { 673 return out, err 674 } 675 for { 676 if pe32.ParentProcessID == uint32(p.Pid) { 677 p, err := NewProcessWithContext(ctx, int32(pe32.ProcessID)) 678 if err == nil { 679 out = append(out, p) 680 } 681 } 682 if err = windows.Process32Next(snap, &pe32); err != nil { 683 break 684 } 685 } 686 return out, nil 687 } 688 689 func (p *Process) OpenFilesWithContext(ctx context.Context) ([]OpenFilesStat, error) { 690 files := make([]OpenFilesStat, 0) 691 fileExists := make(map[string]bool) 692 693 process, err := windows.OpenProcess(common.ProcessQueryInformation, false, uint32(p.Pid)) 694 if err != nil { 695 return nil, err 696 } 697 698 buffer := make([]byte, 1024) 699 var size uint32 700 701 st := common.CallWithExpandingBuffer( 702 func() common.NtStatus { 703 return common.NtQuerySystemInformation( 704 common.SystemExtendedHandleInformationClass, 705 &buffer[0], 706 uint32(len(buffer)), 707 &size, 708 ) 709 }, 710 &buffer, 711 &size, 712 ) 713 if st.IsError() { 714 return nil, st.Error() 715 } 716 717 handlesList := (*common.SystemExtendedHandleInformation)(unsafe.Pointer(&buffer[0])) 718 handles := make([]common.SystemExtendedHandleTableEntryInformation, int(handlesList.NumberOfHandles)) 719 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&handles)) 720 hdr.Data = uintptr(unsafe.Pointer(&handlesList.Handles[0])) 721 722 currentProcess, err := windows.GetCurrentProcess() 723 if err != nil { 724 return nil, err 725 } 726 727 for _, handle := range handles { 728 var file uintptr 729 if int32(handle.UniqueProcessId) != p.Pid { 730 continue 731 } 732 if windows.DuplicateHandle(process, windows.Handle(handle.HandleValue), currentProcess, (*windows.Handle)(&file), 733 0, true, windows.DUPLICATE_SAME_ACCESS) != nil { 734 continue 735 } 736 fileType, _ := windows.GetFileType(windows.Handle(file)) 737 if fileType != windows.FILE_TYPE_DISK { 738 continue 739 } 740 741 var fileName string 742 ch := make(chan struct{}) 743 744 go func() { 745 var buf [syscall.MAX_LONG_PATH]uint16 746 n, err := windows.GetFinalPathNameByHandle(windows.Handle(file), &buf[0], syscall.MAX_LONG_PATH, 0) 747 if err != nil { 748 return 749 } 750 751 fileName = string(utf16.Decode(buf[:n])) 752 ch <- struct{}{} 753 }() 754 755 select { 756 case <-time.NewTimer(100 * time.Millisecond).C: 757 continue 758 case <-ch: 759 fileInfo, _ := os.Stat(fileName) 760 if fileInfo.IsDir() { 761 continue 762 } 763 764 if _, exists := fileExists[fileName]; !exists { 765 files = append(files, OpenFilesStat{ 766 Path: fileName, 767 Fd: uint64(file), 768 }) 769 fileExists[fileName] = true 770 } 771 case <-ctx.Done(): 772 return files, ctx.Err() 773 } 774 } 775 776 return files, nil 777 } 778 779 func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) { 780 return net.ConnectionsPidWithContext(ctx, "all", p.Pid) 781 } 782 783 func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) { 784 return nil, common.ErrNotImplementedError 785 } 786 787 func (p *Process) NetIOCountersWithContext(ctx context.Context, pernic bool) ([]net.IOCountersStat, error) { 788 return nil, common.ErrNotImplementedError 789 } 790 791 func (p *Process) MemoryMapsWithContext(ctx context.Context, grouped bool) (*[]MemoryMapsStat, error) { 792 return nil, common.ErrNotImplementedError 793 } 794 795 func (p *Process) SendSignalWithContext(ctx context.Context, sig syscall.Signal) error { 796 return common.ErrNotImplementedError 797 } 798 799 func (p *Process) SuspendWithContext(ctx context.Context) error { 800 c, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(p.Pid)) 801 if err != nil { 802 return err 803 } 804 defer func(handle windows.Handle) { 805 _ = windows.CloseHandle(handle) 806 }(c) 807 808 r1, _, _ := procNtSuspendProcess.Call(uintptr(c)) 809 if r1 != 0 { 810 // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55 811 return fmt.Errorf("NtStatus='0x%.8X'", r1) 812 } 813 814 return nil 815 } 816 817 func (p *Process) ResumeWithContext(ctx context.Context) error { 818 c, err := windows.OpenProcess(windows.PROCESS_SUSPEND_RESUME, false, uint32(p.Pid)) 819 if err != nil { 820 return err 821 } 822 defer func(handle windows.Handle) { 823 _ = windows.CloseHandle(handle) 824 }(c) 825 826 r1, _, _ := procNtResumeProcess.Call(uintptr(c)) 827 if r1 != 0 { 828 // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55 829 return fmt.Errorf("NtStatus='0x%.8X'", r1) 830 } 831 832 return nil 833 } 834 835 func (p *Process) TerminateWithContext(ctx context.Context) error { 836 proc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, uint32(p.Pid)) 837 if err != nil { 838 return err 839 } 840 err = windows.TerminateProcess(proc, 0) 841 _ = windows.CloseHandle(proc) 842 return err 843 } 844 845 func (p *Process) KillWithContext(ctx context.Context) error { 846 process, err := os.FindProcess(int(p.Pid)) 847 if err != nil { 848 return err 849 } 850 return process.Kill() 851 } 852 853 func (p *Process) EnvironWithContext(ctx context.Context) ([]string, error) { 854 envVars, err := getProcessEnvironmentVariables(p.Pid, ctx) 855 if err != nil { 856 return nil, fmt.Errorf("could not get environment variables: %s", err) 857 } 858 return envVars, nil 859 } 860 861 // retrieve Ppid in a thread-safe manner 862 func (p *Process) getPpid() int32 { 863 p.parentMutex.RLock() 864 defer p.parentMutex.RUnlock() 865 return p.parent 866 } 867 868 // cache Ppid in a thread-safe manner (WINDOWS ONLY) 869 // see https://psutil.readthedocs.io/en/latest/#psutil.Process.ppid 870 func (p *Process) setPpid(ppid int32) { 871 p.parentMutex.Lock() 872 defer p.parentMutex.Unlock() 873 p.parent = ppid 874 } 875 876 func getFromSnapProcess(pid int32) (int32, int32, string, error) { 877 snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(pid)) 878 if err != nil { 879 return 0, 0, "", err 880 } 881 defer func(handle windows.Handle) { 882 _ = windows.CloseHandle(handle) 883 }(snap) 884 var pe32 windows.ProcessEntry32 885 pe32.Size = uint32(unsafe.Sizeof(pe32)) 886 if err = windows.Process32First(snap, &pe32); err != nil { 887 return 0, 0, "", err 888 } 889 for { 890 if pe32.ProcessID == uint32(pid) { 891 szexe := windows.UTF16ToString(pe32.ExeFile[:]) 892 return int32(pe32.ParentProcessID), int32(pe32.Threads), szexe, nil 893 } 894 if err = windows.Process32Next(snap, &pe32); err != nil { 895 break 896 } 897 } 898 return 0, 0, "", fmt.Errorf("couldn't find pid: %d", pid) 899 } 900 901 func ProcessesWithContext(ctx context.Context) ([]*Process, error) { 902 var out []*Process 903 904 pids, err := PidsWithContext(ctx) 905 if err != nil { 906 return out, fmt.Errorf("could not get Processes %s", err) 907 } 908 909 for _, pid := range pids { 910 p, err := NewProcessWithContext(ctx, pid) 911 if err != nil { 912 continue 913 } 914 out = append(out, p) 915 } 916 917 return out, nil 918 } 919 920 func getRusage(pid int32) (*windows.Rusage, error) { 921 var CPU windows.Rusage 922 923 c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) 924 if err != nil { 925 return nil, err 926 } 927 defer func(handle windows.Handle) { 928 _ = windows.CloseHandle(handle) 929 }(c) 930 931 if err := windows.GetProcessTimes(c, &CPU.CreationTime, &CPU.ExitTime, &CPU.KernelTime, &CPU.UserTime); err != nil { 932 return nil, err 933 } 934 935 return &CPU, nil 936 } 937 938 func getMemoryInfo(pid int32) (PROCESS_MEMORY_COUNTERS, error) { 939 var mem PROCESS_MEMORY_COUNTERS 940 c, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) 941 if err != nil { 942 return mem, err 943 } 944 defer func(handle windows.Handle) { 945 _ = windows.CloseHandle(handle) 946 }(c) 947 if err := getProcessMemoryInfo(c, &mem); err != nil { 948 return mem, err 949 } 950 951 return mem, err 952 } 953 954 func getProcessMemoryInfo(h windows.Handle, mem *PROCESS_MEMORY_COUNTERS) (err error) { 955 r1, _, e1 := syscall.Syscall(procGetProcessMemoryInfo.Addr(), 3, uintptr(h), uintptr(unsafe.Pointer(mem)), uintptr(unsafe.Sizeof(*mem))) 956 if r1 == 0 { 957 if e1 != 0 { 958 err = error(e1) 959 } else { 960 err = syscall.EINVAL 961 } 962 } 963 return 964 } 965 966 type SYSTEM_TIMES struct { 967 CreateTime syscall.Filetime 968 ExitTime syscall.Filetime 969 KernelTime syscall.Filetime 970 UserTime syscall.Filetime 971 } 972 973 func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) { 974 var times SYSTEM_TIMES 975 976 h, err := windows.OpenProcess(processQueryInformation, false, uint32(pid)) 977 if err != nil { 978 return times, err 979 } 980 defer func(handle windows.Handle) { 981 _ = windows.CloseHandle(handle) 982 }(h) 983 984 err = syscall.GetProcessTimes( 985 syscall.Handle(h), 986 ×.CreateTime, 987 ×.ExitTime, 988 ×.KernelTime, 989 ×.UserTime, 990 ) 991 992 return times, err 993 } 994 995 func getUserProcessParams32(handle windows.Handle) (rtlUserProcessParameters32, error) { 996 pebAddress, err := queryPebAddress(syscall.Handle(handle), true) 997 if err != nil { 998 return rtlUserProcessParameters32{}, fmt.Errorf("cannot locate process PEB: %w", err) 999 } 1000 1001 buf := readProcessMemory(syscall.Handle(handle), true, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock32{}))) 1002 if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock32{})) { 1003 return rtlUserProcessParameters32{}, fmt.Errorf("cannot read process PEB") 1004 } 1005 peb := (*processEnvironmentBlock32)(unsafe.Pointer(&buf[0])) 1006 userProcessAddress := uint64(peb.ProcessParameters) 1007 buf = readProcessMemory(syscall.Handle(handle), true, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters32{}))) 1008 if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters32{})) { 1009 return rtlUserProcessParameters32{}, fmt.Errorf("cannot read user process parameters") 1010 } 1011 return *(*rtlUserProcessParameters32)(unsafe.Pointer(&buf[0])), nil 1012 } 1013 1014 func getUserProcessParams64(handle windows.Handle) (rtlUserProcessParameters64, error) { 1015 pebAddress, err := queryPebAddress(syscall.Handle(handle), false) 1016 if err != nil { 1017 return rtlUserProcessParameters64{}, fmt.Errorf("cannot locate process PEB: %w", err) 1018 } 1019 1020 buf := readProcessMemory(syscall.Handle(handle), false, pebAddress, uint(unsafe.Sizeof(processEnvironmentBlock64{}))) 1021 if len(buf) != int(unsafe.Sizeof(processEnvironmentBlock64{})) { 1022 return rtlUserProcessParameters64{}, fmt.Errorf("cannot read process PEB") 1023 } 1024 peb := (*processEnvironmentBlock64)(unsafe.Pointer(&buf[0])) 1025 userProcessAddress := peb.ProcessParameters 1026 buf = readProcessMemory(syscall.Handle(handle), false, userProcessAddress, uint(unsafe.Sizeof(rtlUserProcessParameters64{}))) 1027 if len(buf) != int(unsafe.Sizeof(rtlUserProcessParameters64{})) { 1028 return rtlUserProcessParameters64{}, fmt.Errorf("cannot read user process parameters") 1029 } 1030 return *(*rtlUserProcessParameters64)(unsafe.Pointer(&buf[0])), nil 1031 } 1032 1033 func is32BitProcess(h windows.Handle) bool { 1034 const ( 1035 PROCESSOR_ARCHITECTURE_INTEL = 0 1036 PROCESSOR_ARCHITECTURE_ARM = 5 1037 PROCESSOR_ARCHITECTURE_ARM64 = 12 1038 PROCESSOR_ARCHITECTURE_IA64 = 6 1039 PROCESSOR_ARCHITECTURE_AMD64 = 9 1040 ) 1041 1042 var procIs32Bits bool 1043 switch processorArchitecture { 1044 case PROCESSOR_ARCHITECTURE_INTEL: 1045 fallthrough 1046 case PROCESSOR_ARCHITECTURE_ARM: 1047 procIs32Bits = true 1048 case PROCESSOR_ARCHITECTURE_ARM64: 1049 fallthrough 1050 case PROCESSOR_ARCHITECTURE_IA64: 1051 fallthrough 1052 case PROCESSOR_ARCHITECTURE_AMD64: 1053 var wow64 uint 1054 1055 ret, _, _ := common.ProcNtQueryInformationProcess.Call( 1056 uintptr(h), 1057 uintptr(common.ProcessWow64Information), 1058 uintptr(unsafe.Pointer(&wow64)), 1059 unsafe.Sizeof(wow64), 1060 uintptr(0), 1061 ) 1062 if int(ret) >= 0 { 1063 if wow64 != 0 { 1064 procIs32Bits = true 1065 } 1066 } else { 1067 //if the OS does not support the call, we fallback into the bitness of the app 1068 if unsafe.Sizeof(wow64) == 4 { 1069 procIs32Bits = true 1070 } 1071 } 1072 1073 default: 1074 //for other unknown platforms, we rely on process platform 1075 if unsafe.Sizeof(processorArchitecture) == 8 { 1076 procIs32Bits = false 1077 } else { 1078 procIs32Bits = true 1079 } 1080 } 1081 return procIs32Bits 1082 } 1083 1084 func getProcessEnvironmentVariables(pid int32, ctx context.Context) ([]string, error) { 1085 h, err := windows.OpenProcess(processQueryInformation|windows.PROCESS_VM_READ, false, uint32(pid)) 1086 if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER { 1087 return nil, nil 1088 } 1089 if err != nil { 1090 return nil, err 1091 } 1092 defer func(handle syscall.Handle) { 1093 _ = syscall.CloseHandle(handle) 1094 }(syscall.Handle(h)) 1095 1096 procIs32Bits := is32BitProcess(h) 1097 1098 var processParameterBlockAddress uint64 1099 1100 if procIs32Bits { 1101 peb, err := getUserProcessParams32(h) 1102 if err != nil { 1103 return nil, err 1104 } 1105 processParameterBlockAddress = uint64(peb.EnvironmentAddress) 1106 } else { 1107 peb, err := getUserProcessParams64(h) 1108 if err != nil { 1109 return nil, err 1110 } 1111 processParameterBlockAddress = peb.EnvironmentAddress 1112 } 1113 envvarScanner := bufio.NewScanner(&processReader{ 1114 processHandle: h, 1115 is32BitProcess: procIs32Bits, 1116 offset: processParameterBlockAddress, 1117 }) 1118 envvarScanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { 1119 if atEOF && len(data) == 0 { 1120 return 0, nil, nil 1121 } 1122 // Check for UTF-16 zero character 1123 for i := 0; i < len(data)-1; i += 2 { 1124 if data[i] == 0 && data[i+1] == 0 { 1125 return i + 2, data[0:i], nil 1126 } 1127 } 1128 if atEOF { 1129 return len(data), data, nil 1130 } 1131 // Request more data 1132 return 0, nil, nil 1133 }) 1134 var envVars []string 1135 for envvarScanner.Scan() { 1136 entry := envvarScanner.Bytes() 1137 if len(entry) == 0 { 1138 break // Block is finished 1139 } 1140 envVars = append(envVars, convertUTF16ToString(entry)) 1141 select { 1142 case <-ctx.Done(): 1143 break 1144 default: 1145 continue 1146 } 1147 } 1148 if err := envvarScanner.Err(); err != nil { 1149 return nil, err 1150 } 1151 return envVars, nil 1152 } 1153 1154 type processReader struct { 1155 processHandle windows.Handle 1156 is32BitProcess bool 1157 offset uint64 1158 } 1159 1160 func (p *processReader) Read(buf []byte) (int, error) { 1161 processMemory := readProcessMemory(syscall.Handle(p.processHandle), p.is32BitProcess, p.offset, uint(len(buf))) 1162 if len(processMemory) == 0 { 1163 return 0, io.EOF 1164 } 1165 copy(buf, processMemory) 1166 p.offset += uint64(len(processMemory)) 1167 return len(processMemory), nil 1168 } 1169 1170 func getProcessCommandLine(pid int32) (string, error) { 1171 h, err := windows.OpenProcess(processQueryInformation|windows.PROCESS_VM_READ, false, uint32(pid)) 1172 if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER { 1173 return "", nil 1174 } 1175 if err != nil { 1176 return "", err 1177 } 1178 defer func(handle syscall.Handle) { 1179 _ = syscall.CloseHandle(handle) 1180 }(syscall.Handle(h)) 1181 1182 procIs32Bits := is32BitProcess(h) 1183 1184 if procIs32Bits { 1185 userProcParams, err := getUserProcessParams32(h) 1186 if err != nil { 1187 return "", err 1188 } 1189 if userProcParams.CommandLineLength > 0 { 1190 cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CommandLineAddress), uint(userProcParams.CommandLineLength)) 1191 if len(cmdLine) != int(userProcParams.CommandLineLength) { 1192 return "", errors.New("cannot read cmdline") 1193 } 1194 1195 return convertUTF16ToString(cmdLine), nil 1196 } 1197 } else { 1198 userProcParams, err := getUserProcessParams64(h) 1199 if err != nil { 1200 return "", err 1201 } 1202 if userProcParams.CommandLineLength > 0 { 1203 cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams.CommandLineAddress, uint(userProcParams.CommandLineLength)) 1204 if len(cmdLine) != int(userProcParams.CommandLineLength) { 1205 return "", errors.New("cannot read cmdline") 1206 } 1207 1208 return convertUTF16ToString(cmdLine), nil 1209 } 1210 } 1211 1212 //if we reach here, we have no command line 1213 return "", nil 1214 } 1215 1216 func convertUTF16ToString(src []byte) string { 1217 srcLen := len(src) / 2 1218 1219 codePoints := make([]uint16, srcLen) 1220 1221 srcIdx := 0 1222 for i := 0; i < srcLen; i++ { 1223 codePoints[i] = uint16(src[srcIdx]) | uint16(src[srcIdx+1])<<8 1224 srcIdx += 2 1225 } 1226 return syscall.UTF16ToString(codePoints) 1227 }