1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use crate::{Error, PsxViewInfo, Result};
12
13pub fn walk_psxview<P: PhysicalMemoryProvider>(
15 reader: &ObjectReader<P>,
16) -> Result<Vec<PsxViewInfo>> {
17 let init_task_addr = reader
18 .symbols()
19 .symbol_address("init_task")
20 .ok_or_else(|| Error::MissingKernelSymbol {
21 name: "init_task".into(),
22 })?;
23
24 let tasks_offset = reader
25 .symbols()
26 .field_offset("task_struct", "tasks")
27 .ok_or_else(|| Error::MissingField {
28 struct_name: "task_struct".into(),
29 field_name: "tasks".into(),
30 })?;
31
32 let head_vaddr = init_task_addr + tasks_offset;
33 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
34
35 let pid_hash_pids = collect_pid_hash_pids(reader).filter(|s| !s.is_empty());
40
41 let mut results = Vec::new();
42
43 if let Ok(info) = read_task_info(reader, init_task_addr) {
44 let in_pid_hash = pid_hash_pids
45 .as_ref()
46 .map_or(true, |set| set.contains(&info.0));
47 results.push(PsxViewInfo {
48 pid: info.0,
49 comm: info.1,
50 in_task_list: true,
51 in_pid_hash,
52 });
53 }
54
55 for &task_addr in &task_addrs {
56 if let Ok(info) = read_task_info(reader, task_addr) {
57 let in_pid_hash = pid_hash_pids
58 .as_ref()
59 .map_or(true, |set| set.contains(&info.0));
60 results.push(PsxViewInfo {
61 pid: info.0,
62 comm: info.1,
63 in_task_list: true,
64 in_pid_hash,
65 });
66 }
67 }
68
69 Ok(results)
70}
71
72fn collect_pid_hash_pids<P: PhysicalMemoryProvider>(
84 reader: &ObjectReader<P>,
85) -> Option<std::collections::HashSet<u64>> {
86 let pid_hash_addr = reader.symbols().symbol_address("pid_hash")?;
88 let pid_links_offset = reader.symbols().field_offset("task_struct", "pid_links")?;
89 let hlist_next_offset = reader.symbols().field_offset("hlist_node", "next")?;
90
91 let mut found_pids = std::collections::HashSet::new();
92
93 let mut bucket_offset: u64 = 0;
97 loop {
98 let bucket_head_addr = pid_hash_addr.wrapping_add(bucket_offset);
99 let first_node_ptr: u64 = match reader
101 .read_bytes(bucket_head_addr, 8)
102 .ok()
103 .and_then(|b| b.try_into().ok())
104 .map(u64::from_le_bytes)
105 {
106 Some(v) => v,
107 None => break,
108 };
109
110 let mut node_ptr = first_node_ptr;
112 let mut depth = 0usize;
113 while node_ptr != 0 && depth < 10_000 {
114 let task_addr = node_ptr.wrapping_sub(pid_links_offset);
116 if let Ok(pid) = reader.read_field::<u32>(task_addr, "task_struct", "pid") {
117 found_pids.insert(u64::from(pid));
118 }
119 let next_ptr: u64 = match reader
121 .read_bytes(node_ptr.wrapping_add(hlist_next_offset), 8)
122 .ok()
123 .and_then(|b| b.try_into().ok())
124 .map(u64::from_le_bytes)
125 {
126 Some(v) => v,
127 None => break,
128 };
129 node_ptr = next_ptr;
130 depth += 1;
131 }
132
133 bucket_offset = bucket_offset.wrapping_add(8);
134 if bucket_offset >= 8 * 4096 {
138 break;
139 }
140 }
141
142 Some(found_pids)
143}
144
145fn read_task_info<P: PhysicalMemoryProvider>(
146 reader: &ObjectReader<P>,
147 task_addr: u64,
148) -> Result<(u64, String)> {
149 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
150 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
151 Ok((u64::from(pid), comm))
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use memf_core::test_builders::{flags as ptflags, PageTableBuilder, SyntheticPhysMem};
158 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
159 use memf_symbols::isf::IsfResolver;
160 use memf_symbols::test_builders::IsfBuilder;
161
162 fn make_test_reader(
163 data: &[u8],
164 vaddr: u64,
165 paddr: u64,
166 extra_mappings: &[(u64, u64, &[u8])],
167 ) -> ObjectReader<SyntheticPhysMem> {
168 let isf = IsfBuilder::new()
169 .add_struct("task_struct", 128)
170 .add_field("task_struct", "pid", 0, "int")
171 .add_field("task_struct", "state", 4, "long")
172 .add_field("task_struct", "tasks", 16, "list_head")
173 .add_field("task_struct", "comm", 32, "char")
174 .add_field("task_struct", "mm", 48, "pointer")
175 .add_field("task_struct", "pid_links", 56, "hlist_node")
176 .add_struct("list_head", 16)
177 .add_field("list_head", "next", 0, "pointer")
178 .add_field("list_head", "prev", 8, "pointer")
179 .add_struct("hlist_node", 16)
180 .add_field("hlist_node", "next", 0, "pointer")
181 .add_field("hlist_node", "pprev", 8, "pointer")
182 .add_struct("pid", 32)
183 .add_field("pid", "nr", 0, "unsigned int")
184 .add_symbol("init_task", vaddr)
185 .add_symbol("pid_hash", vaddr + 0x800)
186 .build_json();
187
188 let resolver = IsfResolver::from_value(&isf).unwrap();
189 let mut builder = PageTableBuilder::new()
190 .map_4k(vaddr, paddr, ptflags::WRITABLE)
191 .write_phys(paddr, data);
192
193 for &(ev, ep, edata) in extra_mappings {
194 builder = builder
195 .map_4k(ev, ep, ptflags::WRITABLE)
196 .write_phys(ep, edata);
197 }
198
199 let (cr3, mem) = builder.build();
200 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
201 ObjectReader::new(vas, Box::new(resolver))
202 }
203
204 #[test]
205 fn all_processes_visible_in_both_views() {
206 let vaddr: u64 = 0xFFFF_8000_0010_0000;
207 let paddr: u64 = 0x0080_0000;
208 let mut data = vec![0u8; 4096];
209
210 data[0..4].copy_from_slice(&1u32.to_le_bytes());
211 let tasks_addr = vaddr + 16;
212 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
213 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
214 data[32..36].copy_from_slice(b"init");
215
216 let reader = make_test_reader(&data, vaddr, paddr, &[]);
217 let results = walk_psxview(&reader).unwrap();
218
219 assert!(!results.is_empty());
220 assert!(results[0].in_task_list);
221 }
222
223 #[test]
224 fn missing_init_task_symbol() {
225 let isf = IsfBuilder::new()
226 .add_struct("task_struct", 64)
227 .add_field("task_struct", "pid", 0, "int")
228 .add_field("task_struct", "tasks", 8, "list_head")
229 .add_struct("list_head", 16)
230 .add_field("list_head", "next", 0, "pointer")
231 .add_field("list_head", "prev", 8, "pointer")
232 .build_json();
233
234 let resolver = IsfResolver::from_value(&isf).unwrap();
235 let (cr3, mem) = PageTableBuilder::new().build();
236 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
237 let reader = ObjectReader::new(vas, Box::new(resolver));
238
239 let result = walk_psxview(&reader);
240 assert!(
241 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "init_task"),
242 "expected MissingKernelSymbol {{name: \"init_task\"}}, got {result:?}"
243 );
244 }
245
246 #[test]
247 fn missing_tasks_field_returns_missing_field() {
248 let isf = IsfBuilder::new()
249 .add_struct("task_struct", 128)
250 .add_field("task_struct", "pid", 0, "int")
251 .add_field("task_struct", "comm", 32, "char")
252 .add_struct("list_head", 16)
253 .add_field("list_head", "next", 0, "pointer")
254 .add_field("list_head", "prev", 8, "pointer")
255 .add_symbol("init_task", 0xFFFF_8000_0010_0000)
256 .build_json();
257
258 let resolver = IsfResolver::from_value(&isf).unwrap();
259 let (cr3, mem) = PageTableBuilder::new().build();
260 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
261 let reader = ObjectReader::new(vas, Box::new(resolver));
262
263 let result = walk_psxview(&reader);
264 assert!(
265 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "task_struct" && field_name == "tasks"),
266 "expected MissingField task_struct.tasks, got {result:?}"
267 );
268 }
269
270 #[test]
271 fn walk_psxview_multiple_tasks_in_list() {
272 let init_vaddr: u64 = 0xFFFF_8000_0020_0000;
273 let init_paddr: u64 = 0x0090_0000;
274 let task2_vaddr: u64 = 0xFFFF_8000_0021_0000;
275 let task2_paddr: u64 = 0x0091_0000;
276
277 let mut init_data = vec![0u8; 4096];
278 init_data[0..4].copy_from_slice(&1u32.to_le_bytes());
279 let task2_tasks = task2_vaddr + 16;
280 init_data[16..24].copy_from_slice(&task2_tasks.to_le_bytes());
281 init_data[24..32].copy_from_slice(&task2_tasks.to_le_bytes());
282 init_data[32..38].copy_from_slice(b"init\0\0");
283
284 let mut task2_data = vec![0u8; 4096];
285 task2_data[0..4].copy_from_slice(&2u32.to_le_bytes());
286 let init_tasks = init_vaddr + 16;
287 task2_data[16..24].copy_from_slice(&init_tasks.to_le_bytes());
288 task2_data[24..32].copy_from_slice(&init_tasks.to_le_bytes());
289 task2_data[32..36].copy_from_slice(b"sh\0\0");
290
291 let isf = IsfBuilder::new()
292 .add_struct("task_struct", 128)
293 .add_field("task_struct", "pid", 0, "int")
294 .add_field("task_struct", "state", 4, "long")
295 .add_field("task_struct", "tasks", 16, "list_head")
296 .add_field("task_struct", "comm", 32, "char")
297 .add_struct("list_head", 16)
298 .add_field("list_head", "next", 0, "pointer")
299 .add_field("list_head", "prev", 8, "pointer")
300 .add_symbol("init_task", init_vaddr)
301 .build_json();
302
303 let resolver = IsfResolver::from_value(&isf).unwrap();
304 let (cr3, mem) = PageTableBuilder::new()
305 .map_4k(init_vaddr, init_paddr, ptflags::WRITABLE)
306 .write_phys(init_paddr, &init_data)
307 .map_4k(task2_vaddr, task2_paddr, ptflags::WRITABLE)
308 .write_phys(task2_paddr, &task2_data)
309 .build();
310 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
311 let reader = ObjectReader::new(vas, Box::new(resolver));
312
313 let results = walk_psxview(&reader).unwrap();
314
315 assert_eq!(results.len(), 2, "expected two tasks: init + task2");
316
317 let init_entry = results
318 .iter()
319 .find(|r| r.pid == 1)
320 .expect("init_task missing");
321 assert!(init_entry.in_task_list);
322 assert!(init_entry.in_pid_hash);
323
324 let task2_entry = results.iter().find(|r| r.pid == 2).expect("task2 missing");
325 assert!(task2_entry.in_task_list);
326 assert!(task2_entry.in_pid_hash);
327 assert_eq!(task2_entry.comm, "sh");
328 }
329
330 #[test]
331 fn psxview_entries_have_correct_visibility_flags() {
332 let vaddr: u64 = 0xFFFF_8000_0010_0000;
333 let paddr: u64 = 0x0080_0000;
334 let mut data = vec![0u8; 4096];
335
336 data[0..4].copy_from_slice(&1u32.to_le_bytes());
337 let tasks_addr = vaddr + 16;
338 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
339 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
340 data[32..39].copy_from_slice(b"swapper");
341
342 let reader = make_test_reader(&data, vaddr, paddr, &[]);
343 let results = walk_psxview(&reader).unwrap();
344
345 assert!(!results.is_empty(), "should find at least init_task");
346 let init = &results[0];
347 assert!(init.in_task_list, "init_task must be in_task_list");
348 assert!(init.in_pid_hash, "init_task must be in_pid_hash");
349 assert_eq!(init.pid, 1);
350 }
351}