use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
use std::sync::{Arc, Mutex};
mod stream_output;
use stream_output::stream_output;
mod exec_result;
use exec_result::exec_result;
use log::{debug, trace, warn};
use crate::mi::{MIResponse, data_read_sp_bytes, parse_key_value_pairs, parse_mi_response};
use crate::{PtrSize, State, Written};
pub fn gdb_interact(gdb_stdout: BufReader<Box<dyn Read + Send>>, state: Arc<Mutex<State>>) {
let mut current_map = (None, String::new());
let mut current_symbols = String::new();
for line in gdb_stdout.lines().map_while(Result::ok) {
trace!("{line:?}");
let mut state = state.lock().unwrap();
let response = parse_mi_response(&line);
trace!("response {response:?}");
match &response {
MIResponse::AsyncRecord(reason, kv) => {
if reason == "stopped" {
async_record_stopped(&mut state, kv);
}
}
MIResponse::ExecResult(status, kv) => {
exec_result(&mut state, status, &mut current_map, &mut current_symbols, kv);
}
MIResponse::StreamOutput(t, s) => {
stream_output(t, s, &mut state, &mut current_map, &mut current_symbols);
}
MIResponse::Unknown(s) => {
unknown(&mut state, s);
}
MIResponse::Notify(..) => (),
}
}
}
fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
state.executing = false;
state.async_result.clear();
state.async_result.push_str("Status: ");
if kv.get("bkptno").is_some() {
if let Some(val) = kv.get("bkptno") {
state.async_result.push_str(&format!("bkptno={val}, "));
}
} else if kv.get("signal-name").is_some() {
if let Some(val) = kv.get("signal-name") {
state.async_result.push_str(&format!("signal-name={val}"));
}
if let Some(val) = kv.get("signal-meaning") {
state.async_result.push_str(&format!(", signal-meaning={val}, "));
}
}
if let Some(val) = kv.get("reason") {
state.async_result.push_str(&format!("reason={val}"));
}
if let Some(val) = kv.get("stopped-threads") {
state.async_result.push_str(&format!(", stopped-threads={val}"));
}
if let Some(val) = kv.get("thread-id") {
state.async_result.push_str(&format!(", thread-id={val}"));
}
if state.ptr_size == PtrSize::Auto {
state.next_write.push("-data-evaluate-expression \"sizeof(long)\"".to_string());
state.written.push_back(Written::SizeOfVoidStar);
}
state.next_write.push(r#"-interpreter-exec console "info proc mappings""#.to_string());
state.next_write.push(r#"-interpreter-exec console "show endian""#.to_string());
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
state.next_write.push("-data-list-register-names".to_string());
state.next_write.push("-data-list-register-values x".to_string());
state.next_write.push("-data-list-changed-registers".to_string());
state.next_write.push("-stack-list-frames".to_string());
if let (Some(fullname), Some(line)) = (kv.get("fullname"), kv.get("line")) {
debug!("Source location from stopped event: {fullname}:{line}");
if let Ok(line_num) = line.parse::<u32>() {
let file_changed = state.current_source_file.as_ref() != Some(fullname);
state.current_source_file = Some(fullname.clone());
state.current_source_line = Some(line_num);
if let Ok(content) = std::fs::read_to_string(std::path::Path::new(fullname)) {
state.source_lines =
content.lines().map(std::string::ToString::to_string).collect();
debug!("Read {} lines from source file", state.source_lines.len());
} else {
warn!("Could not read source file: {fullname}");
state.source_lines.clear();
}
if file_changed {
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
}
}
} else if let (Some(file), Some(line)) = (kv.get("file"), kv.get("line")) {
debug!("Source location from stopped event (fallback): {file}:{line}");
if let Ok(line_num) = line.parse::<u32>() {
let file_changed = state.current_source_file.as_ref() != Some(file);
state.current_source_file = Some(file.clone());
state.current_source_line = Some(line_num);
if let Ok(content) = std::fs::read_to_string(std::path::Path::new(file)) {
state.source_lines =
content.lines().map(std::string::ToString::to_string).collect();
debug!("Read {} lines from source file", state.source_lines.len());
} else {
warn!("Could not read source file: {file}");
state.source_lines.clear();
}
if file_changed {
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
}
}
} else {
debug!("No source location information in stopped event");
state.current_source_file = None;
state.current_source_line = None;
state.source_lines.clear();
}
}
fn read_memory(memory: &String) -> (HashMap<String, String>, String) {
let mem_str = memory.strip_prefix(r"[{").unwrap();
let mem_str = mem_str.strip_suffix(r"}]").unwrap();
let data = parse_key_value_pairs(mem_str);
let begin = data["begin"].clone();
let begin = begin.strip_prefix("0x").unwrap().to_string();
(data, begin)
}
fn dump_sp_bytes(state: &mut State, size: u64, amt: u64) {
let mut curr_offset = 0;
for _ in 0..amt {
state.next_write.push(data_read_sp_bytes(curr_offset, size));
state.written.push_back(Written::Stack(None));
curr_offset += size;
}
}
fn unknown(state: &mut State, s: &str) {
let trimmed = s.trim_end();
if trimmed == "(gdb)" {
state.stream_output_prompt = s.to_string();
} else if !trimmed.is_empty() {
push_inferior_line(state, trimmed);
}
}
pub fn push_inferior_line(state: &mut State, line: &str) {
state.output.push(format!("p> {line}"));
}
pub fn write_mi(gdb_stdin_arc: &Arc<Mutex<dyn Write + Send>>, w: &str) {
let mut stdin = gdb_stdin_arc.lock().unwrap();
debug!("writing {w}");
writeln!(stdin, "{w}").expect("Failed to send command");
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Args, PtrSize};
fn create_test_state() -> State {
let args = Args {
gdb_path: None,
remote: None,
ptr_size: PtrSize::Size64,
cmds: None,
log_path: None,
};
State::new(args)
}
#[test]
fn test_unknown_prompt() {
let mut state = create_test_state();
unknown(&mut state, "(gdb) ");
assert_eq!(state.stream_output_prompt, "(gdb) ");
assert_eq!(state.output.len(), 0);
}
#[test]
fn test_unknown_inferior_line() {
let mut state = create_test_state();
state.stream_output_prompt = "(gdb) ".to_string();
unknown(&mut state, "hello world");
assert_eq!(state.output, vec!["p> hello world".to_string()]);
assert_eq!(state.stream_output_prompt, "(gdb) ");
}
#[test]
fn test_unknown_empty_line() {
let mut state = create_test_state();
state.stream_output_prompt = "(gdb) ".to_string();
unknown(&mut state, "");
assert_eq!(state.output.len(), 0);
assert_eq!(state.stream_output_prompt, "(gdb) ");
}
}