Skip to content

Run GDB in async mode to allow interrupts #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Adjusted size of UI elements in Main View [#102](https://github.com/wcampbell0x2a/heretek/pull/102)
- Add `--gdb-path` to override gdb executated [#101](https://github.com/wcampbell0x2a/heretek/pull/101)
- Show `Running` in status [#106](https://github.com/wcampbell0x2a/heretek/pull/106)
- Allow `control+c` to send `SIGINT` to process [#106](https://github.com/wcampbell0x2a/heretek/pull/106)
- Always use `mi-async`
- Override `continue` into `-exec-continue`
- Override `stepi` into `-exec-step-instruction`
- Override `step` into `-exec-step`
- Change `--cmd` into `--cmds` and from using `gdb> source` to just running each line as a gdb cmd [#106](https://github.com/wcampbell0x2a/heretek/pull/106)

## [0.2.0] - 2025-01-02
- Remove `--local` argument, `heretek` now runs gdb locally by default [#96](https://github.com/wcampbell0x2a/heretek/pull/96)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Options:
--32
Switch into 32-bit mode

-c, --cmd <CMD>
-c, --cmds <CMDS>
Execute GDB commands

-h, --help
Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Update screenshot
```
$ cargo r --profile=dist -- --cmd "source test-sources/test.source"
$ cargo r --profile=dist -- --cmds "test-sources/test.source"
$ wmctrl -lx
$ import -window {id} images/screenshot.png
```
Binary file modified images/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 9 additions & 5 deletions src/gdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ fn exec_result_running(

// reset status
let mut async_result = async_result_arc.lock().unwrap();
*async_result = String::new();
*async_result = "Status: running".to_string();

// reset written
// TODO: research this. This prevents the "hold down enter and confuse this program".
Expand All @@ -219,7 +219,8 @@ fn async_record_stopped(
// in the case of a breakpoint, save the output
// Either it's a breakpoint event, step, signal
let mut async_result = async_result_arc.lock().unwrap();
async_result.push_str("Status(");
async_result.clear();
async_result.push_str("Status: ");
if v.get("bkptno").is_some() {
if let Some(val) = v.get("bkptno") {
async_result.push_str(&format!("bkptno={val}, "));
Expand All @@ -241,7 +242,6 @@ fn async_record_stopped(
if let Some(val) = v.get("thread-id") {
async_result.push_str(&format!(", thread-id={val}"));
}
async_result.push(')');

let mut next_write = next_write.lock().unwrap();
// get the memory mapping. We do this first b/c most of the deref logic needs
Expand Down Expand Up @@ -566,13 +566,14 @@ fn update_stack(
let is_path = r.is_path(filepath_lock.as_ref().unwrap().to_str().unwrap());
if r.contains(val) && (is_path || r.is_exec()) {
// send a search for a symbol!
debug!("stack deref: trying to read as asm: {val:02x}");
next_write.push(data_disassemble(val as usize, INSTRUCTION_LEN));
written.push_back(Written::SymbolAtAddrStack(begin.clone()));
return;
}
}
// TODO: endian?
debug!("stack deref: trying to read: {}", data["contents"]);
debug!("stack deref: trying to read as data: {val:02x}");
next_write.push(data_read_memory_bytes(val, 0, len));
written.push_back(Written::Stack(Some(begin)));
}
Expand Down Expand Up @@ -696,17 +697,20 @@ fn recv_exec_results_register_values(
*regs = registers.clone();

// assuming we have a valid $pc, get the bytes
trace!("requesting pc bytes");
let val = read_pc_value();
next_write.push(val);

// assuming we have a valid $sp, get the bytes
// assuming we have a valid Stack ($sp), get the bytes
trace!("requesting stack");
if thirty {
dump_sp_bytes(next_write, written, 4, u64::from(SAVED_STACK));
} else {
dump_sp_bytes(next_write, written, 8, u64::from(SAVED_STACK));
}

// update current asm at pc
trace!("updating pc asm");
let instruction_length = 8;
next_write.push(data_disassemble_pc(instruction_length * 5, instruction_length * 15));
written.push_back(Written::AsmAtPc);
Expand Down
69 changes: 60 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::fs::{self, File};
use std::io::{BufReader, Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::path::{Path, PathBuf};
Expand All @@ -11,6 +11,7 @@ use std::{env, thread};
use std::{error::Error, io};

use clap::Parser;
use crossterm::event::KeyModifiers;
use deku::ctx::Endian;
use deref::Deref;
use env_logger::{Builder, Env};
Expand Down Expand Up @@ -96,7 +97,7 @@ struct Args {

/// Execute GDB commands
#[arg(short, long)]
cmd: Option<String>,
cmds: Option<String>,
}

enum Mode {
Expand Down Expand Up @@ -173,7 +174,9 @@ struct App {
hexdump_scroll: usize,
hexdump_scroll_state: ScrollbarState,
hexdump_popup: Input,
/// Right side of status in TUI
async_result: Arc<Mutex<String>>,
/// Left side of status in TUI
status: Arc<Mutex<String>>,
}

Expand Down Expand Up @@ -340,8 +343,11 @@ fn main() -> Result<(), Box<dyn Error>> {

into_gdb(&app, gdb_stdout);

if let Some(cmd) = args.cmd {
process_line(&mut app, &cmd);
if let Some(cmds) = args.cmds {
let data = fs::read_to_string(cmds).unwrap();
for cmd in data.lines() {
process_line(&mut app, &cmd);
}
}

// Run tui application
Expand Down Expand Up @@ -438,6 +444,10 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<

if crossterm::event::poll(Duration::from_millis(10))? {
if let Event::Key(key) = event::read()? {
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
gdb::write_mi(&app.gdb_stdin, "-exec-interrupt");
continue;
}
match (&app.input_mode, key.code, &app.mode) {
// hexdump popup
(_, KeyCode::Esc, Mode::OnlyHexdumpPopup) => {
Expand Down Expand Up @@ -738,7 +748,45 @@ fn process_line(app: &mut App, val: &str) {
// Resolve parens with expresions
resolve_paren_expressions(&mut val);

if val.starts_with("file") {
if val == "r" || val == "ru" || val == "run" {
// Replace run with -exec-run and target-async
// This is to allow control+C to interrupt
// gdb::write_mi(&app.gdb_stdin, "-gdb-set target-async on");

let cmd = "-gdb-set mi-async on";
gdb::write_mi(&app.gdb_stdin, cmd);

let cmd = "-exec-run";
gdb::write_mi(&app.gdb_stdin, cmd);

app.input.reset();
return;
} else if val == "c"
|| val == "co"
|| val == "con"
|| val == "cont"
|| val == "conti"
|| val == "continu"
|| val == "continue"
{
let cmd = "-exec-continue";
gdb::write_mi(&app.gdb_stdin, cmd);

app.input.reset();
return;
} else if val == "si" || val == "stepi" {
let cmd = "-exec-step-instruction";
gdb::write_mi(&app.gdb_stdin, cmd);

app.input.reset();
return;
} else if val == "step" {
let cmd = "-exec-step";
gdb::write_mi(&app.gdb_stdin, cmd);

app.input.reset();
return;
} else if val.starts_with("file") {
// we parse file, but still send it on
app.save_filepath(&val);
} else if val.starts_with("hexdump") {
Expand Down Expand Up @@ -892,8 +940,11 @@ mod tests {
let (gdb_stdout, mut app) = App::new_stream(args.clone());
into_gdb(&app, gdb_stdout);

if let Some(cmd) = args.cmd {
process_line(&mut app, &cmd);
if let Some(cmds) = args.cmds {
let data = fs::read_to_string(cmds).unwrap();
for cmd in data.lines() {
process_line(&mut app, &cmd);
}
}
let mut terminal = Terminal::new(TestBackend::new(160, 50)).unwrap();
let start_time = Instant::now();
Expand Down Expand Up @@ -956,7 +1007,7 @@ mod tests {
unsafe { chmod(c_path.as_ptr(), mode) };

let mut args = Args::default();
args.cmd = Some("source test-sources/repeated_ptr.source".to_string());
args.cmds = Some("test-sources/repeated_ptr.source".to_string());

let (app, terminal) = run_a_bit(args);
let _output = terminal.backend();
Expand Down Expand Up @@ -1018,7 +1069,7 @@ mod tests {
unsafe { chmod(c_path.as_ptr(), mode) };

let mut args = Args::default();
args.cmd = Some("source test-sources/test.source".to_string());
args.cmds = Some("test-sources/test.source".to_string());

let (app, terminal) = run_a_bit(args);
let output = terminal.backend();
Expand Down
10 changes: 9 additions & 1 deletion src/mi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,16 @@ impl MemoryMapping {
}

/// Mapping filepath matches `filepath`
///
/// This could be set from something like "file test-assets/test_render_app/a.out"
/// so we make sure to match with a mapping such as:
/// "/home/wcampbell/projects/wcampbell/heretek/test-assets/test_render_app/a.out"
pub fn is_path(&self, filepath: &str) -> bool {
self.path == Some(filepath.to_owned())
if let Some(path) = &self.path {
path.ends_with(&filepath.to_owned())
} else {
false
}
}

pub fn is_exec(&self) -> bool {
Expand Down
8 changes: 4 additions & 4 deletions src/snapshots/heretek__tests__render_app.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ snapshot_kind: text
" 0x401845 main+20 movq $0x44444444,-0x20(%rbp) "
" 0x40184d main+28 movq $0x55555555,-0x28(%rbp) "
"┌Output ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"
"│source test-sources/test.source │"
"│file test-assets/test_render_app/a.out │"
"│Reading symbols from test-assets/test_render_app/a.out... │"
"│break main │"
"│Breakpoint 1 at 0x40182d: file test.c, line 11. │"
"│Breakpoint 1, main () at test.c:11 │"
"│warning: 11 test.c: No such file or directory │"
"│ │"
"│ │"
"│ │"
"│ │"
"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"
"┌|Press q to exit, i to enter input|─|Status(bkptno=1, reason=breakpoint-hit, stopped-threads=all, thread-id=1)|───────────────────────────────────────────────┐"
"┌|Press q to exit, i to enter input|─|Status: bkptno=1, reason=breakpoint-hit, stopped-threads=all, thread-id=1|───────────────────────────────────────────────┐"
"│(gdb) │"
"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"