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