add debugger
This commit is contained in:
parent
71bb007499
commit
cd68bb0d0a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -724,6 +724,7 @@ dependencies = [
|
||||
"pixels",
|
||||
"prometeu-core",
|
||||
"ringbuf",
|
||||
"serde_json",
|
||||
"winit",
|
||||
]
|
||||
|
||||
|
||||
@ -8,4 +8,5 @@ prometeu-core = { path = "../prometeu-core" }
|
||||
winit = "0.30.12"
|
||||
pixels = "0.15.0"
|
||||
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 mut fs_root = 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
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--run" => {
|
||||
if i + 1 < args.len() {
|
||||
boot_target = BootTarget::Cartridge {
|
||||
path: args[i + 1].clone(),
|
||||
debug: false,
|
||||
};
|
||||
cartridge_path = Some(args[i + 1].clone());
|
||||
debug_mode = false;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
"--debug" => {
|
||||
if i + 1 < args.len() {
|
||||
boot_target = BootTarget::Cartridge {
|
||||
path: args[i + 1].clone(),
|
||||
debug: true,
|
||||
};
|
||||
cartridge_path = Some(args[i + 1].clone());
|
||||
debug_mode = true;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
"--port" => {
|
||||
if i + 1 < args.len() {
|
||||
if let Ok(port) = args[i + 1].parse::<u16>() {
|
||||
debug_port = port;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
@ -78,6 +84,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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 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 ringbuf::traits::{Consumer, Producer, Split};
|
||||
use ringbuf::HeapRb;
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::application::ApplicationHandler;
|
||||
@ -17,8 +18,10 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow};
|
||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||
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::debugger_protocol::*;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub struct PrometeuRunner {
|
||||
window: Option<&'static Window>,
|
||||
@ -43,6 +46,10 @@ pub struct PrometeuRunner {
|
||||
overlay_enabled: 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_samples: u64,
|
||||
@ -55,17 +62,206 @@ pub struct PrometeuRunner {
|
||||
impl PrometeuRunner {
|
||||
pub(crate) fn set_boot_target(&mut self, boot_target: BootTarget) {
|
||||
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;
|
||||
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)?;
|
||||
self.firmware.load_cartridge(cartridge);
|
||||
Ok(())
|
||||
|
||||
fn send_debug_response(&mut self, resp: DebugResponse) {
|
||||
if let Some(stream) = &mut self.debug_stream {
|
||||
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 {
|
||||
@ -94,6 +290,10 @@ impl PrometeuRunner {
|
||||
current_fps: 0.0,
|
||||
overlay_enabled: 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_samples: 0,
|
||||
audio_perf_consumer: None,
|
||||
@ -240,6 +440,7 @@ impl ApplicationHandler for PrometeuRunner {
|
||||
|
||||
if is_down && code == KeyCode::KeyD && self.debug_waiting_for_start {
|
||||
self.debug_waiting_for_start = false;
|
||||
self.debug_listener = None; // Fecha o socket
|
||||
println!("[Debugger] Execution started!");
|
||||
}
|
||||
|
||||
@ -295,6 +496,8 @@ impl ApplicationHandler for PrometeuRunner {
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
self.check_debug_commands();
|
||||
|
||||
// Atualiza estado do Filesystem no OS (específico do host-desktop)
|
||||
if let Some(root) = &self.fs_root {
|
||||
use prometeu_core::fs::FsState;
|
||||
@ -454,4 +657,56 @@ fn rgb565_to_rgb888(px: u16) -> (u8, u8, u8) {
|
||||
let b8 = (b5 << 3) | (b5 >> 2);
|
||||
|
||||
(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)]
|
||||
pub enum BootTarget {
|
||||
Hub,
|
||||
Cartridge { path: String, debug: bool },
|
||||
Cartridge { path: String, debug: bool, debug_port: u16 },
|
||||
}
|
||||
|
||||
impl Default for BootTarget {
|
||||
|
||||
@ -14,7 +14,7 @@ impl LaunchHubStep {
|
||||
}
|
||||
|
||||
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) {
|
||||
Ok(cartridge) => {
|
||||
// No caso de debug, aqui poderíamos pausar, mas o requisito diz
|
||||
|
||||
@ -5,6 +5,7 @@ pub mod model;
|
||||
pub mod firmware;
|
||||
pub mod fs;
|
||||
pub mod telemetry;
|
||||
pub mod debugger_protocol;
|
||||
mod prometeu_os;
|
||||
mod prometeu_hub;
|
||||
|
||||
|
||||
@ -32,12 +32,17 @@ pub struct PrometeuOS {
|
||||
// Log Service
|
||||
pub log_service: LogService,
|
||||
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>,
|
||||
|
||||
// Telemetria e Certificação
|
||||
pub telemetry_current: TelemetryFrame,
|
||||
pub telemetry_last: TelemetryFrame,
|
||||
pub certifier: Certifier,
|
||||
pub paused: bool,
|
||||
pub debug_step_request: bool,
|
||||
|
||||
boot_time: Instant,
|
||||
}
|
||||
@ -66,10 +71,15 @@ impl PrometeuOS {
|
||||
next_handle: 1,
|
||||
log_service: LogService::new(4096),
|
||||
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(),
|
||||
telemetry_current: TelemetryFrame::default(),
|
||||
telemetry_last: TelemetryFrame::default(),
|
||||
certifier: Certifier::new(cap_config.unwrap_or_default()),
|
||||
paused: false,
|
||||
debug_step_request: false,
|
||||
boot_time,
|
||||
};
|
||||
|
||||
@ -131,6 +141,21 @@ impl PrometeuOS {
|
||||
|
||||
// Determina o app_id numérico
|
||||
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).
|
||||
@ -138,6 +163,10 @@ impl PrometeuOS {
|
||||
let start = std::time::Instant::now();
|
||||
self.tick_index += 1;
|
||||
|
||||
if self.paused && !self.debug_step_request {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.update_fs();
|
||||
|
||||
if !self.logical_frame_active {
|
||||
@ -167,6 +196,12 @@ impl PrometeuOS {
|
||||
self.telemetry_current.cycles_used += run.cycles_used;
|
||||
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 {
|
||||
hw.gfx_mut().render_all();
|
||||
|
||||
@ -183,6 +218,11 @@ impl PrometeuOS {
|
||||
self.logical_frame_index += 1;
|
||||
self.logical_frame_active = false;
|
||||
self.logical_frame_remaining_cycles = 0;
|
||||
|
||||
if self.debug_step_request {
|
||||
self.paused = true;
|
||||
self.debug_step_request = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@ -11,6 +11,7 @@ pub enum LogicalFrameEndingReason {
|
||||
BudgetExhausted,
|
||||
Halted,
|
||||
EndOfRom,
|
||||
Breakpoint,
|
||||
}
|
||||
|
||||
#[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 cycles: u64,
|
||||
pub halted: bool,
|
||||
pub breakpoints: std::collections::HashSet<usize>,
|
||||
}
|
||||
|
||||
impl VirtualMachine {
|
||||
@ -42,13 +44,21 @@ impl VirtualMachine {
|
||||
heap: Vec::new(),
|
||||
cycles: 0,
|
||||
halted: false,
|
||||
breakpoints: std::collections::HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, program_bytes: Vec<u8>, entrypoint: &str) {
|
||||
// Por enquanto, tratamos os bytes como a ROM diretamente.
|
||||
// TODO: Implementar parser de .pbc para extrair constant_pool e rom reais.
|
||||
self.program = Program::new(program_bytes, vec![]);
|
||||
if program_bytes.starts_with(b"PPBC") {
|
||||
if let Ok((rom, cp)) = self.parse_pbc(&program_bytes) {
|
||||
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 não, por enquanto ignoramos ou começamos do 0.
|
||||
@ -65,6 +75,73 @@ impl VirtualMachine {
|
||||
self.cycles = 0;
|
||||
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 {
|
||||
@ -88,6 +165,11 @@ impl VirtualMachine {
|
||||
&& !self.halted
|
||||
&& 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 cycles_before = self.cycles;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user