paavohtl Claude Opus 4.6 commited on
Commit
5018b6b
·
1 Parent(s): 77457b4

feat: add debug server module to openwa-wormkit

Browse files

TCP server on 127.0.0.1:19840 for live memory inspection. Spawns when
OPENWA_DEBUG_SERVER=1. Handles ping, help, and read (Ghidra VA) requests
with automatic ASLR rebasing and pointer classification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

crates/openwa-wormkit/Cargo.toml CHANGED
@@ -12,6 +12,7 @@ debug-ui = ["dep:openwa-debugui"]
12
 
13
  [dependencies]
14
  openwa-core = { path = "../openwa-core" }
 
15
  openwa-debugui = { path = "../openwa-debugui", optional = true }
16
  heapless = "0.9"
17
  hound = "3"
 
12
 
13
  [dependencies]
14
  openwa-core = { path = "../openwa-core" }
15
+ openwa-debug-proto = { path = "../openwa-debug-proto" }
16
  openwa-debugui = { path = "../openwa-debugui", optional = true }
17
  heapless = "0.9"
18
  hound = "3"
crates/openwa-wormkit/src/debug_server.rs ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! TCP debug server for live memory inspection.
2
+ //!
3
+ //! When `OPENWA_DEBUG_SERVER=1`, spawns a background thread that listens
4
+ //! on `127.0.0.1:19840` and serves memory read requests. One client at
5
+ //! a time. All addresses are Ghidra VAs — the server rebases automatically.
6
+
7
+ use std::io::{self, BufReader, BufWriter};
8
+ use std::net::{TcpListener, TcpStream};
9
+ use std::sync::atomic::{AtomicBool, Ordering};
10
+ use std::time::Duration;
11
+
12
+ use openwa_core::address::va;
13
+ use openwa_core::mem;
14
+ use openwa_core::rebase::rb;
15
+ use openwa_debug_proto::*;
16
+
17
+ use crate::log_line;
18
+
19
+ static SHUTDOWN: AtomicBool = AtomicBool::new(false);
20
+
21
+ /// Start the debug server if `OPENWA_DEBUG_SERVER=1`.
22
+ /// Called from `run()` in lib.rs. No-op if env var is not set.
23
+ pub fn maybe_start() {
24
+ if std::env::var("OPENWA_DEBUG_SERVER").is_ok() {
25
+ let _ = log_line("[DebugServer] Starting...");
26
+ std::thread::spawn(|| {
27
+ if let Err(e) = server_thread() {
28
+ let _ = log_line(&format!("[DebugServer] Fatal: {e}"));
29
+ }
30
+ });
31
+ }
32
+ }
33
+
34
+ fn server_thread() -> io::Result<()> {
35
+ let listener = match TcpListener::bind(("127.0.0.1", DEFAULT_PORT)) {
36
+ Ok(l) => l,
37
+ Err(e) => {
38
+ let _ = log_line(&format!(
39
+ "[DebugServer] Failed to bind port {}: {e} (skipping)",
40
+ DEFAULT_PORT
41
+ ));
42
+ return Ok(());
43
+ }
44
+ };
45
+
46
+ let _ = log_line(&format!(
47
+ "[DebugServer] Listening on 127.0.0.1:{}",
48
+ DEFAULT_PORT
49
+ ));
50
+
51
+ while !SHUTDOWN.load(Ordering::Relaxed) {
52
+ match listener.accept() {
53
+ Ok((stream, addr)) => {
54
+ let _ = log_line(&format!("[DebugServer] Client connected: {addr}"));
55
+ if let Err(e) = handle_client(stream) {
56
+ let _ = log_line(&format!("[DebugServer] Client error: {e}"));
57
+ }
58
+ let _ = log_line("[DebugServer] Client disconnected");
59
+ }
60
+ Err(e) => {
61
+ if !SHUTDOWN.load(Ordering::Relaxed) {
62
+ let _ = log_line(&format!("[DebugServer] Accept error: {e}"));
63
+ }
64
+ }
65
+ }
66
+ }
67
+ Ok(())
68
+ }
69
+
70
+ fn handle_client(stream: TcpStream) -> io::Result<()> {
71
+ stream.set_read_timeout(Some(Duration::from_secs(10)))?;
72
+ stream.set_nodelay(true)?;
73
+
74
+ let mut reader = BufReader::new(stream.try_clone()?);
75
+ let mut writer = BufWriter::new(stream);
76
+
77
+ loop {
78
+ let request: Request = match read_frame(&mut reader) {
79
+ Ok(r) => r,
80
+ Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(()),
81
+ Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(()),
82
+ Err(e) if e.kind() == io::ErrorKind::TimedOut => return Ok(()),
83
+ Err(e) => return Err(e),
84
+ };
85
+
86
+ let response = handle_request(request);
87
+ write_frame(&mut writer, &response)?;
88
+ }
89
+ }
90
+
91
+ fn handle_request(request: Request) -> Response {
92
+ match request {
93
+ Request::Ping => Response::Pong,
94
+ Request::Help => Response::Help {
95
+ commands: vec![
96
+ CommandHelp {
97
+ name: "ping".into(),
98
+ usage: "ping".into(),
99
+ description: "Check if the debug server is running".into(),
100
+ },
101
+ CommandHelp {
102
+ name: "help".into(),
103
+ usage: "help".into(),
104
+ description: "List available commands".into(),
105
+ },
106
+ CommandHelp {
107
+ name: "read".into(),
108
+ usage: "read <ghidra_addr> [len]".into(),
109
+ description: "Read memory at a Ghidra VA (default len=256, max 1MB)".into(),
110
+ },
111
+ ],
112
+ },
113
+ Request::Read { addr, len } => handle_read(addr, len),
114
+ }
115
+ }
116
+
117
+ fn handle_read(ghidra_addr: u32, len: u32) -> Response {
118
+ if len == 0 {
119
+ return Response::Error {
120
+ message: "len must be > 0".into(),
121
+ };
122
+ }
123
+ if len > MAX_READ_SIZE {
124
+ return Response::Error {
125
+ message: format!("len {} exceeds max {} bytes", len, MAX_READ_SIZE),
126
+ };
127
+ }
128
+
129
+ let wa_base = rb(va::IMAGE_BASE);
130
+ let delta = wa_base.wrapping_sub(va::IMAGE_BASE);
131
+ let runtime_addr = ghidra_addr.wrapping_add(delta);
132
+
133
+ unsafe {
134
+ if !mem::can_read(runtime_addr, len) {
135
+ return Response::Error {
136
+ message: format!(
137
+ "Cannot read {} bytes at ghidra:0x{:08X} (runtime:0x{:08X})",
138
+ len, ghidra_addr, runtime_addr
139
+ ),
140
+ };
141
+ }
142
+
143
+ // Copy the memory region
144
+ let mut data = vec![0u8; len as usize];
145
+ std::ptr::copy_nonoverlapping(
146
+ runtime_addr as *const u8,
147
+ data.as_mut_ptr(),
148
+ len as usize,
149
+ );
150
+
151
+ // Classify pointers in the copied data
152
+ let pointers = mem::classify_region(&data, 0, delta);
153
+
154
+ Response::ReadResult {
155
+ ghidra_addr,
156
+ runtime_addr,
157
+ data,
158
+ pointers,
159
+ }
160
+ }
161
+ }
crates/openwa-wormkit/src/lib.rs CHANGED
@@ -2,6 +2,7 @@
2
 
3
  use std::ffi::c_void;
4
 
 
5
  mod debug_ui;
6
  #[allow(dead_code)]
7
  mod debug_watchpoint;
@@ -94,6 +95,9 @@ fn run() -> Result<(), String> {
94
  // Debug UI window (requires "debug-ui" feature + OPENWA_DEBUG_UI=1)
95
  debug_ui::maybe_spawn();
96
 
 
 
 
97
  Ok(())
98
  }
99
 
 
2
 
3
  use std::ffi::c_void;
4
 
5
+ mod debug_server;
6
  mod debug_ui;
7
  #[allow(dead_code)]
8
  mod debug_watchpoint;
 
95
  // Debug UI window (requires "debug-ui" feature + OPENWA_DEBUG_UI=1)
96
  debug_ui::maybe_spawn();
97
 
98
+ // Debug server (requires OPENWA_DEBUG_SERVER=1)
99
+ debug_server::maybe_start();
100
+
101
  Ok(())
102
  }
103