1use chrono::Local;
7use clap::Parser;
8
9use jit::{ExitReason, GuestMemory, GuestState, JitRuntime, ProgramImage, RemuConfig};
10use lifter::{MemoryView, RiscVArch, lift_block};
11
12use std::env;
13use std::fs;
14use std::io::{self, Write as _};
15use std::path::{Path, PathBuf};
16use std::sync::atomic::Ordering;
17
18#[derive(Parser, Debug)]
19#[command(name = "remu")]
20struct Cli {
22 input_file: PathBuf,
24
25 #[arg(long)]
26 entry_symbol: Option<String>,
28
29 #[arg(long)]
30 no_aot: bool,
32
33 #[arg(long)]
34 sysroot: Option<PathBuf>,
36
37 #[arg(long)]
38 workdir: Option<PathBuf>,
40
41 #[arg(short, long)]
42 verbose: bool,
44
45 #[arg(long)]
46 debug: bool,
48
49 #[arg(
50 long,
51 default_value_t = 2,
52 value_parser = clap::value_parser!(u8).range(0..=2)
53 )]
54 jit_opt_level: u8,
57
58 #[arg(long)]
59 no_jit_cache: bool,
61
62 #[arg(long)]
64 raw_jit: bool,
66
67 #[arg(last = true)]
69 guest_args: Vec<String>,
71}
72
73fn main() {
84 let cli = Cli::parse();
86
87 println!("\n--------- remu ---------");
88
89 let output_dir = match create_output_dir(&cli.input_file) {
91 Ok(path) => path,
92 Err(err) => {
93 eprintln!("error while creating output dir: {err}");
94 return;
95 }
96 };
97
98 let image = match fs::read(&cli.input_file) {
100 Ok(bytes) => bytes,
101 Err(err) => {
102 eprintln!(
103 "error while reading input file {}: {err}",
104 cli.input_file.display()
105 );
106 return;
107 }
108 };
109
110 let program = match ProgramImage::from_elf_bytes(&image) {
111 Ok(program) => program,
112 Err(err) => {
113 eprintln!("error while loading ELF: {err}");
114 return;
115 }
116 };
117
118 println!("\n\n----- ELF loaded -----");
119 println!("Input: {}", cli.input_file.display());
120 println!("Output dir: {}", output_dir.display());
121 println!("Entry point: 0x{:x}", program.entry_pc);
122
123 if let Some(value) = &cli.entry_symbol
125 && let Some(entry_sym) = program.find_symbol(value)
126 {
127 println!("{} symbol gefunden: 0x{:x}", value, entry_sym.value);
128 }
130
131 let mut argv = vec![input_basename(&cli.input_file)];
133 argv.extend(cli.guest_args);
134 let jit_cache_dir = if cli.no_jit_cache {
136 None
137 } else {
138 match project_root() {
140 Ok(root) => Some(root.join("jit-tmp/cache")),
141 Err(err) => {
142 eprintln!("error while finding project root: {err}");
143 return;
144 }
145 }
146 };
147 let process = RemuConfig {
148 argv,
149 envp: Vec::new(),
150 sysroot: cli.sysroot,
151 workdir: cli.workdir,
152 jit_opt_level: cli.jit_opt_level,
153 jit_cache_dir,
154 raw_jit: cli.raw_jit,
155 };
156
157 let mut runtime =
158 match JitRuntime::init_runtime(program, process, output_dir.clone(), |memory, pc| {
160 let view = MemoryView { memory };
162
163 match lift_block(RiscVArch::Rv64, &view, pc) {
165 Some(nnil) => Ok(nnil),
166 None => Err(io::Error::other(format!(
167 "no executable region contains guest pc 0x{pc:x}"
168 ))
169 .into()),
170 }
171 }) {
172 Ok(runtime) => runtime,
173 Err(err) => {
174 eprintln!("error while creating runtime: {err}");
175 return;
176 }
177 };
178
179 runtime.set_verbose(cli.verbose);
180
181 if cli.verbose {
182 println!("AOT precompile: {}", if cli.no_aot { "off" } else { "on" });
183 }
184
185 if !cli.no_aot {
186 println!("\n----- pre-compiling as much as possible now -----");
188 let compiled_regions = match runtime.precompile() {
189 Ok(count) => count,
190 Err(err) => {
191 eprintln!("aot precompile failed: {err}");
192 return;
193 }
194 };
195
196 if cli.verbose {
197 println!("amount of precompiled regions: {}", compiled_regions);
198 }
199 }
200
201 println!("\n----- starting execution + JIT now -----");
202
203 let exit_reason = match if cli.debug {
204 run_debugger(&mut runtime)
205 } else {
206 runtime.run()
207 } {
208 Ok(reason) => reason,
209 Err(err) => {
210 eprintln!("oh oh: {err}");
211 return;
212 }
213 };
214
215 let state = runtime.state();
216
217 println!("Exit reason: {exit_reason:?}");
218 println!("pc = 0x{:x}", state.pc);
219 println!("a0/x10 = {}", state.regs[10]);
221 println!("sp/x2 = 0x{:x}", state.regs[2]);
222 println!("fault_addr = 0x{:x}", state.fault_addr);
223 println!("fault_access = {:?}", state.fault_access);
224 println!("cached regions = {}", runtime.cache_len());
225
226 match exit_reason {
227 ExitReason::UnhandledHostCall => {
228 println!("UnhandledHostCall");
229 }
230 ExitReason::GuestExit => {
231 println!("GuestExit");
232 }
233 _ => {}
234 }
235}
236
237
238fn run_debugger<L>(runtime: &mut JitRuntime<L>) -> Result<ExitReason, Box<dyn std::error::Error>>
240where
241 L: Fn(&GuestMemory, u64) -> Result<liil::nnil::Nnil, Box<dyn std::error::Error>>,
242{
243 let pause = runtime.pause_flag();
246 ctrlc::set_handler(move || pause.store(true, Ordering::Relaxed))?;
249
250 let mut checkpoint = None;
252 let mut reason = ExitReason::Paused;
253 print_stop(runtime, reason);
254
255 loop {
256 print!("(remu) ");
257 io::stdout().flush()?;
258 let mut line = String::new();
259
260 if io::stdin().read_line(&mut line)? == 0 {
262 return Ok(reason);
263 }
264
265 let mut words = line.split_whitespace();
266 let Some(command) = words.next() else {
267 continue;
268 };
269
270 match command {
271 "continue" | "c" => {
272 reason = match runtime.run_for_blocks(u64::MAX) {
274 Ok(reason) => reason,
275 Err(err) => {
276 println!("execution error: {err}");
277 print_stop(runtime, runtime.state().exit_reason);
278 continue;
279 }
280 };
281 print_stop(runtime, reason);
282
283 if reason == ExitReason::GuestExit {
284 return Ok(reason);
285 }
286 }
287 "step" | "s" => {
288 reason = match runtime.run_for_blocks(1) {
291 Ok(reason) => reason,
292 Err(err) => {
293 println!("execution error: {err}");
294 print_stop(runtime, runtime.state().exit_reason);
295 continue;
296 }
297 };
298 print_stop(runtime, reason);
299 }
300 "break" | "b" => {
301 if let Some(value) = words.next() {
302 match parse_address(runtime.program(), value) {
303 Some(address) => match runtime.add_breakpoint(address) {
304 Ok(()) => println!("breakpoint at 0x{address:x}"),
305 Err(err) => println!("error: {err}"),
306 },
307 None => println!("unknown address or symbol: {value}"),
308 }
309 } else {
310 for address in runtime.breakpoints() {
311 println!("0x{address:x}");
312 }
313 }
314 }
315 "delete" => match words.next() {
316 Some("all") => runtime.clear_breakpoints(),
317 Some(value) => match parse_address(runtime.program(), value) {
318 Some(address) => runtime.remove_breakpoint(address),
319 None => println!("unknown address or symbol: {value}"),
320 },
321 None => println!("usage: delete <address|symbol|all>"),
322 },
323 "regs" => print_registers(runtime.state()),
324 "mem" => {
325 let Some(value) = words.next() else {
326 println!("usage: mem <address> [length]");
327 continue;
328 };
329 let Some(address) = parse_address(runtime.program(), value) else {
330 println!("unknown address or symbol: {value}");
331 continue;
332 };
333 let length = words.next().and_then(parse_number).unwrap_or(64).min(4096) as usize;
335 print_memory(runtime.memory(), address, length);
336 }
337 "maps" => {
338 for (start, end, perms) in runtime.memory().mappings() {
339 println!(
342 "0x{start:016x}-0x{end:016x} {}{}{}",
343 if perms.contains(jit::PagePerms::READ) {
344 'r'
345 } else {
346 '-'
347 },
348 if perms.contains(jit::PagePerms::WRITE) {
349 'w'
350 } else {
351 '-'
352 },
353 if perms.contains(jit::PagePerms::EXEC) {
354 'x'
355 } else {
356 '-'
357 },
358 );
359 }
360 }
361 "where" => print_stop(runtime, reason),
362 "nnil" => match runtime.lift_at(runtime.state().pc) {
363 Ok(nnil) => println!("{nnil:?}"),
364 Err(err) => println!("error: {err}"),
365 },
366 "trace" => {
367 for pc in runtime.trace() {
368 println!("0x{pc:016x}");
369 }
370 }
371 "snapshot" => {
372 checkpoint = Some(runtime.snapshot()?);
373 println!("snapshot saved");
374 }
375 "restore" => match &checkpoint {
376 Some(snapshot) => {
377 runtime.restore(snapshot)?;
378 reason = ExitReason::Paused;
379 print_stop(runtime, reason);
380 }
381 None => println!("wip! no snapshot saved"),
382 },
383 "reset" => {
384 runtime.reset()?;
385 reason = ExitReason::Paused;
386 print_stop(runtime, reason);
387 }
388 "help" | "h" => println!(
389 "ACHTUNG, NOCH WORK-IN-PROGRES. Commands: continue, step, break <address oder symbol>, delete <address oder symbol oder all>, regs, mem <address> [length], maps, where, nnil, trace, snapshot, restore, reset, quit"
390 ),
391 "quit" | "q" => return Ok(reason),
392 _ => println!("unknown command, use help"),
393 }
394 }
395}
396
397fn parse_number(value: &str) -> Option<u64> {
398 value
400 .strip_prefix("0x")
401 .and_then(|value| u64::from_str_radix(value, 16).ok())
402 .or_else(|| value.parse().ok())
403}
404
405fn parse_address(program: &ProgramImage, value: &str) -> Option<u64> {
406 parse_number(value).or_else(|| program.find_symbol(value).map(|symbol| symbol.value))
408}
409
410fn print_stop<L>(runtime: &JitRuntime<L>, reason: ExitReason)
411where
412 L: Fn(&GuestMemory, u64) -> Result<liil::nnil::Nnil, Box<dyn std::error::Error>>,
413{
414 let state = runtime.state();
415 print!("stopped: {reason:?}, pc=0x{:x}", state.pc);
416 if let Some(symbol) = runtime.program().symbol_at(state.pc) {
417 print!(" <{}+0x{:x}>", symbol.name, state.pc - symbol.value);
418 }
419 println!();
420
421 if let Some(half) = runtime.memory().fetch_u16(state.pc) {
423 if half & 3 == 3 {
427 if let Some(word) = runtime.memory().fetch_u32(state.pc) {
428 println!("instruction: 0x{word:08x}");
430 }
431 } else {
432 println!("instruction: 0x{half:04x}");
434 }
435 }
436 if state.fault_access != jit::AccessKind::None {
437 println!(
438 "fault: {:?} at 0x{:x}",
439 state.fault_access, state.fault_addr
440 );
441 }
442 if let Some([nr, pc, a0, a1, a2, a3, a4, a5]) = runtime.last_syscall() {
443 println!(
446 "last syscall: {nr} at 0x{pc:x} (0x{a0:x}, 0x{a1:x}, 0x{a2:x}, 0x{a3:x}, 0x{a4:x}, 0x{a5:x})"
447 );
448 }
449}
450
451fn print_registers(state: &GuestState) {
452 const NAMES: [&str; 32] = [
454 "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4",
455 "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4",
456 "t5", "t6",
457 ];
458 println!("pc 0x{:016x}", state.pc);
460 for (index, name) in NAMES.iter().enumerate() {
461 println!("x{index:<2} {name:<4} 0x{:016x}", state.regs[index]);
462 }
463 for index in 0..32 {
465 println!("f{index:<2} 0x{:016x}", state.regs[32 + index]);
466 }
467}
468
469fn print_memory(memory: &GuestMemory, address: u64, length: usize) {
470 for offset in (0..length).step_by(16) {
472 let count = 16.min(length - offset);
474 let Some(bytes) = memory.load_bytes(address + offset as u64, count) else {
475 println!("0x{:016x}: <unreadable>", address + offset as u64);
476 return;
477 };
478 print!("0x{:016x}:", address + offset as u64);
479 for byte in &bytes {
480 print!(" {byte:02x}");
482 }
483 println!();
484 }
485}
486
487fn create_output_dir(input_file: &Path) -> io::Result<PathBuf> {
490 let root = project_root()?.join("output");
491 fs::create_dir_all(&root)?;
492
493 let file_name = input_basename(input_file);
494 let timestamp = Local::now().format("%Y-%m-%d_%H-%M-%S"); let output_dir = root.join(format!("run-{file_name}-{timestamp}"));
498 fs::create_dir_all(&output_dir)?;
499
500 Ok(output_dir)
501}
502
503fn project_root() -> io::Result<PathBuf> {
504 Path::new(env!("CARGO_MANIFEST_DIR"))
507 .ancestors()
508 .nth(2)
509 .map(Path::to_path_buf)
510 .ok_or_else(|| io::Error::other("project root not found!"))
511}
512
513fn input_basename(input_file: &Path) -> String {
514 input_file
516 .file_name()
517 .unwrap_or(input_file.as_os_str())
518 .to_string_lossy()
519 .into_owned()
520}