Skip to content

Commit a860f82

Browse files
committed
Add test for segfault
* Add Config.deref_show_string, in order to more easily test binaries that display strings that change (such as env and such) * Add segfault test binary and test
1 parent 7bb0a89 commit a860f82

File tree

5 files changed

+110
-38
lines changed

5 files changed

+110
-38
lines changed

src/gdb/exec_result/recv/result_memory.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,18 +176,19 @@ fn update_stack(data: HashMap<String, String>, state: &mut State, begin: String)
176176
}
177177
}
178178

179-
// all string? Request the next
180-
if val > 0xff {
181-
let bytes = val.to_le_bytes();
182-
if bytes
183-
.iter()
184-
.all(|a| a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace())
185-
{
186-
let addr = data["begin"].strip_prefix("0x").unwrap().to_string();
187-
let addr = u64::from_str_radix(&addr, 16).unwrap();
188-
state.next_write.push(data_read_memory_bytes(addr + len, 0, len));
189-
state.written.push_back(Written::Stack(Some(begin)));
190-
return;
179+
if state.config.deref_show_string {
180+
//all string? Request the next
181+
if val > 0xff {
182+
let bytes = val.to_le_bytes();
183+
if bytes.iter().all(|a| {
184+
a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace()
185+
}) {
186+
let addr = data["begin"].strip_prefix("0x").unwrap().to_string();
187+
let addr = u64::from_str_radix(&addr, 16).unwrap();
188+
state.next_write.push(data_read_memory_bytes(addr + len, 0, len));
189+
state.written.push_back(Written::Stack(Some(begin)));
190+
return;
191+
}
191192
}
192193
}
193194

src/main.rs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,18 @@ impl Scroll {
204204
}
205205
}
206206

207+
#[derive(Clone, Debug)]
208+
struct Config {
209+
// Deref and show string values
210+
deref_show_string: bool,
211+
}
212+
213+
impl Default for Config {
214+
fn default() -> Self {
215+
Self { deref_show_string: true }
216+
}
217+
}
218+
207219
#[derive(Clone, Debug)]
208220
struct State {
209221
/// Messages to write to gdb mi
@@ -253,10 +265,11 @@ struct State {
253265
status: String,
254266
bt: Vec<Bt>,
255267
completions: Vec<String>,
268+
config: Config,
256269
}
257270

258271
impl State {
259-
pub fn new(args: Args) -> State {
272+
pub fn new(args: Args, config: Config) -> State {
260273
State {
261274
next_write: vec![],
262275
written: VecDeque::new(),
@@ -286,6 +299,7 @@ impl State {
286299
status: String::new(),
287300
bt: vec![],
288301
completions: vec![],
302+
config,
289303
}
290304
}
291305
}
@@ -425,7 +439,8 @@ fn main() -> anyhow::Result<()> {
425439
}
426440
// Start rx thread
427441
let (gdb_stdout, mut app) = App::new_stream(args.clone());
428-
let state = State::new(args.clone());
442+
let config = Config::default();
443+
let state = State::new(args.clone(), config);
429444
let mut state_share = StateShare { state: Arc::new(Mutex::new(state)) };
430445

431446
// Setup terminal
@@ -1067,9 +1082,15 @@ mod tests {
10671082
use ratatui::{Terminal, backend::TestBackend};
10681083
use test_assets_ureq::{TestAssetDef, dl_test_files_backoff};
10691084

1070-
fn run_a_bit(args: Args) -> (App, StateShare, Terminal<TestBackend>) {
1085+
fn run_a_bit(
1086+
args: Args,
1087+
mode: Mode,
1088+
size: Option<(u16, u16)>,
1089+
) -> (App, StateShare, Terminal<TestBackend>) {
10711090
let (gdb_stdout, mut app) = App::new_stream(args.clone());
1072-
let state = State::new(args.clone());
1091+
let config = Config { deref_show_string: false, ..Config::default() };
1092+
let mut state = State::new(args.clone(), config);
1093+
state.mode = mode;
10731094
let state_share = StateShare { state: Arc::new(Mutex::new(state)) };
10741095
spawn_gdb_interact(&state_share, gdb_stdout);
10751096

@@ -1083,7 +1104,11 @@ mod tests {
10831104
}
10841105
}
10851106
}
1086-
let mut terminal = Terminal::new(TestBackend::new(160, 50)).unwrap();
1107+
let mut terminal = if let Some((x, y)) = size {
1108+
Terminal::new(TestBackend::new(x, y)).unwrap()
1109+
} else {
1110+
Terminal::new(TestBackend::new(160, 50)).unwrap()
1111+
};
10871112
let start_time = Instant::now();
10881113
let duration = Duration::from_secs(10);
10891114

@@ -1147,7 +1172,7 @@ mod tests {
11471172
let mut args = Args::default();
11481173
args.cmds = Some(PathBuf::from("test-sources/repeated_ptr.source"));
11491174

1150-
let (_, state, terminal) = run_a_bit(args);
1175+
let (_, state, terminal) = run_a_bit(args, Mode::All, None);
11511176
let _output = terminal.backend();
11521177
let registers = state.state.lock().unwrap().registers.clone();
11531178
let stack = state.state.lock().unwrap().stack.clone();
@@ -1164,6 +1189,37 @@ mod tests {
11641189
assert!(stack[5].1.repeated_pattern);
11651190
}
11661191

1192+
#[test]
1193+
fn test_segfault() {
1194+
// ```
1195+
// int main() {
1196+
// int *p = (int*)0xdeadbeef;
1197+
// *p = 42;
1198+
// return 0;
1199+
// }
1200+
// ```
1201+
const FILE_NAME: &str = "segfault";
1202+
const TEST_PATH: &str = "test-assets/segfault/";
1203+
let file_path = format!("{TEST_PATH}/{FILE_NAME}");
1204+
let asset_defs = [TestAssetDef {
1205+
filename: FILE_NAME.to_string(),
1206+
hash: "8d486249e73785737cc1dd8c973f43c4dbb79e723d6a262846a625c184b95d7b".to_string(),
1207+
url: "https://wcampbell.dev/heretek/segfault/segfault".to_string(),
1208+
}];
1209+
1210+
dl_test_files_backoff(&asset_defs, TEST_PATH, true, Duration::from_secs(1)).unwrap();
1211+
let c_path = CString::new(file_path.to_string()).expect("CString::new failed");
1212+
let mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
1213+
unsafe { chmod(c_path.as_ptr(), mode) };
1214+
1215+
let mut args = Args::default();
1216+
args.cmds = Some(PathBuf::from("test-sources/segfault.source"));
1217+
1218+
let (_, state, terminal) = run_a_bit(args, Mode::All, Some((160, 40)));
1219+
let output = terminal.backend();
1220+
assert_snapshot!(output);
1221+
}
1222+
11671223
#[test]
11681224
fn test_render_app() {
11691225
// gcc test.c -g -fno-stack-protector -static
@@ -1209,7 +1265,7 @@ mod tests {
12091265
let mut args = Args::default();
12101266
args.cmds = Some(PathBuf::from("test-sources/test.source"));
12111267

1212-
let (_, state, terminal) = run_a_bit(args);
1268+
let (_, state, terminal) = run_a_bit(args, Mode::All, None);
12131269
let output = terminal.backend();
12141270

12151271
// Now, we need to rewrite all the addresses that change for the registers and stack

src/snapshots/heretek__tests__render_app.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ snapshot_kind: text
99
" rax → 0x401825 → main+0 (push rbp) █"
1010
" rbx → 0x1 ║"
1111
" rcx → <rcx_0> → 0x04 ║"
12-
" rdx → <rdx_0> → <rdx_1> → <rdx_2> "
13-
" rsi → <rsi_0> → <rsi_1> → <rsi_2> "
12+
" rdx → <rdx_0> → <rdx_1> → <string>"
13+
" rsi → <rsi_0> → <rsi_1> → <string>"
1414
" rdi → 0x1 ║"
1515
" rbp → <stack_8> → <rbp_1> → <rbp_2> → 0x00 ║"
1616
" rsp → <stack_0> → <stack_6> → <stack_6_0> → <stack_6_1> → <rbp_1> → <rbp_2> → 0x00 ║"

src/ui/mod.rs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -199,25 +199,38 @@ pub fn add_deref_to_span(
199199
width: usize,
200200
) {
201201
for (i, v) in deref.map.iter().enumerate() {
202-
// check if ascii
203-
if *v > 0xff {
204-
let bytes = (*v).to_le_bytes();
205-
if bytes
206-
.iter()
207-
.all(|a| a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace())
208-
{
209-
// if we detect it's ascii, the rest is ascii
210-
let mut full_s = String::new();
211-
for r in deref.map.iter().skip(i) {
212-
let bytes = (*r).to_le_bytes();
213-
if let Ok(s) = std::str::from_utf8(&bytes) {
214-
full_s.push_str(s);
202+
if state.config.deref_show_string {
203+
// check if ascii
204+
if *v > 0xff {
205+
let bytes = (*v).to_le_bytes();
206+
if bytes.iter().all(|a| {
207+
a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace()
208+
}) {
209+
// if we detect it's ascii, the rest is ascii
210+
let mut full_s = String::new();
211+
for r in deref.map.iter().skip(i) {
212+
let bytes = (*r).to_le_bytes();
213+
if let Ok(s) = std::str::from_utf8(&bytes) {
214+
full_s.push_str(s);
215+
}
215216
}
217+
let cell =
218+
Span::from(format!("→ \"{full_s}\"")).style(Style::new().fg(STRING_COLOR));
219+
spans.push(cell);
220+
return;
221+
}
222+
}
223+
} else {
224+
if *v > 0xff {
225+
let bytes = (*v).to_le_bytes();
226+
if bytes.iter().all(|a| {
227+
a.is_ascii_alphabetic() || a.is_ascii_graphic() || a.is_ascii_whitespace()
228+
}) {
229+
let cell =
230+
Span::from(format!("→ <string>")).style(Style::new().fg(STRING_COLOR));
231+
spans.push(cell);
232+
return;
216233
}
217-
let cell =
218-
Span::from(format!("→ \"{full_s}\"")).style(Style::new().fg(STRING_COLOR));
219-
spans.push(cell);
220-
return;
221234
}
222235
}
223236

test-sources/segfault.source

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
file test-assets/segfault/segfault
2+
run

0 commit comments

Comments
 (0)