mirror of
https://github.com/obhq/obliteration.git
synced 2024-11-23 03:09:52 +00:00
Moves GDB server to GUI thread (#1022)
This commit is contained in:
parent
7a65dbb25a
commit
612ca53580
@ -1,5 +1,5 @@
|
||||
# External dependencies.
|
||||
find_package(Qt6 REQUIRED COMPONENTS Svg Widgets)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Network Svg Widgets)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(WIN32 OR (UNIX AND NOT APPLE))
|
||||
@ -64,7 +64,7 @@ endif()
|
||||
|
||||
target_compile_features(obliteration PRIVATE cxx_std_17)
|
||||
|
||||
target_link_libraries(obliteration PRIVATE Qt6::Svg Qt6::Widgets)
|
||||
target_link_libraries(obliteration PRIVATE Qt6::Network Qt6::Svg Qt6::Widgets)
|
||||
target_link_libraries(obliteration PRIVATE Threads::Threads)
|
||||
target_link_libraries(obliteration PRIVATE ${LIBGUI})
|
||||
|
||||
|
64
gui/core.h
64
gui/core.h
@ -44,6 +44,11 @@ enum VmmLog {
|
||||
VmmLog_Error,
|
||||
};
|
||||
|
||||
/**
|
||||
* Reason for [`VmmEvent::Breakpoint`].
|
||||
*/
|
||||
struct KernelStop;
|
||||
|
||||
/**
|
||||
* Contains settings to launch the kernel.
|
||||
*/
|
||||
@ -86,21 +91,15 @@ struct VmmScreen {
|
||||
*/
|
||||
enum VmmEvent_Tag {
|
||||
VmmEvent_Error,
|
||||
VmmEvent_WaitingDebugger,
|
||||
VmmEvent_DebuggerDisconnected,
|
||||
VmmEvent_Exiting,
|
||||
VmmEvent_Log,
|
||||
VmmEvent_Breakpoint,
|
||||
};
|
||||
|
||||
struct VmmEvent_Error_Body {
|
||||
const struct RustError *reason;
|
||||
};
|
||||
|
||||
struct VmmEvent_WaitingDebugger_Body {
|
||||
const char *addr;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct VmmEvent_Exiting_Body {
|
||||
bool success;
|
||||
};
|
||||
@ -111,13 +110,38 @@ struct VmmEvent_Log_Body {
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct VmmEvent_Breakpoint_Body {
|
||||
struct KernelStop *stop;
|
||||
};
|
||||
|
||||
struct VmmEvent {
|
||||
enum VmmEvent_Tag tag;
|
||||
union {
|
||||
struct VmmEvent_Error_Body error;
|
||||
struct VmmEvent_WaitingDebugger_Body waiting_debugger;
|
||||
struct VmmEvent_Exiting_Body exiting;
|
||||
struct VmmEvent_Log_Body log;
|
||||
struct VmmEvent_Breakpoint_Body breakpoint;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of [`vmm_dispatch_debug()`].
|
||||
*/
|
||||
enum DebugResult_Tag {
|
||||
DebugResult_Ok,
|
||||
DebugResult_Disconnected,
|
||||
DebugResult_ReadFailed,
|
||||
DebugResult_Error,
|
||||
};
|
||||
|
||||
struct DebugResult_Error_Body {
|
||||
struct RustError *reason;
|
||||
};
|
||||
|
||||
struct DebugResult {
|
||||
enum DebugResult_Tag tag;
|
||||
union {
|
||||
struct DebugResult_Error_Body error;
|
||||
};
|
||||
};
|
||||
|
||||
@ -183,18 +207,26 @@ struct RustError *update_firmware(const char *root,
|
||||
void *cx,
|
||||
void (*status)(const char*, uint64_t, uint64_t, void*));
|
||||
|
||||
struct Vmm *vmm_start(const char *kernel,
|
||||
const struct VmmScreen *screen,
|
||||
const struct Profile *profile,
|
||||
bool (*debug)(void*, const uint8_t*, size_t, int*),
|
||||
void (*event)(const struct VmmEvent*, void*),
|
||||
void *cx,
|
||||
struct RustError **err);
|
||||
|
||||
void vmm_free(struct Vmm *vmm);
|
||||
|
||||
struct Vmm *vmm_run(const char *kernel,
|
||||
const struct VmmScreen *screen,
|
||||
const struct Profile *profile,
|
||||
const char *debug,
|
||||
void (*event)(const struct VmmEvent*, void*),
|
||||
void *cx,
|
||||
struct RustError **err);
|
||||
|
||||
struct RustError *vmm_draw(struct Vmm *vmm);
|
||||
|
||||
struct DebugResult vmm_dispatch_debug(struct Vmm *vmm,
|
||||
struct KernelStop *stop,
|
||||
bool (*read)(uint8_t*, void*));
|
||||
|
||||
void vmm_shutdown(struct Vmm *vmm);
|
||||
|
||||
bool vmm_shutting_down(struct Vmm *vmm);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
@ -222,10 +222,8 @@ int main(int argc, char *argv[])
|
||||
win.restoreGeometry();
|
||||
|
||||
// Run main window.
|
||||
auto debug = args.value(Args::debug);
|
||||
|
||||
if (!debug.isEmpty()) {
|
||||
win.startVmm(debug);
|
||||
if (args.isSet(Args::debug)) {
|
||||
win.startDebug(args.value(Args::debug));
|
||||
}
|
||||
|
||||
return QApplication::exec();
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QHostAddress>
|
||||
#include <QIcon>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
@ -28,6 +29,8 @@
|
||||
#include <QScrollBar>
|
||||
#include <QSettings>
|
||||
#include <QStackedWidget>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QToolBar>
|
||||
#include <QUrl>
|
||||
|
||||
@ -54,7 +57,8 @@ MainWindow::MainWindow(
|
||||
m_profiles(nullptr),
|
||||
m_games(nullptr),
|
||||
m_launch(nullptr),
|
||||
m_screen(nullptr)
|
||||
m_screen(nullptr),
|
||||
m_debug(nullptr)
|
||||
{
|
||||
setWindowTitle("Obliteration");
|
||||
|
||||
@ -111,7 +115,13 @@ MainWindow::MainWindow(
|
||||
#endif
|
||||
|
||||
connect(m_launch, &LaunchSettings::saveClicked, this, &MainWindow::saveProfile);
|
||||
connect(m_launch, &LaunchSettings::startClicked, this, &MainWindow::startVmm);
|
||||
connect(m_launch, &LaunchSettings::startClicked, [this](const QString &debug) {
|
||||
if (debug.isEmpty()) {
|
||||
startVmm();
|
||||
} else {
|
||||
startDebug(debug);
|
||||
}
|
||||
});
|
||||
|
||||
m_main->addWidget(m_launch);
|
||||
|
||||
@ -224,19 +234,22 @@ void MainWindow::closeEvent(QCloseEvent *event)
|
||||
|
||||
// Ask user to confirm.
|
||||
if (m_vmm) {
|
||||
QMessageBox confirm(this);
|
||||
// Ask user to confirm only if we did not shutdown the VMM programmatically.
|
||||
if (!vmm_shutting_down(m_vmm)) {
|
||||
QMessageBox confirm(this);
|
||||
|
||||
confirm.setText("Do you want to exit?");
|
||||
confirm.setInformativeText("The running game will be terminated.");
|
||||
confirm.setStandardButtons(QMessageBox::Cancel | QMessageBox::Yes);
|
||||
confirm.setDefaultButton(QMessageBox::Cancel);
|
||||
confirm.setIcon(QMessageBox::Warning);
|
||||
confirm.setText("Do you want to exit?");
|
||||
confirm.setInformativeText("The running game will be terminated.");
|
||||
confirm.setStandardButtons(QMessageBox::Cancel | QMessageBox::Yes);
|
||||
confirm.setDefaultButton(QMessageBox::Cancel);
|
||||
confirm.setIcon(QMessageBox::Warning);
|
||||
|
||||
if (confirm.exec() != QMessageBox::Yes) {
|
||||
return;
|
||||
if (confirm.exec() != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_vmm.free();
|
||||
killVmm();
|
||||
}
|
||||
|
||||
// Close child windows.
|
||||
@ -364,7 +377,7 @@ void MainWindow::updateScreen()
|
||||
error = vmm_draw(m_vmm);
|
||||
|
||||
if (error) {
|
||||
m_vmm.free();
|
||||
killVmm();
|
||||
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
@ -379,7 +392,7 @@ void MainWindow::updateScreen()
|
||||
|
||||
void MainWindow::vmmError(const QString &msg)
|
||||
{
|
||||
m_vmm.free();
|
||||
killVmm();
|
||||
|
||||
QMessageBox::critical(this, "Error", msg);
|
||||
|
||||
@ -390,26 +403,9 @@ void MainWindow::vmmError(const QString &msg)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::waitingDebugger(const QString &addr)
|
||||
{
|
||||
if (!m_args.isSet(Args::debug)) {
|
||||
QMessageBox::information(
|
||||
this,
|
||||
"Debug",
|
||||
QString("The VMM are waiting for a debugger at %1.").arg(addr));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::debuggerDisconnected()
|
||||
{
|
||||
if (m_args.isSet(Args::debug)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::waitKernelExit(bool success)
|
||||
{
|
||||
m_vmm.free();
|
||||
killVmm();
|
||||
|
||||
if (!success) {
|
||||
QMessageBox::critical(
|
||||
@ -438,6 +434,92 @@ void MainWindow::log(VmmLog type, const QString &msg)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::breakpoint(KernelStop *stop)
|
||||
{
|
||||
// Do nothing if the previous thread already trigger the shutdown.
|
||||
if (vmm_shutting_down(m_vmm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch debug events.
|
||||
auto r = vmm_dispatch_debug(m_vmm, stop, [](uint8_t *buf, void *cx) -> bool {
|
||||
auto w = reinterpret_cast<MainWindow *>(cx);
|
||||
|
||||
while (w->m_debug->bytesAvailable() == 0) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
return w->m_debug->getChar(reinterpret_cast<char *>(buf));
|
||||
});
|
||||
|
||||
switch (r.tag) {
|
||||
case DebugResult_Ok:
|
||||
break;
|
||||
case DebugResult_Disconnected:
|
||||
// It is not safe to let the kernel running since it is assume there are a debugger.
|
||||
vmm_shutdown(m_vmm);
|
||||
break;
|
||||
case DebugResult_ReadFailed:
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("Failed to read data from the debugger: %1").arg(m_debug->errorString()));
|
||||
vmm_shutdown(m_vmm);
|
||||
break;
|
||||
case DebugResult_Error:
|
||||
{
|
||||
Rust<RustError> e(r.error.reason);
|
||||
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("Failed to dispatch debug events: %1").arg(error_message(e)));
|
||||
}
|
||||
|
||||
vmm_shutdown(m_vmm);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<QAbstractSocket::SocketError> MainWindow::sendDebug(const uint8_t *data, size_t len)
|
||||
{
|
||||
while (len) {
|
||||
auto r = m_debug->write(reinterpret_cast<const char *>(data), len);
|
||||
|
||||
if (r < 0) {
|
||||
return m_debug->error();
|
||||
}
|
||||
|
||||
data += r;
|
||||
len -= r;
|
||||
|
||||
while (m_debug->bytesToWrite() != 0) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool MainWindow::loadGame(const QString &gameId)
|
||||
{
|
||||
auto gamesDirectory = readGamesDirectorySetting();
|
||||
@ -498,7 +580,103 @@ void MainWindow::restoreGeometry()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::startVmm(const QString &debugAddr)
|
||||
void MainWindow::startDebug(const QString &addr)
|
||||
{
|
||||
// Check address format.
|
||||
auto parts = addr.split(':');
|
||||
|
||||
if (parts.size() > 2) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("%1 is not a valid debug server address").arg(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse IP address.
|
||||
QHostAddress ip(parts[0]);
|
||||
|
||||
if (ip.isNull()) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("%1 is not a valid debug server address").arg(addr));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse port.
|
||||
unsigned short port = 0;
|
||||
|
||||
if (parts.size() == 2) {
|
||||
bool ok;
|
||||
|
||||
port = parts[1].toUShort(&ok);
|
||||
|
||||
if (!ok) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("%1 is not a valid debug server address").arg(addr));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start server.
|
||||
auto server = new QTcpServer(this);
|
||||
|
||||
server->setListenBacklogSize(1);
|
||||
server->setMaxPendingConnections(1);
|
||||
|
||||
connect(
|
||||
server,
|
||||
&QTcpServer::acceptError,
|
||||
[this](QAbstractSocket::SocketError e) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("Failed to accept a debugger connection (%1).").arg(e));
|
||||
});
|
||||
|
||||
connect(
|
||||
server,
|
||||
&QTcpServer::pendingConnectionAvailable,
|
||||
this, [this, server]() {
|
||||
m_debug = server->nextPendingConnection();
|
||||
m_debug->setParent(this);
|
||||
m_debug->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
||||
|
||||
startVmm();
|
||||
|
||||
server->deleteLater();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
if (!server->listen(ip, port)) {
|
||||
auto msg = QString("Failed to start a debug server on %1: %2")
|
||||
.arg(addr)
|
||||
.arg(server->errorString());
|
||||
|
||||
QMessageBox::critical(this, "Error", msg);
|
||||
delete server;
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap launch settings with the screen now to prevent user update settings.
|
||||
m_main->setCurrentIndex(1);
|
||||
|
||||
// Tell the user to connect a debugger.
|
||||
if (!m_args.isSet(Args::debug)) {
|
||||
auto addr = server->serverAddress();
|
||||
auto port = server->serverPort();
|
||||
|
||||
QMessageBox::information(
|
||||
this,
|
||||
"Debug",
|
||||
QString("Waiting for a debugger at %1:%2.").arg(addr.toString()).arg(port));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::startVmm()
|
||||
{
|
||||
// Get full path to kernel binary.
|
||||
std::string kernel;
|
||||
@ -543,8 +721,7 @@ void MainWindow::startVmm(const QString &debugAddr)
|
||||
// Swap launch settings with the screen before getting a Vulkan surface otherwise it will fail.
|
||||
m_main->setCurrentIndex(1);
|
||||
|
||||
// Run.
|
||||
auto debug = debugAddr.toStdString();
|
||||
// Setup the screen.
|
||||
VmmScreen screen;
|
||||
Rust<RustError> error;
|
||||
Rust<Vmm> vmm;
|
||||
@ -559,27 +736,38 @@ void MainWindow::startVmm(const QString &debugAddr)
|
||||
screen.vk_surface = reinterpret_cast<size_t>(QVulkanInstance::surfaceForWindow(m_screen));
|
||||
|
||||
if (!screen.vk_surface) {
|
||||
m_main->setCurrentIndex(0);
|
||||
QMessageBox::critical(this, "Error", "Couldn't create VkSurfaceKHR.");
|
||||
|
||||
m_main->setCurrentIndex(0);
|
||||
|
||||
delete m_debug;
|
||||
m_debug = nullptr;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
vmm = vmm_run(
|
||||
// Run.
|
||||
vmm = vmm_start(
|
||||
kernel.c_str(),
|
||||
&screen,
|
||||
m_launch->currentProfile(),
|
||||
debug.empty() ? nullptr : debug.c_str(),
|
||||
m_debug
|
||||
? qOverload<void *, const uint8_t *, size_t, int *>(MainWindow::sendDebug)
|
||||
: nullptr,
|
||||
MainWindow::vmmHandler,
|
||||
this,
|
||||
&error);
|
||||
|
||||
if (!vmm) {
|
||||
m_main->setCurrentIndex(0);
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Error",
|
||||
QString("Couldn't run %1: %2").arg(kernel.c_str()).arg(error_message(error)));
|
||||
|
||||
m_main->setCurrentIndex(0);
|
||||
|
||||
delete m_debug;
|
||||
m_debug = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -602,12 +790,20 @@ bool MainWindow::requireVmmStopped()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_vmm.free();
|
||||
killVmm();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::killVmm()
|
||||
{
|
||||
m_vmm.free();
|
||||
|
||||
delete m_debug;
|
||||
m_debug = nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
|
||||
{
|
||||
// This method will be called from non-main thread.
|
||||
@ -621,19 +817,6 @@ void MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
|
||||
Qt::QueuedConnection,
|
||||
QString(error_message(ev->error.reason)));
|
||||
break;
|
||||
case VmmEvent_WaitingDebugger:
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::waitingDebugger,
|
||||
Qt::QueuedConnection,
|
||||
QString::fromUtf8(ev->waiting_debugger.addr, ev->waiting_debugger.len));
|
||||
break;
|
||||
case VmmEvent_DebuggerDisconnected:
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::debuggerDisconnected,
|
||||
Qt::QueuedConnection);
|
||||
break;
|
||||
case VmmEvent_Exiting:
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
@ -649,5 +832,26 @@ void MainWindow::vmmHandler(const VmmEvent *ev, void *cx)
|
||||
ev->log.ty,
|
||||
QString::fromUtf8(ev->log.data, ev->log.len));
|
||||
break;
|
||||
case VmmEvent_Breakpoint:
|
||||
QMetaObject::invokeMethod(
|
||||
w,
|
||||
&MainWindow::breakpoint,
|
||||
Qt::BlockingQueuedConnection,
|
||||
ev->breakpoint.stop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::sendDebug(void *cx, const uint8_t *data, size_t len, int *err)
|
||||
{
|
||||
// This method always be called from a main thread.
|
||||
auto w = reinterpret_cast<MainWindow *>(cx);
|
||||
auto r = w->sendDebug(data, len);
|
||||
|
||||
if (r) {
|
||||
*err = *r;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
#include <QAbstractSocket>
|
||||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QPointer>
|
||||
@ -9,6 +10,8 @@
|
||||
#include <QVulkanInstance>
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
|
||||
class GameListModel;
|
||||
class LaunchSettings;
|
||||
class LogsViewer;
|
||||
@ -16,6 +19,7 @@ class ProfileList;
|
||||
class QCommandLineOption;
|
||||
class QCommandLineParser;
|
||||
class QStackedWidget;
|
||||
class QTcpSocket;
|
||||
class Screen;
|
||||
|
||||
class MainWindow final : public QMainWindow {
|
||||
@ -33,10 +37,10 @@ public:
|
||||
bool loadProfiles();
|
||||
bool loadGames();
|
||||
void restoreGeometry();
|
||||
void startVmm(const QString &debugAddr);
|
||||
void startDebug(const QString &addr);
|
||||
void startVmm();
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void installPkg();
|
||||
void openSystemFolder();
|
||||
@ -47,14 +51,16 @@ private slots:
|
||||
void updateScreen();
|
||||
private:
|
||||
void vmmError(const QString &msg);
|
||||
void waitingDebugger(const QString &addr);
|
||||
void debuggerDisconnected();
|
||||
void waitKernelExit(bool success);
|
||||
void log(VmmLog type, const QString &msg);
|
||||
void breakpoint(KernelStop *stop);
|
||||
std::optional<QAbstractSocket::SocketError> sendDebug(const uint8_t *data, size_t len);
|
||||
bool loadGame(const QString &gameId);
|
||||
bool requireVmmStopped();
|
||||
void killVmm();
|
||||
|
||||
static void vmmHandler(const VmmEvent *ev, void *cx);
|
||||
static bool sendDebug(void *cx, const uint8_t *data, size_t len, int *err);
|
||||
|
||||
const QCommandLineParser &m_args;
|
||||
QStackedWidget *m_main;
|
||||
@ -63,6 +69,7 @@ private:
|
||||
LaunchSettings *m_launch;
|
||||
Screen *m_screen;
|
||||
QPointer<LogsViewer> m_logs;
|
||||
QTcpSocket *m_debug;
|
||||
Rust<Vmm> m_vmm; // Destroy first.
|
||||
};
|
||||
|
||||
|
@ -2,78 +2,49 @@
|
||||
pub use self::arch::*;
|
||||
|
||||
use gdbstub::conn::Connection;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::ffi::{c_int, c_void};
|
||||
|
||||
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
|
||||
#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
|
||||
mod arch;
|
||||
|
||||
/// Implementation of [`Connection`] using `select` system call to check if data available.
|
||||
/// Encapsulates a C++ function on Qt side to provide [`Connection`] implementation.
|
||||
pub struct Client {
|
||||
sock: TcpStream,
|
||||
buf: Vec<u8>,
|
||||
next: usize,
|
||||
fp: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut c_int) -> bool,
|
||||
cx: *mut c_void,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(sock: TcpStream) -> Self {
|
||||
Self {
|
||||
sock,
|
||||
buf: Vec::new(),
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Read.
|
||||
let mut buf = [0; 1024];
|
||||
let len = match self.sock.read(&mut buf) {
|
||||
Ok(v) => v,
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
if len == 0 {
|
||||
return Err(std::io::Error::from(ErrorKind::UnexpectedEof));
|
||||
}
|
||||
|
||||
self.buf.extend_from_slice(&buf[..len]);
|
||||
}
|
||||
|
||||
// Get byte.
|
||||
let b = self.buf[self.next];
|
||||
|
||||
self.next += 1;
|
||||
|
||||
Ok(b)
|
||||
pub fn new(
|
||||
fp: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut c_int) -> bool,
|
||||
cx: *mut c_void,
|
||||
) -> Self {
|
||||
Self { fp, cx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection for Client {
|
||||
type Error = std::io::Error;
|
||||
type Error = c_int;
|
||||
|
||||
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)
|
||||
let mut e = 0;
|
||||
|
||||
if unsafe { (self.fp)(self.cx, buf.as_ptr(), buf.len(), &mut e) } {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Write::flush(&mut self.sock)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_session_start(&mut self) -> Result<(), Self::Error> {
|
||||
self.sock.set_nodelay(true)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
57
gui/src/vmm/hw/debugger/context.rs
Normal file
57
gui/src/vmm/hw/debugger/context.rs
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
use super::Debugger;
|
||||
use crate::vmm::hv::CpuIo;
|
||||
use crate::vmm::hw::{read_u8, DeviceContext, MmioError};
|
||||
use crate::vmm::VmmEvent;
|
||||
use obconf::{DebuggerMemory, StopReason};
|
||||
use std::error::Error;
|
||||
use std::mem::offset_of;
|
||||
use std::ptr::null_mut;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Implementation of [`DeviceContext`].
|
||||
pub struct Context<'a> {
|
||||
dev: &'a Debugger,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(dev: &'a Debugger) -> Self {
|
||||
Self { dev }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DeviceContext for Context<'a> {
|
||||
fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<bool, Box<dyn Error>> {
|
||||
// Check field.
|
||||
let off = exit.addr() - self.dev.addr;
|
||||
|
||||
if off == offset_of!(DebuggerMemory, stop) {
|
||||
let stop = read_u8(exit).map_err(|e| ExecError::ReadFailed(off, e))?;
|
||||
let stop: StopReason = stop
|
||||
.try_into()
|
||||
.map_err(|_| Box::new(ExecError::InvalidStop(stop)))?;
|
||||
let stop = match stop {
|
||||
StopReason::WaitForDebugger => null_mut(),
|
||||
};
|
||||
|
||||
unsafe { self.dev.event.invoke(VmmEvent::Breakpoint { stop }) };
|
||||
} else {
|
||||
return Err(Box::new(ExecError::UnknownField(off)));
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error when [`Context::exec()`] fails.
|
||||
#[derive(Debug, Error)]
|
||||
enum ExecError {
|
||||
#[error("unknown field at offset {0:#}")]
|
||||
UnknownField(usize),
|
||||
|
||||
#[error("couldn't read data for offset {0:#}")]
|
||||
ReadFailed(usize, #[source] MmioError),
|
||||
|
||||
#[error("{0:#} is not a valid stop reason")]
|
||||
InvalidStop(u8),
|
||||
}
|
41
gui/src/vmm/hw/debugger/mod.rs
Normal file
41
gui/src/vmm/hw/debugger/mod.rs
Normal file
@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
use self::context::Context;
|
||||
use super::{Device, DeviceContext};
|
||||
use crate::vmm::hv::Hypervisor;
|
||||
use crate::vmm::VmmEventHandler;
|
||||
use obconf::DebuggerMemory;
|
||||
use std::num::NonZero;
|
||||
|
||||
mod context;
|
||||
|
||||
/// Virtual device for the kernel to communicate with the debugger.
|
||||
pub struct Debugger {
|
||||
addr: usize,
|
||||
len: NonZero<usize>,
|
||||
event: VmmEventHandler,
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
pub fn new(addr: usize, block_size: NonZero<usize>, event: VmmEventHandler) -> Self {
|
||||
let len = size_of::<DebuggerMemory>()
|
||||
.checked_next_multiple_of(block_size.get())
|
||||
.and_then(NonZero::new)
|
||||
.unwrap();
|
||||
|
||||
Self { addr, len, event }
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hypervisor> Device<H> for Debugger {
|
||||
fn addr(&self) -> usize {
|
||||
self.addr
|
||||
}
|
||||
|
||||
fn len(&self) -> NonZero<usize> {
|
||||
self.len
|
||||
}
|
||||
|
||||
fn create_context<'a>(&'a self, _: &'a H) -> Box<dyn DeviceContext + 'a> {
|
||||
Box::new(Context::new(self))
|
||||
}
|
||||
}
|
@ -8,9 +8,11 @@ use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use self::console::*;
|
||||
pub use self::debugger::*;
|
||||
pub use self::vmm::*;
|
||||
|
||||
mod console;
|
||||
mod debugger;
|
||||
mod vmm;
|
||||
|
||||
pub fn setup_devices<H: Hypervisor>(
|
||||
@ -25,10 +27,12 @@ pub fn setup_devices<H: Hypervisor>(
|
||||
|
||||
let vmm = b.push(|addr| Vmm::new(addr, block_size, event));
|
||||
let console = b.push(|addr| Console::new(addr, block_size, event));
|
||||
let debugger = b.push(|addr| Debugger::new(addr, block_size, event));
|
||||
|
||||
DeviceTree {
|
||||
vmm,
|
||||
console,
|
||||
debugger,
|
||||
map: b.map,
|
||||
}
|
||||
}
|
||||
@ -91,6 +95,7 @@ fn read_bin<'b>(
|
||||
pub struct DeviceTree<H: Hypervisor> {
|
||||
vmm: Arc<Vmm>,
|
||||
console: Arc<Console>,
|
||||
debugger: Arc<Debugger>,
|
||||
map: BTreeMap<usize, Arc<dyn Device<H>>>,
|
||||
}
|
||||
|
||||
@ -103,6 +108,10 @@ impl<H: Hypervisor> DeviceTree<H> {
|
||||
self.console.as_ref()
|
||||
}
|
||||
|
||||
pub fn debugger(&self) -> &impl Device<H> {
|
||||
self.debugger.as_ref()
|
||||
}
|
||||
|
||||
/// Returns iterator ordered by physical address.
|
||||
pub fn map(&self) -> impl Iterator<Item = (usize, &dyn Device<H>)> + '_ {
|
||||
self.map.iter().map(|(addr, dev)| (*addr, dev.as_ref()))
|
||||
|
@ -8,22 +8,19 @@ use self::ram::{Ram, RamMap};
|
||||
use self::screen::Screen;
|
||||
use crate::error::RustError;
|
||||
use crate::profile::Profile;
|
||||
use gdbstub::stub::state_machine::state::Running;
|
||||
use gdbstub::stub::state_machine::{GdbStubStateMachine, GdbStubStateMachineInner};
|
||||
use gdbstub::stub::state_machine::GdbStubStateMachine;
|
||||
use gdbstub::stub::{GdbStub, MultiThreadStopReason};
|
||||
use obconf::{BootEnv, ConsoleType, Vm};
|
||||
use std::cmp::max;
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::ffi::{c_char, c_int, c_void, CStr};
|
||||
use std::io::Read;
|
||||
use std::net::TcpListener;
|
||||
use std::num::NonZero;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread::{sleep, JoinHandle};
|
||||
use std::time::Duration;
|
||||
use std::thread::JoinHandle;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
|
||||
@ -37,29 +34,15 @@ mod ram;
|
||||
mod screen;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) {
|
||||
drop(Box::from_raw(vmm));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_run(
|
||||
pub unsafe extern "C" fn vmm_start(
|
||||
kernel: *const c_char,
|
||||
screen: *const VmmScreen,
|
||||
profile: *const Profile,
|
||||
debug: *const c_char,
|
||||
debug: Option<unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut c_int) -> bool>,
|
||||
event: unsafe extern "C" fn(*const VmmEvent, *mut c_void),
|
||||
cx: *mut c_void,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Vmm {
|
||||
// Setup debug server.
|
||||
let debug = match setup_debug_server(debug) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = e.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Check if path UTF-8.
|
||||
let path = match CStr::from_ptr(kernel).to_str() {
|
||||
Ok(v) => v,
|
||||
@ -424,6 +407,11 @@ pub unsafe extern "C" fn vmm_run(
|
||||
let env = BootEnv::Vm(Vm {
|
||||
vmm: devices.vmm().addr(),
|
||||
console: devices.console().addr(),
|
||||
debugger: if debug.is_some() {
|
||||
devices.debugger().addr()
|
||||
} else {
|
||||
0
|
||||
},
|
||||
host_page_size,
|
||||
});
|
||||
|
||||
@ -450,6 +438,29 @@ pub unsafe extern "C" fn vmm_run(
|
||||
}
|
||||
};
|
||||
|
||||
// Setup GDB stub.
|
||||
let debugger = match debug
|
||||
.map(move |fp| -> Result<Debugger, RustError> {
|
||||
let mut target = self::debug::Target::new();
|
||||
let client = self::debug::Client::new(fp, cx);
|
||||
let state = GdbStub::new(client)
|
||||
.run_state_machine(&mut target)
|
||||
.map_err(|e| RustError::with_source("couldn't setup a GDB stub", e))?;
|
||||
|
||||
Ok(Debugger {
|
||||
target,
|
||||
state: Some(state),
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = e.into_c();
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Setup arguments for main CPU.
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let args = CpuArgs {
|
||||
@ -463,7 +474,7 @@ pub unsafe extern "C" fn vmm_run(
|
||||
|
||||
// Spawn a thread to drive main CPU.
|
||||
let e_entry = file.entry();
|
||||
let main = move || main_cpu(&args, e_entry, map, debug);
|
||||
let main = move || main_cpu(&args, e_entry, map);
|
||||
let main = match std::thread::Builder::new().spawn(main) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@ -476,12 +487,19 @@ pub unsafe extern "C" fn vmm_run(
|
||||
let vmm = Vmm {
|
||||
cpus: vec![main],
|
||||
screen,
|
||||
debugger,
|
||||
shutdown,
|
||||
cx,
|
||||
};
|
||||
|
||||
Box::into_raw(vmm.into())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_free(vmm: *mut Vmm) {
|
||||
drop(Box::from_raw(vmm));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
|
||||
match (*vmm).screen.update() {
|
||||
@ -490,33 +508,68 @@ pub unsafe extern "C" fn vmm_draw(vmm: *mut Vmm) -> *mut RustError {
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `addr` cannot be null and must point to a null-terminated string.
|
||||
unsafe fn setup_debug_server(addr: *const c_char) -> Result<Option<TcpListener>, RustError> {
|
||||
// Get listen address.
|
||||
let addr = match addr.is_null() {
|
||||
true => return Ok(None),
|
||||
false => CStr::from_ptr(addr)
|
||||
.to_str()
|
||||
.map_err(|_| RustError::new("address to listen for a debugger is not UTF-8"))?,
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_dispatch_debug(
|
||||
vmm: *mut Vmm,
|
||||
stop: *mut KernelStop,
|
||||
read: unsafe extern "C" fn(*mut u8, *mut c_void) -> bool,
|
||||
) -> DebugResult {
|
||||
let vmm = &mut *vmm;
|
||||
let debug = vmm.debugger.as_mut().unwrap();
|
||||
let stop = match stop.is_null() {
|
||||
true => None,
|
||||
false => Some(Box::from_raw(stop)),
|
||||
};
|
||||
|
||||
// Setup server.
|
||||
let sock = TcpListener::bind(addr)
|
||||
.map_err(|e| RustError::with_source("couldn't listen for a debugger", e))?;
|
||||
loop {
|
||||
// Check current state.
|
||||
let s = match debug.state.take().unwrap() {
|
||||
GdbStubStateMachine::Idle(s) => s,
|
||||
GdbStubStateMachine::Running(s) => {
|
||||
debug.state = Some(s.into());
|
||||
return DebugResult::Ok;
|
||||
}
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => {
|
||||
match s.interrupt_handled(&mut debug.target, None::<MultiThreadStopReason<u64>>) {
|
||||
Ok(v) => debug.state = 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() };
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
GdbStubStateMachine::Disconnected(_) => return DebugResult::Disconnected,
|
||||
};
|
||||
|
||||
sock.set_nonblocking(true)
|
||||
.map(|_| Some(sock))
|
||||
.map_err(|e| RustError::with_source("couldn't enable non-blocking on a debug server", e))
|
||||
// Read data from the client.
|
||||
let mut b = 0;
|
||||
|
||||
if !read(&mut b, vmm.cx) {
|
||||
return DebugResult::ReadFailed;
|
||||
}
|
||||
|
||||
match s.incoming_data(&mut debug.target, b) {
|
||||
Ok(v) => debug.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() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main_cpu<H: Hypervisor>(
|
||||
args: &CpuArgs<H>,
|
||||
entry: usize,
|
||||
map: RamMap,
|
||||
debug: Option<TcpListener>,
|
||||
) {
|
||||
// Create vCPU.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutdown(vmm: *mut Vmm) {
|
||||
(*vmm).shutdown.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vmm_shutting_down(vmm: *mut Vmm) -> bool {
|
||||
(*vmm).shutdown.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn main_cpu<H: Hypervisor>(args: &CpuArgs<H>, entry: usize, map: RamMap) {
|
||||
let mut cpu = match args.hv.create_cpu(0) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@ -532,81 +585,10 @@ fn main_cpu<H: Hypervisor>(
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if debug.
|
||||
let debug = match debug {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
run_cpu(cpu, args, None);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Get server address.
|
||||
let addr = match debug.local_addr() {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => {
|
||||
let e = RustError::with_source("couldn't get debug server address", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Tell the user to connect a debugger.
|
||||
let len = addr.len();
|
||||
let addr = addr.as_ptr().cast();
|
||||
|
||||
unsafe { args.event.invoke(VmmEvent::WaitingDebugger { addr, len }) };
|
||||
|
||||
// Wait for a debugger.
|
||||
let client = loop {
|
||||
use std::io::ErrorKind;
|
||||
|
||||
if args.shutdown.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try accept a connection.
|
||||
let e = match debug.accept() {
|
||||
Ok(v) => break self::debug::Client::new(v.0),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
match e.kind() {
|
||||
ErrorKind::WouldBlock => sleep(Duration::from_millis(500)),
|
||||
ErrorKind::Interrupted => {}
|
||||
_ => {
|
||||
let e = RustError::with_source("couldn't accept a debugger connection", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Setup GDB stub.
|
||||
let mut target = self::debug::Target::new();
|
||||
let gdb = match GdbStub::new(client).run_state_machine(&mut target) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let e = RustError::with_source("couldn't setup a GDB stub", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Run GDB until the client is waiting for the target to report a stop reason.
|
||||
let gdb = match run_gdb(args, &mut target, gdb) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
run_cpu(cpu, args, Some(gdb));
|
||||
run_cpu(cpu, args);
|
||||
}
|
||||
|
||||
fn run_cpu<H: Hypervisor>(
|
||||
mut cpu: H::Cpu<'_>,
|
||||
args: &CpuArgs<H>,
|
||||
gdb: Option<GdbStubStateMachineInner<Running, self::debug::Target, self::debug::Client>>,
|
||||
) {
|
||||
fn run_cpu<H: Hypervisor>(mut cpu: H::Cpu<'_>, args: &CpuArgs<H>) {
|
||||
// Build device contexts for this CPU.
|
||||
let mut devices = args
|
||||
.devices
|
||||
@ -667,55 +649,6 @@ fn exec_io<'a>(
|
||||
dev.exec(&mut io)
|
||||
}
|
||||
|
||||
fn run_gdb<'a, H: Hypervisor>(
|
||||
args: &CpuArgs<H>,
|
||||
target: &mut self::debug::Target,
|
||||
mut state: GdbStubStateMachine<'a, self::debug::Target, self::debug::Client>,
|
||||
) -> Option<GdbStubStateMachineInner<'a, Running, self::debug::Target, self::debug::Client>> {
|
||||
loop {
|
||||
// Check current state.
|
||||
let mut s = match state {
|
||||
GdbStubStateMachine::Idle(s) => s,
|
||||
GdbStubStateMachine::Running(s) => return Some(s),
|
||||
GdbStubStateMachine::CtrlCInterrupt(s) => {
|
||||
state = match s.interrupt_handled(target, None::<MultiThreadStopReason<u64>>) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let e = RustError::with_source("couldn't handle CTRL+C from a debugger", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
GdbStubStateMachine::Disconnected(_) => {
|
||||
unsafe { args.event.invoke(VmmEvent::DebuggerDisconnected) };
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Read data from the client.
|
||||
let b = match s.borrow_conn().read() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let e = RustError::with_source("couldn't read data from a debugger", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
state = match s.incoming_data(target, b) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let e = RustError::with_source("couldn't process data from a debugger", e);
|
||||
unsafe { args.event.invoke(VmmEvent::Error { reason: &e }) };
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_page_size() -> Result<NonZero<usize>, std::io::Error> {
|
||||
let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) };
|
||||
@ -742,7 +675,9 @@ fn get_page_size() -> Result<NonZero<usize>, std::io::Error> {
|
||||
pub struct Vmm {
|
||||
cpus: Vec<JoinHandle<()>>,
|
||||
screen: self::screen::Default,
|
||||
debugger: Option<Debugger>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
cx: *mut c_void,
|
||||
}
|
||||
|
||||
impl Drop for Vmm {
|
||||
@ -792,11 +727,6 @@ pub enum VmmEvent {
|
||||
Error {
|
||||
reason: *const RustError,
|
||||
},
|
||||
WaitingDebugger {
|
||||
addr: *const c_char,
|
||||
len: usize,
|
||||
},
|
||||
DebuggerDisconnected,
|
||||
Exiting {
|
||||
success: bool,
|
||||
},
|
||||
@ -805,6 +735,9 @@ pub enum VmmEvent {
|
||||
data: *const c_char,
|
||||
len: usize,
|
||||
},
|
||||
Breakpoint {
|
||||
stop: *mut KernelStop,
|
||||
},
|
||||
}
|
||||
|
||||
/// Log category.
|
||||
@ -830,6 +763,20 @@ impl From<ConsoleType> for VmmLog {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reason for [`VmmEvent::Breakpoint`].
|
||||
#[allow(dead_code)]
|
||||
pub struct KernelStop(MultiThreadStopReason<u64>);
|
||||
|
||||
/// Result of [`vmm_dispatch_debug()`].
|
||||
#[allow(dead_code)]
|
||||
#[repr(C)]
|
||||
pub enum DebugResult {
|
||||
Ok,
|
||||
Disconnected,
|
||||
ReadFailed,
|
||||
Error { reason: *mut RustError },
|
||||
}
|
||||
|
||||
/// Encapsulates arguments for a function to run a CPU.
|
||||
struct CpuArgs<H: Hypervisor> {
|
||||
hv: H,
|
||||
@ -840,6 +787,12 @@ struct CpuArgs<H: Hypervisor> {
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// Contains data for a debugger.
|
||||
struct Debugger {
|
||||
target: self::debug::Target,
|
||||
state: Option<GdbStubStateMachine<'static, self::debug::Target, self::debug::Client>>,
|
||||
}
|
||||
|
||||
/// Represents an error when [`vmm_new()`] fails.
|
||||
#[derive(Debug, Error)]
|
||||
enum VmmError {
|
||||
|
12
kernel/src/debug/mod.rs
Normal file
12
kernel/src/debug/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use core::ptr::{addr_of_mut, write_volatile};
|
||||
use obconf::{DebuggerMemory, StopReason, Vm};
|
||||
|
||||
pub fn wait_debugger(env: &Vm) {
|
||||
let debug = env.debugger as *mut DebuggerMemory;
|
||||
|
||||
if debug.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { write_volatile(addr_of_mut!((*debug).stop), StopReason::WaitForDebugger) };
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(not(test), no_main)]
|
||||
|
||||
use crate::config::boot_env;
|
||||
use crate::context::Context;
|
||||
use crate::malloc::KernelHeap;
|
||||
use crate::proc::{ProcMgr, Thread};
|
||||
@ -16,6 +17,7 @@ mod arch;
|
||||
mod config;
|
||||
mod console;
|
||||
mod context;
|
||||
mod debug;
|
||||
mod imgfmt;
|
||||
mod lock;
|
||||
mod malloc;
|
||||
@ -66,6 +68,11 @@ unsafe extern "C" fn _start(env: &'static BootEnv, conf: &'static Config) -> ! {
|
||||
}
|
||||
|
||||
fn main(pmgr: Arc<ProcMgr>) -> ! {
|
||||
// Wait for debugger.
|
||||
match boot_env() {
|
||||
BootEnv::Vm(vm) => crate::debug::wait_debugger(vm),
|
||||
}
|
||||
|
||||
// Activate stage 2 heap.
|
||||
info!("Activating stage 2 heap.");
|
||||
|
||||
|
17
src/obconf/src/env/vm.rs
vendored
17
src/obconf/src/env/vm.rs
vendored
@ -7,6 +7,8 @@ pub struct Vm {
|
||||
pub vmm: usize,
|
||||
/// Address of [ConsoleMemory].
|
||||
pub console: usize,
|
||||
/// Address of [DebuggerMemory].
|
||||
pub debugger: usize,
|
||||
/// Page size on the host.
|
||||
pub host_page_size: NonZero<usize>,
|
||||
}
|
||||
@ -56,3 +58,18 @@ pub enum ConsoleType {
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// Layout of a memory for Memory-mapped I/O to communicate with a debugger.
|
||||
#[cfg(feature = "virt")]
|
||||
#[repr(C)]
|
||||
pub struct DebuggerMemory {
|
||||
pub stop: StopReason,
|
||||
}
|
||||
|
||||
/// Reason why the kernel stopped the execution.
|
||||
#[cfg(feature = "virt")]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
|
||||
pub enum StopReason {
|
||||
WaitForDebugger,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user