1use std::env;
3use std::result::Result;
4
5use udmp_parser::UserDumpParser;
6
7struct Cli {
9 dump_path: String,
10 show_all: bool,
11 show_mods: bool,
12 show_memmap: bool,
13 show_threads: bool,
14 show_foreground_thread: bool,
15 thread: Option<u32>,
16 address: Option<u64>,
17}
18
19fn string_to_hex(s: &str) -> Result<u64, String> {
21 u64::from_str_radix(s.trim_start_matches("0x"), 16).map_err(|e| e.to_string())
22}
23
24fn parse_args() -> Result<Cli, String> {
26 let mut dump_path = None;
27 let mut show_all = false;
28 let mut show_mods = false;
29 let mut show_memmap = false;
30 let mut show_threads = false;
31 let mut show_foreground_thread = false;
32 let mut thread = None;
33 let mut address = None;
34
35 let args = env::args().collect::<Vec<_>>();
36 let mut idx = 1;
37 while idx < args.len() {
38 let cur = &args[idx];
39 let is_final = (idx + 1) >= args.len();
40 if is_final {
41 dump_path = Some(cur.clone());
42 break;
43 }
44
45 let next = if is_final { None } else { Some(&args[idx + 1]) };
46 match cur.as_str() {
47 "-a" => {
48 show_all = true;
49 }
50 "-mods" => {
51 show_mods = true;
52 }
53 "-mem" => {
54 show_memmap = true;
55 }
56 "-t" => {
57 show_threads = true;
58 let Some(next) = next else {
59 break;
60 };
61
62 if next == "main" {
63 show_foreground_thread = true;
64 } else {
65 thread = next.parse().map(Some).unwrap_or(None);
66 }
67
68 if show_foreground_thread || thread.is_some() {
69 idx += 1;
70 }
71 }
72 "-dump" => {
73 let Some(next) = next else {
74 return Err("-dump needs to be followed by an address".into());
75 };
76
77 address = Some(string_to_hex(next)?);
78 idx += 1;
79 }
80 rest => {
81 return Err(format!("{} is not a valid option", rest));
82 }
83 };
84
85 idx += 1;
86 }
87
88 let Some(dump_path) = dump_path else {
89 return Err("You didn't specify a dump path".into());
90 };
91
92 Ok(Cli {
93 dump_path,
94 show_all,
95 show_mods,
96 show_memmap,
97 show_threads,
98 show_foreground_thread,
99 thread,
100 address,
101 })
102}
103
104fn hexdump(address: u64, mut data_iter: impl ExactSizeIterator<Item = u8>) {
106 let len = data_iter.len();
107 for i in (0..len).step_by(16) {
108 print!("{:016x}: ", address + (i as u64 * 16));
109 let mut row = [None; 16];
110 for item in row.iter_mut() {
111 if let Some(c) = data_iter.next() {
112 *item = Some(c);
113 print!("{:02x}", c);
114 } else {
115 print!(" ");
116 }
117 }
118 print!(" |");
119 for item in &row {
120 if let Some(c) = item {
121 let c = char::from(*c);
122 print!("{}", if c.is_ascii_graphic() { c } else { '.' });
123 } else {
124 print!(" ");
125 }
126 }
127 println!("|");
128 }
129}
130
131fn help() {
133 println!("parser.exe [-a] [-mods] [-mem] [-t [<TID|main>]] [-dump <addr>] <dump path>");
134 println!();
135 println!("Examples:");
136 println!(" Show all:");
137 println!(" parser.exe -a user.dmp");
138 println!(" Show loaded modules:");
139 println!(" parser.exe -mods user.dmp");
140 println!(" Show memory map:");
141 println!(" parser.exe -mem user.dmp");
142 println!(" Show all threads:");
143 println!(" parser.exe -t user.dmp");
144 println!(" Show thread w/ specific TID:");
145 println!(" parser.exe -t 1337 user.dmp");
146 println!(" Show foreground thread:");
147 println!(" parser.exe -t main user.dmp");
148 println!(" Dump a memory page at a specific address:");
149 println!(" parser.exe -dump 0x7ff00 user.dmp");
150}
151
152fn main() -> Result<(), String> {
153 if env::args().len() == 1 {
155 help();
156 return Ok(());
157 }
158
159 let cli = parse_args()?;
161
162 let dump = UserDumpParser::new(cli.dump_path).map_err(|e| e.to_string())?;
164
165 if cli.show_mods || cli.show_all {
167 println!("Loaded modules:");
168
169 for (base, module) in dump.modules() {
171 println!("{:016x}: {}", base, module.path.display());
172 }
173 }
174
175 if cli.show_memmap || cli.show_all {
177 println!("Memory map:");
178
179 for block in dump.mem_blocks().values() {
181 let state = block.state_as_str();
183 let type_ = block.type_as_str();
184 let protect = block.protect_as_str();
185
186 print!(
188 "{:016x} {:016x} {:016x} {:11} {:11} {:22}",
189 block.range.start,
190 block.range.end,
191 block.len(),
192 type_,
193 state,
194 protect
195 );
196
197 let module = dump.get_module(block.range.start);
199
200 if let Some(module) = module {
202 print!(
203 " [{}; \"{}\"]",
204 module.file_name().unwrap(),
205 module.path.display()
206 );
207 }
208
209 if block.data.len() >= 4 {
212 print!(
213 " {:02x} {:02x} {:02x} {:02x}...",
214 block.data[0], block.data[1], block.data[2], block.data[3]
215 );
216 }
217
218 println!();
219 }
220 }
221
222 if cli.show_threads || cli.show_all {
224 println!("Threads:");
225
226 let foreground_tid = dump.foreground_tid;
228
229 for (tid, thread) in dump.threads() {
231 if let Some(wanted_tid) = cli.thread {
233 if *tid != wanted_tid {
235 continue;
236 }
237
238 }
240
241 if cli.show_foreground_thread
244 && *tid != foreground_tid.expect("no foreground thread id in dump")
245 {
246 continue;
247 }
248
249 println!("TID {}, TEB {:016x}", tid, thread.teb);
251 println!("Context:");
252 println!("{}", thread.context());
253 }
254 }
255
256 if let Some(address) = cli.address {
258 println!("Memory:");
259
260 let block = dump.get_mem_block(address);
262
263 if let Some(block) = block {
265 if let Some(data) = block.data_from(address) {
267 println!("{:016x} -> {:016x}", address, block.end_addr());
268 hexdump(address, data.iter().take(0x1_00).copied());
269 }
270 else {
272 println!(
273 "The memory at {:016x} (from block {:016x} -> {:016x}) has no backing data",
274 address, block.range.start, block.range.end
275 );
276 }
277 }
278 else {
280 println!("No memory block were found for {:016x}", address);
281 }
282 }
283
284 Ok(())
286}