1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use crate::{Error, FileDescriptorInfo, Result};
12
13pub fn walk_files<P: PhysicalMemoryProvider>(
19 reader: &ObjectReader<P>,
20) -> Result<Vec<FileDescriptorInfo>> {
21 let init_task_addr = reader
22 .symbols()
23 .symbol_address("init_task")
24 .ok_or_else(|| Error::MissingKernelSymbol {
25 name: "init_task".into(),
26 })?;
27
28 let tasks_offset = reader
29 .symbols()
30 .field_offset("task_struct", "tasks")
31 .ok_or_else(|| Error::MissingField {
32 struct_name: "task_struct".into(),
33 field_name: "tasks".into(),
34 })?;
35
36 let head_vaddr = init_task_addr + tasks_offset;
37 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
38
39 let mut all_fds = Vec::new();
40
41 collect_process_files(reader, init_task_addr, &mut all_fds);
43
44 for &task_addr in &task_addrs {
45 collect_process_files(reader, task_addr, &mut all_fds);
46 }
47
48 Ok(all_fds)
49}
50
51fn collect_process_files<P: PhysicalMemoryProvider>(
53 reader: &ObjectReader<P>,
54 task_addr: u64,
55 out: &mut Vec<FileDescriptorInfo>,
56) {
57 let files_ptr: u64 = match reader.read_field(task_addr, "task_struct", "files") {
58 Ok(v) => v,
59 Err(_) => return,
60 };
61 if files_ptr == 0 {
62 return; }
64
65 if let Ok(fds) = walk_process_files(reader, task_addr) {
66 out.extend(fds);
67 }
68}
69
70pub fn walk_process_files<P: PhysicalMemoryProvider>(
72 reader: &ObjectReader<P>,
73 task_addr: u64,
74) -> Result<Vec<FileDescriptorInfo>> {
75 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
76 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
77 let files_ptr: u64 = reader.read_field(task_addr, "task_struct", "files")?;
78
79 if files_ptr == 0 {
80 return Err(Error::WalkFailed {
81 walker: "walk_process_files",
82 reason: format!("task {comm} (PID {pid}) has NULL files pointer"),
83 });
84 }
85
86 let fdt_ptr: u64 = reader.read_field(files_ptr, "files_struct", "fdt")?;
87 let max_fds: u32 = reader.read_field(fdt_ptr, "fdtable", "max_fds")?;
88 let fd_array_ptr: u64 = reader.read_field(fdt_ptr, "fdtable", "fd")?;
89
90 let f_path_offset = reader
92 .symbols()
93 .field_offset("file", "f_path")
94 .ok_or_else(|| Error::MissingField {
95 struct_name: "file".into(),
96 field_name: "f_path".into(),
97 })?;
98 let dentry_in_path_offset =
99 reader
100 .symbols()
101 .field_offset("path", "dentry")
102 .ok_or_else(|| Error::MissingField {
103 struct_name: "path".into(),
104 field_name: "dentry".into(),
105 })?;
106 let name_in_qstr_offset = reader
107 .symbols()
108 .field_offset("qstr", "name")
109 .ok_or_else(|| Error::MissingField {
110 struct_name: "qstr".into(),
111 field_name: "name".into(),
112 })?;
113
114 let array_size = usize::try_from(max_fds).unwrap_or(0) * 8;
116 let fd_raw = reader.read_bytes(fd_array_ptr, array_size)?;
117
118 let d_name_offset = reader
119 .symbols()
120 .field_offset("dentry", "d_name")
121 .ok_or_else(|| Error::MissingField {
122 struct_name: "dentry".into(),
123 field_name: "d_name".into(),
124 })?;
125
126 let mut fds = Vec::new();
127
128 for fd_num in 0..max_fds {
129 let off = usize::try_from(fd_num).unwrap_or(0) * 8;
130 let file_ptr = fd_raw[off..off + 8]
131 .try_into()
132 .map_or(0, u64::from_le_bytes);
133
134 if file_ptr == 0 {
135 continue; }
137
138 let f_pos: u64 = reader.read_field(file_ptr, "file", "f_pos")?;
140
141 let f_inode_ptr: u64 = reader.read_field(file_ptr, "file", "f_inode")?;
143 let inode = if f_inode_ptr != 0 {
144 reader.read_field::<u64>(f_inode_ptr, "inode", "i_ino").ok()
145 } else {
146 None
147 };
148
149 let dentry_addr = file_ptr + f_path_offset + dentry_in_path_offset;
152 let dentry_raw = reader.read_bytes(dentry_addr, 8)?;
153 let dentry_ptr = dentry_raw.try_into().map_or(0, u64::from_le_bytes);
154
155 let path = if dentry_ptr != 0 {
156 let name_addr = dentry_ptr + d_name_offset + name_in_qstr_offset;
158 let name_raw = reader.read_bytes(name_addr, 8)?;
159 let name_ptr = name_raw.try_into().map_or(0, u64::from_le_bytes);
160 if name_ptr != 0 {
161 reader.read_string(name_ptr, 256).unwrap_or_default()
162 } else {
163 String::new()
164 }
165 } else {
166 String::new()
167 };
168
169 fds.push(FileDescriptorInfo {
170 pid: u64::from(pid),
171 comm: comm.clone(),
172 fd: fd_num,
173 path,
174 inode,
175 pos: f_pos,
176 });
177 }
178
179 Ok(fds)
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
186 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
187 use memf_symbols::isf::IsfResolver;
188 use memf_symbols::test_builders::IsfBuilder;
189
190 fn make_test_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
191 let isf = IsfBuilder::new()
192 .add_struct("task_struct", 128)
194 .add_field("task_struct", "pid", 0, "int")
195 .add_field("task_struct", "state", 4, "long")
196 .add_field("task_struct", "tasks", 16, "list_head")
197 .add_field("task_struct", "comm", 32, "char")
198 .add_field("task_struct", "mm", 48, "pointer")
199 .add_field("task_struct", "files", 56, "pointer")
200 .add_struct("list_head", 16)
202 .add_field("list_head", "next", 0, "pointer")
203 .add_field("list_head", "prev", 8, "pointer")
204 .add_struct("files_struct", 32)
206 .add_field("files_struct", "fdt", 0, "pointer")
207 .add_struct("fdtable", 16)
209 .add_field("fdtable", "max_fds", 0, "unsigned int")
210 .add_field("fdtable", "fd", 8, "pointer")
211 .add_struct("file", 64)
213 .add_field("file", "f_path", 0, "path")
214 .add_field("file", "f_inode", 16, "pointer")
215 .add_field("file", "f_pos", 24, "long long")
216 .add_struct("path", 16)
218 .add_field("path", "dentry", 8, "pointer")
219 .add_struct("dentry", 64)
221 .add_field("dentry", "d_name", 0, "qstr")
222 .add_field("dentry", "d_inode", 48, "pointer")
223 .add_struct("qstr", 16)
225 .add_field("qstr", "name", 8, "pointer")
226 .add_struct("inode", 64)
228 .add_field("inode", "i_ino", 0, "unsigned long")
229 .add_symbol("init_task", vaddr)
231 .build_json();
232
233 let resolver = IsfResolver::from_value(&isf).unwrap();
234 let (cr3, mem) = PageTableBuilder::new()
235 .map_4k(vaddr, paddr, flags::WRITABLE)
236 .write_phys(paddr, data)
237 .build();
238 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
239 ObjectReader::new(vas, Box::new(resolver))
240 }
241
242 #[test]
243 fn walk_single_process_two_fds() {
244 let vaddr: u64 = 0xFFFF_8000_0010_0000;
245 let paddr: u64 = 0x0080_0000;
246 let mut data = vec![0u8; 4096];
247
248 data[0..4].copy_from_slice(&1u32.to_le_bytes()); data[4..12].copy_from_slice(&0i64.to_le_bytes()); let tasks_addr = vaddr + 16;
252 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes()); data[24..32].copy_from_slice(&tasks_addr.to_le_bytes()); data[32..36].copy_from_slice(b"bash"); data[48..56].copy_from_slice(&0u64.to_le_bytes()); let files_struct_addr = vaddr + 0x200;
257 data[56..64].copy_from_slice(&files_struct_addr.to_le_bytes()); let fdtable_addr = vaddr + 0x300;
261 data[0x200..0x208].copy_from_slice(&fdtable_addr.to_le_bytes()); data[0x300..0x304].copy_from_slice(&3u32.to_le_bytes()); let fd_array_addr = vaddr + 0x400;
266 data[0x308..0x310].copy_from_slice(&fd_array_addr.to_le_bytes()); let file0_addr = vaddr + 0x500;
270 data[0x400..0x408].copy_from_slice(&file0_addr.to_le_bytes()); data[0x408..0x410].copy_from_slice(&0u64.to_le_bytes()); let file2_addr = vaddr + 0x600;
273 data[0x410..0x418].copy_from_slice(&file2_addr.to_le_bytes()); let dentry0_addr = vaddr + 0x700;
278 data[0x508..0x510].copy_from_slice(&dentry0_addr.to_le_bytes()); let inode0_addr = vaddr + 0x800;
280 data[0x510..0x518].copy_from_slice(&inode0_addr.to_le_bytes()); data[0x518..0x520].copy_from_slice(&0u64.to_le_bytes()); let name0_addr = vaddr + 0x780;
286 data[0x708..0x710].copy_from_slice(&name0_addr.to_le_bytes()); data[0x780..0x78A].copy_from_slice(b"/dev/pts/0"); data[0x730..0x738].copy_from_slice(&inode0_addr.to_le_bytes()); data[0x800..0x808].copy_from_slice(&4u64.to_le_bytes()); let dentry2_addr = vaddr + 0x900;
296 data[0x608..0x610].copy_from_slice(&dentry2_addr.to_le_bytes()); let inode2_addr = vaddr + 0xA00;
298 data[0x610..0x618].copy_from_slice(&inode2_addr.to_le_bytes()); data[0x618..0x620].copy_from_slice(&1024u64.to_le_bytes()); let name2_addr = vaddr + 0x980;
303 data[0x908..0x910].copy_from_slice(&name2_addr.to_le_bytes()); data[0x980..0x988].copy_from_slice(b"/tmp/log"); data[0x930..0x938].copy_from_slice(&inode2_addr.to_le_bytes()); data[0xA00..0xA08].copy_from_slice(&42u64.to_le_bytes()); let reader = make_test_reader(&data, vaddr, paddr);
311 let fds = walk_files(&reader).unwrap();
312
313 assert_eq!(fds.len(), 2);
314
315 assert_eq!(fds[0].pid, 1);
316 assert_eq!(fds[0].comm, "bash");
317 assert_eq!(fds[0].fd, 0);
318 assert_eq!(fds[0].path, "/dev/pts/0");
319 assert_eq!(fds[0].inode, Some(4));
320 assert_eq!(fds[0].pos, 0);
321
322 assert_eq!(fds[1].fd, 2);
323 assert_eq!(fds[1].path, "/tmp/log");
324 assert_eq!(fds[1].inode, Some(42));
325 assert_eq!(fds[1].pos, 1024);
326 }
327
328 #[test]
329 fn walk_files_skips_kernel_threads() {
330 let vaddr: u64 = 0xFFFF_8000_0010_0000;
332 let paddr: u64 = 0x0080_0000;
333 let mut data = vec![0u8; 4096];
334
335 data[0..4].copy_from_slice(&0u32.to_le_bytes()); let tasks_addr = vaddr + 16;
337 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes()); data[24..32].copy_from_slice(&tasks_addr.to_le_bytes()); data[32..41].copy_from_slice(b"swapper/0");
340 data[56..64].copy_from_slice(&0u64.to_le_bytes()); let reader = make_test_reader(&data, vaddr, paddr);
343 let fds = walk_files(&reader).unwrap();
344
345 assert!(fds.is_empty());
346 }
347
348 #[test]
349 fn walk_process_files_null_files_returns_error() {
350 let vaddr: u64 = 0xFFFF_8000_0010_0000;
351 let paddr: u64 = 0x0080_0000;
352 let mut data = vec![0u8; 4096];
353
354 data[56..64].copy_from_slice(&0u64.to_le_bytes()); let reader = make_test_reader(&data, vaddr, paddr);
357 let result = walk_process_files(&reader, vaddr);
358 assert!(result.is_err());
359 }
360
361 #[test]
362 fn missing_init_task_symbol() {
363 let isf = IsfBuilder::new()
364 .add_struct("task_struct", 64)
365 .add_field("task_struct", "pid", 0, "int")
366 .add_field("task_struct", "tasks", 8, "list_head")
367 .add_struct("list_head", 16)
368 .add_field("list_head", "next", 0, "pointer")
369 .add_field("list_head", "prev", 8, "pointer")
370 .build_json();
371
372 let resolver = IsfResolver::from_value(&isf).unwrap();
373 let (cr3, mem) = PageTableBuilder::new().build();
374 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
375 let reader = ObjectReader::new(vas, Box::new(resolver));
376
377 let result = walk_files(&reader);
378 assert!(
379 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "init_task"),
380 "expected MissingKernelSymbol {{name: \"init_task\"}}, got {result:?}"
381 );
382 }
383
384 #[test]
386 fn walk_files_missing_tasks_field_returns_error() {
387 let vaddr: u64 = 0xFFFF_8000_0010_0000;
388 let paddr: u64 = 0x0080_0000;
389 let data = vec![0u8; 4096];
390
391 let isf = IsfBuilder::new()
392 .add_struct("task_struct", 128)
393 .add_field("task_struct", "pid", 0, "int")
394 .add_struct("list_head", 16)
396 .add_field("list_head", "next", 0, "pointer")
397 .add_field("list_head", "prev", 8, "pointer")
398 .add_symbol("init_task", vaddr)
399 .build_json();
400
401 let resolver = IsfResolver::from_value(&isf).unwrap();
402 let (cr3, mem) = PageTableBuilder::new()
403 .map_4k(vaddr, paddr, flags::WRITABLE)
404 .write_phys(paddr, &data)
405 .build();
406 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
407 let reader = ObjectReader::new(vas, Box::new(resolver));
408
409 let result = walk_files(&reader);
410 assert!(
411 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "task_struct" && field_name == "tasks"),
412 "expected MissingField task_struct.tasks, got {result:?}"
413 );
414 }
415
416 #[test]
418 fn walk_process_files_null_inode_gives_none() {
419 let vaddr: u64 = 0xFFFF_8000_0010_0000;
420 let paddr: u64 = 0x0080_0000;
421 let mut data = vec![0u8; 4096];
422
423 data[0..4].copy_from_slice(&1u32.to_le_bytes());
425 let tasks_addr = vaddr + 16;
426 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
427 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
428 data[32..36].copy_from_slice(b"bash");
429 let files_struct_addr = vaddr + 0x200;
430 data[56..64].copy_from_slice(&files_struct_addr.to_le_bytes());
431
432 let fdtable_addr = vaddr + 0x300;
434 data[0x200..0x208].copy_from_slice(&fdtable_addr.to_le_bytes());
435
436 data[0x300..0x304].copy_from_slice(&1u32.to_le_bytes());
438 let fd_array_addr = vaddr + 0x400;
439 data[0x308..0x310].copy_from_slice(&fd_array_addr.to_le_bytes());
440
441 let file_addr = vaddr + 0x500;
443 data[0x400..0x408].copy_from_slice(&file_addr.to_le_bytes());
444
445 let dentry_addr = vaddr + 0x700;
447 data[0x508..0x510].copy_from_slice(&dentry_addr.to_le_bytes()); data[0x510..0x518].copy_from_slice(&0u64.to_le_bytes()); data[0x518..0x520].copy_from_slice(&999u64.to_le_bytes()); let name_addr = vaddr + 0x780;
453 data[0x708..0x710].copy_from_slice(&name_addr.to_le_bytes());
454 data[0x780..0x789].copy_from_slice(b"/dev/null");
455
456 let reader = make_test_reader(&data, vaddr, paddr);
457 let fds = walk_process_files(&reader, vaddr).unwrap();
458
459 assert_eq!(fds.len(), 1);
460 assert_eq!(fds[0].inode, None, "f_inode=0 should yield inode=None");
461 assert_eq!(fds[0].pos, 999);
462 assert_eq!(fds[0].path, "/dev/null");
463 }
464
465 #[test]
467 fn walk_process_files_null_dentry_gives_empty_path() {
468 let vaddr: u64 = 0xFFFF_8000_0010_0000;
469 let paddr: u64 = 0x0080_0000;
470 let mut data = vec![0u8; 4096];
471
472 data[0..4].copy_from_slice(&2u32.to_le_bytes());
473 let tasks_addr = vaddr + 16;
474 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
475 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
476 data[32..36].copy_from_slice(b"bash");
477 let files_struct_addr = vaddr + 0x200;
478 data[56..64].copy_from_slice(&files_struct_addr.to_le_bytes());
479
480 let fdtable_addr = vaddr + 0x300;
481 data[0x200..0x208].copy_from_slice(&fdtable_addr.to_le_bytes());
482 data[0x300..0x304].copy_from_slice(&1u32.to_le_bytes()); let fd_array_addr = vaddr + 0x400;
484 data[0x308..0x310].copy_from_slice(&fd_array_addr.to_le_bytes());
485
486 let file_addr = vaddr + 0x500;
487 data[0x400..0x408].copy_from_slice(&file_addr.to_le_bytes());
488
489 data[0x508..0x510].copy_from_slice(&0u64.to_le_bytes()); data[0x510..0x518].copy_from_slice(&0u64.to_le_bytes()); data[0x518..0x520].copy_from_slice(&0u64.to_le_bytes()); let reader = make_test_reader(&data, vaddr, paddr);
495 let fds = walk_process_files(&reader, vaddr).unwrap();
496
497 assert_eq!(fds.len(), 1);
498 assert_eq!(fds[0].path, "", "null dentry → empty path");
499 }
500
501 #[test]
503 fn walk_process_files_null_name_ptr_gives_empty_path() {
504 let vaddr: u64 = 0xFFFF_8000_0010_0000;
505 let paddr: u64 = 0x0080_0000;
506 let mut data = vec![0u8; 4096];
507
508 data[0..4].copy_from_slice(&3u32.to_le_bytes());
509 let tasks_addr = vaddr + 16;
510 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
511 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
512 data[32..36].copy_from_slice(b"bash");
513 let files_struct_addr = vaddr + 0x200;
514 data[56..64].copy_from_slice(&files_struct_addr.to_le_bytes());
515
516 let fdtable_addr = vaddr + 0x300;
517 data[0x200..0x208].copy_from_slice(&fdtable_addr.to_le_bytes());
518 data[0x300..0x304].copy_from_slice(&1u32.to_le_bytes());
519 let fd_array_addr = vaddr + 0x400;
520 data[0x308..0x310].copy_from_slice(&fd_array_addr.to_le_bytes());
521
522 let file_addr = vaddr + 0x500;
523 data[0x400..0x408].copy_from_slice(&file_addr.to_le_bytes());
524
525 let dentry_addr = vaddr + 0x700;
527 data[0x508..0x510].copy_from_slice(&dentry_addr.to_le_bytes());
528 data[0x510..0x518].copy_from_slice(&0u64.to_le_bytes()); data[0x518..0x520].copy_from_slice(&0u64.to_le_bytes());
530
531 data[0x708..0x710].copy_from_slice(&0u64.to_le_bytes()); let reader = make_test_reader(&data, vaddr, paddr);
535 let fds = walk_process_files(&reader, vaddr).unwrap();
536
537 assert_eq!(fds.len(), 1);
538 assert_eq!(fds[0].path, "", "null name_ptr → empty path");
539 }
540}