From 54326ece55bf02c5bdeba75596d421814b0173cb Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Fri, 27 Dec 2024 03:40:29 +0700 Subject: [PATCH] Displays waiting for debugger window (#1203) --- Cargo.lock | 1 + gui/Cargo.toml | 1 + gui/src/debug/mod.rs | 43 --------------- gui/src/{debug/client.rs => gdb/mod.rs} | 0 gui/src/main.rs | 71 ++++++++++++++++++------- gui/src/ui/backend/window.rs | 11 ++-- gui/src/vmm/channel/mod.rs | 14 +++++ gui/src/vmm/channel/screen.rs | 19 +++++++ gui/src/vmm/channel/vmm.rs | 8 +++ gui/src/vmm/debug/mod.rs | 2 +- gui/src/vmm/mod.rs | 5 +- gui/ui/debug.slint | 19 +++++++ gui/ui/main.slint | 1 + 13 files changed, 128 insertions(+), 67 deletions(-) delete mode 100644 gui/src/debug/mod.rs rename gui/src/{debug/client.rs => gdb/mod.rs} (100%) create mode 100644 gui/src/vmm/channel/mod.rs create mode 100644 gui/src/vmm/channel/screen.rs create mode 100644 gui/src/vmm/channel/vmm.rs create mode 100644 gui/ui/debug.slint diff --git a/Cargo.lock b/Cargo.lock index 30590de0f..fef570e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1714,6 +1714,7 @@ dependencies = [ "ash", "ash-window", "ashpd", + "async-net", "bitfield-struct", "ciborium", "clap", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 7ee3ec571..fe53158e1 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -8,6 +8,7 @@ name = "obliteration" path = "src/main.rs" [dependencies] +async-net = "2.0.0" bitfield-struct = "0.9.2" ciborium = "0.2.2" clap = { version = "4.5.21", features = ["derive"] } diff --git a/gui/src/debug/mod.rs b/gui/src/debug/mod.rs deleted file mode 100644 index bb091aa14..000000000 --- a/gui/src/debug/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pub use self::client::*; - -use std::ffi::CString; -use std::net::{TcpListener, ToSocketAddrs}; -use thiserror::Error; - -mod client; - -/// TCP listener to accept a debugger connection. -pub struct DebugServer { - addr: CString, - sock: TcpListener, -} - -impl DebugServer { - pub fn new(addr: impl ToSocketAddrs) -> Result { - let sock = TcpListener::bind(addr).map_err(StartDebugServerError::BindFailed)?; - let addr = sock - .local_addr() - .map_err(StartDebugServerError::GetAddrFailed)?; - - Ok(Self { - addr: CString::new(addr.to_string()).unwrap(), - sock, - }) - } - - pub fn accept(&self) -> std::io::Result { - let (sock, _) = self.sock.accept()?; - - Ok(DebugClient::new(sock)) - } -} - -#[derive(Debug, Error)] -pub enum StartDebugServerError { - #[error("couldn't bind to the specified address")] - BindFailed(#[source] std::io::Error), - - #[error("couldn't get server address")] - GetAddrFailed(#[source] std::io::Error), -} diff --git a/gui/src/debug/client.rs b/gui/src/gdb/mod.rs similarity index 100% rename from gui/src/debug/client.rs rename to gui/src/gdb/mod.rs diff --git a/gui/src/main.rs b/gui/src/main.rs index 3d5f0f0bc..2974271c7 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -4,11 +4,14 @@ use self::data::{DataError, DataMgr}; use self::graphics::{Graphics, GraphicsError, PhysicalDevice}; use self::profile::{DisplayResolution, Profile}; use self::setup::{run_setup, SetupError}; -use self::ui::{MainWindow, ProfileModel, ResolutionModel, RuntimeExt, SlintBackend}; +use self::ui::{ + MainWindow, ProfileModel, ResolutionModel, RuntimeExt, SlintBackend, WaitForDebugger, +}; +use async_net::{TcpListener, TcpStream}; use clap::{Parser, ValueEnum}; -use debug::DebugServer; use erdp::ErrorDisplay; -use slint::{ComponentHandle, ModelRc, SharedString, VecModel, WindowHandle}; +use futures::{select_biased, FutureExt}; +use slint::{ComponentHandle, ModelRc, SharedString, ToSharedString, VecModel, WindowHandle}; use std::cell::Cell; use std::error::Error; use std::net::SocketAddrV4; @@ -21,8 +24,8 @@ use winit::dpi::PhysicalSize; use winit::window::Window; mod data; -mod debug; mod dialogs; +mod gdb; mod graphics; mod hv; mod panic; @@ -112,7 +115,7 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { rlim::set_rlimit_nofile().map_err(ProgramError::FdLimit)?; // Initialize graphics engine. - let mut graphics = graphics::new().map_err(ProgramError::InitGraphics)?; + let graphics = graphics::new().map_err(ProgramError::InitGraphics)?; // Run setup wizard. This will simply return the data manager if the user already has required // settings. @@ -188,15 +191,14 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> { }; // Wait for debugger. - let debugger = if let Some(listen) = debug { - let debug_server = - DebugServer::new(listen).map_err(|e| ProgramError::StartDebugServer(e, listen))?; + let debugger = if let Some(addr) = debug { + let v = wait_for_debugger(addr).await?; - let debugger = debug_server - .accept() - .map_err(ProgramError::CreateDebugClient)?; + if v.is_none() { + return Ok(()); + } - Some(debugger) + v } else { None }; @@ -334,6 +336,31 @@ async fn run_launcher( Ok(Some((profile, exit))) } +async fn wait_for_debugger(addr: SocketAddrV4) -> Result, ProgramError> { + // Start server. + let server = TcpListener::bind(addr) + .await + .map_err(|e| ProgramError::StartDebugServer(addr, e))?; + let addr = server.local_addr().map_err(ProgramError::GetDebugAddr)?; + + // Tell the user that we are waiting for a debugger. + let win = WaitForDebugger::new().map_err(ProgramError::CreateDebugWindow)?; + + win.set_address(addr.to_shared_string()); + win.show().map_err(ProgramError::ShowDebugWindow)?; + + // Wait for connection. + let client = select_biased! { + _ = win.wait().fuse() => None, + v = server.accept().fuse() => match v { + Ok(v) => Some(v.0), + Err(e) => return Err(ProgramError::AcceptDebugger(e)), + } + }; + + Ok(client) +} + /// Program arguments parsed from command line. #[derive(Parser)] #[command(about = None)] @@ -384,14 +411,20 @@ enum ProgramError { #[error("couldn't save default profile")] SaveDefaultProfile(#[source] self::profile::SaveError), - #[error("failed to start debug server on {1}")] - StartDebugServer( - #[source] debug::StartDebugServerError, - std::net::SocketAddrV4, - ), + #[error("couldn't start debug server on {0}")] + StartDebugServer(SocketAddrV4, #[source] std::io::Error), + + #[error("couldn't get debug server address")] + GetDebugAddr(#[source] std::io::Error), + + #[error("couldn't create debug server window")] + CreateDebugWindow(#[source] slint::PlatformError), + + #[error("couldn't show debug server window")] + ShowDebugWindow(#[source] slint::PlatformError), - #[error("failed to accept debug connection")] - CreateDebugClient(#[source] std::io::Error), + #[error("couldn't accept a connection from debugger")] + AcceptDebugger(#[source] std::io::Error), #[error("failed to create main window")] CreateMainWindow(#[source] slint::PlatformError), diff --git a/gui/src/ui/backend/window.rs b/gui/src/ui/backend/window.rs index 45be2cc79..4ad28ac8d 100644 --- a/gui/src/ui/backend/window.rs +++ b/gui/src/ui/backend/window.rs @@ -213,7 +213,7 @@ impl WindowAdapter for Window { let size = properties.layout_constraints(); let min = size.min.map(&map); let max = size.max.map(&map); - let preferred = map(size.preferred); + let pre = map(size.preferred); if self.minimum_size.replace(min) != min { self.winit.set_min_inner_size(min); @@ -223,9 +223,14 @@ impl WindowAdapter for Window { self.winit.set_max_inner_size(max); } + // Winit on Wayland will panic if either width or height is zero. // TODO: Not sure why Slint also update the preferred size when window size is changed. - if self.preferred_size.replace(Some(preferred)).is_none() { - let _ = self.winit.request_inner_size(preferred); + if self.preferred_size.replace(Some(pre)).is_none() && pre.width != 0 && pre.height != 0 { + let _ = self.winit.request_inner_size(pre); + + if matches!((min, max), (Some(min), Some(max)) if min == max && pre == max) { + self.winit.set_resizable(false); + } } } diff --git a/gui/src/vmm/channel/mod.rs b/gui/src/vmm/channel/mod.rs new file mode 100644 index 000000000..8c9bc724f --- /dev/null +++ b/gui/src/vmm/channel/mod.rs @@ -0,0 +1,14 @@ +pub use self::screen::*; +pub use self::vmm::*; + +mod screen; +mod vmm; + +/// Create a new channel to communicate with the VMM. +pub fn create_channel() -> (VmmStream, ScreenStream) { + // Create streams. + let vmm = VmmStream::new(); + let screen = ScreenStream::new(); + + (vmm, screen) +} diff --git a/gui/src/vmm/channel/screen.rs b/gui/src/vmm/channel/screen.rs new file mode 100644 index 000000000..4d1218ee9 --- /dev/null +++ b/gui/src/vmm/channel/screen.rs @@ -0,0 +1,19 @@ +use gdbstub::stub::MultiThreadStopReason; + +/// Provides method to send and receive events from the screen. +pub struct ScreenStream {} + +impl ScreenStream { + pub(super) fn new() -> Self { + Self {} + } + + pub fn recv(&self) {} + + pub fn breakpoint(&self, stop: Option>) -> BreakpointLock { + BreakpointLock {} + } +} + +/// This struct will prevent the other CPU from entering a debugger dispatch loop. +pub struct BreakpointLock {} diff --git a/gui/src/vmm/channel/vmm.rs b/gui/src/vmm/channel/vmm.rs new file mode 100644 index 000000000..8b2c6dcf6 --- /dev/null +++ b/gui/src/vmm/channel/vmm.rs @@ -0,0 +1,8 @@ +/// Provides method to send and receive events from the VMM. +pub struct VmmStream {} + +impl VmmStream { + pub(super) fn new() -> Self { + Self {} + } +} diff --git a/gui/src/vmm/debug/mod.rs b/gui/src/vmm/debug/mod.rs index 4a328755d..960a9c767 100644 --- a/gui/src/vmm/debug/mod.rs +++ b/gui/src/vmm/debug/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use super::cpu::{CpuManager, GdbError}; -use crate::debug::DebugClient; +use crate::gdb::DebugClient; use crate::hv::Hypervisor; use gdbstub::stub::state_machine::state::{Idle, Running}; use gdbstub::stub::state_machine::{GdbStubStateMachine, GdbStubStateMachineInner}; diff --git a/gui/src/vmm/mod.rs b/gui/src/vmm/mod.rs index 5cd8b2496..c8848aa7f 100644 --- a/gui/src/vmm/mod.rs +++ b/gui/src/vmm/mod.rs @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 +pub use self::channel::*; + use self::cpu::CpuManager; use self::hw::{setup_devices, Device}; use self::kernel::{ Kernel, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_GNU_RELRO, PT_GNU_STACK, PT_LOAD, PT_NOTE, PT_PHDR, }; use self::ram::RamBuilder; -use crate::debug::DebugClient; +use crate::gdb::DebugClient; use crate::hv::{Hypervisor, Ram}; use crate::profile::Profile; use cpu::GdbError; @@ -28,6 +30,7 @@ use winit::event_loop::EventLoopProxy; #[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")] #[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")] mod arch; +mod channel; mod cpu; mod debug; mod hw; diff --git a/gui/ui/debug.slint b/gui/ui/debug.slint new file mode 100644 index 000000000..a85abbb2a --- /dev/null +++ b/gui/ui/debug.slint @@ -0,0 +1,19 @@ +import { VerticalBox } from "std-widgets.slint"; + +export component WaitForDebugger inherits Window { + in property address; + + title: "Obliteration"; + icon: @image-url("icon.png"); + width: 400px; + height: 50px; + + VerticalBox { + alignment: center; + + Text { + text: "Waiting for debugger at \{address}."; + horizontal-alignment: center; + } + } +} diff --git a/gui/ui/main.slint b/gui/ui/main.slint index cbb02fb33..75657e564 100644 --- a/gui/ui/main.slint +++ b/gui/ui/main.slint @@ -2,6 +2,7 @@ import { Tabs } from "main/tabs.slint"; import { Actions } from "main/actions.slint"; import { VerticalBox } from "std-widgets.slint"; +export { WaitForDebugger } from "debug.slint"; export { ErrorWindow } from "error.slint"; export { SetupWizard } from "setup.slint";