proc/debug: implement basic single-stepping facilities
This commit is contained in:
parent
890204e473
commit
1b69be1664
2
Cargo.lock
generated
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>>(
|
||||
|
24
kernel/libk/src/task/debug.rs
Normal file
24
kernel/libk/src/task/debug.rs
Normal file
@ -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();
|
||||
|
||||
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
|
||||
// XXX
|
||||
// frame.dump(debug::LogLevel::Warning);
|
||||
warnln!("CR3 = {:#x}", cr3);
|
||||
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);
|
||||
}
|
||||
|
31
kernel/src/syscall/imp/sys_debug.rs
Normal file
31
kernel/src/syscall/imp/sys_debug.rs
Normal file
@ -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
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
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
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"] }
|
56
userspace/rdb/src/aarch64.rs
Normal file
56
userspace/rdb/src/aarch64.rs
Normal file
@ -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
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()
|
||||
}
|
||||
}
|
309
userspace/rdb/src/debugger.rs
Normal file
309
userspace/rdb/src/debugger.rs
Normal file
@ -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
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();
|
||||
}
|
46
userspace/rdb/src/state.rs
Normal file
46
userspace/rdb/src/state.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
93
userspace/rdb/src/x86_64.rs
Normal file
93
userspace/rdb/src/x86_64.rs
Normal file
@ -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> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user