add debugger

This commit is contained in:
Nilton Constantino 2026-01-17 21:14:36 +00:00
parent 990e3ead2a
commit f04c12d78c
No known key found for this signature in database
10 changed files with 506 additions and 24 deletions

1
Cargo.lock generated
View File

@ -724,6 +724,7 @@ dependencies = [
"pixels", "pixels",
"prometeu-core", "prometeu-core",
"ringbuf", "ringbuf",
"serde_json",
"winit", "winit",
] ]

View File

@ -9,3 +9,4 @@ 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"

View File

@ -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);

View File

@ -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);
} }
} }
pub(crate) fn load_cartridge(&mut self, path: &str) -> Result<(), CartridgeError> { println!("[Debugger] (Or press D to start execution)");
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 { 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;
@ -455,3 +658,55 @@ fn rgb565_to_rgb888(px: u16) -> (u8, u8, u8) {
(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");
}
}

View 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,
},
}

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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) => {

View File

@ -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(rom, cp);
} else {
self.program = Program::new(program_bytes, vec![]); 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;