Skip to main content

windows_erg/process/
modules.rs

1//! Module (DLL) enumeration.
2
3use windows::Win32::Foundation::HMODULE;
4use windows::Win32::System::ProcessStatus::{
5    EnumProcessModules, GetModuleBaseNameW, GetModuleFileNameExW, GetModuleInformation, MODULEINFO,
6};
7
8use super::processes::Process;
9use super::types::{ImagePath, ModuleInfo};
10use crate::error::{Error, ProcessError, ProcessOpenError, Result};
11
12impl Process {
13    /// Enumerate all modules (DLLs) loaded in this process.
14    pub fn modules(&self) -> Result<Vec<ModuleInfo>> {
15        let mut out_modules = Vec::with_capacity(32);
16        let mut work_buffer = Vec::with_capacity(8192);
17        self.modules_with_buffer(&mut out_modules, &mut work_buffer)?;
18        Ok(out_modules)
19    }
20
21    /// Enumerate modules using reusable output and work buffers.
22    ///
23    /// # Arguments
24    /// - `out_modules`: Output buffer to store ModuleInfo results
25    /// - `work_buffer`: Work buffer for module handles and string data (reused across calls)
26    pub fn modules_with_buffer(
27        &self,
28        out_modules: &mut Vec<ModuleInfo>,
29        work_buffer: &mut Vec<u8>,
30    ) -> Result<usize> {
31        self.modules_with_filter_impl(out_modules, work_buffer, |_| true)
32    }
33
34    /// Enumerate modules matching a filter using reusable output and work buffers.
35    ///
36    /// The filter function is called for each module. Only modules where the filter
37    /// returns `true` are added to the output buffer. This is more efficient than enumerating all
38    /// and filtering afterwards.
39    ///
40    /// # Arguments
41    /// - `out_modules`: Output buffer to store ModuleInfo results
42    /// - `work_buffer`: Work buffer for module handles and string data (should be reused)
43    /// - `filter`: Predicate function to filter modules
44    ///
45    /// Returns the number of matching modules found and added to the buffer.
46    pub fn modules_with_filter<F>(
47        &self,
48        out_modules: &mut Vec<ModuleInfo>,
49        work_buffer: &mut Vec<u8>,
50        filter: F,
51    ) -> Result<usize>
52    where
53        F: Fn(&ModuleInfo) -> bool,
54    {
55        self.modules_with_filter_impl(out_modules, work_buffer, filter)
56    }
57
58    /// Internal: Enumerate modules with pre-allocated work buffer.
59    fn modules_with_filter_impl<F>(
60        &self,
61        out_modules: &mut Vec<ModuleInfo>,
62        work_buffer: &mut Vec<u8>,
63        filter: F,
64    ) -> Result<usize>
65    where
66        F: Fn(&ModuleInfo) -> bool,
67    {
68        out_modules.clear();
69
70        // Ensure work buffer is large enough for module handles (8KB)
71        if work_buffer.capacity() < 8192 {
72            work_buffer.reserve(8192 - work_buffer.capacity());
73        }
74        unsafe {
75            work_buffer.set_len(8192);
76        }
77
78        let mut bytes_needed = 0u32;
79
80        // First call to get size
81        unsafe {
82            EnumProcessModules(
83                self.as_raw_handle(),
84                work_buffer.as_mut_ptr() as *mut HMODULE,
85                work_buffer.len() as u32,
86                &mut bytes_needed,
87            )
88        }
89        .map_err(|e| {
90            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
91                self.id().as_u32(),
92                "Failed to enumerate modules (first call)",
93                e.code().0,
94            )))
95        })?;
96
97        // Resize if needed
98        if bytes_needed as usize > work_buffer.len() {
99            work_buffer.clear();
100            if work_buffer.capacity() < bytes_needed as usize {
101                work_buffer.reserve(bytes_needed as usize - work_buffer.capacity());
102            }
103            unsafe {
104                work_buffer.set_len(bytes_needed as usize);
105            }
106        }
107
108        // Second call with correct size
109        unsafe {
110            EnumProcessModules(
111                self.as_raw_handle(),
112                work_buffer.as_mut_ptr() as *mut HMODULE,
113                work_buffer.len() as u32,
114                &mut bytes_needed,
115            )
116        }
117        .map_err(|e| {
118            Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
119                self.id().as_u32(),
120                "Failed to enumerate modules (second call)",
121                e.code().0,
122            )))
123        })?;
124
125        let module_count = bytes_needed as usize / std::mem::size_of::<HMODULE>();
126        let module_handles = unsafe {
127            std::slice::from_raw_parts(work_buffer.as_ptr() as *const HMODULE, module_count)
128        };
129
130        // Reuse work buffer for string data starting after module handles
131        // Ensure it has enough space for string operations (need room for module name and path)
132        let string_buffer_start = bytes_needed as usize;
133        if work_buffer.len() < string_buffer_start + 2048 {
134            let needed = string_buffer_start + 2048;
135            if work_buffer.capacity() < needed {
136                work_buffer.reserve(needed - work_buffer.capacity());
137            }
138            unsafe {
139                work_buffer.set_len(needed);
140            }
141        }
142
143        let string_buffer_ptr =
144            unsafe { work_buffer.as_mut_ptr().add(string_buffer_start) as *mut u16 };
145        let string_buffer_len = (work_buffer.len() - string_buffer_start) / 2;
146        let string_buffer_slice =
147            unsafe { std::slice::from_raw_parts_mut(string_buffer_ptr, string_buffer_len) };
148
149        for &hmodule in module_handles {
150            // Get module name
151            let name_len =
152                unsafe { GetModuleBaseNameW(self.as_raw_handle(), hmodule, string_buffer_slice) }
153                    as usize;
154
155            let name = if name_len > 0 {
156                String::from_utf16_lossy(&string_buffer_slice[..name_len])
157            } else {
158                continue;
159            };
160
161            // Get full path (reuse same buffer)
162            let path_len =
163                unsafe { GetModuleFileNameExW(self.as_raw_handle(), hmodule, string_buffer_slice) }
164                    as usize;
165
166            let path = if path_len > 0 {
167                // Use from_utf16 for efficient cache lookup without intermediate String
168                ImagePath::from_utf16(&string_buffer_slice[..path_len])
169            } else {
170                ImagePath::from_str(&name)
171            };
172
173            // Get module info (base address and size)
174            let mut mod_info = MODULEINFO::default();
175            let (base_address, size) = if unsafe {
176                GetModuleInformation(
177                    self.as_raw_handle(),
178                    hmodule,
179                    &mut mod_info,
180                    std::mem::size_of::<MODULEINFO>() as u32,
181                )
182            }
183            .is_ok()
184            {
185                (mod_info.lpBaseOfDll as usize, mod_info.SizeOfImage)
186            } else {
187                (0, 0)
188            };
189
190            let module_info = ModuleInfo {
191                name,
192                path,
193                base_address,
194                size,
195            };
196
197            // Apply filter and add to output buffer if it matches
198            if filter(&module_info) {
199                out_modules.push(module_info);
200            }
201        }
202
203        Ok(out_modules.len())
204    }
205}