use log::trace;
use crate::deref::Deref;
use crate::gdb::dump_sp_bytes;
use crate::mi::{
INSTRUCTION_LEN, data_disassemble, data_disassemble_pc, data_read_memory_bytes, join_registers,
parse_register_values, read_pc_value,
};
use crate::register::RegisterStorage;
use crate::ui::SAVED_STACK;
use crate::{PtrSize, State, Written};
pub fn recv_exec_results_register_values(register_values: &String, state: &mut State) {
let registers_local = parse_register_values(register_values);
for r in registers_local.iter().flatten() {
if r.is_set()
&& let Some(val) = &r.value
{
if state.ptr_size == PtrSize::Size32 {
if let Ok(val_u32) = u32::from_str_radix(&val[2..], 16) {
if val_u32 != 0 {
let mut asked_for_code = false;
if let Some(memory_map) = state.memory_map.as_ref() {
for b in memory_map {
let is_path =
b.is_path(state.filepath.as_ref().unwrap().to_str().unwrap());
if b.contains(u64::from(val_u32)) && (is_path || b.is_exec()) {
state
.next_write
.push(data_disassemble(val_u32 as usize, INSTRUCTION_LEN));
state.written.push_back(Written::SymbolAtAddrRegister((
r.number.clone(),
u64::from(val_u32),
)));
asked_for_code = true;
}
}
}
if !asked_for_code {
state.next_write.push(data_read_memory_bytes(u64::from(val_u32), 0, 4));
state.written.push_back(Written::RegisterValue((
r.number.clone(),
u64::from(val_u32),
)));
}
}
}
} else {
if let Ok(val_u64) = u64::from_str_radix(&val[2..], 16) {
if val_u64 != 0 {
let mut asked_for_code = false;
if let Some(memory_map) = state.memory_map.as_ref() {
for b in memory_map {
let is_path =
b.is_path(state.filepath.as_ref().unwrap().to_str().unwrap());
if b.contains(val_u64) && (is_path || b.is_exec()) {
state
.next_write
.push(data_disassemble(val_u64 as usize, INSTRUCTION_LEN));
state.written.push_back(Written::SymbolAtAddrRegister((
r.number.clone(),
val_u64,
)));
asked_for_code = true;
}
}
}
if !asked_for_code {
state.next_write.push(data_read_memory_bytes(val_u64, 0, 8));
state
.written
.push_back(Written::RegisterValue((r.number.clone(), val_u64)));
}
}
}
}
}
}
let registers_new = join_registers(&state.register_names, ®isters_local);
let registers_new: Vec<RegisterStorage> = registers_new
.iter()
.map(|(a, b)| RegisterStorage::new(a.clone(), b.clone(), Deref::new()))
.collect();
state.registers = registers_new.clone();
trace!("requesting pc bytes");
let val = read_pc_value();
state.next_write.push(val);
trace!("requesting stack");
if state.ptr_size == PtrSize::Size32 {
dump_sp_bytes(state, 4, u64::from(SAVED_STACK));
} else {
dump_sp_bytes(state, 8, u64::from(SAVED_STACK));
}
trace!("updating pc asm");
let instruction_length = 8;
state.next_write.push(data_disassemble_pc(instruction_length * 5, instruction_length * 15));
state.written.push_back(Written::AsmAtPc);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Args, mi::MemoryMapping};
use rstest::rstest;
use std::path::PathBuf;
fn create_test_state(ptr_size: PtrSize) -> State {
let args = Args { gdb_path: None, remote: None, ptr_size, cmds: None, log_path: None };
let mut state = State::new(args);
state.register_names = vec!["rax".to_string(), "rbx".to_string()];
state
}
fn create_memory_map(filepath: &str) -> Vec<MemoryMapping> {
vec![
MemoryMapping {
start_address: 0x400000,
end_address: 0x500000,
size: 0x100000,
offset: 0,
permissions: Some("r-xp".to_string()),
path: Some(filepath.to_string()),
},
MemoryMapping {
start_address: 0x600000,
end_address: 0x700000,
size: 0x100000,
offset: 0,
permissions: Some("rw-p".to_string()),
path: Some("[heap]".to_string()),
},
]
}
#[rstest]
#[case(PtrSize::Size64, "0x450000", true)]
#[case(PtrSize::Size64, "0x650000", false)]
#[case(PtrSize::Size32, "0x450000", true)]
#[case(PtrSize::Size32, "0x650000", false)]
fn test_register_values_code_vs_data(
#[case] ptr_size: PtrSize,
#[case] addr: &str,
#[case] is_code: bool,
) {
let mut state = create_test_state(ptr_size);
state.filepath = Some(PathBuf::from("/usr/bin/test"));
state.memory_map = Some(create_memory_map("/usr/bin/test"));
let register_values = format!(r#"[{{number="0",value="{addr}"}}]"#);
recv_exec_results_register_values(®ister_values, &mut state);
if is_code {
assert!(state.next_write.iter().any(|w| w.contains("data-disassemble")));
assert!(state.written.iter().any(|w| matches!(w, Written::SymbolAtAddrRegister(_))));
} else {
assert!(state.next_write.iter().any(|w| w.contains("data-read-memory-bytes")));
assert!(state.written.iter().any(|w| matches!(w, Written::RegisterValue(_))));
}
}
#[rstest]
#[case(PtrSize::Size32, 4)]
#[case(PtrSize::Size64, 8)]
fn test_register_values_stack_size(#[case] ptr_size: PtrSize, #[case] expected_size: u8) {
let mut state = create_test_state(ptr_size);
let register_values = r#"[{number="0",value="0x1000"}]"#.to_string();
recv_exec_results_register_values(®ister_values, &mut state);
let stack_writes: Vec<_> = state.next_write.iter().filter(|w| w.contains("$sp")).collect();
assert!(!stack_writes.is_empty());
assert!(stack_writes.iter().any(|w| w.contains(&format!(" {expected_size}"))));
}
#[test]
fn test_register_values_null_pointer() {
let mut state = create_test_state(PtrSize::Size64);
state.filepath = Some(PathBuf::from("/usr/bin/test"));
let register_values = r#"[{number="0",value="0x0"}]"#.to_string();
recv_exec_results_register_values(®ister_values, &mut state);
let has_register_memory_request =
state.written.iter().any(|w| matches!(w, Written::RegisterValue((_, 0))));
assert!(!has_register_memory_request);
assert!(state.next_write.iter().any(|w| w.contains("$pc")));
}
#[test]
fn test_register_values_no_memory_map() {
let mut state = create_test_state(PtrSize::Size64);
state.filepath = Some(PathBuf::from("/usr/bin/test"));
state.memory_map = None;
let register_values = r#"[{number="0",value="0x450000"}]"#.to_string();
recv_exec_results_register_values(®ister_values, &mut state);
assert!(!state.registers.is_empty());
}
#[rstest]
#[case(r#"[{number="0",value="<unavailable>"}]"#)]
#[case(r#"[{number="0",error="not available"}]"#)]
fn test_register_values_unavailable(#[case] register_values: &str) {
let mut state = create_test_state(PtrSize::Size64);
recv_exec_results_register_values(®ister_values.to_string(), &mut state);
let has_register_memory_request = state
.written
.iter()
.any(|w| matches!(w, Written::RegisterValue(_) | Written::SymbolAtAddrRegister(_)));
assert!(!has_register_memory_request);
assert!(!state.next_write.is_empty());
assert!(!state.registers.is_empty());
}
#[test]
fn test_register_values_multiple_registers() {
let mut state = create_test_state(PtrSize::Size64);
state.filepath = Some(PathBuf::from("/usr/bin/test"));
state.memory_map = Some(create_memory_map("/usr/bin/test"));
let register_values =
r#"[{number="0",value="0x450000"},{number="1",value="0x460000"}]"#.to_string();
recv_exec_results_register_values(®ister_values, &mut state);
assert_eq!(state.registers.len(), 2);
assert_eq!(state.registers[0].name, "rax");
assert_eq!(state.registers[1].name, "rbx");
}
#[test]
fn test_register_values_requests_pc_and_stack() {
let mut state = create_test_state(PtrSize::Size64);
let register_values = r#"[{number="0",value="0x1000"}]"#.to_string();
recv_exec_results_register_values(®ister_values, &mut state);
assert!(
state
.next_write
.iter()
.any(|w| w.contains("data-evaluate-expression") && w.contains("$pc"))
);
assert!(state.next_write.iter().any(|w| w.contains("$sp")));
assert!(state.written.iter().any(|w| matches!(w, Written::AsmAtPc)));
}
}