1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{Error, ProcessInfo, ProcessState, PsTreeEntry, Result};
11
12pub fn walk_processes<P: PhysicalMemoryProvider>(
14 reader: &ObjectReader<P>,
15) -> Result<Vec<ProcessInfo>> {
16 let init_task_addr = reader.required_symbol("init_task")?;
17 let tasks_offset = reader.required_field_offset("task_struct", "tasks")?;
18
19 let head_vaddr = init_task_addr + tasks_offset as u64;
20 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
21
22 let mut processes = Vec::new();
23
24 if let Ok(info) = read_process_info(reader, init_task_addr) {
26 processes.push(info);
27 }
28
29 for &task_addr in &task_addrs {
30 if let Ok(info) = read_process_info(reader, task_addr) {
31 processes.push(info);
32 }
33 }
34
35 processes.sort_by_key(|p| p.pid);
36 Ok(processes)
37}
38
39pub fn build_pstree(procs: &[ProcessInfo]) -> Vec<PsTreeEntry> {
45 use std::collections::{HashMap, HashSet};
46
47 let pid_set: HashSet<u64> = procs.iter().map(|p| p.pid).collect();
48 let mut children: HashMap<u64, Vec<usize>> = HashMap::new();
49 let mut roots = Vec::new();
50
51 for (i, proc) in procs.iter().enumerate() {
52 if proc.ppid == 0 || !pid_set.contains(&proc.ppid) {
53 roots.push(i);
54 } else {
55 children.entry(proc.ppid).or_default().push(i);
56 }
57 }
58
59 roots.sort_by_key(|&i| procs[i].pid);
61 for kids in children.values_mut() {
62 kids.sort_by_key(|&i| procs[i].pid);
63 }
64
65 let mut result = Vec::with_capacity(procs.len());
67 let mut stack: Vec<(usize, u32)> = roots.into_iter().rev().map(|i| (i, 0)).collect();
68
69 while let Some((idx, depth)) = stack.pop() {
70 result.push(PsTreeEntry {
71 process: procs[idx].clone(),
72 depth,
73 });
74 if let Some(kids) = children.get(&procs[idx].pid) {
75 for &kid_idx in kids.iter().rev() {
76 stack.push((kid_idx, depth + 1));
77 }
78 }
79 }
80
81 result
82}
83
84fn read_process_info<P: PhysicalMemoryProvider>(
85 reader: &ObjectReader<P>,
86 task_addr: u64,
87) -> Result<ProcessInfo> {
88 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
89 let state: i64 = reader.read_field(task_addr, "task_struct", "state")?;
90 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
91 let ppid = read_parent_pid(reader, task_addr).unwrap_or(0);
92 let cr3 = read_cr3(reader, task_addr).ok();
93
94 let start_time: u64 = reader
98 .read_field(task_addr, "task_struct", "real_start_time")
99 .or_else(|_| reader.read_field(task_addr, "task_struct", "start_time"))
100 .unwrap_or(0);
101
102 Ok(ProcessInfo {
103 pid: u64::from(pid),
104 ppid,
105 comm,
106 state: ProcessState::from_raw(state),
107 vaddr: task_addr,
108 cr3,
109 start_time,
110 })
111}
112
113fn read_parent_pid<P: PhysicalMemoryProvider>(
114 reader: &ObjectReader<P>,
115 task_addr: u64,
116) -> Result<u64> {
117 let parent_ptr: u64 = reader.read_field(task_addr, "task_struct", "real_parent")?;
118 if parent_ptr == 0 {
119 return Ok(0);
120 }
121 let ppid: u32 = reader.read_field(parent_ptr, "task_struct", "pid")?;
122 Ok(u64::from(ppid))
123}
124
125fn read_cr3<P: PhysicalMemoryProvider>(reader: &ObjectReader<P>, task_addr: u64) -> Result<u64> {
126 let mm_ptr: u64 = reader.read_field(task_addr, "task_struct", "mm")?;
127 if mm_ptr == 0 {
128 return Err(Error::WalkFailed {
129 walker: "read_cr3",
130 reason: "mm is NULL (kernel thread)".into(),
131 });
132 }
133 let pgd: u64 = reader.read_field(mm_ptr, "mm_struct", "pgd")?;
134 Ok(pgd)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::testing::make_reader;
141 use memf_core::object_reader::ObjectReader;
142 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
143 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
144 use memf_symbols::isf::IsfResolver;
145 use memf_symbols::test_builders::IsfBuilder;
146
147 const START_TIME_OFF: usize = 88;
159
160 fn make_test_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
161 let isf = IsfBuilder::new()
162 .add_struct("task_struct", 128)
163 .add_field("task_struct", "pid", 0, "int")
164 .add_field("task_struct", "state", 4, "long")
165 .add_field("task_struct", "tasks", 16, "list_head")
166 .add_field("task_struct", "comm", 32, "char")
167 .add_field("task_struct", "mm", 48, "pointer")
168 .add_field("task_struct", "real_parent", 56, "pointer")
169 .add_field("task_struct", "start_time", 88, "unsigned long")
170 .add_struct("list_head", 16)
171 .add_field("list_head", "next", 0, "pointer")
172 .add_field("list_head", "prev", 8, "pointer")
173 .add_struct("mm_struct", 128)
174 .add_field("mm_struct", "pgd", 0, "pointer")
175 .add_symbol("init_task", vaddr);
176 let ptb = PageTableBuilder::new()
177 .map_4k(vaddr, paddr, flags::WRITABLE)
178 .write_phys(paddr, data);
179 make_reader(&isf, ptb)
180 }
181
182 #[test]
183 fn walk_single_process() {
184 let vaddr: u64 = 0xFFFF_8000_0010_0000;
185 let paddr: u64 = 0x0080_0000;
186 let mut data = vec![0u8; 4096];
187
188 data[0..4].copy_from_slice(&0u32.to_le_bytes());
189 data[4..12].copy_from_slice(&0i64.to_le_bytes());
190 let tasks_addr = vaddr + 16;
191 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
192 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
193 data[32..41].copy_from_slice(b"swapper/0");
194 data[56..64].copy_from_slice(&vaddr.to_le_bytes());
195 data[START_TIME_OFF..START_TIME_OFF + 8].copy_from_slice(&0u64.to_le_bytes());
197
198 let reader = make_test_reader(&data, vaddr, paddr);
199 let procs = walk_processes(&reader).unwrap();
200
201 assert_eq!(procs.len(), 1);
202 assert_eq!(procs[0].pid, 0);
203 assert_eq!(procs[0].comm, "swapper/0");
204 assert_eq!(procs[0].state, ProcessState::Running);
205 assert_eq!(procs[0].cr3, None);
206 assert_eq!(procs[0].start_time, 0);
207 }
208
209 #[test]
210 fn walk_three_processes() {
211 let vaddr: u64 = 0xFFFF_8000_0010_0000;
212 let paddr: u64 = 0x0080_0000;
213 let mut data = vec![0u8; 4096];
214
215 let init_addr = vaddr;
216 let a_addr = vaddr + 0x200;
217 let b_addr = vaddr + 0x400;
218 let init_tasks = init_addr + 16;
219 let a_tasks = a_addr + 16;
220 let b_tasks = b_addr + 16;
221
222 data[0..4].copy_from_slice(&0u32.to_le_bytes());
224 data[4..12].copy_from_slice(&0i64.to_le_bytes());
225 data[16..24].copy_from_slice(&a_tasks.to_le_bytes());
226 data[24..32].copy_from_slice(&b_tasks.to_le_bytes());
227 data[32..41].copy_from_slice(b"swapper/0");
228 data[56..64].copy_from_slice(&init_addr.to_le_bytes());
229 data[START_TIME_OFF..START_TIME_OFF + 8].copy_from_slice(&0u64.to_le_bytes());
230
231 data[0x200..0x204].copy_from_slice(&1u32.to_le_bytes());
233 data[0x204..0x20C].copy_from_slice(&1i64.to_le_bytes());
234 data[0x210..0x218].copy_from_slice(&b_tasks.to_le_bytes());
235 data[0x218..0x220].copy_from_slice(&init_tasks.to_le_bytes());
236 data[0x220..0x227].copy_from_slice(b"systemd");
237 data[0x238..0x240].copy_from_slice(&init_addr.to_le_bytes());
238 data[0x200 + START_TIME_OFF..0x200 + START_TIME_OFF + 8]
239 .copy_from_slice(&1_000_000_000u64.to_le_bytes());
240
241 data[0x400..0x404].copy_from_slice(&42u32.to_le_bytes());
243 data[0x404..0x40C].copy_from_slice(&0i64.to_le_bytes());
244 data[0x410..0x418].copy_from_slice(&init_tasks.to_le_bytes());
245 data[0x418..0x420].copy_from_slice(&a_tasks.to_le_bytes());
246 data[0x420..0x424].copy_from_slice(b"bash");
247 data[0x438..0x440].copy_from_slice(&a_addr.to_le_bytes());
248 data[0x400 + START_TIME_OFF..0x400 + START_TIME_OFF + 8]
249 .copy_from_slice(&5_000_000_000u64.to_le_bytes());
250
251 let reader = make_test_reader(&data, vaddr, paddr);
252 let procs = walk_processes(&reader).unwrap();
253
254 assert_eq!(procs.len(), 3);
255 assert_eq!(procs[0].pid, 0);
256 assert_eq!(procs[0].comm, "swapper/0");
257 assert_eq!(procs[1].pid, 1);
258 assert_eq!(procs[1].comm, "systemd");
259 assert_eq!(procs[1].state, ProcessState::Sleeping);
260 assert_eq!(procs[1].ppid, 0);
261 assert_eq!(procs[2].pid, 42);
262 assert_eq!(procs[2].comm, "bash");
263 assert_eq!(procs[2].state, ProcessState::Running);
264 assert_eq!(procs[2].ppid, 1);
265 assert_eq!(procs[0].start_time, 0); assert_eq!(procs[1].start_time, 1_000_000_000); assert_eq!(procs[2].start_time, 5_000_000_000); }
270
271 #[test]
272 fn missing_init_task_symbol() {
273 let isf = IsfBuilder::new()
274 .add_struct("task_struct", 64)
275 .add_field("task_struct", "pid", 0, "int")
276 .add_field("task_struct", "tasks", 8, "list_head")
277 .add_struct("list_head", 16)
278 .add_field("list_head", "next", 0, "pointer")
279 .add_field("list_head", "prev", 8, "pointer")
280 .build_json();
281
282 let resolver = IsfResolver::from_value(&isf).unwrap();
283 let (cr3, mem) = PageTableBuilder::new().build();
284 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
285 let reader = ObjectReader::new(vas, Box::new(resolver));
286
287 let result = walk_processes(&reader);
288 assert!(result.is_err());
289 }
290
291 fn make_proc(pid: u64, ppid: u64, comm: &str) -> ProcessInfo {
296 ProcessInfo {
297 pid,
298 ppid,
299 comm: comm.to_string(),
300 state: ProcessState::Running,
301 vaddr: 0xFFFF_0000_0000_0000 + pid * 0x1000,
302 cr3: None,
303 start_time: pid * 1_000_000_000, }
305 }
306
307 #[test]
309 fn pstree_single_root() {
310 let procs = vec![make_proc(1, 0, "init")];
311 let tree = build_pstree(&procs);
312
313 assert_eq!(tree.len(), 1);
314 assert_eq!(tree[0].process.pid, 1);
315 assert_eq!(tree[0].depth, 0);
316 }
317
318 #[test]
321 fn pstree_linear_chain() {
322 let procs = vec![
323 make_proc(1, 0, "init"),
324 make_proc(100, 1, "bash"),
325 make_proc(200, 100, "vim"),
326 ];
327 let tree = build_pstree(&procs);
328
329 assert_eq!(tree.len(), 3);
330 assert_eq!(tree[0].process.pid, 1);
331 assert_eq!(tree[0].depth, 0);
332 assert_eq!(tree[1].process.pid, 100);
333 assert_eq!(tree[1].depth, 1);
334 assert_eq!(tree[2].process.pid, 200);
335 assert_eq!(tree[2].depth, 2);
336 }
337
338 #[test]
341 fn pstree_branching_sorted_by_pid() {
342 let procs = vec![
343 make_proc(1, 0, "init"),
344 make_proc(50, 1, "sshd"),
345 make_proc(30, 1, "cron"),
346 ];
347 let tree = build_pstree(&procs);
348
349 assert_eq!(tree.len(), 3);
350 assert_eq!(tree[0].process.pid, 1);
351 assert_eq!(tree[0].depth, 0);
352 assert_eq!(tree[1].process.pid, 30);
354 assert_eq!(tree[1].process.comm, "cron");
355 assert_eq!(tree[1].depth, 1);
356 assert_eq!(tree[2].process.pid, 50);
357 assert_eq!(tree[2].process.comm, "sshd");
358 assert_eq!(tree[2].depth, 1);
359 }
360
361 #[test]
363 fn pstree_orphan_becomes_root() {
364 let procs = vec![
365 make_proc(1, 0, "init"),
366 make_proc(999, 777, "orphan"), ];
368 let tree = build_pstree(&procs);
369
370 assert_eq!(tree.len(), 2);
371 assert_eq!(tree[0].process.pid, 1);
373 assert_eq!(tree[0].depth, 0);
374 assert_eq!(tree[1].process.pid, 999);
375 assert_eq!(tree[1].depth, 0);
376 }
377
378 #[test]
380 fn pstree_empty() {
381 let tree = build_pstree(&[]);
382 assert!(tree.is_empty());
383 }
384
385 #[test]
388 fn pstree_full_tree_dfs_order() {
389 let procs = vec![
390 make_proc(1, 0, "init"),
391 make_proc(2, 1, "systemd"),
392 make_proc(3, 1, "cron"),
393 make_proc(10, 2, "sshd"),
394 make_proc(20, 10, "bash"),
395 ];
396 let tree = build_pstree(&procs);
397
398 assert_eq!(tree.len(), 5);
399 assert_eq!(tree[0].process.pid, 1);
401 assert_eq!(tree[0].depth, 0);
402 assert_eq!(tree[1].process.pid, 2);
403 assert_eq!(tree[1].depth, 1);
404 assert_eq!(tree[2].process.pid, 10);
405 assert_eq!(tree[2].depth, 2);
406 assert_eq!(tree[3].process.pid, 20);
407 assert_eq!(tree[3].depth, 3);
408 assert_eq!(tree[4].process.pid, 3);
409 assert_eq!(tree[4].depth, 1);
410 }
411}