use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use iced_x86::Register;
use crate::cli::Command;
use crate::commands::run;
use crate::image::{Image, Segment};
use crate::layout::{FieldInfo, layout, parse_reg, scan_fn};
use crate::layout_fmt::render_layout;
use crate::matching::best_matches;
use crate::runtime::RuntimeMap;
use crate::variant::{arms, variants};
fn build_test_image() -> Image {
let mut data = vec![0u8; 0x400];
let code: &[u8] = &[
0x48, 0x8d, 0x0d, 0xf9, 0x00, 0x00, 0x00, 0x48, 0x63, 0x04, 0x89, 0xff, 0xe0, 0x48, 0x8d, 0x05, 0x2c, 0x00, 0x00, 0x00, 0xc3, ];
data[..code.len()].copy_from_slice(code);
data[0x40..0x4a].copy_from_slice(b"target_err");
data[0x100..0x104].copy_from_slice(&(-0xf3i32).to_le_bytes());
data[0x104..0x108].copy_from_slice(&(-0xeci32).to_le_bytes());
data[0x108..0x10c].copy_from_slice(&0i32.to_le_bytes());
Image {
data,
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x400 }],
functions: vec![0x1000..0x1020, 0x1020..0x1030],
pointers: vec![(0x1060, 0x1040)],
imports: vec![(0x1070, "test_import".to_owned())],
symbols: vec![(0x1000, "main_func".to_owned())],
metadata: crate::image_format::Metadata::default(),
}
}
fn build_matching_image(shift: u32, line_shift: u32) -> Image {
let mut data = vec![0xc3u8; 0x500];
let offset1 = usize::try_from(shift).unwrap_or_default();
let entry_addr = 0x1000 + u64::from(shift);
let second_addr = 0x1100 + u64::from(shift);
let rel_call = 0xfbi32;
data[offset1] = 0xe8;
data[offset1 + 1..offset1 + 5].copy_from_slice(&rel_call.to_le_bytes());
data[offset1 + 5..offset1 + 12].copy_from_slice(&[0x48, 0x8b, 0x85, 0xd0, 0x06, 0x00, 0x00]);
let caller_loc = 0x300i32 - i32::try_from(shift).unwrap_or_default() - 19;
data[offset1 + 12..offset1 + 15].copy_from_slice(&[0x48, 0x8d, 0x3d]);
data[offset1 + 15..offset1 + 19].copy_from_slice(&caller_loc.to_le_bytes());
data[offset1 + 19] = 0xc3;
let offset2 = 0x100 + offset1;
data[offset2..offset2 + 2].copy_from_slice(&[0x48, 0xb9]);
data[offset2 + 2..offset2 + 10].copy_from_slice(b"token mi");
let loc_rel = 0x200i32 - i32::try_from(shift).unwrap_or_default() - 17;
data[offset2 + 10..offset2 + 13].copy_from_slice(&[0x48, 0x8d, 0x3d]);
data[offset2 + 13..offset2 + 17].copy_from_slice(&loc_rel.to_le_bytes());
let str_rel = 0x240i32 - i32::try_from(shift).unwrap_or_default() - 24;
data[offset2 + 17..offset2 + 20].copy_from_slice(&[0x48, 0x8d, 0x35]);
data[offset2 + 20..offset2 + 24].copy_from_slice(&str_rel.to_le_bytes());
data[offset2 + 24..offset2 + 31].copy_from_slice(&[0x48, 0x81, 0xfe, 0x99, 0x01, 0x00, 0x00]);
data[offset2 + 31] = 0xc3;
data[0x308..0x310].copy_from_slice(&11u64.to_le_bytes());
data[0x310..0x314].copy_from_slice(&(100 + line_shift).to_le_bytes());
data[0x314..0x318].copy_from_slice(&1u32.to_le_bytes());
data[0x320..0x32b].copy_from_slice(b"src/core.rs");
data[0x340..0x350].copy_from_slice(b"unique error msg");
Image {
data,
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x500 }],
functions: vec![entry_addr..entry_addr + 20, second_addr..second_addr + 32],
pointers: vec![(0x1300, 0x1320)],
imports: Vec::new(),
symbols: Vec::new(),
metadata: crate::image_format::Metadata::default(),
}
}
fn build_layout_image() -> Image {
let mut data = vec![0x90u8; 0x100];
let code: &[u8] = &[
0x48, 0x8d, 0x77, 0x10, 0x48, 0x83, 0xc7, 0x20, 0x48, 0x8b, 0x77, 0x08, 0xe8, 0x0f, 0x00, 0x00, 0x00, 0xc3, ];
data[..code.len()].copy_from_slice(code);
Image {
data,
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x100 }],
functions: vec![0x1000..0x1020, 0x1020..0x1030],
pointers: Vec::new(),
imports: Vec::new(),
symbols: Vec::new(),
metadata: crate::image_format::Metadata::default(),
}
}
#[test]
fn commands_diffc_dump_stats_func_error_and_variants() {
let img = build_test_image();
let mut out = Vec::new();
assert!(run(&img, Command::Stats, &mut out).is_ok());
assert!(run(&img, Command::Func { addrs: vec![0x1005] }, &mut out).is_ok());
assert!(run(&img, Command::Error { text: "target_err".to_owned(), back: 1 }, &mut out).is_ok());
assert!(run(&img, Command::Variant { addr: 0x1000, indices: vec![0, 99] }, &mut out).is_ok());
assert!(run(&img, Command::Arm { addrs: vec![0x100d] }, &mut out).is_ok());
let temp_dump = std::env::temp_dir().join("xref_cmd_search_dump");
drop(run(&img, Command::Dump { out: temp_dump.clone(), src: None, fields: false, keep_gotos: false }, &mut out));
drop(std::fs::remove_dir_all(temp_dump));
drop(run(
&img,
Command::DiffC {
other: PathBuf::from("nonexistent_test_bin.elf"),
addrs: vec!["0x1000".to_owned()],
target: None,
fields: false,
},
&mut out,
));
if let Ok(exe) = std::env::current_exe() {
drop(run(
&img,
Command::DiffC { other: exe, addrs: vec!["0x1000".to_owned()], target: None, fields: false },
&mut out,
));
}
}
#[test]
fn matching_best_matches_finds_shifted_pair() {
let img_a = build_matching_image(0, 0);
let img_b = build_matching_image(0x20, 5);
let res = best_matches(&img_a, &img_b, &[format!("{:#x}", 0x1100)]).unwrap_or_default();
assert_eq!(res.len(), 1);
assert!(res[0].1.is_some());
}
#[test]
fn image_function_or_span_and_load_paths() {
let img = Image {
data: vec![0x90; 0x200],
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x200 }],
functions: vec![0x1050..0x1070, 0x1070..0x1080],
pointers: Vec::new(),
imports: Vec::new(),
symbols: Vec::new(),
metadata: crate::image_format::Metadata::default(),
};
assert_eq!(img.function_or_span(0x1010, None), Some(0x1010..0x1050));
assert_eq!(img.function_or_span(0x1010, Some(0x1030)), Some(0x1010..0x1030));
assert_eq!(img.function_or_span(0x1080, None), Some(0x1080..0x1200));
if let Ok(exe) = std::env::current_exe() {
drop(Image::load(&exe));
}
}
#[test]
fn layout_scan_fn_and_render_layout_formatting() {
let img = build_layout_image();
let mut fields = BTreeMap::new();
let mut forwarded = Vec::new();
scan_fn(&img, &(0x1000..0x1020), Register::RDI, 0, Some((0x3000, "via_callee")), &mut fields, &mut forwarded);
let f_info = FieldInfo {
width: 8,
ty: "",
reads: 1,
writes: 1,
leas: 1,
stride: Some(8),
ptr_callees: BTreeSet::from([(0x1020, "rdi")]),
val_callees: BTreeSet::from([(0x1020, "rsi")]),
via_callees: BTreeSet::from([(0x1020, "rdx")]),
};
let fields_map = BTreeMap::from([(0x10, f_info)]);
let mut hops = BTreeSet::new();
hops.insert((0i64, 0x1020u64, "rdi"));
hops.insert((8i64, 0x1020u64, "rsi"));
let mut out = Vec::new();
assert!(render_layout(&img, &(0x1000..0x1020), Register::RDI, "rdi", &fields_map, &hops, &mut out).is_ok());
assert!(parse_reg("rdi").is_ok());
assert!(parse_reg("invalid_reg_name").is_err());
drop(layout(&img, "0x1000", "rdi", true, &mut out));
drop(layout(&img, "0x1000", "rdi", false, &mut out));
}
#[test]
fn variant_arms_and_variants_resolution() {
let img = build_test_image();
let mut out = Vec::new();
assert!(arms(&img, &[0x100d], &mut out).is_ok());
assert!(variants(&img, 0x1000, &[0, 99], &mut out).is_ok());
}
#[test]
fn runtime_detect_with_callers_exercises_stats() {
let img = build_test_image();
let rt = RuntimeMap::detect_with_callers(&img);
let _stats = rt.caller_stats(&img, 0x1000);
let _label = rt.label(&img, 0x1000);
}