add debugger
This commit is contained in:
parent
990e3ead2a
commit
f04c12d78c
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -724,6 +724,7 @@ dependencies = [
|
|||||||
"pixels",
|
"pixels",
|
||||||
"prometeu-core",
|
"prometeu-core",
|
||||||
"ringbuf",
|
"ringbuf",
|
||||||
|
"serde_json",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -8,4 +8,5 @@ prometeu-core = { path = "../prometeu-core" }
|
|||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
pixels = "0.15.0"
|
pixels = "0.15.0"
|
||||||
cpal = "0.15.3"
|
cpal = "0.15.3"
|
||||||
ringbuf = "0.4.7"
|
ringbuf = "0.4.7"
|
||||||
|
serde_json = "1.0"
|
||||||
@ -38,26 +38,32 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let mut fs_root = None;
|
let mut fs_root = None;
|
||||||
let mut cap_config = None;
|
let mut cap_config = None;
|
||||||
let mut boot_target = BootTarget::Hub;
|
let mut cartridge_path = None;
|
||||||
|
let mut debug_mode = false;
|
||||||
|
let mut debug_port = 7777;
|
||||||
|
|
||||||
let mut i = 1; // Pula o nome do executável
|
let mut i = 1; // Pula o nome do executável
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
match args[i].as_str() {
|
match args[i].as_str() {
|
||||||
"--run" => {
|
"--run" => {
|
||||||
if i + 1 < args.len() {
|
if i + 1 < args.len() {
|
||||||
boot_target = BootTarget::Cartridge {
|
cartridge_path = Some(args[i + 1].clone());
|
||||||
path: args[i + 1].clone(),
|
debug_mode = false;
|
||||||
debug: false,
|
|
||||||
};
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"--debug" => {
|
"--debug" => {
|
||||||
if i + 1 < args.len() {
|
if i + 1 < args.len() {
|
||||||
boot_target = BootTarget::Cartridge {
|
cartridge_path = Some(args[i + 1].clone());
|
||||||
path: args[i + 1].clone(),
|
debug_mode = true;
|
||||||
debug: true,
|
i += 1;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
"--port" => {
|
||||||
|
if i + 1 < args.len() {
|
||||||
|
if let Ok(port) = args[i + 1].parse::<u16>() {
|
||||||
|
debug_port = port;
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,6 +84,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let boot_target = if let Some(path) = cartridge_path {
|
||||||
|
BootTarget::Cartridge {
|
||||||
|
path,
|
||||||
|
debug: debug_mode,
|
||||||
|
debug_port: if debug_mode { debug_port } else { 7777 },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BootTarget::Hub
|
||||||
|
};
|
||||||
|
|
||||||
let event_loop = EventLoop::new()?;
|
let event_loop = EventLoop::new()?;
|
||||||
|
|
||||||
let mut runner = PrometeuRunner::new(fs_root, cap_config);
|
let mut runner = PrometeuRunner::new(fs_root, cap_config);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ use prometeu_core::hardware::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE};
|
|||||||
use prometeu_core::Hardware;
|
use prometeu_core::Hardware;
|
||||||
use ringbuf::traits::{Consumer, Producer, Split};
|
use ringbuf::traits::{Consumer, Producer, Split};
|
||||||
use ringbuf::HeapRb;
|
use ringbuf::HeapRb;
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
@ -17,8 +18,10 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
|||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
use prometeu_core::model::{CartridgeError, CartridgeLoader};
|
use prometeu_core::model::CartridgeLoader;
|
||||||
use prometeu_core::telemetry::CertificationConfig;
|
use prometeu_core::telemetry::CertificationConfig;
|
||||||
|
use prometeu_core::debugger_protocol::*;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
pub struct PrometeuRunner {
|
pub struct PrometeuRunner {
|
||||||
window: Option<&'static Window>,
|
window: Option<&'static Window>,
|
||||||
@ -43,6 +46,10 @@ pub struct PrometeuRunner {
|
|||||||
overlay_enabled: bool,
|
overlay_enabled: bool,
|
||||||
|
|
||||||
debug_waiting_for_start: bool,
|
debug_waiting_for_start: bool,
|
||||||
|
debug_listener: Option<TcpListener>,
|
||||||
|
debug_stream: Option<TcpStream>,
|
||||||
|
debug_last_log_seq: u64,
|
||||||
|
debug_last_telemetry_frame: u64,
|
||||||
|
|
||||||
audio_load_accum_us: u64,
|
audio_load_accum_us: u64,
|
||||||
audio_load_samples: u64,
|
audio_load_samples: u64,
|
||||||
@ -55,17 +62,206 @@ pub struct PrometeuRunner {
|
|||||||
impl PrometeuRunner {
|
impl PrometeuRunner {
|
||||||
pub(crate) fn set_boot_target(&mut self, boot_target: BootTarget) {
|
pub(crate) fn set_boot_target(&mut self, boot_target: BootTarget) {
|
||||||
self.firmware.boot_target = boot_target.clone();
|
self.firmware.boot_target = boot_target.clone();
|
||||||
if let BootTarget::Cartridge { debug: true, .. } = boot_target {
|
if let BootTarget::Cartridge { path, debug: true, debug_port } = boot_target {
|
||||||
self.debug_waiting_for_start = true;
|
self.debug_waiting_for_start = true;
|
||||||
println!("[Debugger] Waiting for start command...");
|
|
||||||
println!("[Debugger] (Stub: press D to start execution)");
|
// Pré-carrega informações do cartucho para o handshake
|
||||||
|
if let Ok(cartridge) = CartridgeLoader::load(&path) {
|
||||||
|
self.firmware.os.initialize_vm(&mut self.firmware.vm, &cartridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
|
||||||
|
Ok(listener) => {
|
||||||
|
listener.set_nonblocking(true).expect("Cannot set non-blocking");
|
||||||
|
self.debug_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)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_cartridge(&mut self, path: &str) -> Result<(), CartridgeError> {
|
|
||||||
let cartridge = CartridgeLoader::load(path)?;
|
fn send_debug_response(&mut self, resp: DebugResponse) {
|
||||||
self.firmware.load_cartridge(cartridge);
|
if let Some(stream) = &mut self.debug_stream {
|
||||||
Ok(())
|
if let Ok(json) = serde_json::to_string(&resp) {
|
||||||
|
let _ = stream.write_all(json.as_bytes());
|
||||||
|
let _ = stream.write_all(b"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_debug_event(&mut self, event: DebugEvent) {
|
||||||
|
if let Some(stream) = &mut self.debug_stream {
|
||||||
|
if let Ok(json) = serde_json::to_string(&event) {
|
||||||
|
let _ = stream.write_all(json.as_bytes());
|
||||||
|
let _ = stream.write_all(b"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_debug_commands(&mut self) {
|
||||||
|
if self.debug_waiting_for_start {
|
||||||
|
if let Some(listener) = &self.debug_listener {
|
||||||
|
if let Ok((stream, _addr)) = listener.accept() {
|
||||||
|
println!("[Debugger] Connection received!");
|
||||||
|
stream.set_nonblocking(true).expect("Cannot set non-blocking on stream");
|
||||||
|
|
||||||
|
self.debug_stream = Some(stream);
|
||||||
|
|
||||||
|
// Enviar Handshake
|
||||||
|
let handshake = DebugResponse::Handshake {
|
||||||
|
runtime_version: "0.1".to_string(),
|
||||||
|
cartridge: HandshakeCartridge {
|
||||||
|
app_id: self.firmware.os.current_app_id,
|
||||||
|
title: self.firmware.os.current_cartridge_title.clone(),
|
||||||
|
app_version: self.firmware.os.current_cartridge_app_version.clone(),
|
||||||
|
app_mode: self.firmware.os.current_cartridge_app_mode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.send_debug_response(handshake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut stream) = self.debug_stream.take() {
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
match stream.read(&mut buf) {
|
||||||
|
Ok(0) => {
|
||||||
|
println!("[Debugger] Connection closed by remote.");
|
||||||
|
self.debug_stream = None;
|
||||||
|
}
|
||||||
|
Ok(n) => {
|
||||||
|
let data = &buf[..n];
|
||||||
|
// Processar múltiplos comandos se houver \n
|
||||||
|
let msg = String::from_utf8_lossy(data);
|
||||||
|
for line in msg.lines() {
|
||||||
|
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(line) {
|
||||||
|
self.handle_debug_command(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.debug_stream = Some(stream);
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||||
|
self.debug_stream = Some(stream);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[Debugger] Connection error: {}", e);
|
||||||
|
self.debug_stream = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streaming de eventos
|
||||||
|
if self.debug_stream.is_some() {
|
||||||
|
self.stream_debug_events();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_debug_command(&mut self, cmd: DebugCommand) {
|
||||||
|
match cmd {
|
||||||
|
DebugCommand::Ok | DebugCommand::Start => {
|
||||||
|
if self.debug_waiting_for_start {
|
||||||
|
println!("[Debugger] Starting execution...");
|
||||||
|
self.debug_waiting_for_start = false;
|
||||||
|
self.debug_listener = None;
|
||||||
|
}
|
||||||
|
self.firmware.os.paused = false;
|
||||||
|
}
|
||||||
|
DebugCommand::Pause => {
|
||||||
|
self.firmware.os.paused = true;
|
||||||
|
}
|
||||||
|
DebugCommand::Resume => {
|
||||||
|
self.firmware.os.paused = false;
|
||||||
|
}
|
||||||
|
DebugCommand::Step => {
|
||||||
|
self.firmware.os.paused = true;
|
||||||
|
// Executa uma instrução imediatamente
|
||||||
|
let _ = self.firmware.os.debug_step_instruction(&mut self.firmware.vm, &mut self.hardware);
|
||||||
|
}
|
||||||
|
DebugCommand::StepFrame => {
|
||||||
|
self.firmware.os.paused = false;
|
||||||
|
self.firmware.os.debug_step_request = true;
|
||||||
|
}
|
||||||
|
DebugCommand::GetState => {
|
||||||
|
let stack_top = self.firmware.vm.operand_stack.iter()
|
||||||
|
.rev().take(10).filter_map(|v| v.as_integer()).collect();
|
||||||
|
|
||||||
|
let resp = DebugResponse::State {
|
||||||
|
pc: self.firmware.vm.pc,
|
||||||
|
stack_top,
|
||||||
|
frame_index: self.firmware.os.logical_frame_index,
|
||||||
|
app_id: self.firmware.os.current_app_id,
|
||||||
|
};
|
||||||
|
self.send_debug_response(resp);
|
||||||
|
}
|
||||||
|
DebugCommand::SetBreakpoint { pc } => {
|
||||||
|
self.firmware.vm.breakpoints.insert(pc);
|
||||||
|
}
|
||||||
|
DebugCommand::ListBreakpoints => {
|
||||||
|
let pcs = self.firmware.vm.breakpoints.iter().cloned().collect();
|
||||||
|
self.send_debug_response(DebugResponse::Breakpoints { pcs });
|
||||||
|
}
|
||||||
|
DebugCommand::ClearBreakpoint { pc } => {
|
||||||
|
self.firmware.vm.breakpoints.remove(&pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream_debug_events(&mut self) {
|
||||||
|
// Logs
|
||||||
|
let new_events = self.firmware.os.log_service.get_after(self.debug_last_log_seq);
|
||||||
|
for event in new_events {
|
||||||
|
self.debug_last_log_seq = event.seq;
|
||||||
|
|
||||||
|
// Verifica se é um breakpoint hit via tag
|
||||||
|
if event.tag == 0xDEB1 {
|
||||||
|
self.send_debug_event(DebugEvent::BreakpointHit {
|
||||||
|
pc: self.firmware.vm.pc,
|
||||||
|
frame_index: self.firmware.os.logical_frame_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificação via Tags 0xCA01-0xCA03
|
||||||
|
if event.tag >= 0xCA01 && event.tag <= 0xCA03 {
|
||||||
|
let rule = match event.tag {
|
||||||
|
0xCA01 => "cycles_budget",
|
||||||
|
0xCA02 => "max_syscalls",
|
||||||
|
0xCA03 => "max_host_cpu_us",
|
||||||
|
_ => "unknown"
|
||||||
|
}.to_string();
|
||||||
|
|
||||||
|
self.send_debug_event(DebugEvent::Cert {
|
||||||
|
rule,
|
||||||
|
used: 0, // Simplificado, informações detalhadas estão na msg do log
|
||||||
|
limit: 0,
|
||||||
|
frame_index: self.firmware.os.logical_frame_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_debug_event(DebugEvent::Log {
|
||||||
|
level: format!("{:?}", event.level),
|
||||||
|
source: format!("{:?}", event.source),
|
||||||
|
msg: event.msg.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Telemetria (a cada novo frame)
|
||||||
|
let current_frame = self.firmware.os.logical_frame_index;
|
||||||
|
if current_frame > self.debug_last_telemetry_frame {
|
||||||
|
let tel = &self.firmware.os.telemetry_last;
|
||||||
|
self.send_debug_event(DebugEvent::Telemetry {
|
||||||
|
frame_index: tel.frame_index,
|
||||||
|
vm_steps: tel.vm_steps,
|
||||||
|
syscalls: tel.syscalls,
|
||||||
|
cycles: tel.cycles_used,
|
||||||
|
});
|
||||||
|
self.debug_last_telemetry_frame = current_frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(fs_root: Option<String>, cap_config: Option<CertificationConfig>) -> Self {
|
pub(crate) fn new(fs_root: Option<String>, cap_config: Option<CertificationConfig>) -> Self {
|
||||||
@ -94,6 +290,10 @@ impl PrometeuRunner {
|
|||||||
current_fps: 0.0,
|
current_fps: 0.0,
|
||||||
overlay_enabled: false,
|
overlay_enabled: false,
|
||||||
debug_waiting_for_start: false,
|
debug_waiting_for_start: false,
|
||||||
|
debug_listener: None,
|
||||||
|
debug_stream: None,
|
||||||
|
debug_last_log_seq: 0,
|
||||||
|
debug_last_telemetry_frame: 0,
|
||||||
audio_load_accum_us: 0,
|
audio_load_accum_us: 0,
|
||||||
audio_load_samples: 0,
|
audio_load_samples: 0,
|
||||||
audio_perf_consumer: None,
|
audio_perf_consumer: None,
|
||||||
@ -240,6 +440,7 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
|
|
||||||
if is_down && code == KeyCode::KeyD && self.debug_waiting_for_start {
|
if is_down && code == KeyCode::KeyD && self.debug_waiting_for_start {
|
||||||
self.debug_waiting_for_start = false;
|
self.debug_waiting_for_start = false;
|
||||||
|
self.debug_listener = None; // Fecha o socket
|
||||||
println!("[Debugger] Execution started!");
|
println!("[Debugger] Execution started!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,6 +496,8 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
||||||
|
self.check_debug_commands();
|
||||||
|
|
||||||
// Atualiza estado do Filesystem no OS (específico do host-desktop)
|
// Atualiza estado do Filesystem no OS (específico do host-desktop)
|
||||||
if let Some(root) = &self.fs_root {
|
if let Some(root) = &self.fs_root {
|
||||||
use prometeu_core::fs::FsState;
|
use prometeu_core::fs::FsState;
|
||||||
@ -454,4 +657,56 @@ fn rgb565_to_rgb888(px: u16) -> (u8, u8, u8) {
|
|||||||
let b8 = (b5 << 3) | (b5 >> 2);
|
let b8 = (b5 << 3) | (b5 >> 2);
|
||||||
|
|
||||||
(r8, g8, b8)
|
(r8, g8, b8)
|
||||||
}
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use prometeu_core::firmware::BootTarget;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug_port_opens() {
|
||||||
|
let mut runner = PrometeuRunner::new(None, None);
|
||||||
|
let port = 9999;
|
||||||
|
runner.set_boot_target(BootTarget::Cartridge {
|
||||||
|
path: "dummy.bin".to_string(),
|
||||||
|
debug: true,
|
||||||
|
debug_port: port,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(runner.debug_waiting_for_start);
|
||||||
|
assert!(runner.debug_listener.is_some());
|
||||||
|
|
||||||
|
// Verifica se conseguimos conectar
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::io::{Write, Read};
|
||||||
|
{
|
||||||
|
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar");
|
||||||
|
// Pequeno sleep para garantir que o SO processe
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
// Simula o loop para aceitar a conexão
|
||||||
|
runner.check_debug_commands();
|
||||||
|
assert!(runner.debug_stream.is_some(), "Stream deve ter sido mantido aberto");
|
||||||
|
|
||||||
|
// Verifica Handshake
|
||||||
|
let mut buf = [0u8; 2048];
|
||||||
|
let n = stream.read(&mut buf).expect("Deve ler handshake");
|
||||||
|
let resp: serde_json::Value = serde_json::from_slice(&buf[..n]).expect("Handshake deve ser JSON válido");
|
||||||
|
assert_eq!(resp["type"], "handshake");
|
||||||
|
|
||||||
|
// Envia start via JSON
|
||||||
|
stream.write_all(b"{\"type\":\"start\"}\n").expect("Conexão deve estar aberta para escrita");
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
|
||||||
|
// Processa o comando recebido
|
||||||
|
runner.check_debug_commands();
|
||||||
|
assert!(!runner.debug_waiting_for_start, "Execução deve ter iniciado após comando start");
|
||||||
|
assert!(runner.debug_listener.is_none(), "Listener deve ter sido fechado");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agora que o stream saiu do escopo do teste, o runner deve detectar o fechamento no próximo check
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
runner.check_debug_commands();
|
||||||
|
assert!(runner.debug_stream.is_none(), "Stream deve ter sido fechado após o cliente desconectar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
86
crates/prometeu-core/src/debugger_protocol.rs
Normal file
86
crates/prometeu-core/src/debugger_protocol.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::model::AppMode;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum DebugCommand {
|
||||||
|
#[serde(rename = "ok")]
|
||||||
|
Ok,
|
||||||
|
#[serde(rename = "start")]
|
||||||
|
Start,
|
||||||
|
#[serde(rename = "pause")]
|
||||||
|
Pause,
|
||||||
|
#[serde(rename = "resume")]
|
||||||
|
Resume,
|
||||||
|
#[serde(rename = "step")]
|
||||||
|
Step,
|
||||||
|
#[serde(rename = "stepFrame")]
|
||||||
|
StepFrame,
|
||||||
|
#[serde(rename = "getState")]
|
||||||
|
GetState,
|
||||||
|
#[serde(rename = "setBreakpoint")]
|
||||||
|
SetBreakpoint { pc: usize },
|
||||||
|
#[serde(rename = "listBreakpoints")]
|
||||||
|
ListBreakpoints,
|
||||||
|
#[serde(rename = "clearBreakpoint")]
|
||||||
|
ClearBreakpoint { pc: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum DebugResponse {
|
||||||
|
#[serde(rename = "handshake")]
|
||||||
|
Handshake {
|
||||||
|
runtime_version: String,
|
||||||
|
cartridge: HandshakeCartridge,
|
||||||
|
},
|
||||||
|
#[serde(rename = "state")]
|
||||||
|
State {
|
||||||
|
pc: usize,
|
||||||
|
stack_top: Vec<i64>,
|
||||||
|
frame_index: u64,
|
||||||
|
app_id: u32,
|
||||||
|
},
|
||||||
|
#[serde(rename = "breakpoints")]
|
||||||
|
Breakpoints {
|
||||||
|
pcs: Vec<usize>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct HandshakeCartridge {
|
||||||
|
pub app_id: u32,
|
||||||
|
pub title: String,
|
||||||
|
pub app_version: String,
|
||||||
|
pub app_mode: AppMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "event")]
|
||||||
|
pub enum DebugEvent {
|
||||||
|
#[serde(rename = "breakpointHit")]
|
||||||
|
BreakpointHit {
|
||||||
|
pc: usize,
|
||||||
|
frame_index: u64,
|
||||||
|
},
|
||||||
|
#[serde(rename = "log")]
|
||||||
|
Log {
|
||||||
|
level: String,
|
||||||
|
source: String,
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
#[serde(rename = "telemetry")]
|
||||||
|
Telemetry {
|
||||||
|
frame_index: u64,
|
||||||
|
vm_steps: u32,
|
||||||
|
syscalls: u32,
|
||||||
|
cycles: u64,
|
||||||
|
},
|
||||||
|
#[serde(rename = "cert")]
|
||||||
|
Cert {
|
||||||
|
rule: String,
|
||||||
|
used: u64,
|
||||||
|
limit: u64,
|
||||||
|
frame_index: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum BootTarget {
|
pub enum BootTarget {
|
||||||
Hub,
|
Hub,
|
||||||
Cartridge { path: String, debug: bool },
|
Cartridge { path: String, debug: bool, debug_port: u16 },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BootTarget {
|
impl Default for BootTarget {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ impl LaunchHubStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
if let BootTarget::Cartridge { path, debug } = ctx.boot_target {
|
if let BootTarget::Cartridge { path, .. } = ctx.boot_target {
|
||||||
match CartridgeLoader::load(path) {
|
match CartridgeLoader::load(path) {
|
||||||
Ok(cartridge) => {
|
Ok(cartridge) => {
|
||||||
// No caso de debug, aqui poderíamos pausar, mas o requisito diz
|
// No caso de debug, aqui poderíamos pausar, mas o requisito diz
|
||||||
|
|||||||
@ -5,6 +5,7 @@ pub mod model;
|
|||||||
pub mod firmware;
|
pub mod firmware;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
|
pub mod debugger_protocol;
|
||||||
mod prometeu_os;
|
mod prometeu_os;
|
||||||
mod prometeu_hub;
|
mod prometeu_hub;
|
||||||
|
|
||||||
|
|||||||
@ -32,12 +32,17 @@ pub struct PrometeuOS {
|
|||||||
// Log Service
|
// Log Service
|
||||||
pub log_service: LogService,
|
pub log_service: LogService,
|
||||||
pub current_app_id: u32,
|
pub current_app_id: u32,
|
||||||
|
pub current_cartridge_title: String,
|
||||||
|
pub current_cartridge_app_version: String,
|
||||||
|
pub current_cartridge_app_mode: crate::model::AppMode,
|
||||||
pub logs_written_this_frame: HashMap<u32, u32>,
|
pub logs_written_this_frame: HashMap<u32, u32>,
|
||||||
|
|
||||||
// Telemetria e Certificação
|
// Telemetria e Certificação
|
||||||
pub telemetry_current: TelemetryFrame,
|
pub telemetry_current: TelemetryFrame,
|
||||||
pub telemetry_last: TelemetryFrame,
|
pub telemetry_last: TelemetryFrame,
|
||||||
pub certifier: Certifier,
|
pub certifier: Certifier,
|
||||||
|
pub paused: bool,
|
||||||
|
pub debug_step_request: bool,
|
||||||
|
|
||||||
boot_time: Instant,
|
boot_time: Instant,
|
||||||
}
|
}
|
||||||
@ -66,10 +71,15 @@ impl PrometeuOS {
|
|||||||
next_handle: 1,
|
next_handle: 1,
|
||||||
log_service: LogService::new(4096),
|
log_service: LogService::new(4096),
|
||||||
current_app_id: 0,
|
current_app_id: 0,
|
||||||
|
current_cartridge_title: String::new(),
|
||||||
|
current_cartridge_app_version: String::new(),
|
||||||
|
current_cartridge_app_mode: crate::model::AppMode::Game,
|
||||||
logs_written_this_frame: HashMap::new(),
|
logs_written_this_frame: HashMap::new(),
|
||||||
telemetry_current: TelemetryFrame::default(),
|
telemetry_current: TelemetryFrame::default(),
|
||||||
telemetry_last: TelemetryFrame::default(),
|
telemetry_last: TelemetryFrame::default(),
|
||||||
certifier: Certifier::new(cap_config.unwrap_or_default()),
|
certifier: Certifier::new(cap_config.unwrap_or_default()),
|
||||||
|
paused: false,
|
||||||
|
debug_step_request: false,
|
||||||
boot_time,
|
boot_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,6 +141,21 @@ impl PrometeuOS {
|
|||||||
|
|
||||||
// Determina o app_id numérico
|
// Determina o app_id numérico
|
||||||
self.current_app_id = cartridge.app_id;
|
self.current_app_id = cartridge.app_id;
|
||||||
|
self.current_cartridge_title = cartridge.title.clone();
|
||||||
|
self.current_cartridge_app_version = cartridge.app_version.clone();
|
||||||
|
self.current_cartridge_app_mode = cartridge.app_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executa uma única instrução da VM (Debug).
|
||||||
|
pub fn debug_step_instruction(&mut self, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Option<String> {
|
||||||
|
match vm.step(self, hw) {
|
||||||
|
Ok(_) => None,
|
||||||
|
Err(e) => {
|
||||||
|
let err_msg = format!("PVM Fault during Step: {:?}", e);
|
||||||
|
self.log(LogLevel::Error, LogSource::Vm, 0, err_msg.clone());
|
||||||
|
Some(err_msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executa um tick do host (60Hz).
|
/// Executa um tick do host (60Hz).
|
||||||
@ -138,6 +163,10 @@ impl PrometeuOS {
|
|||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
self.tick_index += 1;
|
self.tick_index += 1;
|
||||||
|
|
||||||
|
if self.paused && !self.debug_step_request {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
self.update_fs();
|
self.update_fs();
|
||||||
|
|
||||||
if !self.logical_frame_active {
|
if !self.logical_frame_active {
|
||||||
@ -167,6 +196,12 @@ impl PrometeuOS {
|
|||||||
self.telemetry_current.cycles_used += run.cycles_used;
|
self.telemetry_current.cycles_used += run.cycles_used;
|
||||||
self.telemetry_current.vm_steps += run.steps_executed;
|
self.telemetry_current.vm_steps += run.steps_executed;
|
||||||
|
|
||||||
|
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::Breakpoint {
|
||||||
|
self.paused = true;
|
||||||
|
self.debug_step_request = false;
|
||||||
|
self.log(LogLevel::Info, LogSource::Vm, 0xDEB1, format!("Breakpoint hit at PC 0x{:X}", vm.pc));
|
||||||
|
}
|
||||||
|
|
||||||
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
|
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
|
||||||
hw.gfx_mut().render_all();
|
hw.gfx_mut().render_all();
|
||||||
|
|
||||||
@ -183,6 +218,11 @@ impl PrometeuOS {
|
|||||||
self.logical_frame_index += 1;
|
self.logical_frame_index += 1;
|
||||||
self.logical_frame_active = false;
|
self.logical_frame_active = false;
|
||||||
self.logical_frame_remaining_cycles = 0;
|
self.logical_frame_remaining_cycles = 0;
|
||||||
|
|
||||||
|
if self.debug_step_request {
|
||||||
|
self.paused = true;
|
||||||
|
self.debug_step_request = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ pub enum LogicalFrameEndingReason {
|
|||||||
BudgetExhausted,
|
BudgetExhausted,
|
||||||
Halted,
|
Halted,
|
||||||
EndOfRom,
|
EndOfRom,
|
||||||
|
Breakpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -29,6 +30,7 @@ pub struct VirtualMachine {
|
|||||||
pub heap: Vec<Value>, // Simplificado para demo, futuramente RAM/Heap real
|
pub heap: Vec<Value>, // Simplificado para demo, futuramente RAM/Heap real
|
||||||
pub cycles: u64,
|
pub cycles: u64,
|
||||||
pub halted: bool,
|
pub halted: bool,
|
||||||
|
pub breakpoints: std::collections::HashSet<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualMachine {
|
impl VirtualMachine {
|
||||||
@ -42,13 +44,21 @@ impl VirtualMachine {
|
|||||||
heap: Vec::new(),
|
heap: Vec::new(),
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
halted: false,
|
halted: false,
|
||||||
|
breakpoints: std::collections::HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize(&mut self, program_bytes: Vec<u8>, entrypoint: &str) {
|
pub fn initialize(&mut self, program_bytes: Vec<u8>, entrypoint: &str) {
|
||||||
// Por enquanto, tratamos os bytes como a ROM diretamente.
|
if program_bytes.starts_with(b"PPBC") {
|
||||||
// TODO: Implementar parser de .pbc para extrair constant_pool e rom reais.
|
if let Ok((rom, cp)) = self.parse_pbc(&program_bytes) {
|
||||||
self.program = Program::new(program_bytes, vec![]);
|
self.program = Program::new(rom, cp);
|
||||||
|
} else {
|
||||||
|
self.program = Program::new(program_bytes, vec![]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Por enquanto, tratamos os bytes como a ROM diretamente.
|
||||||
|
self.program = Program::new(program_bytes, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
// Se o entrypoint for numérico, podemos tentar usá-lo como PC inicial.
|
// Se o entrypoint for numérico, podemos tentar usá-lo como PC inicial.
|
||||||
// Se não, por enquanto ignoramos ou começamos do 0.
|
// Se não, por enquanto ignoramos ou começamos do 0.
|
||||||
@ -65,6 +75,73 @@ impl VirtualMachine {
|
|||||||
self.cycles = 0;
|
self.cycles = 0;
|
||||||
self.halted = false;
|
self.halted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_pbc(&self, bytes: &[u8]) -> Result<(Vec<u8>, Vec<Value>), String> {
|
||||||
|
let mut cursor = 4; // Skip "PPBC"
|
||||||
|
|
||||||
|
let cp_count = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||||
|
let mut cp = Vec::with_capacity(cp_count);
|
||||||
|
|
||||||
|
for _ in 0..cp_count {
|
||||||
|
if cursor >= bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let tag = bytes[cursor];
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
match tag {
|
||||||
|
1 => { // Integer
|
||||||
|
let val = self.read_i64_at(bytes, &mut cursor)?;
|
||||||
|
cp.push(Value::Integer(val));
|
||||||
|
}
|
||||||
|
2 => { // Float
|
||||||
|
let val = self.read_f64_at(bytes, &mut cursor)?;
|
||||||
|
cp.push(Value::Float(val));
|
||||||
|
}
|
||||||
|
3 => { // Boolean
|
||||||
|
if cursor >= bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let val = bytes[cursor] != 0;
|
||||||
|
cursor += 1;
|
||||||
|
cp.push(Value::Boolean(val));
|
||||||
|
}
|
||||||
|
4 => { // String
|
||||||
|
let len = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||||
|
if cursor + len > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let s = String::from_utf8_lossy(&bytes[cursor..cursor + len]).into_owned();
|
||||||
|
cursor += len;
|
||||||
|
cp.push(Value::String(s));
|
||||||
|
}
|
||||||
|
_ => cp.push(Value::Null),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rom_size = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||||
|
if cursor + rom_size > bytes.len() {
|
||||||
|
return Err("Invalid ROM size in PBC".into());
|
||||||
|
}
|
||||||
|
let rom = bytes[cursor..cursor + rom_size].to_vec();
|
||||||
|
|
||||||
|
Ok((rom, cp))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<u32, String> {
|
||||||
|
if *cursor + 4 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let val = u32::from_le_bytes(bytes[*cursor..*cursor + 4].try_into().unwrap());
|
||||||
|
*cursor += 4;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_i64_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<i64, String> {
|
||||||
|
if *cursor + 8 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let val = i64::from_le_bytes(bytes[*cursor..*cursor + 8].try_into().unwrap());
|
||||||
|
*cursor += 8;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_f64_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<f64, String> {
|
||||||
|
if *cursor + 8 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||||
|
let val = f64::from_le_bytes(bytes[*cursor..*cursor + 8].try_into().unwrap());
|
||||||
|
*cursor += 8;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VirtualMachine {
|
impl Default for VirtualMachine {
|
||||||
@ -88,6 +165,11 @@ impl VirtualMachine {
|
|||||||
&& !self.halted
|
&& !self.halted
|
||||||
&& self.pc < self.program.rom.len()
|
&& self.pc < self.program.rom.len()
|
||||||
{
|
{
|
||||||
|
if steps_executed > 0 && self.breakpoints.contains(&self.pc) {
|
||||||
|
ending_reason = Some(LogicalFrameEndingReason::Breakpoint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let pc_before = self.pc;
|
let pc_before = self.pc;
|
||||||
let cycles_before = self.cycles;
|
let cycles_before = self.cycles;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user