299 lines
12 KiB
Rust

use prometeu_drivers::hardware::Hardware;
use prometeu_firmware::{BootTarget, Firmware};
use prometeu_hal::cartridge_loader::CartridgeLoader;
use prometeu_hal::debugger_protocol::*;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
/// Host-side implementation of the PROMETEU DevTools Protocol.
///
/// This component acts as a TCP server that allows external tools (like the
/// Prometeu Debugger) to observe and control the execution of the virtual machine.
///
/// Communication is based on JSONL (JSON lines) over TCP.
pub struct HostDebugger {
/// If true, the VM will not start execution until a 'start' command is received.
pub waiting_for_start: bool,
/// The TCP listener for incoming debugger connections.
pub(crate) listener: Option<TcpListener>,
/// The currently active connection to a debugger client.
pub(crate) stream: Option<TcpStream>,
/// Sequence tracker to ensure logs are sent only once.
last_log_seq: u64,
/// Frame tracker to send telemetry snapshots periodically.
last_telemetry_frame: u64,
}
impl HostDebugger {
/// Creates a new debugger interface in an idle state.
pub fn new() -> Self {
Self {
waiting_for_start: false,
listener: None,
stream: None,
last_log_seq: 0,
last_telemetry_frame: 0,
}
}
/// Configures the debugger based on the boot target.
/// If debug mode is enabled, it binds to the specified TCP port.
pub fn setup_boot_target(&mut self, boot_target: &BootTarget, firmware: &mut Firmware) {
if let BootTarget::Cartridge { path, debug: true, debug_port } = boot_target {
self.waiting_for_start = true;
// Pre-load cartridge metadata so the Handshake message can contain
// valid information about the App being debugged.
if let Ok(cartridge) = CartridgeLoader::load(path) {
let _ = firmware.os.initialize_vm(&mut firmware.vm, &cartridge);
}
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
Ok(listener) => {
// Set listener to non-blocking so it doesn't halt the main loop.
listener.set_nonblocking(true).expect("Cannot set non-blocking");
self.listener = Some(listener);
println!("[Debugger] Listening for start command on port {}...", debug_port);
}
Err(e) => {
eprintln!("[Debugger] Failed to bind to port {}: {}", debug_port, e);
}
}
println!("[Debugger] (Or press D to start execution)");
}
}
/// Sends a structured response to the connected debugger client.
fn send_response(&mut self, resp: DebugResponse) {
if let Some(stream) = &mut self.stream {
if let Ok(json) = serde_json::to_string(&resp) {
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
}
}
/// Sends an asynchronous event to the connected debugger client.
fn send_event(&mut self, event: DebugEvent) {
if let Some(stream) = &mut self.stream {
if let Ok(json) = serde_json::to_string(&event) {
let _ = stream.write_all(json.as_bytes());
let _ = stream.write_all(b"\n");
}
}
}
/// Main maintenance method called by the HostRunner every iteration.
/// It handles new connections and processes incoming commands.
pub fn check_commands(&mut self, firmware: &mut Firmware, hardware: &mut Hardware) {
// 1. Accept new client connections.
if let Some(listener) = &self.listener {
if let Ok((stream, _addr)) = listener.accept() {
// Currently, only one debugger client is supported at a time.
if self.stream.is_none() {
println!("[Debugger] Connection received!");
stream.set_nonblocking(true).expect("Cannot set non-blocking on stream");
self.stream = Some(stream);
// Immediately send the Handshake message to identify the Runtime and App.
let handshake = DebugResponse::Handshake {
protocol_version: DEVTOOLS_PROTOCOL_VERSION,
runtime_version: "0.1".to_string(),
cartridge: HandshakeCartridge {
app_id: firmware.os.current_app_id,
title: firmware.os.current_cartridge_title.clone(),
app_version: firmware.os.current_cartridge_app_version.clone(),
app_mode: firmware.os.current_cartridge_app_mode,
},
};
self.send_response(handshake);
} else {
println!("[Debugger] Connection refused: already connected.");
}
}
}
// 2. Read and process pending commands from the network stream.
if let Some(mut stream) = self.stream.take() {
let mut buf = [0u8; 4096];
match stream.read(&mut buf) {
Ok(0) => {
// TCP socket closed by the client.
println!("[Debugger] Connection closed by remote.");
self.stream = None;
// Resume VM execution if it was paused waiting for the debugger.
firmware.os.paused = false;
self.waiting_for_start = false;
}
Ok(n) => {
let data = &buf[..n];
let msg = String::from_utf8_lossy(data);
self.stream = Some(stream);
// Support multiple JSON messages in a single TCP packet.
for line in msg.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(trimmed) {
self.handle_command(cmd, firmware, hardware);
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// No data available right now, continue.
self.stream = Some(stream);
}
Err(e) => {
eprintln!("[Debugger] Connection error: {}", e);
self.stream = None;
firmware.os.paused = false;
self.waiting_for_start = false;
}
}
}
// 3. Push events (logs, telemetry) to the client.
if self.stream.is_some() {
self.stream_events(firmware);
}
}
/// Dispatches a specific DebugCommand to the system components.
fn handle_command(
&mut self,
cmd: DebugCommand,
firmware: &mut Firmware,
hardware: &mut Hardware,
) {
match cmd {
DebugCommand::Ok | DebugCommand::Start => {
if self.waiting_for_start {
println!("[Debugger] Starting execution...");
self.waiting_for_start = false;
}
firmware.os.paused = false;
}
DebugCommand::Pause => {
firmware.os.paused = true;
}
DebugCommand::Resume => {
firmware.os.paused = false;
}
DebugCommand::Step => {
// Execute exactly one instruction and keep paused.
firmware.os.paused = true;
let _ = firmware.os.debug_step_instruction(&mut firmware.vm, hardware);
}
DebugCommand::StepFrame => {
// Execute until the end of the current logical frame.
firmware.os.paused = false;
firmware.os.debug_step_request = true;
}
DebugCommand::GetState => {
// Return detailed VM register and stack state.
let stack_top = firmware.vm.operand_stack_top(10);
let resp = DebugResponse::GetState {
pc: firmware.vm.pc(),
stack_top,
frame_index: firmware.os.logical_frame_index,
app_id: firmware.os.current_app_id,
};
self.send_response(resp);
}
DebugCommand::SetBreakpoint { pc } => {
firmware.vm.insert_breakpoint(pc);
}
DebugCommand::ListBreakpoints => {
let pcs = firmware.vm.breakpoints_list();
self.send_response(DebugResponse::Breakpoints { pcs });
}
DebugCommand::ClearBreakpoint { pc } => {
firmware.vm.remove_breakpoint(pc);
}
}
}
/// Scans the system for new information to push to the debugger client.
fn stream_events(&mut self, firmware: &mut Firmware) {
// 1. Process and send new log entries.
let new_events = firmware.os.log_service.get_after(self.last_log_seq);
for event in new_events {
self.last_log_seq = event.seq;
// Map specific internal log tags to protocol events.
if event.tag == 0xDEB1 {
self.send_event(DebugEvent::BreakpointHit {
pc: firmware.vm.pc(),
frame_index: firmware.os.logical_frame_index,
});
}
// Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events.
if event.tag >= 0xCA01 && event.tag <= 0xCA03 {
let tel = &firmware.os.telemetry_last;
let cert_config = &firmware.os.certifier.config;
let (rule, used, limit) = match event.tag {
0xCA01 => (
"cycles_budget".to_string(),
tel.cycles_used,
cert_config.cycles_budget_per_frame.unwrap_or(0),
),
0xCA02 => (
"max_syscalls".to_string(),
tel.syscalls as u64,
cert_config.max_syscalls_per_frame.unwrap_or(0) as u64,
),
0xCA03 => (
"max_host_cpu_us".to_string(),
tel.host_cpu_time_us,
cert_config.max_host_cpu_us_per_frame.unwrap_or(0),
),
_ => ("unknown".to_string(), 0, 0),
};
self.send_event(DebugEvent::Cert {
rule,
used,
limit,
frame_index: firmware.os.logical_frame_index,
});
}
self.send_event(DebugEvent::Log {
level: format!("{:?}", event.level),
source: format!("{:?}", event.source),
msg: event.msg.clone(),
});
}
// 2. Send telemetry snapshots at the completion of every frame.
let current_frame = firmware.os.logical_frame_index;
if current_frame > self.last_telemetry_frame {
let tel = &firmware.os.telemetry_last;
self.send_event(DebugEvent::Telemetry {
frame_index: tel.frame_index,
vm_steps: tel.vm_steps,
syscalls: tel.syscalls,
cycles: tel.cycles_used,
cycles_budget: tel.cycles_budget,
host_cpu_time_us: tel.host_cpu_time_us,
violations: tel.violations,
gfx_used_bytes: tel.gfx_used_bytes,
gfx_inflight_bytes: tel.gfx_inflight_bytes,
gfx_slots_occupied: tel.gfx_slots_occupied,
audio_used_bytes: tel.audio_used_bytes,
audio_inflight_bytes: tel.audio_inflight_bytes,
audio_slots_occupied: tel.audio_slots_occupied,
});
self.last_telemetry_frame = current_frame;
}
}
}