Skip to content

Commit 73331f2

Browse files
Merge pull request #205 from wcampbell0x2a/updates
2 parents 7406a35 + 8ea63bc commit 73331f2

File tree

17 files changed

+457
-54
lines changed

17 files changed

+457
-54
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
## [0.8.0] - 2025-12-15
9+
- Add `Source` improvements:
10+
- Add `Source` display page
11+
- Add `Source` highlight support for Rust, C, and C++ using [arborium](arborium.bearcove.eu)(treesitter).
12+
- Send child process stderr to null
13+
- Resolve info addresses with another symbol address lookup in `Symbol` page.
14+
- Poll slower when not expecting gdb response
815

916
## [0.7.0] - 2025-11-14
1017
- Hexdump memory mapping selection using `H` [#192](https://github.com/wcampbell0x2a/heretek/pull/192)

Cargo.lock

Lines changed: 115 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "heretek"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
edition = "2024"
55
description = "GDB TUI Dashboard for the understanding of vast knowledge"
66
license = "MIT OR Apache-2.0"
@@ -23,6 +23,7 @@ once_cell = "1.20.2"
2323
ratatui = "0.29.0"
2424
regex = "1.11.1"
2525
tui-input = "0.14.0"
26+
arborium = { version = "2.1.1", features = ["lang-c", "lang-cpp", "lang-rust"] }
2627

2728
[dev-dependencies]
2829
insta = "1.41.1"

docs/hexdump_section.gif

3.19 KB
Loading

docs/main_section.gif

431 KB
Loading

docs/vhs/readme.tape

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,7 @@ Sleep 2s
3131
Type "si"
3232
Enter
3333
Sleep 2s
34-
Type "si"
35-
Enter
36-
Sleep 2s
37-
Type "si"
38-
Enter
39-
Sleep 2s
40-
Type "si"
41-
Enter
42-
Sleep 2s
43-
Type "si"
44-
Enter
45-
Sleep 2s
46-
Type "si"
34+
Type "finish"
4735
Enter
4836
Sleep 2s
4937

@@ -97,3 +85,6 @@ Sleep 2s
9785
Enter
9886
Type "d"
9987
Sleep 2s
88+
Tab
89+
# Source
90+
Sleep 2s

images/readme.gif

-106 KB
Loading

src/gdb.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub fn gdb_interact(gdb_stdout: BufReader<Box<dyn Read + Send>>, state: Arc<Mute
4343
}
4444

4545
fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
46+
// Execution has stopped, clear the executing flag for slower polling
47+
state.executing = false;
48+
4649
// in the case of a breakpoint, save the output
4750
// Either it's a breakpoint event, step, signal
4851
state.async_result.clear();
@@ -82,6 +85,8 @@ fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
8285
// Get endian
8386
state.next_write.push(r#"-interpreter-exec console "show endian""#.to_string());
8487
// TODO: We only need to do this once
88+
// Get source language
89+
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
8590
state.next_write.push("-data-list-register-names".to_string());
8691
// When a breakpoint is hit, query for register values
8792
state.next_write.push("-data-list-register-values x".to_string());
@@ -95,6 +100,8 @@ fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
95100
debug!("Source location from stopped event: {fullname}:{line}");
96101

97102
if let Ok(line_num) = line.parse::<u32>() {
103+
let file_changed = state.current_source_file.as_ref() != Some(fullname);
104+
98105
state.current_source_file = Some(fullname.clone());
99106
state.current_source_line = Some(line_num);
100107

@@ -107,12 +114,18 @@ fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
107114
warn!("Could not read source file: {fullname}");
108115
state.source_lines.clear();
109116
}
117+
118+
if file_changed {
119+
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
120+
}
110121
}
111122
} else if let (Some(file), Some(line)) = (kv.get("file"), kv.get("line")) {
112123
// Fallback to 'file' if 'fullname' is not available
113124
debug!("Source location from stopped event (fallback): {file}:{line}");
114125

115126
if let Ok(line_num) = line.parse::<u32>() {
127+
let file_changed = state.current_source_file.as_ref() != Some(file);
128+
116129
state.current_source_file = Some(file.clone());
117130
state.current_source_line = Some(line_num);
118131

@@ -125,6 +138,10 @@ fn async_record_stopped(state: &mut State, kv: &HashMap<String, String>) {
125138
warn!("Could not read source file: {file}");
126139
state.source_lines.clear();
127140
}
141+
142+
if file_changed {
143+
state.next_write.push(r#"-interpreter-exec console "show language""#.to_string());
144+
}
128145
}
129146
} else {
130147
debug!("No source location information in stopped event");

src/gdb/exec_result/recv/asm_insns.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ pub fn recv_exec_result_asm_insns(state: &mut State, asm: &String) {
1212
if let Written::AsmAtPc = last_written {
1313
state.asm = parse_asm_insns_values(asm).clone();
1414
}
15-
if let Written::SymbolDisassembly(_name) = &last_written {
15+
if let Written::SymbolDisassembly(name) = &last_written {
1616
state.symbol_asm = parse_asm_insns_values(asm).clone();
17+
state.symbol_asm_name = name.clone();
1718
}
1819
if let Written::SymbolAtAddrRegister((base_reg, _n)) = &last_written {
1920
for RegisterStorage { name: _, register, deref } in &mut state.registers {

src/gdb/stream_output.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,37 @@ pub fn stream_output(
2929
return;
3030
}
3131

32+
if s.starts_with("The current source language") {
33+
let unescaped = s.replace(r#"\""#, "\"");
34+
35+
if let Some(is_idx) = unescaped.find(" is ") {
36+
let after_is = &unescaped[is_idx + 4..];
37+
if let Some(quote_start) = after_is.find('"') {
38+
let after_quote = &after_is[quote_start + 1..];
39+
40+
let language = if let Some(currently_idx) = after_quote.find("currently ") {
41+
let lang_start = currently_idx + 10;
42+
let after_currently = &after_quote[lang_start..];
43+
let end_idx = after_currently
44+
.find('"')
45+
.or_else(|| after_currently.find('.'))
46+
.unwrap_or(after_currently.len());
47+
after_currently[..end_idx].trim()
48+
} else {
49+
let end_idx = after_quote
50+
.find('"')
51+
.or_else(|| after_quote.find('.'))
52+
.unwrap_or(after_quote.len());
53+
after_quote[..end_idx].trim()
54+
};
55+
56+
state.source_language = Some(language.to_string());
57+
debug!("detected source language: {}", language);
58+
}
59+
}
60+
return;
61+
}
62+
3263
// When using attach, assume the first symbols found are the text field
3364
// StreamOutput("~", "Reading symbols from /home/wcampbell/a.out...\n")
3465
if state.filepath.is_none() {
@@ -75,6 +106,24 @@ pub fn stream_output(
75106
return;
76107
}
77108

109+
if let Some(Written::SymbolAddressLookup(symbol_name)) = state.written.front() {
110+
let symbol_name = symbol_name.clone();
111+
if let Some(addr_start) = s.find(" at address ") {
112+
let after_at = &s[addr_start + 12..];
113+
let addr_end =
114+
after_at.find('.').or_else(|| after_at.find(' ')).unwrap_or(after_at.len());
115+
let addr_str = &after_at[..addr_end];
116+
if let Some(hex_addr) = addr_str.strip_prefix("0x") {
117+
if let Ok(address) = u64::from_str_radix(hex_addr, 16) {
118+
state.next_write.push(crate::mi::data_disassemble(address as usize, 500));
119+
state.written.pop_front();
120+
state.written.push_back(Written::SymbolDisassembly(symbol_name));
121+
return;
122+
}
123+
}
124+
}
125+
}
126+
78127
let split: Vec<String> =
79128
s.split('\n').map(String::from).map(|a| a.trim_end().to_string()).collect();
80129
for s in split {
@@ -274,4 +323,24 @@ mod tests {
274323

275324
assert_eq!(state.filepath, Some(PathBuf::from("/original/path"))); // should not change
276325
}
326+
327+
#[rstest]
328+
#[case(r#"The current source language is \"auto; currently c\"."#, "c")]
329+
#[case(r#"The current source language is \"auto; currently rust\"."#, "rust")]
330+
#[case(r#"The current source language is \"auto; currently c++\"."#, "c++")]
331+
#[case(r#"The current source language is \"c\"."#, "c")]
332+
#[case(r#"The current source language is \"rust\"."#, "rust")]
333+
#[case(r#"The current source language is \"c++\"."#, "c++")]
334+
#[case(r#"The current source language is \"auto; currently c\".\n"#, "c")]
335+
#[case(r#"The current source language is \"rust\".\n"#, "rust")]
336+
fn test_stream_output_language_detection(#[case] input: &str, #[case] expected_lang: &str) {
337+
let mut state = create_test_state();
338+
let mut current_map = (None, String::new());
339+
let mut current_symbols = String::new();
340+
341+
stream_output("~", input, &mut state, &mut current_map, &mut current_symbols);
342+
343+
assert_eq!(state.source_language, Some(expected_lang.to_string()));
344+
assert_eq!(state.output.len(), 0); // should not be added to output
345+
}
277346
}

0 commit comments

Comments
 (0)