1#![deny(unsafe_code)]
2#![warn(missing_docs)]
3pub mod lzo;
11#[allow(missing_docs)]
13pub mod framebuffer;
14pub mod object_reader;
15pub mod pagefile;
16pub mod proto_pte;
17pub mod test_builders;
18pub mod vas;
19
20#[non_exhaustive]
22#[derive(Debug, thiserror::Error)]
23pub enum Error {
24 #[error("physical memory error: {0}")]
26 Physical(#[from] memf_format::Error),
27
28 #[error("symbol error: {0}")]
30 Symbol(#[from] memf_symbols::Error),
31
32 #[error("page not present at virtual address {0:#018x}")]
34 PageNotPresent(u64),
35
36 #[error("partial read: got {got} of {requested} bytes at {addr:#018x}")]
38 PartialRead {
39 addr: u64,
41 requested: usize,
43 got: usize,
45 },
46
47 #[error("missing symbol or field: {0}")]
49 MissingSymbol(String),
50
51 #[error("type size mismatch: expected {expected}, got {got}")]
53 SizeMismatch {
54 expected: usize,
56 got: usize,
58 },
59
60 #[error("list walk exceeded {0} iterations (possible cycle)")]
62 ListCycle(usize),
63
64 #[error("page at {vaddr:#018x} paged out to pagefile {pagefile_num} offset {page_offset:#x}")]
66 PagedOut {
67 vaddr: u64,
69 pagefile_num: u8,
71 page_offset: u64,
73 },
74
75 #[error("prototype PTE at {0:#018x} (not yet supported)")]
77 PrototypePte(u64),
78}
79
80pub type Result<T> = std::result::Result<T, Error>;
82
83#[derive(Debug, Clone, Default, serde::Serialize)]
90pub struct WalkResult<T: serde::Serialize> {
91 pub items: Vec<T>,
93 pub skipped: u32,
95}
96
97impl<T: serde::Serialize> WalkResult<T> {
98 pub fn new(items: Vec<T>, skipped: u32) -> Self {
100 Self { items, skipped }
101 }
102
103 pub fn push(&mut self, item: T) {
105 self.items.push(item);
106 }
107
108 pub fn skip(&mut self) {
110 self.skipped += 1;
111 }
112}
113
114#[cfg(test)]
115mod walk_result_tests {
116 use super::WalkResult;
117
118 #[test]
119 fn walk_result_new_has_correct_counts() {
120 let r: WalkResult<u32> = WalkResult::new(vec![1, 2, 3], 5);
121 assert_eq!(r.items.len(), 3);
122 assert_eq!(r.skipped, 5);
123 }
124
125 #[test]
126 fn walk_result_skip_increments_counter() {
127 let mut r: WalkResult<u32> = WalkResult::default();
128 r.skip();
129 r.skip();
130 assert_eq!(r.skipped, 2);
131 assert!(r.items.is_empty());
132 }
133
134 #[test]
135 fn walk_result_push_adds_item() {
136 let mut r: WalkResult<u32> = WalkResult::default();
137 r.push(42u32);
138 assert_eq!(r.items, vec![42]);
139 assert_eq!(r.skipped, 0);
140 }
141
142 #[test]
143 fn walk_result_serializes_with_skipped_field() {
144 let r = WalkResult::new(vec![1u32, 2], 3);
145 let json = serde_json::to_string(&r).unwrap();
146 assert!(json.contains("\"skipped\":3"), "json: {json}");
147 assert!(json.contains("\"items\""), "json: {json}");
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn error_display_page_not_present() {
157 let e = Error::PageNotPresent(0xFFFF_8000_0000_1000);
158 assert!(e.to_string().contains("0xffff800000001000"));
159 }
160
161 #[test]
162 fn error_display_partial_read() {
163 let e = Error::PartialRead {
164 addr: 0x1000,
165 requested: 8,
166 got: 4,
167 };
168 assert!(e.to_string().contains("4 of 8"));
169 }
170
171 #[test]
172 fn error_display_list_cycle() {
173 let e = Error::ListCycle(10000);
174 assert!(e.to_string().contains("10000"));
175 }
176
177 #[test]
178 fn error_display_missing_symbol() {
179 let e = Error::MissingSymbol("task_struct.pid".into());
180 assert!(e.to_string().contains("task_struct.pid"));
181 }
182
183 #[test]
184 fn error_display_size_mismatch() {
185 let e = Error::SizeMismatch {
186 expected: 8,
187 got: 4,
188 };
189 let msg = e.to_string();
190 assert!(msg.contains('8'));
191 assert!(msg.contains('4'));
192 }
193
194 #[test]
195 fn error_from_physical() {
196 let phys_err = memf_format::Error::UnknownFormat;
197 let e: Error = Error::from(phys_err);
198 assert!(matches!(e, Error::Physical(_)));
199 assert!(e.to_string().contains("unknown dump format"));
200 }
201
202 #[test]
203 fn error_from_symbol() {
204 let sym_err = memf_symbols::Error::NotFound("init_task".into());
205 let e: Error = Error::from(sym_err);
206 assert!(matches!(e, Error::Symbol(_)));
207 assert!(e.to_string().contains("init_task"));
208 }
209
210 #[test]
211 fn error_from_io_via_physical() {
212 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
213 let phys_err = memf_format::Error::from(io_err);
214 let e: Error = Error::from(phys_err);
215 assert!(matches!(e, Error::Physical(_)));
216 }
217
218 #[test]
219 fn error_display_paged_out() {
220 let e = Error::PagedOut {
221 vaddr: 0xFFFF_8000_0000_2000,
222 pagefile_num: 0,
223 page_offset: 0x1234,
224 };
225 let msg = e.to_string();
226 assert!(msg.contains("0xffff800000002000"));
227 assert!(msg.contains("pagefile 0"));
228 assert!(msg.contains("0x1234"));
229 }
230
231 #[test]
232 fn error_display_prototype_pte() {
233 let e = Error::PrototypePte(0xFFFF_8000_DEAD_0000);
234 let msg = e.to_string();
235 assert!(msg.contains("0xffff8000dead0000"));
236 assert!(msg.contains("prototype PTE"));
237 }
238}