proc/debug: implement basic single-stepping facilities

This commit is contained in:
Mark Poliakov 2024-03-19 23:11:03 +02:00
parent 890204e473
commit 1b69be1664
44 changed files with 1205 additions and 101 deletions

2
Cargo.lock generated

@ -769,6 +769,8 @@ dependencies = [
"libk-mm",
"libk-util",
"log",
"serde",
"serde_json",
"yggdrasil-abi",
]

@ -3,7 +3,7 @@ use core::{arch::global_asm, cell::UnsafeCell, fmt, marker::PhantomData};
use kernel_arch_interface::{
mem::{KernelTableManager, PhysicalMemoryAllocator},
task::{StackBuilder, TaskContext, TaskFrame},
task::{StackBuilder, TaskContext, TaskFrame, UserContextInfo},
};
use libk_mm_interface::address::PhysicalAddress;
use yggdrasil_abi::{arch::SavedFrame, error::Error};
@ -19,8 +19,8 @@ pub struct ExceptionFrame {
pub elr_el1: u64,
/// SP_EL0, userspace stack pointer
pub sp_el0: u64,
_x: u64,
// ...
// MDSCR_EL1, debug control
pub mdscr_el1: u64,
}
#[repr(C, align(0x10))]
@ -52,6 +52,7 @@ impl TaskFrame for ExceptionFrame {
spsr_el1: self.spsr_el1,
elr_el1: self.elr_el1,
sp_el0: self.sp_el0,
mdscr_el1: self.mdscr_el1,
}
}
@ -60,6 +61,7 @@ impl TaskFrame for ExceptionFrame {
self.spsr_el1 = saved.spsr_el1;
self.elr_el1 = saved.elr_el1;
self.sp_el0 = saved.sp_el0;
self.mdscr_el1 = saved.mdscr_el1;
}
fn argument(&self) -> u64 {
@ -89,6 +91,14 @@ impl TaskFrame for ExceptionFrame {
fn set_user_sp(&mut self, value: usize) {
self.sp_el0 = value as _;
}
fn set_single_step(&mut self, step: bool) {
if step {
self.mdscr_el1 |= 1 << 0;
} else {
self.mdscr_el1 &= !(1 << 0);
}
}
}
impl fmt::Debug for ExceptionFrame {
@ -149,29 +159,23 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
})
}
fn user(
entry: usize,
arg: usize,
ttbr0: u64,
user_stack_sp: usize,
tpidr_el0: usize,
) -> Result<Self, Error> {
fn user(context: UserContextInfo) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 16;
let stack_base_phys = PA::allocate_contiguous_pages(USER_TASK_PAGES)?;
let stack_base = stack_base_phys.raw_virtualize::<K>();
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
stack.push(entry as _);
stack.push(arg);
stack.push(context.entry);
stack.push(context.argument);
stack.push(0);
stack.push(user_stack_sp);
stack.push(context.stack_pointer);
setup_common_context(
&mut stack,
__aarch64_task_enter_user as _,
ttbr0,
tpidr_el0 as _,
context.address_space,
context.tls as _,
);
let sp = stack.build();

@ -57,6 +57,8 @@ pub trait TaskFrame {
fn user_sp(&self) -> usize;
/// Returns the userspace instruction pointer
fn user_ip(&self) -> usize;
fn set_single_step(&mut self, step: bool);
}
/// Interface for performing context fork operations
@ -75,6 +77,15 @@ pub trait ForkFrame<K: KernelTableManager, PA: PhysicalMemoryAllocator>: Sized {
fn set_return_value(&mut self, value: u64);
}
pub struct UserContextInfo {
pub entry: usize,
pub argument: usize,
pub stack_pointer: usize,
pub tls: usize,
pub address_space: u64,
pub single_step: bool,
}
/// Platform-specific task context implementation
pub trait TaskContext<K: KernelTableManager, PA: PhysicalMemoryAllocator>: Sized {
/// Number of bytes to offset the signal stack pointer by
@ -87,13 +98,7 @@ pub trait TaskContext<K: KernelTableManager, PA: PhysicalMemoryAllocator>: Sized
/// Constructs a user thread context. The caller is responsible for allocating the userspace
/// stack and setting up a valid address space for the context.
fn user(
entry: usize,
arg: usize,
cr3: u64,
user_stack_sp: usize,
tls_address: usize,
) -> Result<Self, Error>;
fn user(context: UserContextInfo) -> Result<Self, Error>;
/// Performs an entry into a context.
///

@ -62,6 +62,9 @@
.section .text
__x86_64_task_enter_from_fork:
// TODO
jmp .
xorq %rax, %rax
xorq %rcx, %rcx
@ -85,13 +88,15 @@ __x86_64_task_enter_user:
popq %rdi
// Entry address
popq %rax
// Flags
popq %rsi
// SS:RSP
pushq $0x1B
pushq %rcx
// RFLAGS
pushq $0x200
pushq %rsi
// CS:RIP
pushq $0x23

@ -2,7 +2,7 @@ use core::{arch::global_asm, cell::UnsafeCell, marker::PhantomData};
use kernel_arch_interface::{
mem::{KernelTableManager, PhysicalMemoryAllocator},
task::{ForkFrame, StackBuilder, TaskContext, TaskFrame},
task::{ForkFrame, StackBuilder, TaskContext, TaskFrame, UserContextInfo},
};
use libk_mm_interface::address::{AsPhysicalAddress, IntoRaw, PhysicalAddress};
use yggdrasil_abi::{arch::SavedFrame, error::Error};
@ -169,6 +169,14 @@ impl TaskFrame for IrqFrame {
fn set_user_sp(&mut self, value: usize) {
self.rsp = value as _;
}
fn set_single_step(&mut self, step: bool) {
if step {
self.rflags |= 1 << 8;
} else {
self.rflags &= !(1 << 8);
}
}
}
impl TaskFrame for ExceptionFrame {
@ -226,6 +234,14 @@ impl TaskFrame for ExceptionFrame {
fn set_argument(&mut self, value: u64) {
self.rdi = value;
}
fn set_single_step(&mut self, step: bool) {
if step {
self.rflags |= 1 << 8;
} else {
self.rflags &= !(1 << 8);
}
}
}
impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddress>> ForkFrame<K, PA>
@ -317,6 +333,14 @@ impl TaskFrame for SyscallFrame {
fn set_argument(&mut self, value: u64) {
self.args[0] = value;
}
fn set_single_step(&mut self, step: bool) {
if step {
self.user_flags |= 1 << 8;
} else {
self.user_flags &= !(1 << 8);
}
}
}
impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddress>>
@ -419,11 +443,11 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
}
fn user(
entry: usize,
arg: usize,
cr3: u64,
user_stack_sp: usize,
fs_base: usize,
context: UserContextInfo, // entry: usize,
// arg: usize,
// cr3: u64,
// user_stack_sp: usize,
// fs_base: usize,
) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 8;
@ -432,11 +456,17 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
stack.push(entry as _);
stack.push(arg);
stack.push(user_stack_sp);
stack.push(0x200);
stack.push(context.entry as _);
stack.push(context.argument);
stack.push(context.stack_pointer);
setup_common_context(&mut stack, __x86_64_task_enter_user as _, cr3, fs_base);
setup_common_context(
&mut stack,
__x86_64_task_enter_user as _,
context.address_space,
context.tls,
);
let sp = stack.build();
let rsp0 = stack_base + USER_TASK_PAGES * 0x1000;

@ -157,6 +157,10 @@ mod msr_ia32_sfmask {
IF OFFSET(9) NUMBITS(1) [
Masked = 1,
Unmasked = 0
],
TF OFFSET(8) NUMBITS(1) [
Masked = 1,
Unmasked = 0
]
]
}

@ -11,7 +11,7 @@ use crate::block::{self, BlockAllocator, BlockData, BlockIndirect};
// 16.125M total
const L0_BLOCKS: usize = 32; // 128K in L0
const L1_BLOCKS: usize = 8; // 16M in L1
const L1_BLOCKS: usize = 16; // 16M in L1
/// Block vector for efficient in-memory files
pub struct BVec<'a, A: BlockAllocator> {

@ -11,7 +11,7 @@ libk-device = { path = "libk-device" }
kernel-arch = { path = "../arch" }
abi-lib = { path = "../../lib/abi-lib" }
yggdrasil-abi = { path = "../../lib/abi", features = ["alloc"] }
yggdrasil-abi = { path = "../../lib/abi", features = ["alloc", "serde"] }
device-api = { path = "../lib/device-api", features = ["derive"] }
cfg-if = "1.0.0"
@ -20,6 +20,9 @@ atomic_enum = "0.2.0"
futures-util = { version = "0.3.28", default-features = false, features = ["alloc", "async-await"] }
crossbeam-queue = { version = "0.3.8", default-features = false, features = ["alloc"] }
serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] }
serde = { version = "1.0.193", features = ["derive"], default-features = false }
[dependencies.elf]
version = "0.7.2"
git = "https://git.alnyan.me/yggdrasil/yggdrasil-elf.git"

@ -154,7 +154,7 @@ pub fn load_elf_from_file<F: Read + Seek>(
);
// Load the segments
for segment in elf.segments().into_iter().filter_map(ElfSegment::from_phdr) {
for segment in elf.segments().iter().filter_map(ElfSegment::from_phdr) {
match segment.ty {
ElfSegmentType::Load => {
load_segment(space, &file, &segment, image_load_base, vaddr_min)?;
@ -176,7 +176,11 @@ pub fn load_elf_from_file<F: Read + Seek>(
log::debug!("Entry: {:#x}", entry);
Ok(ProcessImage { entry, tls })
Ok(ProcessImage {
entry,
tls,
load_base: image_load_base,
})
}
/// Creates a new copy of the TLS from given master image
@ -225,7 +229,7 @@ fn elf_virtual_range<F: Read + Seek>(elf: &ElfStream<AnyEndian, FileReader<F>>)
for (start, end) in elf
.segments()
.into_iter()
.iter()
.filter_map(ElfSegment::from_phdr)
.filter_map(ElfSegment::into_vaddr_bounds)
{
@ -266,8 +270,7 @@ fn handle_relocations<F: Read + Seek>(
.iter()
.find(|sh| sh.sh_type == elf::abi::SHT_RELA);
if let Some(rela_section) = rela_section {
let rela_section = rela_section.clone();
if let Some(&rela_section) = rela_section {
let rela_iter = elf
.section_data_as_relas(&rela_section)
.map_err(from_parse_error)?;
@ -290,10 +293,7 @@ fn handle_tls<F: Read + Seek>(
// TODO check if it's possible to have more than one TLS segment
// Locate TLS master copy information, if any
let tls_segment = elf
.segments()
.into_iter()
.find(|s| s.p_type == elf::abi::PT_TLS);
let tls_segment = elf.segments().iter().find(|s| s.p_type == elf::abi::PT_TLS);
if let Some(tls_segment) = tls_segment {
// Can't yet handle higher align values

@ -6,7 +6,7 @@ use alloc::{
sync::{Arc, Weak},
vec::Vec,
};
use kernel_arch::task::TaskContext;
use kernel_arch::task::{TaskContext, UserContextInfo};
use libk_mm::{
pointer::PhysicalRefMut,
process::{ProcessAddressSpace, VirtualRangeBacking},
@ -156,13 +156,14 @@ fn setup_context(
let tls_address = elf::clone_tls(space, image)?;
TaskContext::user(
image.entry,
arg,
space.as_address_with_asid(),
user_sp,
tls_address,
)
TaskContext::user(UserContextInfo {
entry: image.entry,
argument: arg,
stack_pointer: user_sp,
tls: tls_address,
address_space: space.as_address_with_asid(),
single_step: false,
})
}
fn setup_binary<S: Into<String>>(

@ -0,0 +1,24 @@
use yggdrasil_abi::{debug::DebugFrame, error::Error, io::MessageDestination};
use crate::vfs::{ChannelDescriptor, MessagePayload};
pub struct ThreadDebugger {
channel: ChannelDescriptor,
}
impl ThreadDebugger {
pub fn new(channel: ChannelDescriptor) -> Self {
Self { channel }
}
pub fn send(&self, frame: &DebugFrame) -> Result<(), Error> {
let bytes = serde_json::to_vec(frame).unwrap();
self.channel
.send_message(
MessagePayload::Data(bytes.into_boxed_slice()),
MessageDestination::AllExceptSelf,
)
.unwrap();
Ok(())
}
}

@ -5,6 +5,7 @@ use libk_mm::phys::GlobalPhysicalAllocator;
pub mod runtime;
pub mod binary;
pub mod debug;
pub mod futex;
pub mod mem;
pub mod process;

@ -46,6 +46,7 @@ pub struct ProcessManager {
/// Describes information about a program's image in memory
#[derive(Clone)]
pub struct ProcessImage {
pub load_base: usize,
/// Entry point address
pub entry: usize,
/// Thread-local storage information
@ -227,6 +228,18 @@ impl Process {
self.inner.read().space.clone().unwrap()
}
pub fn image_base(&self) -> Option<usize> {
self.inner.read().image.as_ref().map(|img| img.load_base)
}
pub fn as_single_thread(&self) -> Option<Arc<Thread>> {
let inner = self.inner.read();
if inner.threads.len() != 1 {
return None;
}
Some(inner.threads[0].clone())
}
/// Returns the process group ID of the process
pub fn group_id(&self) -> ProcessGroupId {
self.inner.read().group_id

@ -17,6 +17,8 @@ use libk_util::{
sync::{spin_rwlock::IrqSafeRwLock, IrqGuard, IrqSafeSpinlock},
};
use yggdrasil_abi::{
arch::SavedFrame,
debug::DebugFrame,
error::Error,
process::{ExitCode, ProcessId, Signal, SignalEntryData},
};
@ -28,7 +30,7 @@ use crate::task::{
TaskContextImpl,
};
use super::process::Process;
use super::{debug::ThreadDebugger, process::Process};
/// Provides details about how the thread is scheduled onto CPUs
pub struct ThreadSchedulingInfo {
@ -41,6 +43,12 @@ pub struct ThreadSchedulingInfo {
pub queue: Option<&'static CpuQueue>,
}
pub struct ThreadDebuggingInfo {
pub single_step: bool,
pub debugger: Option<ThreadDebugger>,
pub saved_frame: Option<SavedFrame>,
}
struct SignalEntry {
entry: usize,
stack: usize,
@ -62,6 +70,7 @@ pub struct Thread {
pub context: Cell<TaskContextImpl>,
process: Option<ProcessId>,
space: Option<Arc<ProcessAddressSpace>>,
debug: IrqSafeSpinlock<ThreadDebuggingInfo>,
inner: IrqSafeSpinlock<ThreadInner>,
signal_queue: SegQueue<Signal>,
@ -105,6 +114,11 @@ impl Thread {
in_queue: false,
queue: None,
}),
debug: IrqSafeSpinlock::new(ThreadDebuggingInfo {
single_step: false,
debugger: None,
saved_frame: None,
}),
context: Cell::new(context),
process,
space,
@ -214,6 +228,28 @@ impl Thread {
self.enqueue();
}
// Debugging
pub fn attach_debugger(&self, debugger: ThreadDebugger) {
// TODO kick out the previous debugger
let mut debug = self.debug.lock();
debug.saved_frame = None;
debug.single_step = true;
debug.debugger = Some(debugger);
self.signal_queue.push(Signal::Debug);
}
pub fn resume(&self, single_step: bool) {
{
let mut debug = self.debug.lock();
debug.single_step = single_step;
}
self.enqueue();
}
// Scheduling
/// Changes thread state to "Ready" and inserts it into given `queue`, if it's not yet in one
@ -367,6 +403,12 @@ impl CurrentThread {
/// Terminate the current thread
pub fn exit(&self, code: ExitCode) -> ! {
// Can detach debugger now
let debug = self.debug.lock();
if let Some(debugger) = debug.debugger.as_ref() {
debugger.send(&DebugFrame::Exited).ok();
}
if let Some(process) = self.try_get_process() {
process.handle_thread_exit(self.id, code);
}
@ -403,6 +445,27 @@ impl CurrentThread {
Ok(())
}
pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) {
{
let mut debug = self.debug.lock();
// Single step cleared
if !debug.single_step {
frame.set_single_step(false);
return;
}
let frame = frame.store();
debug.saved_frame = Some(frame.clone());
// TODO handle cases of detached debugger
let debugger = debug.debugger.as_ref().unwrap();
debugger.send(&DebugFrame::Step { frame }).ok();
}
self.suspend().unwrap();
}
/// Sets up a return frame to handle a pending signal, if any is present in the task's queue.
///
/// # Safety
@ -415,6 +478,27 @@ impl CurrentThread {
}
if let Some(signal) = self.signal_queue.pop() {
if signal == Signal::Debug {
log::info!("Entered debug signal");
let mut debug = self.debug.lock();
debug.single_step = true;
let debugger = debug.debugger.as_ref().unwrap();
let process = self.process();
frame.set_single_step(true);
// Send initial frame
let saved_frame = frame.store();
debugger
.send(&DebugFrame::Startup {
image_base: process.image_base().unwrap(),
frame: saved_frame,
})
.ok();
return;
}
let inner = self.inner.lock();
let Some(entry) = inner.signal_entry.as_ref() else {

@ -122,6 +122,15 @@ impl ChannelDescriptor {
}
}
impl Clone for ChannelDescriptor {
fn clone(&self) -> Self {
let tx = self.tx.clone();
let id = tx.last_id.fetch_add(1, Ordering::SeqCst);
// TODO subcribe here?
Self { tx, id, rx: None }
}
}
impl Channel {
fn new() -> Arc<Channel> {
Arc::new(Self {

@ -186,6 +186,15 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
let result = raw_syscall_handler(func, args);
frame.r[0] = result;
}
// Software Step from lower Exception Level
0b110010 => {
let thread = Thread::current();
thread.handle_single_step(frame);
// Make the PE actually step the instruction
frame.spsr_el1 |= 1 << 21;
}
// BRK in AArch64
0b111100 => {
let thread = Thread::current();

@ -47,10 +47,11 @@ __aa\bits\()_el\el\ht\()_\kind:
mrs x0, spsr_el1
mrs x1, elr_el1
mrs x2, sp_el0
mrs x3, mdscr_el1
// TODO
stp x0, x1, [sp, #16 * 16]
stp x2, xzr, [sp, #16 * 17]
stp x2, x3, [sp, #16 * 17]
.endm
.macro EXC_RESTORE_STATE
@ -60,6 +61,7 @@ __aa\bits\()_el\el\ht\()_\kind:
msr spsr_el1, x0
msr elr_el1, x1
msr sp_el0, x2
msr mdscr_el1, x3
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]

@ -159,16 +159,23 @@ impl Entry {
static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE];
fn user_exception_inner(kind: ExceptionKind, _frame: &ExceptionFrame) {
fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) {
let thread = Thread::current();
let cr3 = CR3.get();
if kind != ExceptionKind::Debug {
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
// XXX
// frame.dump(debug::LogLevel::Warning);
warnln!("CR3 = {:#x}", cr3);
}
match kind {
ExceptionKind::Debug => {
// TODO check if the thread was really in single-step mode or has debugging related to
// the address in exception description
thread.handle_single_step(frame);
}
ExceptionKind::PageFault => {
let cr2: usize;
unsafe {

@ -66,7 +66,7 @@ pub unsafe fn init_syscall() {
// Initialize syscall vector
MSR_IA32_LSTAR.set(__x86_64_syscall_vector as u64);
MSR_IA32_SFMASK.write(MSR_IA32_SFMASK::IF::Masked);
MSR_IA32_SFMASK.write(MSR_IA32_SFMASK::IF::Masked + MSR_IA32_SFMASK::TF::Masked);
MSR_IA32_STAR.write(
// On sysret, CS = val + 16 (0x23), SS = val + 8 (0x1B)
MSR_IA32_STAR::SYSRET_CS_SS.val(0x1B - 8) +

@ -115,8 +115,8 @@ impl TtyDevice for Pl011 {
}
impl FileReadiness for Pl011 {
fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
todo!()
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.context.poll(cx)
}
}
@ -219,7 +219,7 @@ impl Device for Pl011 {
self.inner.init(IrqSafeSpinlock::new(inner));
debug::add_sink(self, LogLevel::Debug);
debug::add_sink(self, LogLevel::Info);
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
Ok(())

@ -43,6 +43,16 @@ impl TerminalRing {
self.notify.wake_all();
}
fn poll(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.notify.register(cx.waker());
if self.buffer.lock().is_readable() {
self.notify.remove(cx.waker());
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn read_blocking(&self) -> impl Future<Output = Option<u8>> + '_ {
struct F<'f> {
ring: &'f TerminalRing,
@ -281,6 +291,11 @@ impl TtyContext {
self.ring.read_blocking().await
}
/// Polls TTY for input readiness
pub fn poll(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.ring.poll(cx)
}
/// Changes the configuration of the terminal
pub fn set_config(&self, config: &TerminalOptions) -> Result<(), Error> {
self.inner.lock().config = *config;

@ -13,32 +13,21 @@ pub(crate) use abi::{
use libk::{random, task::thread::Thread};
use libk_mm::phys;
use crate::{debug::LogLevel, fs};
use crate::fs;
use super::run_with_io;
pub(super) mod sys_debug;
pub(super) mod sys_io;
pub(super) mod sys_net;
pub(super) mod sys_process;
pub(super) use sys_debug::*;
pub(super) use sys_io::*;
pub(super) use sys_net::*;
pub(super) use sys_process::*;
// Misc
pub(crate) fn debug_trace(message: &str) {
let thread = Thread::current();
let process = thread.process();
log_print_raw!(
LogLevel::Debug,
"[{}:{}] TRACE: {}\n",
process.id,
thread.id,
message
);
}
pub(crate) fn get_random(buffer: &mut [u8]) {
random::read(buffer);
}

@ -0,0 +1,31 @@
use abi::{debug::DebugOperation, error::Error, process::ProcessId};
use libk::task::{process::Process, thread::Thread};
use crate::debug::LogLevel;
pub(crate) fn debug_trace(message: &str) {
let thread = Thread::current();
let process = thread.process();
log_print_raw!(
LogLevel::Debug,
"[{}:{}] TRACE: {}\n",
process.id,
thread.id,
message
);
}
pub(crate) fn debug_control(pid: ProcessId, op: &DebugOperation) -> Result<(), Error> {
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
let target_thread = target.as_single_thread().unwrap();
match op {
&DebugOperation::Continue(single_step) => {
// TODO check if it's paused currently
target_thread.resume(single_step);
Ok(())
}
_ => todo!(),
}
}

@ -11,7 +11,7 @@ use abi::{
use alloc::sync::Arc;
use libk::{
block,
task::{process::Process, runtime, thread::Thread},
task::{debug::ThreadDebugger, process::Process, runtime, thread::Thread},
vfs::IoContext,
};
use libk_mm::{
@ -88,6 +88,17 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
let process = thread.process();
run_with_io(&process, |mut io| {
let mut attach_debugger = None;
// TODO try_find_map()
for entry in options.optional {
if let &SpawnOption::AttachDebug(fd) = entry {
let channel = io.files.file(fd)?;
let channel = channel.as_message_channel()?.clone();
attach_debugger = Some(channel);
}
}
// Setup a new process from the file
let (child_process, child_main) = proc::load_binary(
io.ioctx_mut(),
@ -147,6 +158,10 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
}
drop(child_io);
if let Some(debugger) = attach_debugger {
child_main.attach_debugger(ThreadDebugger::new(debugger));
}
child_main.enqueue();
Ok(pid as _)

@ -9,6 +9,8 @@ enum Signal(u32) {
Killed = 3,
/// Process was interrupted
Interrupted = 4,
/// Debugger attached
Debug = 5,
}
newtype ProcessId(u32);

@ -22,6 +22,9 @@ extern {
type DeviceRequest = yggdrasil_abi::io::DeviceRequest;
type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate;
type DebugOperation = yggdrasil_abi::debug::DebugOperation;
type DebugFrame = yggdrasil_abi::debug::DebugFrame;
#[thin]
type SeekFrom = yggdrasil_abi::io::SeekFrom;
#[thin]
@ -41,7 +44,6 @@ enum SocketType(u32) {
// TODO allow splitting types into separate modules
syscall debug_trace(message: &str);
syscall get_random(buffer: &mut [u8]);
syscall get_system_info(info: &mut SystemInfo) -> Result<()>;
syscall mount(opts: &MountOptions<'_>) -> Result<()>;
@ -125,3 +127,7 @@ syscall set_socket_option(sock_fd: RawFd, option: &SocketOption<'_>) -> Result<(
// C compat
syscall fork() -> Result<ProcessId>;
syscall execve(options: &ExecveOptions<'_>) -> Result<()>;
// Debugging
syscall debug_trace(message: &str);
syscall debug_control(pid: ProcessId, op: &DebugOperation) -> Result<()>;

@ -1,9 +1,12 @@
#[allow(missing_docs)]
#[derive(Clone, Debug)]
#![allow(missing_docs)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct SavedFrame {
pub gp_regs: [u64; 32],
pub spsr_el1: u64,
pub elr_el1: u64,
pub sp_el0: u64,
pub mdscr_el1: u64,
}

@ -1,6 +1,7 @@
#![allow(missing_docs)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct SavedFrame {
pub rax: u64,

34
lib/abi/src/debug.rs Normal file

@ -0,0 +1,34 @@
use crate::{arch::SavedFrame, io::RawFd};
#[derive(Debug)]
pub enum DebugOperation<'a> {
Attach(RawFd),
Detach,
Interrupt,
Continue(bool),
ReadMemory {
address: usize,
buffer: &'a mut [u8],
},
WriteMemory {
address: usize,
buffer: &'a [u8],
},
}
// TODO fill this
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub enum DebugFrame {
Startup {
image_base: usize,
frame: SavedFrame,
},
Step {
frame: SavedFrame,
},
// TODO exit status
Exited,
}

@ -34,6 +34,7 @@ pub mod error {
pub use generated::SyscallFunction;
pub mod arch;
pub mod debug;
pub mod io;
pub mod mem;
pub mod net;

@ -29,6 +29,9 @@ pub enum SpawnOption {
SetProcessGroup(ProcessGroupId),
/// Gain terminal control for the given FD
GainTerminal(RawFd),
/// Attach debugging to a channel in parent's I/O context. The process will start in
/// single-stepping mode
AttachDebug(RawFd),
}
/// Describes a single mutex operation

@ -2,6 +2,8 @@
use core::fmt;
pub use abi::debug::{DebugFrame, DebugOperation};
///
#[macro_export]
macro_rules! debug_trace {
@ -39,7 +41,10 @@ pub fn trace_raw(data: &[u8]) {
pub fn _debug_trace(a: core::fmt::Arguments<'_>) {
use fmt::Write;
let mut printer = TracePrinter { buf: [0; 512], len: 0 };
let mut printer = TracePrinter {
buf: [0; 512],
len: 0,
};
printer.write_fmt(a).ok();
if printer.len != 0 {

197
userspace/Cargo.lock generated

@ -16,12 +16,54 @@ dependencies = [
name = "abi-lib"
version = "0.1.0"
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -95,9 +137,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.2"
version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
dependencies = [
"clap_builder",
"clap_derive",
@ -118,15 +160,17 @@ version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
dependencies = [
"heck",
"proc-macro2",
@ -140,6 +184,12 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "colors"
version = "0.1.0"
@ -216,6 +266,12 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "elf"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -290,9 +346,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hostname"
@ -314,6 +370,15 @@ dependencies = [
"libm",
]
[[package]]
name = "iced-x86"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
dependencies = [
"lazy_static",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -440,7 +505,7 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -540,7 +605,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@ -628,6 +693,12 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "rangemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
[[package]]
name = "raqote"
version = "0.8.3"
@ -640,6 +711,22 @@ dependencies = [
"typed-arena",
]
[[package]]
name = "rdb"
version = "0.1.0"
dependencies = [
"clap",
"elf",
"iced-x86",
"libterm",
"rangemap",
"serde",
"serde_json",
"thiserror",
"yggdrasil-abi",
"yggdrasil-rt",
]
[[package]]
name = "red"
version = "0.1.0"
@ -772,6 +859,12 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "sw-composite"
version = "0.7.16"
@ -973,6 +1066,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
@ -1013,7 +1112,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.4",
]
[[package]]
@ -1022,13 +1130,28 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
]
[[package]]
@ -1037,42 +1160,84 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winnow"
version = "0.5.40"

@ -10,5 +10,6 @@ members = [
"lib/libcolors",
"lib/serde-ipc",
"lib/libterm",
"netutils"
"netutils",
"rdb"
]

@ -3,6 +3,7 @@
use std::{
fmt,
io::{self, stdin, stdout, Stdin, Stdout, Write},
os::fd::{AsRawFd, RawFd},
};
use self::{input::ReadChar, sys::RawMode};
@ -133,6 +134,10 @@ impl Term {
true
}
pub fn input_fd(&self) -> RawFd {
self.stdin.as_raw_fd()
}
pub fn open() -> Result<Self, Error> {
let stdin = stdin();
let mut stdout = stdout();

@ -23,7 +23,7 @@ pub unsafe fn terminal_size(stdout: &Stdout) -> io::Result<(usize, usize)> {
let mut req = DeviceRequest::GetTerminalSize(MaybeUninit::uninit());
if stdout.device_request(&mut req).is_err() {
// Fallback
return Ok((60, 20));
return Ok((80, 20));
}
let DeviceRequest::GetTerminalSize(size) = req else {
unreachable!();

19
userspace/rdb/Cargo.toml Normal file

@ -0,0 +1,19 @@
[package]
name = "rdb"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.3", features = ["derive"] }
yggdrasil-rt = { path = "../../lib/runtime" }
yggdrasil-abi = { path = "../../lib/abi", features = ["serde"] }
libterm = { path = "../lib/libterm" }
serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] }
serde = { version = "1.0.193", features = ["derive"], default-features = false }
thiserror = "1.0.58"
elf = "0.7.4"
rangemap = "1.5.1"
[target.'cfg(target_arch = "x86_64")'.dependencies]
iced-x86 = { version = "1.21.0", default-features = false, features = ["gas", "decoder", "std"] }

@ -0,0 +1,56 @@
use std::fmt::{self, Display};
use yggdrasil_abi::arch::SavedFrame;
use super::{Error, InstructionFormatter, Target};
pub struct TargetImpl;
pub struct Unsupported;
pub struct FlagFormat(u64);
impl Display for FlagFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const FLAGS: &[(u64, &str)] = &[(27, "Q"), (28, "V"), (29, "C"), (30, "Z"), (31, "N")];
write!(f, " {: <#8x} [ ", self.0)?;
for (bit, flag) in FLAGS {
if self.0 & (1 << bit) != 0 {
write!(f, "{} ", flag)?;
}
}
write!(f, "]")
}
}
impl InstructionFormatter<()> for Unsupported {
fn format_instruction(&mut self, _insn: &(), _out: &mut String) {}
}
impl Target for TargetImpl {
type Instruction = ();
type InstructionFormatter = Unsupported;
fn disassemble(
_window: &[u8],
_ip: usize,
_limit: usize,
) -> Result<Vec<(usize, Self::Instruction)>, Error> {
Ok(vec![])
}
fn new_instruction_formatter() -> Self::InstructionFormatter {
Unsupported
}
fn flags_register_as_display(frame: &SavedFrame) -> impl Display {
FlagFormat(frame.spsr_el1)
}
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, u64)>) {
for i in 0..30 {
out.push((format!("x{}", i), frame.gp_regs[i]));
}
out.push(("pc".into(), frame.elr_el1));
}
fn real_ip(frame: &SavedFrame) -> usize {
frame.elr_el1 as _
}
}

36
userspace/rdb/src/comm.rs Normal file

@ -0,0 +1,36 @@
use std::{
io,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::message_channel::{MessageChannel, MessageReceiver},
},
};
use yggdrasil_rt::debug::DebugFrame;
pub struct Comm {
channel: MessageChannel,
buffer: [u8; 1024],
}
impl Comm {
pub fn open(name: &str) -> Result<Self, io::Error> {
let channel = MessageChannel::open(name, true)?;
Ok(Self {
channel,
buffer: [0; 1024],
})
}
pub fn recv(&mut self) -> Result<DebugFrame, io::Error> {
let (_, len) = self.channel.receive_message(&mut self.buffer)?;
let message = serde_json::from_slice(&self.buffer[..len]).unwrap();
Ok(message)
}
}
impl AsRawFd for Comm {
fn as_raw_fd(&self) -> RawFd {
self.channel.as_raw_fd()
}
}

@ -0,0 +1,309 @@
use std::{
fmt::Write,
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
os::{
fd::{AsRawFd, RawFd},
yggdrasil::{io::poll::PollChannel, process::CommandExt},
},
path::Path,
process::{Child, Command},
};
use elf::{endian::AnyEndian, segment::ProgramHeader, ElfStream};
use libterm::{Color, Term, TermKey};
use rangemap::RangeMap;
use yggdrasil_rt::{debug::DebugFrame, process::ProcessId};
use crate::state::State;
use crate::InstructionFormatter;
use crate::{comm::Comm, Error, Target};
pub struct Debugger<T: Target> {
comm: Comm,
term: Term,
file: BufReader<File>,
segment_headers: RangeMap<usize, ProgramHeader>,
segments: RangeMap<usize, Vec<u8>>,
term_fd: RawFd,
comm_fd: RawFd,
poll: PollChannel,
child: Child,
child_exited: bool,
state: Option<State<T>>,
}
impl<T: Target> Debugger<T> {
pub fn from_command<P: AsRef<Path>>(image: P, mut command: Command) -> Result<Self, Error> {
let image = image.as_ref();
let segment_headers = extract_segments(image)?;
let file = BufReader::new(File::open(image)?);
let term = Term::open().unwrap();
let comm = Comm::open("rdb-1")?;
let mut poll = PollChannel::new()?;
let comm_fd = comm.as_raw_fd();
let term_fd = term.input_fd();
poll.add(comm_fd)?;
poll.add(term_fd)?;
unsafe {
command.attach_debugger(comm_fd);
}
let child = command.spawn()?;
Ok(Self {
file,
comm,
term,
segment_headers,
segments: RangeMap::new(),
term_fd,
comm_fd,
poll,
child,
child_exited: false,
state: None,
})
}
fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> {
match frame {
DebugFrame::Startup { image_base, frame } => {
let pid = unsafe { ProcessId::from_raw(self.child.id()) };
let mut state = State::new(image_base, pid);
state.update(&frame, true)?;
self.state = Some(state);
Ok(())
}
DebugFrame::Step { frame } => {
let state = self.state.as_mut().unwrap();
state.update(&frame, true)?;
Ok(())
}
DebugFrame::Exited => {
self.child_exited = true;
Ok(())
}
}
}
fn handle_key(&mut self, key: TermKey) -> Result<(), Error> {
match key {
TermKey::Char('q') => {
// TODO send exit to the child
// self.child.kill()?;
todo!();
}
TermKey::Char('s') => {
// Send resume to the debugee
if let Some(state) = self.state.as_mut() {
state.resume(true)?;
}
Ok(())
}
TermKey::Char('c') => {
if let Some(state) = self.state.as_mut() {
state.resume(false)?;
}
Ok(())
}
_ => Ok(()),
}
}
fn disassembly(&mut self, amount: usize) -> Result<Vec<(usize, T::Instruction)>, Error> {
let Some(state) = self.state.as_ref() else {
return Ok(vec![]);
};
// Find segment
let (range, segment) = match self.segments.get_key_value(&state.current_ip) {
Some(seg) => seg,
None if let Some(header) = self.segment_headers.get(&state.current_ip) => {
let start = header.p_vaddr as usize;
let end = (header.p_vaddr + header.p_memsz) as usize;
let mut buffer = vec![0; header.p_memsz as usize];
self.file.seek(SeekFrom::Start(header.p_offset))?;
self.file.read_exact(&mut buffer)?;
self.segments.insert(start..end, buffer);
self.segments.get_key_value(&state.current_ip).unwrap()
}
// Outside of any segments
None => return Ok(vec![]),
};
let offset_within_segment = state.current_ip - range.start;
T::disassemble(
&segment[offset_within_segment..offset_within_segment + amount * 8],
state.current_ip + state.image_base,
amount,
)
}
fn print_registers(&mut self, width: usize) -> Result<usize, Error> {
const REG_WIDTH: usize = 32;
let Some(state) = self.state.as_ref() else {
return Ok(0);
};
let columns = (width - 2) / REG_WIDTH;
let mut gpregs = vec![];
T::register_list(&state.last_frame, &mut gpregs);
let rows = 1 + (gpregs.len() + columns - 1) / columns;
self.term.set_cursor_position(0, 0)?;
write!(self.term, "+ Registers ").ok();
for _ in 13..width {
write!(self.term, "-").ok();
}
write!(self.term, "+").ok();
for (i, (name, reg)) in gpregs.into_iter().enumerate() {
let row = i / columns;
let col = i % columns;
self.term
.set_cursor_position(row + 1, col * REG_WIDTH + 1)?;
write!(self.term, " {:<4} = {: >#18x}", name, reg).ok();
}
self.term.set_cursor_position(rows, 0)?;
write!(
self.term,
" flags = {}",
T::flags_register_as_display(&state.last_frame)
)
.ok();
// 1 extra row for RFLAGS
let rows = rows + 1;
for i in 1..rows {
self.term.set_cursor_position(i, 0)?;
write!(self.term, "|").ok();
self.term.set_cursor_position(i, width - 1)?;
write!(self.term, "|").ok();
}
self.term.set_cursor_position(rows, 0)?;
write!(self.term, "+").ok();
for _ in 2..width {
write!(self.term, "-").ok();
}
write!(self.term, "+").ok();
Ok(rows + 1)
}
fn redraw(&mut self) -> Result<(), Error> {
let (width, height) = self.term.size()?;
self.term.clear(libterm::Clear::All)?;
// Show register block
let regs_rows = self.print_registers(width)?;
let disassembly = self.disassembly(height - regs_rows - 1)?;
if !disassembly.is_empty() {
let mut formatter = T::new_instruction_formatter();
let mut buffer = String::new();
// Show disassembly block
for (i, (ip, insn)) in disassembly.into_iter().enumerate() {
let is_current = i == 0;
self.term.set_cursor_position(i + regs_rows, 0)?;
buffer.clear();
formatter.format_instruction(&insn, &mut buffer);
if is_current {
write!(self.term, ">").ok();
self.term.set_bright(true)?;
self.term.set_foreground(Color::Black)?;
self.term.set_background(Color::Green)?;
write!(self.term, "{:016x} {}", ip, buffer).ok();
} else {
write!(self.term, " ").ok();
self.term.set_foreground(Color::Blue)?;
write!(self.term, "{:016x} ", ip).ok();
self.term.set_foreground(Color::Yellow)?;
write!(self.term, "{}", buffer).ok();
}
self.term.reset_style()?;
}
}
if self.state.is_none() {
self.term.set_foreground(Color::Yellow)?;
write!(self.term, " Waiting for inferior process").ok();
}
self.term.set_cursor_position(height - 1, 0)?;
self.term.reset_style()?;
self.term.flush()?;
Ok(())
}
pub fn run(mut self) -> Result<(), Error> {
while !self.child_exited {
self.redraw()?;
let (fd, result) = self.poll.wait(None)?.unwrap();
result?;
match fd {
_ if fd == self.comm_fd => {
let frame = self.comm.recv()?;
self.handle_frame(frame)?;
}
_ if fd == self.term_fd => {
let key = self.term.read_key()?;
self.handle_key(key)?;
}
_ => unreachable!(),
}
}
let status = self.child.wait()?;
println!("Program exited with status {:?}", status);
Ok(())
}
}
fn extract_segments<P: AsRef<Path>>(path: P) -> Result<RangeMap<usize, ProgramHeader>, Error> {
let file = BufReader::new(File::open(path)?);
let elf = ElfStream::<AnyEndian, _>::open_stream(file).unwrap();
let mut ranges = RangeMap::new();
for seg in elf.segments() {
if seg.p_type != elf::abi::PT_LOAD {
continue;
}
let start = seg.p_vaddr as usize;
let end = (seg.p_vaddr + seg.p_memsz) as usize;
ranges.insert(start..end, *seg);
}
Ok(ranges)
}

64
userspace/rdb/src/main.rs Normal file

@ -0,0 +1,64 @@
#![feature(yggdrasil_os, if_let_guard)]
use std::{fmt::Display, io, path::PathBuf, process::Command};
use clap::Parser;
use debugger::Debugger;
use imp::TargetImpl;
use yggdrasil_abi::arch::SavedFrame;
#[cfg(target_arch = "x86_64")]
#[path = "x86_64.rs"]
pub mod imp;
#[cfg(target_arch = "aarch64")]
#[path = "aarch64.rs"]
pub mod imp;
pub mod comm;
pub mod state;
pub mod debugger;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("Terminal error: {0}")]
TermError(#[from] libterm::Error),
#[error("Debug control error: {0:?}")]
DebugError(yggdrasil_rt::Error),
}
pub trait Target {
type Instruction;
type InstructionFormatter: InstructionFormatter<Self::Instruction>;
fn disassemble(
window: &[u8],
ip: usize,
limit: usize,
) -> Result<Vec<(usize, Self::Instruction)>, Error>;
fn new_instruction_formatter() -> Self::InstructionFormatter;
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, u64)>);
fn flags_register_as_display(frame: &SavedFrame) -> impl Display;
fn real_ip(frame: &SavedFrame) -> usize;
}
pub trait InstructionFormatter<I> {
fn format_instruction(&mut self, insn: &I, out: &mut String);
}
#[derive(Parser)]
struct Args {
program: PathBuf,
}
fn main() {
let args = Args::parse();
let command = Command::new(&args.program);
let debug: Debugger<TargetImpl> = Debugger::from_command(&args.program, command).unwrap();
debug.run().unwrap();
}

@ -0,0 +1,46 @@
use std::marker::PhantomData;
use yggdrasil_abi::arch::SavedFrame;
use yggdrasil_rt::{debug::DebugOperation, process::ProcessId};
use crate::{Error, Target};
pub struct State<T: Target> {
pub pid: ProcessId,
pub current_ip: usize,
pub last_frame: SavedFrame,
pub image_base: usize,
_pd: PhantomData<T>,
}
impl<T: Target> State<T> {
pub fn new(image_base: usize, pid: ProcessId) -> Self {
Self {
current_ip: 0,
last_frame: SavedFrame::default(),
pid,
image_base,
_pd: PhantomData,
}
}
pub fn resume(&mut self, step: bool) -> Result<(), Error> {
unsafe {
yggdrasil_rt::sys::debug_control(self.pid, &DebugOperation::Continue(step))
.map_err(Error::DebugError)
}
}
pub fn update(&mut self, frame: &SavedFrame, _refresh: bool) -> Result<(), Error> {
let ip = T::real_ip(frame) - self.image_base;
self.last_frame = frame.clone();
self.current_ip = ip;
Ok(())
}
}

@ -0,0 +1,93 @@
use std::fmt::{self, Display};
use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction};
use yggdrasil_abi::arch::SavedFrame;
use crate::{InstructionFormatter, Target};
pub struct RflagsDisplay(u64);
pub struct TargetImpl;
impl fmt::Display for RflagsDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const FLAGS: &[(u64, &str)] = &[
(0, "CF"),
(2, "PF"),
(4, "AF"),
(5, "ZF"),
(6, "SF"),
(9, "IF"),
(10, "DF"),
(11, "OF"),
];
write!(f, " {: <#8x} [ ", self.0)?;
for (bit, flag) in FLAGS {
if self.0 & (1 << bit) != 0 {
write!(f, "{} ", flag)?;
}
}
write!(f, "]")
}
}
impl InstructionFormatter<Instruction> for GasFormatter {
fn format_instruction(&mut self, insn: &Instruction, out: &mut String) {
self.format(insn, out)
}
}
impl Target for TargetImpl {
type Instruction = Instruction;
type InstructionFormatter = GasFormatter;
fn disassemble(
window: &[u8],
ip: usize,
limit: usize,
) -> Result<Vec<(usize, Self::Instruction)>, crate::Error> {
let mut instructions = vec![];
let mut decoder = Decoder::with_ip(64, window, ip as _, DecoderOptions::NONE);
while decoder.can_decode() && instructions.len() < limit {
let insn = decoder.decode();
instructions.push((insn.ip() as usize, insn));
}
Ok(instructions)
}
fn new_instruction_formatter() -> Self::InstructionFormatter {
let mut formatter = GasFormatter::new();
formatter.options_mut().set_uppercase_hex(false);
formatter.options_mut().set_branch_leading_zeros(false);
formatter
}
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, u64)>) {
out.push(("rax".into(), frame.rax));
out.push(("rcx".into(), frame.rcx));
out.push(("rdx".into(), frame.rdx));
out.push(("rbx".into(), frame.rbx));
out.push(("rdi".into(), frame.rdi));
out.push(("rsi".into(), frame.rsi));
out.push(("rsp".into(), frame.user_sp));
out.push(("rbp".into(), frame.rbp));
out.push(("r8".into(), frame.r8));
out.push(("r9".into(), frame.r9));
out.push(("r10".into(), frame.r10));
out.push(("r11".into(), frame.r11));
out.push(("r12".into(), frame.r12));
out.push(("r13".into(), frame.r13));
out.push(("r14".into(), frame.r14));
out.push(("r15".into(), frame.r15));
}
fn flags_register_as_display(frame: &SavedFrame) -> impl Display {
RflagsDisplay(frame.rflags)
}
fn real_ip(frame: &SavedFrame) -> usize {
frame.user_ip as _
}
}

@ -45,6 +45,8 @@ const PROGRAMS: &[(&str, &str)] = &[
("term", "bin/term"),
// red
("red", "bin/red"),
// rdb
("rdb", "bin/rdb"),
];
fn build_userspace(env: &BuildEnv, _: AllOk) -> Result<(), Error> {