mirror of
https://github.com/obhq/obliteration.git
synced 2024-11-23 11:19:56 +00:00
Watches for debugger commands (#1028)
This commit is contained in:
parent
0076693dd4
commit
c572230317
@ -265,6 +265,8 @@ struct RustError *vmm_draw(struct Vmm *vmm);
|
||||
|
||||
struct DebugResult vmm_dispatch_debug(struct Vmm *vmm, struct KernelStop *stop);
|
||||
|
||||
ptrdiff_t vmm_debug_socket(struct Vmm *vmm);
|
||||
|
||||
void vmm_shutdown(struct Vmm *vmm);
|
||||
|
||||
bool vmm_shutting_down(struct Vmm *vmm);
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QSettings>
|
||||
#include <QSocketNotifier>
|
||||
#include <QStackedWidget>
|
||||
#include <QToolBar>
|
||||
#include <QUrl>
|
||||
@ -35,6 +36,9 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
namespace Args {
|
||||
@ -55,7 +59,7 @@ MainWindow::MainWindow(
|
||||
m_games(nullptr),
|
||||
m_launch(nullptr),
|
||||
m_screen(nullptr),
|
||||
m_debug(nullptr)
|
||||
m_debugNoti(nullptr)
|
||||
{
|
||||
setWindowTitle("Obliteration");
|
||||
|
||||
@ -393,7 +397,7 @@ void MainWindow::acceptDebuggerFailed(const QString &msg)
|
||||
this,
|
||||
"Error",
|
||||
QString("Failed to accept a debugger connection: %1.").arg(msg));
|
||||
m_debug.free();
|
||||
m_debugServer.free();
|
||||
}
|
||||
|
||||
void MainWindow::vmmError(const QString &msg)
|
||||
@ -440,7 +444,48 @@ void MainWindow::log(VmmLog type, const QString &msg)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::breakpoint(KernelStop *stop)
|
||||
void MainWindow::setupDebugger()
|
||||
{
|
||||
// Setup GDB session.
|
||||
dispatchDebug(nullptr);
|
||||
|
||||
if (vmm_shutting_down(m_vmm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable non-blocking on debug socket. On Windows QSocketNotifier will do this for us.
|
||||
auto sock = vmm_debug_socket(m_vmm);
|
||||
|
||||
#ifndef _WIN32
|
||||
auto flags = fcntl(sock, F_GETFL);
|
||||
|
||||
if (flags < 0) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
"Couldn't get file flags from debug socket.");
|
||||
stopDebug();
|
||||
return;
|
||||
} else if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
"Couldn't enable non-blocking mode on debug socket.");
|
||||
stopDebug();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Watch for incoming data.
|
||||
m_debugNoti = new QSocketNotifier(QSocketNotifier::Read, this);
|
||||
m_debugNoti->setSocket(sock);
|
||||
|
||||
connect(m_debugNoti, &QSocketNotifier::activated, [this] { dispatchDebug(nullptr); });
|
||||
|
||||
m_debugNoti->setEnabled(true);
|
||||
}
|
||||
|
||||
void MainWindow::dispatchDebug(KernelStop *stop)
|
||||
{
|
||||
// Do nothing if the previous thread already trigger the shutdown.
|
||||
if (vmm_shutting_down(m_vmm)) {
|
||||
@ -471,23 +516,8 @@ void MainWindow::breakpoint(KernelStop *stop)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!vmm_shutting_down(m_vmm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't free the VMM here because the thread that trigger this method are waiting
|
||||
// for us to return.
|
||||
if (m_args.isSet(Args::debug)) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
&MainWindow::close,
|
||||
Qt::QueuedConnection);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
&MainWindow::waitKernelExit,
|
||||
Qt::QueuedConnection,
|
||||
true);
|
||||
if (vmm_shutting_down(m_vmm)) {
|
||||
stopDebug();
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,7 +586,7 @@ void MainWindow::startDebug(const QString &addr)
|
||||
// Start debug server.
|
||||
Rust<RustError> error;
|
||||
|
||||
m_debug = debug_server_start(
|
||||
m_debugServer = debug_server_start(
|
||||
this,
|
||||
addr.toStdString().c_str(),
|
||||
&error,
|
||||
@ -583,7 +613,7 @@ void MainWindow::startDebug(const QString &addr)
|
||||
}
|
||||
});
|
||||
|
||||
if (!m_debug) {
|
||||
if (!m_debugServer) {
|
||||
auto msg = QString("Failed to start a debug server on %1: %2")
|
||||
.arg(addr)
|
||||
.arg(error_message(error));
|
||||
@ -600,7 +630,7 @@ void MainWindow::startDebug(const QString &addr)
|
||||
QMessageBox::information(
|
||||
this,
|
||||
"Debug",
|
||||
QString("Waiting for a debugger at %1.").arg(debug_server_addr(m_debug)));
|
||||
QString("Waiting for a debugger at %1.").arg(debug_server_addr(m_debugServer)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -610,7 +640,7 @@ void MainWindow::startVmm(Debugger *d)
|
||||
Rust<Debugger> debug(d);
|
||||
std::string kernel;
|
||||
|
||||
m_debug.free();
|
||||
m_debugServer.free();
|
||||
|
||||
if (QFile::exists(".obliteration-development")) {
|
||||
auto b = std::filesystem::current_path();
|
||||
@ -717,9 +747,30 @@ bool MainWindow::requireVmmStopped()
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::stopDebug()
|
||||
{
|
||||
// We can't free the VMM here because the thread that trigger this method are waiting
|
||||
// for us to return.
|
||||
if (m_args.isSet(Args::debug)) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
&MainWindow::close,
|
||||
Qt::QueuedConnection);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
&MainWindow::waitKernelExit,
|
||||
Qt::QueuedConnection,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::killVmm()
|
||||
{
|
||||
m_vmm.free();
|
||||
|
||||
delete m_debugNoti;
|
||||
m_debugNoti = nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
|
||||
@ -751,11 +802,18 @@ void MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
|
||||
QString::fromUtf8(ev->log.data, ev->log.len));
|
||||
break;
|
||||
case VmmEvent_Breakpoint:
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::breakpoint,
|
||||
Qt::BlockingQueuedConnection,
|
||||
ev->breakpoint.stop);
|
||||
if (auto stop = ev->breakpoint.stop; stop) {
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::dispatchDebug,
|
||||
Qt::BlockingQueuedConnection,
|
||||
stop);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::setupDebugger,
|
||||
Qt::BlockingQueuedConnection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class LogsViewer;
|
||||
class ProfileList;
|
||||
class QCommandLineOption;
|
||||
class QCommandLineParser;
|
||||
class QSocketNotifier;
|
||||
class QStackedWidget;
|
||||
class Screen;
|
||||
|
||||
@ -50,9 +51,11 @@ private:
|
||||
void vmmError(const QString &msg);
|
||||
void waitKernelExit(bool success);
|
||||
void log(VmmLog type, const QString &msg);
|
||||
void breakpoint(KernelStop *stop);
|
||||
void setupDebugger();
|
||||
void dispatchDebug(KernelStop *stop);
|
||||
bool loadGame(const QString &gameId);
|
||||
bool requireVmmStopped();
|
||||
void stopDebug();
|
||||
void killVmm();
|
||||
|
||||
static void vmmHandler(const VmmEvent *ev, void *cx);
|
||||
@ -64,7 +67,8 @@ private:
|
||||
LaunchSettings *m_launch;
|
||||
Screen *m_screen;
|
||||
QPointer<LogsViewer> m_logs;
|
||||
Rust<DebugServer> m_debug;
|
||||
Rust<DebugServer> m_debugServer;
|
||||
QSocketNotifier *m_debugNoti;
|
||||
Rust<Vmm> m_vmm; // Destroy first.
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
use crate::error::RustError;
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::io::{Read, Write};
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@ -65,7 +65,7 @@ pub unsafe extern "C" fn debug_server_start(
|
||||
next: 0,
|
||||
})),
|
||||
},
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => {
|
||||
sleep(Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
@ -138,11 +138,19 @@ pub struct Debugger {
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
#[cfg(unix)]
|
||||
pub fn socket(&self) -> std::os::fd::RawFd {
|
||||
std::os::fd::AsRawFd::as_raw_fd(&self.sock)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn socket(&self) -> std::os::windows::io::RawSocket {
|
||||
std::os::windows::io::AsRawSocket::as_raw_socket(&self.sock)
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Result<u8, std::io::Error> {
|
||||
// Fill the buffer if needed.
|
||||
while self.next == self.buf.len() {
|
||||
use std::io::ErrorKind;
|
||||
|
||||
// Clear previous data.
|
||||
self.buf.clear();
|
||||
self.next = 0;
|
||||
@ -175,15 +183,40 @@ impl gdbstub::conn::Connection for Debugger {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn write(&mut self, byte: u8) -> Result<(), Self::Error> {
|
||||
Write::write_all(&mut self.sock, std::slice::from_ref(&byte))
|
||||
self.write_all(std::slice::from_ref(&byte))
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
||||
Write::write_all(&mut self.sock, buf)
|
||||
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
|
||||
while !buf.is_empty() {
|
||||
let written = match Write::write(&mut self.sock, buf) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if written == 0 {
|
||||
return Err(std::io::Error::from(ErrorKind::WriteZero));
|
||||
}
|
||||
|
||||
buf = &buf[written..];
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Write::flush(&mut self.sock)
|
||||
while let Err(e) = Write::flush(&mut self.sock) {
|
||||
if !matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_session_start(&mut self) -> Result<(), Self::Error> {
|
||||
|
@ -1,6 +1,63 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
pub use self::arch::*;
|
||||
|
||||
use crate::debug::Debugger;
|
||||
use crate::error::RustError;
|
||||
use gdbstub::stub::state_machine::state::{Idle, Running};
|
||||
use gdbstub::stub::state_machine::{GdbStubStateMachine, GdbStubStateMachineInner};
|
||||
use gdbstub::stub::MultiThreadStopReason;
|
||||
|
||||
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
|
||||
#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
|
||||
mod arch;
|
||||
|
||||
pub fn dispatch_idle(
|
||||
target: &mut Target,
|
||||
mut state: GdbStubStateMachineInner<'static, Idle<Target>, Target, Debugger>,
|
||||
) -> Result<GdbStubStateMachine<'static, Target, Debugger>, RustError> {
|
||||
let b = state
|
||||
.borrow_conn()
|
||||
.read()
|
||||
.map_err(|e| RustError::with_source("couldn't read data from the debugger", e))?;
|
||||
|
||||
state
|
||||
.incoming_data(target, b)
|
||||
.map_err(|e| RustError::with_source("couldn't process data from the debugger", e))
|
||||
}
|
||||
|
||||
pub fn dispatch_running(
|
||||
target: &mut Target,
|
||||
mut state: GdbStubStateMachineInner<'static, Running, Target, Debugger>,
|
||||
stop: Option<MultiThreadStopReason<u64>>,
|
||||
) -> Result<
|
||||
Result<
|
||||
GdbStubStateMachine<'static, Target, Debugger>,
|
||||
GdbStubStateMachineInner<'static, Running, Target, Debugger>,
|
||||
>,
|
||||
RustError,
|
||||
> {
|
||||
// Check If we are here because of a breakpoint.
|
||||
if let Some(r) = stop {
|
||||
return state
|
||||
.report_stop(target, r)
|
||||
.map(Ok)
|
||||
.map_err(|e| RustError::with_source("couldn't report stop reason to the debugger", e));
|
||||
}
|
||||
|
||||
// Check for pending command.
|
||||
let b = match state.borrow_conn().read() {
|
||||
Ok(v) => v,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return Ok(Err(state)),
|
||||
Err(e) => {
|
||||
return Err(RustError::with_source(
|
||||
"couldn't read data from the debugger",
|
||||
e,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
state
|
||||
.incoming_data(target, b)
|
||||
.map(Ok)
|
||||
.map_err(|e| RustError::with_source("couldn't process data from the debugger", e))
|
||||
}
|
||||
|
@ -447,16 +447,12 @@ pub unsafe extern "C" fn vmm_start(
|
||||
|
||||
// Setup GDB stub.
|
||||
let gdb = match debugger
|
||||
.map(move |client| -> Result<GdbStub, RustError> {
|
||||
.map(move |client| {
|
||||
let mut target = self::debug::Target::new();
|
||||
let state = gdbstub::stub::GdbStub::new(*client)
|
||||
.run_state_machine(&mut target)
|
||||
.map_err(|e| RustError::with_source("couldn't setup a GDB stub", e))?;
|
||||
|
||||
Ok(GdbStub {
|
||||
target,
|
||||
state: Some(state),
|
||||
})
|
||||
gdbstub::stub::GdbStub::new(*client)
|
||||
.run_state_machine(&mut target)
|
||||
.map_err(|e| RustError::with_source("couldn't setup a GDB stub", e))
|
||||
})
|
||||
.transpose()
|
||||
{
|
||||
@ -491,7 +487,7 @@ pub unsafe extern "C" fn vmm_start(
|
||||
|
||||
// Create VMM.
|
||||
let vmm = Vmm {
|
||||
cpus: vec![main],
|
||||
cpus: vec![CpuController { thread: main }],
|
||||
screen,
|
||||
gdb,
|
||||
shutdown,
|
||||
@ -515,24 +511,34 @@ pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop) -> DebugResult {
|
||||
let vmm = &mut *vmm;
|
||||
let gdb = vmm.gdb.as_mut().unwrap();
|
||||
let stop = match stop.is_null() {
|
||||
// Consume stop reason now to prevent memory leak.
|
||||
let mut stop = match stop.is_null() {
|
||||
true => None,
|
||||
false => Some(Box::from_raw(stop)),
|
||||
false => Some(Box::from_raw(stop).0),
|
||||
};
|
||||
|
||||
// Setup target object.
|
||||
let vmm = &mut *vmm;
|
||||
let mut target = self::debug::Target::new();
|
||||
|
||||
loop {
|
||||
// Check current state.
|
||||
let mut s = match gdb.state.take().unwrap() {
|
||||
GdbStubStateMachine::Idle(s) => s,
|
||||
let r = match vmm.gdb.take().unwrap() {
|
||||
GdbStubStateMachine::Idle(s) => self::debug::dispatch_idle(&mut target, s),
|
||||
GdbStubStateMachine::Running(s) => {
|
||||
gdb.state = Some(s.into());
|
||||
return DebugResult::Ok;
|
||||
match self::debug::dispatch_running(&mut target, s, stop.take()) {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(v)) => {
|
||||
// No pending data from the debugger.
|
||||
vmm.gdb = Some(v.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => {
|
||||
match s.interrupt_handled(&mut gdb.target, None::<MultiThreadStopReason<u64>>) {
|
||||
Ok(v) => gdb.state = Some(v),
|
||||
match s.interrupt_handled(&mut target, None::<MultiThreadStopReason<u64>>) {
|
||||
Ok(v) => vmm.gdb = Some(v),
|
||||
Err(e) => {
|
||||
let r = RustError::with_source("couldn't handle CTRL+C from a debugger", e);
|
||||
return DebugResult::Error { reason: r.into_c() };
|
||||
@ -543,25 +549,28 @@ pub unsafe extern "C" fn vmm_dispatch_debug(vmm: *mut Vmm, stop: *mut KernelStop
|
||||
GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected,
|
||||
};
|
||||
|
||||
// Read data from the client.
|
||||
let b = match s.borrow_conn().read() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let r = RustError::with_source("couldn't read data from a debugger", e);
|
||||
return DebugResult::Error { reason: r.into_c() };
|
||||
}
|
||||
};
|
||||
|
||||
match s.incoming_data(&mut gdb.target, b) {
|
||||
Ok(v) => gdb.state = Some(v),
|
||||
Err(e) => {
|
||||
let r = RustError::with_source("couldn't process data from a debugger", e);
|
||||
return DebugResult::Error { reason: r.into_c() };
|
||||
}
|
||||
match r {
|
||||
Ok(v) => vmm.gdb = Some(v),
|
||||
Err(e) => return DebugResult::Error { reason: e.into_c() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_debug_socket(vmm: *mut Vmm) -> isize {
|
||||
let s = match &mut (*vmm).gdb {
|
||||
Some(v) => v,
|
||||
None => return -1,
|
||||
};
|
||||
|
||||
match s {
|
||||
GdbStubStateMachine::Idle(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Running(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => s.borrow_conn().socket() as _,
|
||||
GdbStubStateMachine::Disconnected(s) => s.borrow_conn().socket() as _,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) {
|
||||
(*vmm).shutdown.store(true, Ordering::Relaxed);
|
||||
@ -676,9 +685,9 @@ fn get_page_size() -> Result<NonZero<usize>, std::io::Error> {
|
||||
|
||||
/// Manage a virtual machine that run the kernel.
|
||||
pub struct Vmm {
|
||||
cpus: Vec<JoinHandle<()>>,
|
||||
cpus: Vec<CpuController>,
|
||||
screen: self::screen::Default,
|
||||
gdb: Option<GdbStub>,
|
||||
gdb: Option<GdbStubStateMachine<'static, self::debug::Target, Debugger>>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
@ -688,7 +697,7 @@ impl Drop for Vmm {
|
||||
self.shutdown.store(true, Ordering::Relaxed);
|
||||
|
||||
for cpu in self.cpus.drain(..) {
|
||||
cpu.join().unwrap();
|
||||
cpu.thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -788,10 +797,9 @@ struct CpuArgs<H: Hypervisor> {
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// States for GDB stub.
|
||||
struct GdbStub {
|
||||
target: self::debug::Target,
|
||||
state: Option<GdbStubStateMachine<'static, self::debug::Target, Debugger>>,
|
||||
/// Contains objects to control a CPU from a GUI.
|
||||
struct CpuController {
|
||||
thread: JoinHandle<()>,
|
||||
}
|
||||
|
||||
/// Represents an error when [`vmm_new()`] fails.
|
||||
|
Loading…
Reference in New Issue
Block a user