1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{Error, ProcessState, Result, ThreadInfo};
11
12pub fn walk_threads<P: PhysicalMemoryProvider>(
19 reader: &ObjectReader<P>,
20 leader_task_addr: u64,
21 tgid: u64,
22) -> Result<Vec<ThreadInfo>> {
23 let mut threads = Vec::new();
24
25 threads.push(read_thread_info(reader, leader_task_addr, tgid)?);
27
28 let thread_group_offset = reader
30 .symbols()
31 .field_offset("task_struct", "thread_group")
32 .ok_or_else(|| Error::MissingField {
33 struct_name: "task_struct".into(),
34 field_name: "thread_group".into(),
35 })?;
36
37 let head_vaddr = leader_task_addr + thread_group_offset;
38 let sibling_addrs = reader.walk_list(head_vaddr, "task_struct", "thread_group")?;
39
40 for &task_addr in &sibling_addrs {
41 if let Ok(info) = read_thread_info(reader, task_addr, tgid) {
42 threads.push(info);
43 }
44 }
45
46 threads.sort_by_key(|t| t.tid);
47 Ok(threads)
48}
49
50fn read_thread_info<P: PhysicalMemoryProvider>(
51 reader: &ObjectReader<P>,
52 task_addr: u64,
53 tgid: u64,
54) -> Result<ThreadInfo> {
55 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
56 let state: i64 = reader.read_field(task_addr, "task_struct", "state")?;
57 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
58
59 Ok(ThreadInfo {
60 tgid,
61 tid: u64::from(pid),
62 comm,
63 state: ProcessState::from_raw(state),
64 })
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::ProcessState;
71 use memf_core::object_reader::ObjectReader;
72 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
73 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
74 use memf_symbols::isf::IsfResolver;
75 use memf_symbols::test_builders::IsfBuilder;
76
77 const PID_OFF: usize = 0;
78 const STATE_OFF: usize = 4;
79 const COMM_OFF: usize = 32;
80 const TGID_OFF: usize = 64;
81 const THREAD_GROUP_OFF: usize = 72;
82
83 fn build_reader_with_pages(pages: &[(u64, u64, &[u8])]) -> ObjectReader<SyntheticPhysMem> {
84 let isf = IsfBuilder::new()
85 .add_struct("task_struct", 128)
86 .add_field("task_struct", "pid", 0, "int")
87 .add_field("task_struct", "state", 4, "long")
88 .add_field("task_struct", "tasks", 16, "list_head")
89 .add_field("task_struct", "comm", 32, "char")
90 .add_field("task_struct", "mm", 48, "pointer")
91 .add_field("task_struct", "real_parent", 56, "pointer")
92 .add_field("task_struct", "tgid", 64, "int")
93 .add_field("task_struct", "thread_group", 72, "list_head")
94 .add_struct("list_head", 16)
95 .add_field("list_head", "next", 0, "pointer")
96 .add_field("list_head", "prev", 8, "pointer")
97 .build_json();
98 let resolver = IsfResolver::from_value(&isf).unwrap();
99
100 let mut builder = PageTableBuilder::new();
101 for &(vaddr, paddr, data) in pages {
102 builder = builder
103 .map_4k(vaddr, paddr, flags::WRITABLE)
104 .write_phys(paddr, data);
105 }
106 let (cr3, mem) = builder.build();
107 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
108 ObjectReader::new(vas, Box::new(resolver))
109 }
110
111 fn write_task(data: &mut [u8], off: usize, pid: u32, tgid: u32, state: i64, comm: &[u8]) {
112 data[off + PID_OFF..off + PID_OFF + 4].copy_from_slice(&pid.to_le_bytes());
113 data[off + STATE_OFF..off + STATE_OFF + 8].copy_from_slice(&state.to_le_bytes());
114 data[off + TGID_OFF..off + TGID_OFF + 4].copy_from_slice(&tgid.to_le_bytes());
115 let end = (off + COMM_OFF + comm.len()).min(off + COMM_OFF + 16);
116 data[off + COMM_OFF..end].copy_from_slice(&comm[..end - off - COMM_OFF]);
117 }
118
119 fn set_thread_group(data: &mut [u8], off: usize, next: u64, prev: u64) {
120 data[off + THREAD_GROUP_OFF..off + THREAD_GROUP_OFF + 8]
121 .copy_from_slice(&next.to_le_bytes());
122 data[off + THREAD_GROUP_OFF + 8..off + THREAD_GROUP_OFF + 16]
123 .copy_from_slice(&prev.to_le_bytes());
124 }
125
126 #[test]
127 fn single_threaded_process() {
128 let vaddr: u64 = 0xFFFF_8000_0010_0000;
129 let paddr: u64 = 0x0080_0000;
130 let mut data = vec![0u8; 4096];
131
132 write_task(&mut data, 0, 1234, 1234, 1, b"nginx");
133 let leader_tg = vaddr + THREAD_GROUP_OFF as u64;
134 set_thread_group(&mut data, 0, leader_tg, leader_tg);
135
136 let reader = build_reader_with_pages(&[(vaddr, paddr, &data)]);
137 let threads = walk_threads(&reader, vaddr, 1234).unwrap();
138
139 assert_eq!(threads.len(), 1);
140 assert_eq!(threads[0].tgid, 1234);
141 assert_eq!(threads[0].tid, 1234);
142 assert_eq!(threads[0].comm, "nginx");
143 assert_eq!(threads[0].state, ProcessState::Sleeping);
144 }
145
146 #[test]
147 fn multi_threaded_process() {
148 let leader_vaddr: u64 = 0xFFFF_8000_0010_0000;
149 let t1_vaddr: u64 = 0xFFFF_8000_0020_0000;
150 let t2_vaddr: u64 = 0xFFFF_8000_0030_0000;
151
152 let leader_paddr: u64 = 0x0080_0000;
153 let t1_paddr: u64 = 0x0090_0000;
154 let t2_paddr: u64 = 0x00A0_0000;
155
156 let mut leader_data = vec![0u8; 4096];
157 let mut t1_data = vec![0u8; 4096];
158 let mut t2_data = vec![0u8; 4096];
159
160 write_task(&mut leader_data, 0, 100, 100, 0, b"java");
161 write_task(&mut t1_data, 0, 101, 100, 1, b"java");
162 write_task(&mut t2_data, 0, 102, 100, 2, b"java");
163
164 let leader_tg = leader_vaddr + THREAD_GROUP_OFF as u64;
165 let t1_tg = t1_vaddr + THREAD_GROUP_OFF as u64;
166 let t2_tg = t2_vaddr + THREAD_GROUP_OFF as u64;
167
168 set_thread_group(&mut leader_data, 0, t1_tg, t2_tg);
169 set_thread_group(&mut t1_data, 0, t2_tg, leader_tg);
170 set_thread_group(&mut t2_data, 0, leader_tg, t1_tg);
171
172 let reader = build_reader_with_pages(&[
173 (leader_vaddr, leader_paddr, &leader_data),
174 (t1_vaddr, t1_paddr, &t1_data),
175 (t2_vaddr, t2_paddr, &t2_data),
176 ]);
177
178 let threads = walk_threads(&reader, leader_vaddr, 100).unwrap();
179
180 assert_eq!(threads.len(), 3);
181 assert_eq!(threads[0].tid, 100);
182 assert_eq!(threads[0].tgid, 100);
183 assert_eq!(threads[0].state, ProcessState::Running);
184 assert_eq!(threads[1].tid, 101);
185 assert_eq!(threads[1].tgid, 100);
186 assert_eq!(threads[1].state, ProcessState::Sleeping);
187 assert_eq!(threads[2].tid, 102);
188 assert_eq!(threads[2].tgid, 100);
189 assert_eq!(threads[2].state, ProcessState::DiskSleep);
190 assert!(threads.iter().all(|t| t.comm == "java"));
191 }
192
193 #[test]
194 fn kernel_thread_no_extra_threads() {
195 let vaddr: u64 = 0xFFFF_8000_0010_0000;
196 let paddr: u64 = 0x0080_0000;
197 let mut data = vec![0u8; 4096];
198
199 write_task(&mut data, 0, 2, 2, 1, b"kthreadd");
200 let leader_tg = vaddr + THREAD_GROUP_OFF as u64;
201 set_thread_group(&mut data, 0, leader_tg, leader_tg);
202
203 let reader = build_reader_with_pages(&[(vaddr, paddr, &data)]);
204 let threads = walk_threads(&reader, vaddr, 2).unwrap();
205
206 assert_eq!(threads.len(), 1);
207 assert_eq!(threads[0].tgid, 2);
208 assert_eq!(threads[0].tid, 2);
209 assert_eq!(threads[0].comm, "kthreadd");
210 }
211
212 #[test]
213 fn missing_thread_group_field_returns_missing_field() {
214 let vaddr: u64 = 0xFFFF_8000_0010_0000;
215 let paddr: u64 = 0x0080_0000;
216 let mut data = vec![0u8; 4096];
217 data[0..4].copy_from_slice(&1u32.to_le_bytes()); data[4..12].copy_from_slice(&1i64.to_le_bytes()); data[32..36].copy_from_slice(b"init"); let isf = IsfBuilder::new()
223 .add_struct("task_struct", 128)
224 .add_field("task_struct", "pid", 0, "int")
225 .add_field("task_struct", "state", 4, "long")
226 .add_field("task_struct", "comm", 32, "char")
227 .add_struct("list_head", 16)
229 .add_field("list_head", "next", 0, "pointer")
230 .add_field("list_head", "prev", 8, "pointer")
231 .build_json();
232 let resolver = IsfResolver::from_value(&isf).unwrap();
233 let (cr3, mem) = PageTableBuilder::new()
234 .map_4k(vaddr, paddr, flags::WRITABLE)
235 .write_phys(paddr, &data)
236 .build();
237 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
238 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
239 let result = walk_threads(&reader, vaddr, 1);
240 assert!(
241 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "task_struct" && field_name == "thread_group"),
242 "expected MissingField task_struct.thread_group, got {result:?}"
243 );
244 }
245}