diff --git a/src/gdb/exec_result/recv/result_memory.rs b/src/gdb/exec_result/recv/result_memory.rs index fa09406..c2c4f9b 100644 --- a/src/gdb/exec_result/recv/result_memory.rs +++ b/src/gdb/exec_result/recv/result_memory.rs @@ -176,18 +176,19 @@ fn update_stack(data: HashMap, state: &mut State, begin: String) } } - // all string? Request the next - if val > 0xff { - let bytes = val.to_le_bytes(); - if bytes - .iter() - .all(|a| a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace()) - { - let addr = data["begin"].strip_prefix("0x").unwrap().to_string(); - let addr = u64::from_str_radix(&addr, 16).unwrap(); - state.next_write.push(data_read_memory_bytes(addr + len, 0, len)); - state.written.push_back(Written::Stack(Some(begin))); - return; + if state.config.deref_show_string { + //all string? Request the next + if val > 0xff { + let bytes = val.to_le_bytes(); + if bytes.iter().all(|a| { + a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace() + }) { + let addr = data["begin"].strip_prefix("0x").unwrap().to_string(); + let addr = u64::from_str_radix(&addr, 16).unwrap(); + state.next_write.push(data_read_memory_bytes(addr + len, 0, len)); + state.written.push_back(Written::Stack(Some(begin))); + return; + } } } diff --git a/src/gdb/exec_result/recv/value.rs b/src/gdb/exec_result/recv/value.rs index d8381e6..be63423 100644 --- a/src/gdb/exec_result/recv/value.rs +++ b/src/gdb/exec_result/recv/value.rs @@ -18,7 +18,8 @@ pub fn recv_exec_result_value(state: &mut State, value: &String) { } else { // program is stopped, get the current pc let pc: Vec<&str> = value.split_whitespace().collect(); - let pc = pc[0].strip_prefix("0x").unwrap(); - state.current_pc = u64::from_str_radix(pc, 16).unwrap(); + if let Some(pc) = pc[0].strip_prefix("0x") { + state.current_pc = u64::from_str_radix(pc, 16).unwrap(); + } } } diff --git a/src/main.rs b/src/main.rs index 30e4ca5..03bf9e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -204,6 +204,18 @@ impl Scroll { } } +#[derive(Clone, Debug)] +struct Config { + // Deref and show string values + deref_show_string: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { deref_show_string: true } + } +} + #[derive(Clone, Debug)] struct State { /// Messages to write to gdb mi @@ -253,10 +265,11 @@ struct State { status: String, bt: Vec, completions: Vec, + config: Config, } impl State { - pub fn new(args: Args) -> State { + pub fn new(args: Args, config: Config) -> State { State { next_write: vec![], written: VecDeque::new(), @@ -286,6 +299,7 @@ impl State { status: String::new(), bt: vec![], completions: vec![], + config, } } } @@ -425,7 +439,8 @@ fn main() -> anyhow::Result<()> { } // Start rx thread let (gdb_stdout, mut app) = App::new_stream(args.clone()); - let state = State::new(args.clone()); + let config = Config::default(); + let state = State::new(args.clone(), config); let mut state_share = StateShare { state: Arc::new(Mutex::new(state)) }; // Setup terminal @@ -1067,9 +1082,15 @@ mod tests { use ratatui::{Terminal, backend::TestBackend}; use test_assets_ureq::{TestAssetDef, dl_test_files_backoff}; - fn run_a_bit(args: Args) -> (App, StateShare, Terminal) { + fn run_a_bit( + args: Args, + mode: Mode, + size: Option<(u16, u16)>, + ) -> (App, StateShare, Terminal) { let (gdb_stdout, mut app) = App::new_stream(args.clone()); - let state = State::new(args.clone()); + let config = Config { deref_show_string: false, ..Config::default() }; + let mut state = State::new(args.clone(), config); + state.mode = mode; let state_share = StateShare { state: Arc::new(Mutex::new(state)) }; spawn_gdb_interact(&state_share, gdb_stdout); @@ -1083,7 +1104,11 @@ mod tests { } } } - let mut terminal = Terminal::new(TestBackend::new(160, 50)).unwrap(); + let mut terminal = if let Some((x, y)) = size { + Terminal::new(TestBackend::new(x, y)).unwrap() + } else { + Terminal::new(TestBackend::new(160, 50)).unwrap() + }; let start_time = Instant::now(); let duration = Duration::from_secs(10); @@ -1147,7 +1172,7 @@ mod tests { let mut args = Args::default(); args.cmds = Some(PathBuf::from("test-sources/repeated_ptr.source")); - let (_, state, terminal) = run_a_bit(args); + let (_, state, terminal) = run_a_bit(args, Mode::All, None); let _output = terminal.backend(); let registers = state.state.lock().unwrap().registers.clone(); let stack = state.state.lock().unwrap().stack.clone(); @@ -1164,6 +1189,37 @@ mod tests { assert!(stack[5].1.repeated_pattern); } + #[test] + fn test_segfault() { + // ``` + // int main() { + // int *p = (int*)0xdeadbeef; + // *p = 42; + // return 0; + // } + // ``` + const FILE_NAME: &str = "segfault"; + const TEST_PATH: &str = "test-assets/segfault/"; + let file_path = format!("{TEST_PATH}/{FILE_NAME}"); + let asset_defs = [TestAssetDef { + filename: FILE_NAME.to_string(), + hash: "61611926b49c78c45d3988dfca1a612c0eb78f68b2cc4ae07ac8d7080f4c2e77".to_string(), + url: "https://wcampbell.dev/heretek/segfault/segfault".to_string(), + }]; + + dl_test_files_backoff(&asset_defs, TEST_PATH, true, Duration::from_secs(1)).unwrap(); + let c_path = CString::new(file_path.to_string()).expect("CString::new failed"); + let mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + unsafe { chmod(c_path.as_ptr(), mode) }; + + let mut args = Args::default(); + args.cmds = Some(PathBuf::from("test-sources/segfault.source")); + + let (_, state, terminal) = run_a_bit(args, Mode::All, Some((160, 40))); + let output = terminal.backend(); + assert_snapshot!(output); + } + #[test] fn test_render_app() { // gcc test.c -g -fno-stack-protector -static @@ -1209,7 +1265,7 @@ mod tests { let mut args = Args::default(); args.cmds = Some(PathBuf::from("test-sources/test.source")); - let (_, state, terminal) = run_a_bit(args); + let (_, state, terminal) = run_a_bit(args, Mode::All, None); let output = terminal.backend(); // Now, we need to rewrite all the addresses that change for the registers and stack diff --git a/src/snapshots/heretek__tests__render_app.snap b/src/snapshots/heretek__tests__render_app.snap index c683d1c..4f0dd1d 100644 --- a/src/snapshots/heretek__tests__render_app.snap +++ b/src/snapshots/heretek__tests__render_app.snap @@ -9,8 +9,8 @@ snapshot_kind: text " rax → 0x401825 → main+0 (push rbp) █" " rbx → 0x1 ║" " rcx → → 0x04 ║" -" rdx → ║" -" rsi → ║" +" rdx → ║" +" rsi → ║" " rdi → 0x1 ║" " rbp → → 0x00 ║" " rsp → → 0x00 ║" diff --git a/src/snapshots/heretek__tests__segfault.snap b/src/snapshots/heretek__tests__segfault.snap new file mode 100644 index 0000000..2cdf695 --- /dev/null +++ b/src/snapshots/heretek__tests__segfault.snap @@ -0,0 +1,45 @@ +--- +source: src/main.rs +expression: output +snapshot_kind: text +--- +"────────────────────────────────────────────────────────────────────────|heretek-v0.5.1|──────────────────────────────── | Heap | Stack | Code | String | Asm | " +" F1 Main | F2 Registers | F3 Stack | F4 Instructions | F5 Output | F6 Mapping | F7 Hexdump " +"Registers──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────▲" +" rax → 0xdeadbeef █" +" rbx → 0x1 ║" +" rcx → 0x111 ║" +" rdx → 0x7fffffffb238 → 0x7fffffffb5dd → ║" +" rsi → 0x7fffffffb228 → 0x7fffffffb594 → ║" +" rdi → 0x1 ║" +" rbp → 0x7fffffffb100 → 0x7fffffffb110 → 0x7fffffffb1b0 → 0x7fffffffb200 → 0x00 ║" +" rsp → 0x7fffffffb100 → 0x7fffffffb110 → 0x7fffffffb1b0 → 0x7fffffffb200 → 0x00 ║" +" r8 → 0x4a71c0 → main_arena+0 (add BYTE PTR [rax],al) ▼" +"Stack───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────" +" 0x7fffffffb100 → 0x7fffffffb110 → 0x7fffffffb1b0 → 0x7fffffffb200 → 0x00 " +" 0x7fffffffb108 → 0x402e6c → main+9 (mov eax,0x0) " +" 0x7fffffffb110 → 0x7fffffffb1b0 → 0x7fffffffb200 → 0x00 " +" 0x7fffffffb118 → 0x4033f5 → __libc_start_call_main+101 (mov edi,eax) " +" 0x7fffffffb120 → 0x4a8ae0 → (bad) " +"Instructions (child)────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────" +" 0x402e46 child+01 mov rbp,rsp " +" 0x402e49 child+04 mov eax,0xdeadbeef " +" 0x402e4e child+09 mov QWORD PTR [rbp-0x8],rax " +" 0x402e52 child+0d mov rax,QWORD PTR [rbp-0x8] " +">>0x402e56 child+11 mov DWORD PTR [rax],0x2a " +"Backtrace───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────" +" 00402e56 → child " +" 00402e6c → main " +"┌Output ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐" +"│h> -gdb-set mi-async on │" +"│run │" +"│file test-assets/segfault/segfault │" +"│Reading symbols from test-assets/segfault/segfault... │" +"│(No debugging symbols found in test-assets/segfault/segfault) │" +"│Program │" +"│ received signal SIGSEGV, Segmentation fault. │" +"│0x0000000000402e56 in child () │" +"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘" +"┌|Press q to exit, i to enter input|─|Status: signal-name=SIGSEGV, signal-meaning=Segmentation fault, reason=signal-received, stopped-threads=all, thread-id=1|┐" +"│(gdb) │" +"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘" diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 13f797d..8045500 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -199,25 +199,38 @@ pub fn add_deref_to_span( width: usize, ) { for (i, v) in deref.map.iter().enumerate() { - // check if ascii - if *v > 0xff { - let bytes = (*v).to_le_bytes(); - if bytes - .iter() - .all(|a| a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace()) - { - // if we detect it's ascii, the rest is ascii - let mut full_s = String::new(); - for r in deref.map.iter().skip(i) { - let bytes = (*r).to_le_bytes(); - if let Ok(s) = std::str::from_utf8(&bytes) { - full_s.push_str(s); + if state.config.deref_show_string { + // check if ascii + if *v > 0xff { + let bytes = (*v).to_le_bytes(); + if bytes.iter().all(|a| { + a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace() + }) { + // if we detect it's ascii, the rest is ascii + let mut full_s = String::new(); + for r in deref.map.iter().skip(i) { + let bytes = (*r).to_le_bytes(); + if let Ok(s) = std::str::from_utf8(&bytes) { + full_s.push_str(s); + } } + let cell = + Span::from(format!("→ \"{full_s}\"")).style(Style::new().fg(STRING_COLOR)); + spans.push(cell); + return; + } + } + } else { + if *v > 0xff { + let bytes = (*v).to_le_bytes(); + if bytes.iter().all(|a| { + a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace() + }) { + let cell = + Span::from(format!("→ ")).style(Style::new().fg(STRING_COLOR)); + spans.push(cell); + return; } - let cell = - Span::from(format!("→ \"{full_s}\"")).style(Style::new().fg(STRING_COLOR)); - spans.push(cell); - return; } } diff --git a/test-sources/segfault.source b/test-sources/segfault.source new file mode 100644 index 0000000..6c75bbb --- /dev/null +++ b/test-sources/segfault.source @@ -0,0 +1,2 @@ +file test-assets/segfault/segfault +run