winapi_easy/
process.rs

1//! Processes, threads.
2
3use std::ffi::c_void;
4use std::{
5    io,
6    mem,
7    process,
8    ptr,
9};
10
11use ntapi::ntpsapi::NtSetInformationProcess;
12use num_enum::{
13    IntoPrimitive,
14    TryFromPrimitive,
15};
16use windows::Wdk::System::Threading::{
17    NtQueryInformationProcess,
18    ProcessIoPriority,
19};
20use windows::Win32::Foundation::{
21    HANDLE,
22    WAIT_ABANDONED,
23    WAIT_EVENT,
24    WAIT_FAILED,
25    WAIT_OBJECT_0,
26    WAIT_TIMEOUT,
27};
28use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
29use windows::Win32::System::Diagnostics::ToolHelp::{
30    CreateToolhelp32Snapshot,
31    TH32CS_SNAPTHREAD,
32    THREADENTRY32,
33    Thread32First,
34    Thread32Next,
35};
36use windows::Win32::System::Memory::{
37    MEM_COMMIT,
38    MEM_DECOMMIT,
39    MEM_RELEASE,
40    MEM_RESERVE,
41    PAGE_READWRITE,
42    VirtualAllocEx,
43    VirtualFreeEx,
44};
45use windows::Win32::System::Threading::{
46    self,
47    CreateRemoteThreadEx,
48    GetCurrentProcess,
49    GetCurrentProcessId,
50    GetCurrentThread,
51    GetCurrentThreadId,
52    GetProcessId,
53    GetThreadId,
54    INFINITE,
55    OpenProcess,
56    OpenThread,
57    PROCESS_ALL_ACCESS,
58    PROCESS_CREATION_FLAGS,
59    PROCESS_MODE_BACKGROUND_BEGIN,
60    PROCESS_MODE_BACKGROUND_END,
61    ResumeThread,
62    SetPriorityClass,
63    SetThreadPriority,
64    THREAD_ALL_ACCESS,
65    THREAD_MODE_BACKGROUND_BEGIN,
66    THREAD_MODE_BACKGROUND_END,
67    THREAD_PRIORITY,
68    WaitForInputIdle,
69    WaitForSingleObject,
70};
71use windows::Win32::UI::WindowsAndMessaging::{
72    PostThreadMessageW,
73    WM_QUIT,
74};
75
76use crate::internal::{
77    AutoClose,
78    ResultExt,
79    ReturnValue,
80    custom_err_with_code,
81};
82
83/// A Windows process.
84pub struct Process {
85    handle: AutoClose<HANDLE>,
86}
87
88impl Process {
89    /// Constructs a special handle that always points to the current process.
90    pub fn current() -> Self {
91        let pseudo_handle = unsafe { GetCurrentProcess() };
92        Self::from_maybe_null(pseudo_handle)
93            .unwrap_or_else(|| unreachable!("Pseudo process handle should never be null"))
94    }
95
96    /// Tries to acquire a process handle from an ID.
97    ///
98    /// This may fail due to insufficient access rights.
99    pub fn from_id<I>(id: I) -> io::Result<Self>
100    where
101        I: Into<ProcessId>,
102    {
103        let raw_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, id.into().0)? };
104        Ok(Self {
105            handle: raw_handle.into(),
106        })
107    }
108
109    pub fn get_id(&self) -> ProcessId {
110        let id = unsafe { GetProcessId(self.handle.entity) };
111        ProcessId(id)
112    }
113
114    /// Sets the current process to background processing mode.
115    ///
116    /// This will also lower the I/O priority of the process, which will lower the impact of heavy disk I/O on other processes.
117    pub fn begin_background_mode() -> io::Result<()> {
118        unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_BEGIN)? };
119        Ok(())
120    }
121
122    /// Ends background processing mode for the current process.
123    pub fn end_background_mode() -> io::Result<()> {
124        unsafe { SetPriorityClass(Self::current().handle.entity, PROCESS_MODE_BACKGROUND_END)? };
125        Ok(())
126    }
127
128    /// Sets the priority of the process.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use winapi_easy::process::{Process, ProcessPriority};
134    ///
135    /// Process::current().set_priority(ProcessPriority::Normal)?;
136    ///
137    /// # Result::<(), std::io::Error>::Ok(())
138    /// ```
139    pub fn set_priority(&self, priority: ProcessPriority) -> io::Result<()> {
140        unsafe { SetPriorityClass(self.handle.entity, priority.into())? };
141        Ok(())
142    }
143
144    /// Returns the I/O priority of the process.
145    ///
146    /// Will return `None` if it is an unknown value.
147    pub fn get_io_priority(&self) -> io::Result<Option<IoPriority>> {
148        let mut raw_io_priority: i32 = 0;
149        let mut return_length: u32 = 0;
150        let ret_val = unsafe {
151            NtQueryInformationProcess(
152                self.handle.entity,
153                ProcessIoPriority,
154                (&raw mut raw_io_priority).cast::<c_void>(),
155                u32::try_from(mem::size_of::<i32>()).unwrap_or_else(|_| unreachable!()),
156                &raw mut return_length,
157            )
158        };
159        ret_val.0.if_non_null_to_error(|| {
160            custom_err_with_code("Getting IO priority failed", ret_val.0)
161        })?;
162        Ok(IoPriority::try_from(raw_io_priority.cast_unsigned()).ok())
163    }
164
165    pub fn set_io_priority(&self, io_priority: IoPriority) -> io::Result<()> {
166        let mut raw_io_priority = u32::from(io_priority);
167        let ret_val = unsafe {
168            NtSetInformationProcess(
169                self.handle.entity.0.cast::<ntapi::winapi::ctypes::c_void>(),
170                ProcessIoPriority.0.cast_unsigned(),
171                (&raw mut raw_io_priority).cast::<ntapi::winapi::ctypes::c_void>(),
172                u32::try_from(mem::size_of::<u32>()).unwrap_or_else(|_| unreachable!()),
173            )
174        };
175        ret_val
176            .if_non_null_to_error(|| custom_err_with_code("Setting IO priority failed", ret_val))?;
177        Ok(())
178    }
179
180    pub fn wait_for_initialization(&self) -> io::Result<()> {
181        let return_code = unsafe { WaitForInputIdle(self.as_raw_handle(), INFINITE) };
182        let return_code = WAIT_EVENT(return_code);
183        match return_code {
184            WAIT_EVENT(0) => Ok(()),
185            _ if return_code == WAIT_FAILED => Err(io::Error::last_os_error()),
186            _ if return_code == WAIT_TIMEOUT => Err(io::ErrorKind::TimedOut.into()),
187            _ => unreachable!(),
188        }
189    }
190
191    /// Creates a thread in another process.
192    ///
193    /// # Safety
194    ///
195    /// Requires `start_address` to be a function pointer valid in the remote process
196    /// and ABI-compatible with the signature: `unsafe extern "system" fn(*mut std::ffi::c_void) -> u32`
197    pub unsafe fn create_remote_thread(
198        &self,
199        start_address: *const c_void,
200        call_param0: Option<*const c_void>,
201    ) -> io::Result<Thread> {
202        let thread_handle = unsafe {
203            let start_fn =
204                mem::transmute::<*const c_void, unsafe extern "system" fn(_) -> _>(start_address);
205            CreateRemoteThreadEx(
206                self.as_raw_handle(),
207                None,
208                0,
209                Some(start_fn),
210                call_param0,
211                0,
212                None,
213                None,
214            )
215        }?;
216        Ok(Thread::from_non_null(thread_handle))
217    }
218
219    fn as_raw_handle(&self) -> HANDLE {
220        self.handle.entity
221    }
222
223    #[expect(dead_code)]
224    fn from_non_null(handle: HANDLE) -> Self {
225        Self {
226            handle: handle.into(),
227        }
228    }
229
230    fn from_maybe_null(handle: HANDLE) -> Option<Self> {
231        if handle.is_null() {
232            None
233        } else {
234            Some(Self {
235                handle: handle.into(),
236            })
237        }
238    }
239}
240
241impl AsRef<Process> for Process {
242    fn as_ref(&self) -> &Process {
243        self
244    }
245}
246
247impl TryFrom<ProcessId> for Process {
248    type Error = io::Error;
249
250    fn try_from(value: ProcessId) -> Result<Self, Self::Error> {
251        Process::from_id(value)
252    }
253}
254
255impl TryFrom<&process::Child> for Process {
256    type Error = io::Error;
257
258    fn try_from(value: &process::Child) -> Result<Self, Self::Error> {
259        Self::try_from(ProcessId::from(value))
260    }
261}
262
263/// ID of a [`Process`].
264#[derive(Copy, Clone, Eq, PartialEq, Debug)]
265pub struct ProcessId(pub(crate) u32);
266
267impl ProcessId {
268    /// Returns the current process ID.
269    pub fn current() -> Self {
270        Self(unsafe { GetCurrentProcessId() })
271    }
272
273    fn from_raw_id(raw_id: u32) -> Self {
274        Self(raw_id)
275    }
276}
277
278impl From<&process::Child> for ProcessId {
279    fn from(value: &process::Child) -> Self {
280        Self::from_raw_id(value.id())
281    }
282}
283
284/// A thread inside a Windows process.
285pub struct Thread {
286    handle: AutoClose<HANDLE>,
287}
288
289impl Thread {
290    /// Constructs a special handle that always points to the current thread.
291    ///
292    /// When transferred to a different thread, it will point to that thread when used from it.
293    pub fn current() -> Self {
294        let pseudo_handle = unsafe { GetCurrentThread() };
295        Self::from_maybe_null(pseudo_handle)
296            .unwrap_or_else(|| unreachable!("Pseudo thread handle should never be null"))
297    }
298
299    /// Tries to acquire a thread handle from an ID.
300    ///
301    /// This may fail due to insufficient access rights.
302    pub fn from_id<I>(id: I) -> io::Result<Self>
303    where
304        I: Into<ThreadId>,
305    {
306        let raw_handle = unsafe { OpenThread(THREAD_ALL_ACCESS, false, id.into().0)? };
307        Ok(Self {
308            handle: raw_handle.into(),
309        })
310    }
311
312    /// Waits for the thread to finish.
313    pub fn join(&self) -> io::Result<()> {
314        let event = unsafe { WaitForSingleObject(self.handle.entity, INFINITE) };
315        match event {
316            _ if event == WAIT_OBJECT_0 => Ok(()),
317            _ if event == WAIT_FAILED => Err(io::Error::last_os_error()),
318            _ if event == WAIT_ABANDONED => Err(io::ErrorKind::InvalidData.into()),
319            _ if event == WAIT_TIMEOUT => Err(io::ErrorKind::TimedOut.into()),
320            _ => unreachable!(),
321        }
322    }
323
324    /// Resumes a suspended thread.
325    pub fn resume(&self) -> io::Result<()> {
326        unsafe { ResumeThread(self.handle.entity) }
327            .cast_signed()
328            .if_eq_to_error(-1, io::Error::last_os_error)
329    }
330
331    /// Sets the current thread to background processing mode.
332    ///
333    /// This will also lower the I/O priority of the thread, which will lower the impact of heavy disk I/O on other threads and processes.
334    pub fn begin_background_mode() -> io::Result<()> {
335        unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_BEGIN)? };
336        Ok(())
337    }
338
339    /// Ends background processing mode for the current thread.
340    pub fn end_background_mode() -> io::Result<()> {
341        unsafe { SetThreadPriority(Self::current().handle.entity, THREAD_MODE_BACKGROUND_END)? };
342        Ok(())
343    }
344
345    /// Sets the priority of the thread.
346    ///
347    /// # Examples
348    ///
349    /// ```
350    /// use winapi_easy::process::{Thread, ThreadPriority};
351    ///
352    /// Thread::current().set_priority(ThreadPriority::Normal)?;
353    ///
354    /// # Result::<(), std::io::Error>::Ok(())
355    /// ```
356    pub fn set_priority(&self, priority: ThreadPriority) -> Result<(), io::Error> {
357        unsafe { SetThreadPriority(self.handle.entity, priority.into())? };
358        Ok(())
359    }
360
361    pub fn get_id(&self) -> ThreadId {
362        let id = unsafe { GetThreadId(self.handle.entity) };
363        ThreadId(id)
364    }
365
366    fn from_non_null(handle: HANDLE) -> Self {
367        Self {
368            handle: handle.into(),
369        }
370    }
371
372    fn from_maybe_null(handle: HANDLE) -> Option<Self> {
373        if handle.is_null() {
374            None
375        } else {
376            Some(Self {
377                handle: handle.into(),
378            })
379        }
380    }
381}
382
383impl TryFrom<ThreadId> for Thread {
384    type Error = io::Error;
385
386    fn try_from(value: ThreadId) -> Result<Self, Self::Error> {
387        Thread::from_id(value)
388    }
389}
390
391/// ID of a [`Thread`].
392#[derive(Copy, Clone, Eq, PartialEq, Debug)]
393pub struct ThreadId(pub(crate) u32);
394
395impl ThreadId {
396    /// Returns the current thread ID.
397    pub fn current() -> Self {
398        Self(unsafe { GetCurrentThreadId() })
399    }
400
401    pub fn post_quit_message(self) -> io::Result<()> {
402        unsafe { PostThreadMessageW(self.0, WM_QUIT, Default::default(), Default::default())? }
403        Ok(())
404    }
405}
406
407/// Infos about a [`Thread`].
408#[derive(Copy, Clone, Debug)]
409pub struct ThreadInfo {
410    raw_entry: THREADENTRY32,
411}
412
413impl ThreadInfo {
414    /// Returns all threads of all processes.
415    pub fn all_threads() -> io::Result<Vec<Self>> {
416        fn get_empty_thread_entry() -> THREADENTRY32 {
417            THREADENTRY32 {
418                dwSize: mem::size_of::<THREADENTRY32>().try_into().unwrap(),
419                ..Default::default()
420            }
421        }
422        let mut result: Vec<Self> = Vec::new();
423        let snapshot: AutoClose<HANDLE> =
424            unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)? }.into();
425
426        let mut thread_entry = get_empty_thread_entry();
427        unsafe {
428            Thread32First(snapshot.entity, &raw mut thread_entry)?;
429        }
430        result.push(Self::from_raw(thread_entry));
431        loop {
432            let mut thread_entry = get_empty_thread_entry();
433            let next_ret_val = unsafe { Thread32Next(snapshot.entity, &raw mut thread_entry) };
434            if next_ret_val.is_ok() {
435                result.push(Self::from_raw(thread_entry));
436            } else {
437                break;
438            }
439        }
440        Ok(result)
441    }
442
443    /// Returns all threads of the given process.
444    pub fn all_process_threads<P>(process_id: P) -> io::Result<Vec<Self>>
445    where
446        P: Into<ProcessId>,
447    {
448        let pid: ProcessId = process_id.into();
449        let result = Self::all_threads()?
450            .into_iter()
451            .filter(|thread_info| thread_info.get_owner_process_id() == pid)
452            .collect();
453        Ok(result)
454    }
455
456    fn from_raw(raw_info: THREADENTRY32) -> Self {
457        ThreadInfo {
458            raw_entry: raw_info,
459        }
460    }
461
462    /// Returns the ID of the thread.
463    pub fn get_thread_id(&self) -> ThreadId {
464        ThreadId(self.raw_entry.th32ThreadID)
465    }
466
467    /// Returns the ID of the process that contains the thread.
468    pub fn get_owner_process_id(&self) -> ProcessId {
469        ProcessId(self.raw_entry.th32OwnerProcessID)
470    }
471}
472
473/// Process CPU priority.
474#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
475#[repr(u32)]
476pub enum ProcessPriority {
477    Idle = Threading::IDLE_PRIORITY_CLASS.0,
478    BelowNormal = Threading::BELOW_NORMAL_PRIORITY_CLASS.0,
479    Normal = Threading::NORMAL_PRIORITY_CLASS.0,
480    AboveNormal = Threading::ABOVE_NORMAL_PRIORITY_CLASS.0,
481    High = Threading::HIGH_PRIORITY_CLASS.0,
482    Realtime = Threading::REALTIME_PRIORITY_CLASS.0,
483}
484
485impl From<ProcessPriority> for PROCESS_CREATION_FLAGS {
486    fn from(value: ProcessPriority) -> Self {
487        PROCESS_CREATION_FLAGS(value.into())
488    }
489}
490
491/// Thread CPU priority.
492#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
493#[repr(i32)]
494pub enum ThreadPriority {
495    Idle = Threading::THREAD_PRIORITY_IDLE.0,
496    Lowest = Threading::THREAD_PRIORITY_LOWEST.0,
497    BelowNormal = Threading::THREAD_PRIORITY_BELOW_NORMAL.0,
498    Normal = Threading::THREAD_PRIORITY_NORMAL.0,
499    AboveNormal = Threading::THREAD_PRIORITY_ABOVE_NORMAL.0,
500    Highest = Threading::THREAD_PRIORITY_HIGHEST.0,
501    TimeCritical = Threading::THREAD_PRIORITY_TIME_CRITICAL.0,
502}
503
504impl From<ThreadPriority> for THREAD_PRIORITY {
505    fn from(value: ThreadPriority) -> Self {
506        THREAD_PRIORITY(value.into())
507    }
508}
509
510/// Process or thread IO priority. This is independent of the standard CPU priorities.
511#[derive(IntoPrimitive, TryFromPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
512#[repr(u32)]
513pub enum IoPriority {
514    VeryLow = 0,
515    Low = 1,
516    Normal = 2,
517}
518
519pub struct ProcessMemoryAllocation<P: AsRef<Process>> {
520    remote_ptr: *mut c_void,
521    num_bytes: usize,
522    process: P,
523    skipped_reserve: bool,
524}
525
526impl<P: AsRef<Process>> ProcessMemoryAllocation<P> {
527    /// Allocates memory in another process and writes the raw bytes of `*data` into it.
528    ///
529    /// The memory has to be reserved first before it can be committed and used. Skipping reserving the memory
530    /// will try to use previously reserved memory, if available.
531    ///
532    /// # Panics
533    ///
534    /// Will panic if the size of `data` is zero.
535    pub fn with_data<D: ?Sized>(process: P, skip_reserve: bool, data: &D) -> io::Result<Self> {
536        let data_size = mem::size_of_val(data);
537        assert_ne!(data_size, 0);
538        let allocation = Self::new_empty(process, skip_reserve, data_size)?;
539        allocation.write(data)?;
540        Ok(allocation)
541    }
542
543    fn new_empty(process: P, skip_reserve: bool, num_bytes: usize) -> io::Result<Self> {
544        assert_ne!(num_bytes, 0);
545        let mut allocation_type = MEM_COMMIT;
546        if !skip_reserve {
547            // Potentially reserves less than a full 64K block, wasting address space: https://stackoverflow.com/q/31586303
548            allocation_type |= MEM_RESERVE;
549        }
550        let remote_ptr = unsafe {
551            VirtualAllocEx(
552                process.as_ref().as_raw_handle(),
553                None,
554                num_bytes,
555                allocation_type,
556                PAGE_READWRITE,
557            )
558        }
559        .if_null_get_last_error()?;
560        Ok(Self {
561            remote_ptr,
562            num_bytes,
563            process,
564            skipped_reserve: skip_reserve,
565        })
566    }
567
568    fn write<D: ?Sized>(&self, data: &D) -> io::Result<()> {
569        let data_size = mem::size_of_val(data);
570        assert_ne!(data_size, 0);
571        assert!(data_size <= self.num_bytes);
572        unsafe {
573            WriteProcessMemory(
574                self.process.as_ref().as_raw_handle(),
575                self.remote_ptr,
576                ptr::from_ref(data).cast::<c_void>(),
577                data_size,
578                None,
579            )?;
580        }
581        Ok(())
582    }
583
584    fn free(&self) -> io::Result<()> {
585        let free_type = if self.skipped_reserve {
586            MEM_DECOMMIT
587        } else {
588            MEM_RELEASE
589        };
590        unsafe {
591            VirtualFreeEx(
592                self.process.as_ref().as_raw_handle(),
593                self.remote_ptr,
594                0,
595                free_type,
596            )?;
597        }
598        Ok(())
599    }
600}
601
602impl<P: AsRef<Process>> Drop for ProcessMemoryAllocation<P> {
603    fn drop(&mut self) {
604        self.free().unwrap_or_default_and_print_error();
605    }
606}
607
608pub trait CommandExtWE {
609    fn set_creation_flags(&mut self, flags: ProcessCreationFlags) -> &mut process::Command;
610}
611
612impl CommandExtWE for process::Command {
613    fn set_creation_flags(&mut self, flags: ProcessCreationFlags) -> &mut process::Command {
614        use std::os::windows::process::CommandExt;
615        self.creation_flags(flags.into())
616    }
617}
618
619#[derive(IntoPrimitive, Clone, Copy, Eq, PartialEq, Debug)]
620#[repr(u32)]
621pub enum ProcessCreationFlags {
622    CreateSuspended = Threading::CREATE_SUSPENDED.0,
623}
624
625impl From<ProcessCreationFlags> for PROCESS_CREATION_FLAGS {
626    fn from(value: ProcessCreationFlags) -> Self {
627        PROCESS_CREATION_FLAGS(value.into())
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use more_asserts::*;
634
635    use super::*;
636    use crate::module::ExecutableModule;
637    use crate::string::ZeroTerminatedString;
638    #[cfg(feature = "ui")]
639    use crate::ui::window::WindowHandle;
640
641    #[test]
642    fn get_all_threads() -> io::Result<()> {
643        let all_threads = ThreadInfo::all_threads()?;
644        assert_gt!(all_threads.len(), 0);
645        Ok(())
646    }
647
648    #[test]
649    fn get_all_process_threads() -> io::Result<()> {
650        let all_threads = ThreadInfo::all_process_threads(Process::current().get_id())?;
651        assert_gt!(all_threads.len(), 0);
652        Ok(())
653    }
654
655    #[cfg(feature = "ui")]
656    #[test]
657    fn get_all_threads_and_windows() -> io::Result<()> {
658        let all_threads = ThreadInfo::all_threads()?;
659        let all_windows: Vec<WindowHandle> = all_threads
660            .into_iter()
661            .flat_map(|thread_info| WindowHandle::get_nonchild_windows(thread_info.get_thread_id()))
662            .collect();
663        assert_gt!(all_windows.len(), 0);
664        Ok(())
665    }
666
667    #[test]
668    fn set_get_io_priority() -> io::Result<()> {
669        let curr_process = Process::current();
670        let target_priority = IoPriority::Low;
671        curr_process.set_io_priority(target_priority)?;
672        let priority = curr_process.get_io_priority()?.unwrap();
673        assert_eq!(priority, target_priority);
674        Ok(())
675    }
676
677    #[test]
678    fn write_process_memory() -> io::Result<()> {
679        write_process_memory_internal(false)?;
680        write_process_memory_internal(true)?;
681        Ok(())
682    }
683
684    fn write_process_memory_internal(skip_reserve: bool) -> io::Result<()> {
685        let process = Process::current();
686        let memory = ProcessMemoryAllocation::with_data(process, skip_reserve, "123")?;
687        assert!(!memory.remote_ptr.is_null());
688        Ok(())
689    }
690
691    #[test]
692    fn create_remote_thread_locally() -> io::Result<()> {
693        let process = Process::current();
694        let module = ExecutableModule::from_loaded("kernel32.dll")?;
695        let load_library_fn_ptr = module.get_symbol_ptr_by_name("LoadLibraryA")?;
696        let raw_lib_name = ZeroTerminatedString::from("kernel32.dll");
697        let thread = unsafe {
698            process.create_remote_thread(
699                load_library_fn_ptr,
700                Some(raw_lib_name.as_raw_pcstr().as_ptr().cast::<c_void>()),
701            )
702        }?;
703        thread.join()
704    }
705}