arch: aarch64 fp context save, proper single-step

This commit is contained in:
Mark Poliakov 2024-10-27 06:46:25 +02:00
parent 0daf7c677c
commit 0436381b33
40 changed files with 846 additions and 171 deletions

1
Cargo.lock generated

@ -838,6 +838,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aarch64-cpu", "aarch64-cpu",
"bitflags 2.6.0", "bitflags 2.6.0",
"cc",
"device-api", "device-api",
"kernel-arch-interface", "kernel-arch-interface",
"libk-mm-interface", "libk-mm-interface",

@ -6,7 +6,7 @@
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
"max-atomic-width": 128, "max-atomic-width": 128,
"target-pointer-width": "64", "target-pointer-width": "64",
"features": "+v8a,+strict-align,+neon,+fp-armv8", "features": "+v8a,+strict-align,-neon,-fp-armv8",
"disable-redzone": true, "disable-redzone": true,
"executables": true, "executables": true,

@ -14,3 +14,6 @@ bitflags = "2.6.0"
static_assertions = "1.1.0" static_assertions = "1.1.0"
aarch64-cpu = "9.4.0" aarch64-cpu = "9.4.0"
tock-registers = "0.8.1" tock-registers = "0.8.1"
[build-dependencies]
cc = "1.0"

@ -0,0 +1,21 @@
use std::env;
fn build_fp_context_obj() {
const FP_CONTEXT_S: &str = "src/fp_context.S";
let out_dir = env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed={}", FP_CONTEXT_S);
cc::Build::new()
.out_dir(&out_dir)
.compiler("clang")
.target("aarch64-unknown-none")
.flag("-march=armv8-a+fp")
.file(FP_CONTEXT_S)
.compile("fp_context");
}
fn main() {
build_fp_context_obj();
}

@ -52,9 +52,10 @@ __aarch64_task_enter_kernel:
eret eret
__aarch64_task_enter_user: __aarch64_task_enter_user:
// x0 == sp, x1 == ignored // x0 == sp, x1 == mdscr_el1
ldp x0, x1, [sp, #16 * 0] ldp x0, x1, [sp, #16 * 0]
msr sp_el0, x0 msr sp_el0, x0
msr mdscr_el1, x1
# EL0t, IRQs unmasked # EL0t, IRQs unmasked
msr spsr_el1, xzr msr spsr_el1, xzr

@ -1,5 +1,5 @@
//! AArch64-specific task context implementation //! AArch64-specific task context implementation
use core::{arch::global_asm, cell::UnsafeCell, fmt, marker::PhantomData}; use core::{arch::global_asm, cell::UnsafeCell, ffi::c_void, fmt, marker::PhantomData};
use kernel_arch_interface::{ use kernel_arch_interface::{
mem::{KernelTableManager, PhysicalMemoryAllocator}, mem::{KernelTableManager, PhysicalMemoryAllocator},
@ -29,6 +29,12 @@ struct TaskContextInner {
sp: usize, sp: usize,
} }
#[repr(align(0x10))]
pub struct FpContext {
// q0..q31 x 128bit + fpcr/fpsr
inner: [u8; 16 * 32 + 16],
}
/// AArch64 implementation of a task context /// AArch64 implementation of a task context
#[allow(unused)] #[allow(unused)]
pub struct TaskContextImpl< pub struct TaskContextImpl<
@ -36,6 +42,7 @@ pub struct TaskContextImpl<
PA: PhysicalMemoryAllocator<Address = PhysicalAddress>, PA: PhysicalMemoryAllocator<Address = PhysicalAddress>,
> { > {
inner: UnsafeCell<TaskContextInner>, inner: UnsafeCell<TaskContextInner>,
fp_context: UnsafeCell<FpContext>,
stack_base_phys: PhysicalAddress, stack_base_phys: PhysicalAddress,
stack_size: usize, stack_size: usize,
@ -45,6 +52,22 @@ pub struct TaskContextImpl<
const COMMON_CONTEXT_SIZE: usize = 8 * 14; const COMMON_CONTEXT_SIZE: usize = 8 * 14;
impl FpContext {
pub const fn new() -> Self {
Self {
inner: [0; 16 * 32 + 16],
}
}
pub unsafe fn store(this: *mut Self) {
__aarch64_fp_store_context(this as _)
}
pub unsafe fn restore(this: *const Self) {
__aarch64_fp_restore_context(this as _)
}
}
impl TaskFrame for ExceptionFrame { impl TaskFrame for ExceptionFrame {
fn store(&self) -> SavedFrame { fn store(&self) -> SavedFrame {
SavedFrame { SavedFrame {
@ -151,6 +174,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
Ok(Self { Ok(Self {
inner: UnsafeCell::new(TaskContextInner { sp }), inner: UnsafeCell::new(TaskContextInner { sp }),
fp_context: UnsafeCell::new(FpContext::new()),
stack_base_phys, stack_base_phys,
stack_size: KERNEL_TASK_PAGES * 0x1000, stack_size: KERNEL_TASK_PAGES * 0x1000,
@ -166,9 +190,11 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000); let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
let mdscr_el1 = if context.single_step { 1 << 0 } else { 0 };
stack.push(context.entry); stack.push(context.entry);
stack.push(context.argument); stack.push(context.argument);
stack.push(0); stack.push(mdscr_el1);
stack.push(context.stack_pointer); stack.push(context.stack_pointer);
setup_common_context( setup_common_context(
@ -182,6 +208,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
Ok(Self { Ok(Self {
inner: UnsafeCell::new(TaskContextInner { sp }), inner: UnsafeCell::new(TaskContextInner { sp }),
fp_context: UnsafeCell::new(FpContext::new()),
stack_base_phys, stack_base_phys,
stack_size: USER_TASK_PAGES * 0x1000, stack_size: USER_TASK_PAGES * 0x1000,
@ -191,14 +218,28 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
} }
unsafe fn enter(&self) -> ! { unsafe fn enter(&self) -> ! {
FpContext::restore(self.fp_context.get());
__aarch64_enter_task(self.inner.get()) __aarch64_enter_task(self.inner.get())
} }
unsafe fn switch(&self, from: &Self) { unsafe fn switch(&self, from: &Self) {
__aarch64_switch_task(self.inner.get(), from.inner.get()) let dst = self.inner.get();
let src = from.inner.get();
if dst != src {
// Save the old context
FpContext::store(from.fp_context.get());
// Load next context
FpContext::restore(self.fp_context.get());
__aarch64_switch_task(self.inner.get(), from.inner.get())
}
} }
unsafe fn switch_and_drop(&self, thread: *const ()) { unsafe fn switch_and_drop(&self, thread: *const ()) {
FpContext::restore(self.fp_context.get());
__aarch64_switch_task_and_drop(self.inner.get(), thread); __aarch64_switch_task_and_drop(self.inner.get(), thread);
} }
} }
@ -241,6 +282,8 @@ extern "C" {
fn __aarch64_switch_task_and_drop(to: *mut TaskContextInner, thread: *const ()) -> !; fn __aarch64_switch_task_and_drop(to: *mut TaskContextInner, thread: *const ()) -> !;
fn __aarch64_task_enter_kernel(); fn __aarch64_task_enter_kernel();
fn __aarch64_task_enter_user(); fn __aarch64_task_enter_user();
fn __aarch64_fp_store_context(to: *mut c_void);
fn __aarch64_fp_restore_context(from: *const c_void);
} }
global_asm!(include_str!("context.S"), context_size = const COMMON_CONTEXT_SIZE); global_asm!(include_str!("context.S"), context_size = const COMMON_CONTEXT_SIZE);

@ -0,0 +1,52 @@
.section .text
.global __aarch64_fp_store_context
.global __aarch64_fp_restore_context
__aarch64_fp_store_context:
// x0 - destination
stp q0, q1, [x0, #16 * 0 ]
stp q2, q3, [x0, #16 * 2 ]
stp q4, q5, [x0, #16 * 4 ]
stp q6, q7, [x0, #16 * 6 ]
stp q8, q9, [x0, #16 * 8 ]
stp q10, q11, [x0, #16 * 10]
stp q12, q13, [x0, #16 * 12]
stp q14, q15, [x0, #16 * 14]
stp q16, q17, [x0, #16 * 16]
stp q18, q19, [x0, #16 * 18]
stp q20, q21, [x0, #16 * 20]
stp q22, q23, [x0, #16 * 22]
stp q24, q25, [x0, #16 * 24]
stp q26, q27, [x0, #16 * 26]
stp q28, q29, [x0, #16 * 28]
stp q30, q31, [x0, #16 * 30]!
mrs x1, fpsr
str x1, [x0, #16 * 2]
mrs x1, fpcr
str x1, [x0, #16 * 2 + 8]
ret
__aarch64_fp_restore_context:
// x0 - source
ldp q0, q1, [x0, #16 * 0 ]
ldp q2, q3, [x0, #16 * 2 ]
ldp q4, q5, [x0, #16 * 4 ]
ldp q6, q7, [x0, #16 * 6 ]
ldp q8, q9, [x0, #16 * 8 ]
ldp q10, q11, [x0, #16 * 10]
ldp q12, q13, [x0, #16 * 12]
ldp q14, q15, [x0, #16 * 14]
ldp q16, q17, [x0, #16 * 16]
ldp q18, q19, [x0, #16 * 18]
ldp q20, q21, [x0, #16 * 20]
ldp q22, q23, [x0, #16 * 22]
ldp q24, q25, [x0, #16 * 24]
ldp q26, q27, [x0, #16 * 26]
ldp q28, q29, [x0, #16 * 28]
ldp q30, q31, [x0, #16 * 30]!
ldp x0, x1, [x0, #16 * 2]
msr fpsr, x0
msr fpcr, x1
ret

@ -50,6 +50,9 @@ impl ArchitectureImpl {
impl Architecture for ArchitectureImpl { impl Architecture for ArchitectureImpl {
type PerCpuData = PerCpuData; type PerCpuData = PerCpuData;
type CpuFeatures = (); type CpuFeatures = ();
type BreakpointType = u32;
const BREAKPOINT_VALUE: Self::BreakpointType = 0x200020D4;
fn cpu_index<S: Scheduler + 'static>() -> u32 { fn cpu_index<S: Scheduler + 'static>() -> u32 {
(MPIDR_EL1.get() & 0xFF) as u32 (MPIDR_EL1.get() & 0xFF) as u32

@ -101,8 +101,13 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let stack_base = stack_base_phys.raw_virtualize::<K>(); let stack_base = stack_base_phys.raw_virtualize::<K>();
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000); let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
let mut flags = 0x200;
stack.push(0x200); if context.single_step {
flags |= 1 << 8;
}
stack.push(flags);
stack.push(context.entry as _); stack.push(context.entry as _);
stack.push(context.stack_pointer); stack.push(context.stack_pointer);
@ -372,7 +377,7 @@ impl TaskFrame for ExceptionFrame {
} }
fn user_ip(&self) -> usize { fn user_ip(&self) -> usize {
todo!() self.eip as _
} }
fn argument(&self) -> u64 { fn argument(&self) -> u64 {

@ -48,6 +48,9 @@ extern "C" fn idle_task(_: usize) -> ! {
impl Architecture for ArchitectureImpl { impl Architecture for ArchitectureImpl {
type PerCpuData = PerCpuData; type PerCpuData = PerCpuData;
type CpuFeatures = CpuFeatures; type CpuFeatures = CpuFeatures;
type BreakpointType = u8;
const BREAKPOINT_VALUE: Self::BreakpointType = 0xCC;
unsafe fn init_local_cpu<S: Scheduler + 'static>(id: Option<u32>, data: Self::PerCpuData) { unsafe fn init_local_cpu<S: Scheduler + 'static>(id: Option<u32>, data: Self::PerCpuData) {
use alloc::boxed::Box; use alloc::boxed::Box;

@ -26,6 +26,9 @@ pub trait Architecture: Sized {
type PerCpuData; type PerCpuData;
type CpuFeatures: CpuFeatureSet; type CpuFeatures: CpuFeatureSet;
type BreakpointType;
const BREAKPOINT_VALUE: Self::BreakpointType;
// Cpu management // Cpu management
/// # Safety /// # Safety

@ -443,13 +443,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
}) })
} }
fn user( fn user(context: UserContextInfo) -> Result<Self, Error> {
context: UserContextInfo, // entry: usize,
// arg: usize,
// cr3: u64,
// user_stack_sp: usize,
// fs_base: usize,
) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 8; const USER_TASK_PAGES: usize = 8;
let stack_base_phys = PA::allocate_contiguous_pages(USER_TASK_PAGES)?; let stack_base_phys = PA::allocate_contiguous_pages(USER_TASK_PAGES)?;
@ -457,7 +451,12 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000); let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
stack.push(0x200); let mut flags = 0x200;
if context.single_step {
flags |= 1 << 8;
}
stack.push(flags);
stack.push(context.entry as _); stack.push(context.entry as _);
stack.push(context.argument); stack.push(context.argument);
stack.push(context.stack_pointer); stack.push(context.stack_pointer);

@ -89,6 +89,9 @@ impl ArchitectureImpl {
impl Architecture for ArchitectureImpl { impl Architecture for ArchitectureImpl {
type PerCpuData = PerCpuData; type PerCpuData = PerCpuData;
type CpuFeatures = CpuFeatures; type CpuFeatures = CpuFeatures;
type BreakpointType = u8;
const BREAKPOINT_VALUE: Self::BreakpointType = 0xCC;
unsafe fn set_local_cpu(cpu: *mut ()) { unsafe fn set_local_cpu(cpu: *mut ()) {
MSR_IA32_KERNEL_GS_BASE.set(cpu as u64); MSR_IA32_KERNEL_GS_BASE.set(cpu as u64);

@ -37,6 +37,15 @@ pub mod elf;
pub type LoadedProcess = (Arc<Process>, Arc<Thread>); pub type LoadedProcess = (Arc<Process>, Arc<Thread>);
pub struct LoadOptions<'e, P: AsRef<Path>> {
pub parent: Option<Weak<Process>>,
pub group_id: ProcessGroupId,
pub path: P,
pub args: &'e [&'e str],
pub envs: &'e [&'e str],
pub single_step: bool,
}
struct BufferPlacer<'a> { struct BufferPlacer<'a> {
buffer: &'a mut [u8], buffer: &'a mut [u8],
virtual_offset: usize, virtual_offset: usize,
@ -122,12 +131,16 @@ fn setup_program_env(
Ok(in_user as *const _ as usize) Ok(in_user as *const _ as usize)
} }
fn setup_context( fn setup_context<P>(
options: &LoadOptions<P>,
space: &ProcessAddressSpace, space: &ProcessAddressSpace,
image: &ProcessImage, image: &ProcessImage,
args: &Vec<String>, args: &Vec<String>,
envs: &Vec<String>, envs: &Vec<String>,
) -> Result<TaskContextImpl, Error> { ) -> Result<TaskContextImpl, Error>
where
P: AsRef<Path>,
{
const USER_STACK_PAGES: usize = 32; const USER_STACK_PAGES: usize = 32;
let virt_stack_base = 0x3000000; let virt_stack_base = 0x3000000;
@ -174,14 +187,13 @@ fn setup_context(
stack_pointer: ptr.addr(), stack_pointer: ptr.addr(),
tls: tls_address, tls: tls_address,
address_space: space.as_address_with_asid(), address_space: space.as_address_with_asid(),
single_step: false, single_step: options.single_step,
}) })
} }
fn setup_binary<S>( fn setup_binary<S, P>(
options: &LoadOptions<P>,
name: S, name: S,
group_id: ProcessGroupId,
parent: Option<Weak<Process>>,
space: ProcessAddressSpace, space: ProcessAddressSpace,
image: ProcessImage, image: ProcessImage,
args: &Vec<String>, args: &Vec<String>,
@ -189,12 +201,13 @@ fn setup_binary<S>(
) -> Result<LoadedProcess, Error> ) -> Result<LoadedProcess, Error>
where where
S: Into<String>, S: Into<String>,
P: AsRef<Path>,
{ {
let context = setup_context(&space, &image, args, envs)?; let context = setup_context(options, &space, &image, args, envs)?;
let (process, main) = Process::new_with_main( let (process, main) = Process::new_with_main(
name, name,
group_id, options.group_id,
parent, options.parent.clone(),
Arc::new(space), Arc::new(space),
context, context,
Some(image), Some(image),
@ -263,24 +276,16 @@ fn xxx_load_program<P: AsRef<Path>>(
} else { } else {
Err(Error::UnrecognizedExecutable) Err(Error::UnrecognizedExecutable)
} }
// let image = load_binary(head, file, space)?;
// Ok((image, args, envs))
} }
/// Loads a program from given `path` /// Loads a program from given `path`
pub fn load<P: AsRef<Path>>( pub fn load<P: AsRef<Path>>(
ioctx: &mut IoContext, ioctx: &mut IoContext,
group_id: ProcessGroupId, options: &LoadOptions<P>,
parent: Option<Weak<Process>>,
path: P,
args: &[&str],
envs: &[&str],
) -> Result<LoadedProcess, Error> { ) -> Result<LoadedProcess, Error> {
let path = path.as_ref(); let path = options.path.as_ref();
let args = args.iter().map(|&s| s.to_owned()).collect(); let args = options.args.iter().map(|&s| s.to_owned()).collect();
let envs = envs.iter().map(|&s| s.to_owned()).collect(); let envs = options.envs.iter().map(|&s| s.to_owned()).collect();
let space = ProcessAddressSpace::new()?; let space = ProcessAddressSpace::new()?;
let (image, args, envs) = xxx_load_program(&space, ioctx, path, args, envs)?; let (image, args, envs) = xxx_load_program(&space, ioctx, path, args, envs)?;
@ -289,22 +294,22 @@ pub fn load<P: AsRef<Path>>(
Some((_, name)) => name, Some((_, name)) => name,
None => name, None => name,
}; };
setup_binary(name, group_id, parent, space, image, &args, &envs) setup_binary(options, name, space, image, &args, &envs)
} }
pub fn load_into<P: AsRef<Path>>( pub fn load_into<P: AsRef<Path>>(
process: &Process, options: &LoadOptions<P>,
path: P,
args: Vec<String>, args: Vec<String>,
envs: Vec<String>, envs: Vec<String>,
) -> Result<(TaskContextImpl, ProcessImage), Error> { ) -> Result<(TaskContextImpl, ProcessImage), Error> {
let process = options.parent.as_ref().unwrap().upgrade().unwrap();
let mut io = process.io.lock(); let mut io = process.io.lock();
// Have to make the Path owned, going to drop the address space from which it came // Have to make the Path owned, going to drop the address space from which it came
let path = path.as_ref().to_owned(); let path = options.path.as_ref().to_owned();
let space = process.space(); let space = process.space();
space.clear()?; space.clear()?;
let (image, args, envs) = xxx_load_program(&space, io.ioctx_mut(), &path, args, envs)?; let (image, args, envs) = xxx_load_program(&space, io.ioctx_mut(), &path, args, envs)?;
let context = setup_context(&space, &image, &args, &envs)?; let context = setup_context(options, &space, &image, &args, &envs)?;
Ok((context, image)) Ok((context, image))
} }

@ -27,6 +27,17 @@ pub trait ForeignPointer: Sized {
/// As this function allows direct memory writes, it is inherently unsafe. /// As this function allows direct memory writes, it is inherently unsafe.
unsafe fn write_foreign_volatile(self: *mut Self, space: &ProcessAddressSpace, value: Self); unsafe fn write_foreign_volatile(self: *mut Self, space: &ProcessAddressSpace, value: Self);
unsafe fn try_write_foreign_volatile(
self: *mut Self,
space: &ProcessAddressSpace,
value: Self,
) -> Result<(), Error>;
unsafe fn try_read_foreign_volatile(
self: *const Self,
space: &ProcessAddressSpace,
) -> Result<Self, Error>;
/// Performs pointer validation for given address space: /// Performs pointer validation for given address space:
/// ///
/// * Checks if the pointer has proper alignment for the type. /// * Checks if the pointer has proper alignment for the type.
@ -80,6 +91,15 @@ pub trait ForeignPointer: Sized {
impl<T> ForeignPointer for T { impl<T> ForeignPointer for T {
unsafe fn write_foreign_volatile(self: *mut Self, space: &ProcessAddressSpace, value: T) { unsafe fn write_foreign_volatile(self: *mut Self, space: &ProcessAddressSpace, value: T) {
self.try_write_foreign_volatile(space, value)
.expect("Invalid foreign pointer, could not write")
}
unsafe fn try_write_foreign_volatile(
self: *mut Self,
space: &ProcessAddressSpace,
value: Self,
) -> Result<(), Error> {
// TODO check align // TODO check align
let addr = self as usize; let addr = self as usize;
let start_page = addr & !0xFFF; let start_page = addr & !0xFFF;
@ -90,12 +110,30 @@ impl<T> ForeignPointer for T {
todo!("Foreign pointer write crossed a page boundary"); todo!("Foreign pointer write crossed a page boundary");
} }
let phys_page = space let phys_page = space.translate(start_page)?;
.translate(start_page)
.expect("Address is not mapped in the target address space");
let virt_ptr = phys_page.add(page_offset).virtualize() as *mut T; let virt_ptr = phys_page.add(page_offset).virtualize() as *mut T;
virt_ptr.write_volatile(value); virt_ptr.write_unaligned(value);
Ok(())
}
unsafe fn try_read_foreign_volatile(
self: *const Self,
space: &ProcessAddressSpace,
) -> Result<Self, Error> {
let addr = self as usize;
let start_page = addr & !0xFFF;
let end_page = (addr + size_of::<T>() - 1) & !0xFFF;
let page_offset = addr & 0xFFF;
if start_page != end_page {
todo!("Foreign pointer write crossed a page boundary");
}
let phys_page = space.translate(start_page)?;
let virt_ptr = phys_page.add(page_offset).virtualize() as *const Self;
Ok(virt_ptr.read_unaligned())
} }
unsafe fn validate_user_slice_mut<'a>( unsafe fn validate_user_slice_mut<'a>(

@ -1,7 +1,7 @@
use core::{cell::Cell, mem::size_of, ops::Deref}; use core::{cell::Cell, mem::size_of, ops::Deref};
use alloc::{ use alloc::{
collections::BTreeMap, collections::{btree_map, BTreeMap, BTreeSet},
string::String, string::String,
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
@ -9,7 +9,7 @@ use crossbeam_queue::SegQueue;
use futures_util::task::ArcWake; use futures_util::task::ArcWake;
use kernel_arch::{ use kernel_arch::{
task::{Scheduler, TaskContext, TaskFrame}, task::{Scheduler, TaskContext, TaskFrame},
CpuImpl, Architecture, ArchitectureImpl, CpuImpl,
}; };
use libk_mm::process::ProcessAddressSpace; use libk_mm::process::ProcessAddressSpace;
use libk_util::{ use libk_util::{
@ -24,7 +24,7 @@ use yggdrasil_abi::{
}; };
use crate::task::{ use crate::task::{
mem::ForeignPointer, mem::{self, ForeignPointer},
sched::CpuQueue, sched::CpuQueue,
types::{ThreadAffinity, ThreadId, ThreadState}, types::{ThreadAffinity, ThreadId, ThreadState},
TaskContextImpl, TaskContextImpl,
@ -32,6 +32,9 @@ use crate::task::{
use super::{debug::ThreadDebugger, process::Process}; use super::{debug::ThreadDebugger, process::Process};
type BreakpointType = <ArchitectureImpl as Architecture>::BreakpointType;
const BREAKPOINT_VALUE: BreakpointType = ArchitectureImpl::BREAKPOINT_VALUE;
/// Provides details about how the thread is scheduled onto CPUs /// Provides details about how the thread is scheduled onto CPUs
pub struct ThreadSchedulingInfo { pub struct ThreadSchedulingInfo {
/// Current state /// Current state
@ -45,8 +48,10 @@ pub struct ThreadSchedulingInfo {
pub struct ThreadDebuggingInfo { pub struct ThreadDebuggingInfo {
pub single_step: bool, pub single_step: bool,
pub restore_breakpoint: Option<usize>,
pub debugger: Option<ThreadDebugger>, pub debugger: Option<ThreadDebugger>,
pub saved_frame: Option<SavedFrame>, pub saved_frame: Option<SavedFrame>,
pub breakpoints: BTreeMap<usize, BreakpointType>,
} }
struct SignalEntry { struct SignalEntry {
@ -115,10 +120,13 @@ impl Thread {
in_queue: false, in_queue: false,
queue: None, queue: None,
}), }),
// TODO lazy initialization for debugging info
debug: IrqSafeSpinlock::new(ThreadDebuggingInfo { debug: IrqSafeSpinlock::new(ThreadDebuggingInfo {
single_step: false, single_step: false,
restore_breakpoint: None,
debugger: None, debugger: None,
saved_frame: None, saved_frame: None,
breakpoints: BTreeMap::new(),
}), }),
context: Cell::new(context), context: Cell::new(context),
process, process,
@ -243,6 +251,46 @@ impl Thread {
debug.debugger = Some(debugger); debug.debugger = Some(debugger);
self.signal_queue.push(Signal::Debug); self.signal_queue.push(Signal::Debug);
let frame = self
.process()
.map_image(|img| DebugFrame::Startup {
image_base: img.load_base,
ip_offset: img.ip_offset,
ip: img.entry,
})
.unwrap();
debug.debugger.as_mut().unwrap().send(&frame).ok();
}
pub fn set_breakpoint(&self, address: usize) -> Result<(), Error> {
log::debug!(
"Set breakpoint in {} ({:?}) @ {:#x}",
self.id,
self.name,
address
);
let mut debug = self.debug.lock();
debug.set_breakpoint_inner(self.address_space(), address)
}
pub fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<(), Error> {
log::debug!(
"Read memory in {} ({:?}) @ {:#x}",
self.id,
self.name,
address
);
let space = self.address_space();
// TODO optimize this later
for i in 0..buffer.len() {
buffer[i] = unsafe { ((address + i) as *const u8).try_read_foreign_volatile(space) }?;
}
Ok(())
} }
pub fn resume(&self, single_step: bool) { pub fn resume(&self, single_step: bool) {
@ -453,23 +501,77 @@ impl CurrentThread {
pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) { pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) {
{ {
let mut debug = self.debug.lock(); let mut debug = self.debug.lock();
let space = self.address_space();
// Single step cleared if let Some(original) = debug.restore_breakpoint.take() {
if !debug.single_step { let brk_range = original..original + size_of::<BreakpointType>();
log::debug!("Clear single step ({} {:?})", self.id, self.name); assert!(!brk_range.contains(&frame.user_ip()));
frame.set_single_step(false);
return; log::debug!(
"Restore breakpoint, current_ip={:#x}, breakpoint_ip={:#x}",
frame.user_ip(),
original
);
debug.set_breakpoint_inner(space, original).unwrap();
} else {
// Single step cleared
if !debug.single_step {
log::debug!("Clear single step ({} {:?})", self.id, self.name);
frame.set_single_step(false);
return;
}
} }
let frame = frame.store(); let frame = frame.store();
debug.saved_frame = Some(frame.clone()); debug.saved_frame = Some(frame.clone());
// TODO handle cases of detached debugger // TODO handle cases of detached debugger
let debugger = debug.debugger.as_ref().unwrap(); let debugger = debug.debugger.as_ref().unwrap();
debugger.send(&DebugFrame::Step { frame }).ok(); debugger.send(&DebugFrame::Step { frame }).ok();
} }
self.suspend().unwrap(); match self.suspend() {
Ok(_) | Err(Error::Interrupted) => (),
Err(err) => panic!("TODO: handle error in debug suspend: {:?}", err),
}
}
pub fn handle_breakpoint<F: TaskFrame>(&self, frame: &mut F) -> bool {
let mut debug = self.debug.lock();
let ip = frame.user_ip();
if let Some(value) = debug.breakpoints.remove(&ip) {
let space = self.address_space();
// Restore original code
let pointer = ip as *mut BreakpointType;
unsafe { pointer.write_foreign_volatile(space, value) };
log::debug!(
"Thread {} ({:?}) hit a breakpoint @ {:#x}, step={}",
self.id,
self.name,
ip,
debug.single_step
);
// TODO handle cases when no debugger is attached (clear breakpoint and resume)
frame.set_single_step(true);
let frame = frame.store();
debug.restore_breakpoint = Some(ip);
debug.saved_frame = Some(frame.clone());
let debugger = debug.debugger.as_ref().unwrap();
debugger.send(&DebugFrame::HitBreakpoint { frame }).ok();
drop(debug);
self.suspend().unwrap();
true
} else {
false
}
} }
/// Sets up a return frame to handle a pending signal, if any is present in the task's queue. /// Sets up a return frame to handle a pending signal, if any is present in the task's queue.
@ -485,26 +587,18 @@ impl CurrentThread {
if let Some(signal) = self.signal_queue.pop() { if let Some(signal) = self.signal_queue.pop() {
if signal == Signal::Debug { if signal == Signal::Debug {
log::info!("Entered debug signal"); frame.set_single_step(true);
let frame = frame.store();
let mut debug = self.debug.lock(); let mut debug = self.debug.lock();
debug.single_step = true; debug.single_step = true;
debug.saved_frame = Some(frame.clone());
let debugger = debug.debugger.as_ref().unwrap(); let debugger = debug.debugger.as_ref().unwrap();
let process = self.process(); let process = self.process();
frame.set_single_step(true); debugger.send(&DebugFrame::Step { frame }).ok();
// Send initial frame
let saved_frame = frame.store();
let (image_base, ip_offset) = process
.map_image(|img| (img.load_base, img.ip_offset))
.unwrap();
debugger
.send(&DebugFrame::Startup {
image_base,
ip_offset,
frame: saved_frame,
})
.ok();
return; return;
} }
@ -558,3 +652,28 @@ impl Deref for CurrentThread {
&self.0 &self.0
} }
} }
impl ThreadDebuggingInfo {
fn set_breakpoint_inner(
&mut self,
space: &ProcessAddressSpace,
address: usize,
) -> Result<(), Error> {
match self.breakpoints.entry(address) {
btree_map::Entry::Vacant(vacant) => {
let pointer = address as *mut BreakpointType;
// Read old code from the address space at that location
let original =
unsafe { (pointer as *const BreakpointType).try_read_foreign_volatile(space) }?;
unsafe { pointer.write_foreign_volatile(space, BREAKPOINT_VALUE) };
vacant.insert(original);
Ok(())
}
// No need, breakpoint already present
btree_map::Entry::Occupied(_) => Err(Error::AlreadyExists),
}
}
}

@ -43,8 +43,8 @@ pub trait TerminalOutput: Sync {
fn size(&self) -> TerminalSize { fn size(&self) -> TerminalSize {
TerminalSize { TerminalSize {
rows: 80, rows: 24,
columns: 24, columns: 80,
} }
} }
fn set_size(&self, size: TerminalSize) -> Result<(), Error> { fn set_size(&self, size: TerminalSize) -> Result<(), Error> {

@ -237,6 +237,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aarch64-cpu", "aarch64-cpu",
"bitflags", "bitflags",
"cc",
"device-api", "device-api",
"kernel-arch-interface", "kernel-arch-interface",
"libk-mm-interface", "libk-mm-interface",

@ -149,6 +149,16 @@ impl Entry {
static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE]; static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE];
fn dump_user_exception(kind: ExceptionKind, frame: &ExceptionFrame) {
let thread = Thread::current();
warnln!("{:?} in {} ({:?})", kind, thread.id, thread.name);
warnln!("ip = {:02x}:{:08x}", frame.cs, frame.eip);
warnln!("cr3 = {:#010x}", CR3.get());
if kind == ExceptionKind::PageFault {
warnln!("cr2 = {:#010x}", CR2.get());
}
}
fn kernel_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) -> ! { fn kernel_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) -> ! {
let cr3 = CR3.get(); let cr3 = CR3.get();
@ -165,35 +175,36 @@ fn kernel_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) -> ! {
fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) { fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) {
let thread = Thread::current(); let thread = Thread::current();
if kind != ExceptionKind::Debug { let dump = match kind {
let cr3 = CR3.get();
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
warnln!("CS:EIP = {:#02x}:{:#010x}", frame.cs, frame.eip);
warnln!("CR3 = {:#x}", cr3);
}
match kind {
ExceptionKind::PageFault => { ExceptionKind::PageFault => {
let cr2 = CR2.get();
warnln!("CR2 = {:#x}", cr2);
thread.raise_signal(Signal::MemoryAccessViolation); thread.raise_signal(Signal::MemoryAccessViolation);
true
} }
ExceptionKind::GeneralProtectionFault => { ExceptionKind::GeneralProtectionFault => {
thread.raise_signal(Signal::MemoryAccessViolation); if thread.handle_breakpoint(frame) {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
} }
ExceptionKind::InvalidOpcode => { ExceptionKind::InvalidOpcode => {
thread.raise_signal(Signal::Aborted); thread.raise_signal(Signal::Aborted);
true
} }
ExceptionKind::Debug => { ExceptionKind::Debug => {
// TODO check if the thread was really in single-step mode or has debugging related to // TODO check if the thread was really in single-step mode or has debugging related to
// the address in exception description // the address in exception description
thread.handle_single_step(frame); thread.handle_single_step(frame);
false
} }
_ => { _ => {
todo!() todo!()
} }
};
if dump {
dump_user_exception(kind, frame);
} }
} }

@ -113,45 +113,54 @@ impl Entry {
static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE]; static mut IDT: [Entry; SIZE] = [Entry::NULL; SIZE];
fn dump_user_exception(kind: ExceptionKind, frame: &ExceptionFrame) {
let thread = Thread::current();
warnln!("{:?} in {} ({:?})", kind, thread.id, thread.name);
warnln!("ip = {:02x}:{:08x}", frame.cs, frame.rip);
warnln!("cr3 = {:#010x}", CR3.get());
if kind == ExceptionKind::PageFault {
warnln!("cr2 = {:#010x}", CR2.get());
}
}
fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) { fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) {
let thread = Thread::current(); let thread = Thread::current();
let cr3 = CR3.get();
if kind != ExceptionKind::Debug { let dump = match kind {
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
// XXX
// frame.dump(debug::LogLevel::Warning);
warnln!("CR3 = {:#x}", cr3);
}
log::warn!("{:#x?}", frame);
match kind {
ExceptionKind::Debug => { ExceptionKind::Debug => {
// TODO check if the thread was really in single-step mode or has debugging related to // TODO check if the thread was really in single-step mode or has debugging related to
// the address in exception description // the address in exception description
thread.handle_single_step(frame); thread.handle_single_step(frame);
false
} }
ExceptionKind::PageFault => { ExceptionKind::PageFault => {
let cr2 = CR2.get();
warnln!("CR2 = {:#x}", cr2);
thread.raise_signal(Signal::MemoryAccessViolation); thread.raise_signal(Signal::MemoryAccessViolation);
true
} }
ExceptionKind::GeneralProtectionFault => { ExceptionKind::GeneralProtectionFault => {
thread.raise_signal(Signal::MemoryAccessViolation); if thread.handle_breakpoint(frame) {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
} }
ExceptionKind::FpuException => { ExceptionKind::FpuException => {
todo!() todo!()
} }
ExceptionKind::InvalidOpcode => { ExceptionKind::InvalidOpcode => {
// TODO handle ud2 as breakpoint? (it's 2 bytes)
thread.raise_signal(Signal::Aborted); thread.raise_signal(Signal::Aborted);
true
} }
ExceptionKind::Breakpoint => { ExceptionKind::Breakpoint => {
todo!() todo!()
} }
_ => todo!("No handler for exception: {:?}", kind), _ => todo!("No handler for exception: {:?}", kind),
};
if dump {
dump_user_exception(kind, frame);
} }
} }

@ -6,7 +6,7 @@ use kernel_fs::devfs;
use libk::{ use libk::{
module::load_kernel_symbol_table, module::load_kernel_symbol_table,
random, random,
task::{process::Process, runtime, thread::Thread}, task::{binary::LoadOptions, process::Process, runtime, thread::Thread},
vfs::{impls::fn_symlink, IoContext, NodeRef}, vfs::{impls::fn_symlink, IoContext, NodeRef},
}; };
use memfs::MemoryFilesystem; use memfs::MemoryFilesystem;
@ -75,8 +75,15 @@ pub fn kinit() -> Result<(), Error> {
{ {
let group_id = Process::create_group(); let group_id = Process::create_group();
let (user_init, user_init_main) = let options = LoadOptions {
proc::load_binary(&mut ioctx, group_id, None, "/init", &["/init", "xxx"], &[])?; group_id,
parent: None,
path: "/init",
args: &["/init", "xxx"],
envs: &[],
single_step: false,
};
let (user_init, user_init_main) = proc::load_binary(&mut ioctx, &options)?;
let mut io = user_init.io.lock(); let mut io = user_init.io.lock();
io.set_ioctx(ioctx); io.set_ioctx(ioctx);

@ -3,7 +3,7 @@
use abi::{error::Error, path::Path, process::ProcessGroupId}; use abi::{error::Error, path::Path, process::ProcessGroupId};
use alloc::sync::{Arc, Weak}; use alloc::sync::{Arc, Weak};
use libk::{ use libk::{
task::{process::Process, thread::Thread}, task::{binary::LoadOptions, process::Process, thread::Thread},
vfs::IoContext, vfs::IoContext,
}; };
@ -11,11 +11,7 @@ use libk::{
#[inline] #[inline]
pub fn load_binary<P: AsRef<Path>>( pub fn load_binary<P: AsRef<Path>>(
ioctx: &mut IoContext, ioctx: &mut IoContext,
group_id: ProcessGroupId, options: &LoadOptions<P>,
parent: Option<Weak<Process>>,
path: P,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> { ) -> Result<(Arc<Process>, Arc<Thread>), Error> {
libk::task::binary::load(ioctx, group_id, parent, path, args, envs) libk::task::binary::load(ioctx, options)
} }

@ -16,16 +16,21 @@ pub(crate) fn debug_trace(message: &str) {
); );
} }
pub(crate) fn debug_control(pid: ProcessId, op: &DebugOperation) -> Result<(), Error> { pub(crate) fn debug_control(pid: ProcessId, op: &mut DebugOperation) -> Result<(), Error> {
let target = Process::get(pid).ok_or(Error::DoesNotExist)?; let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
let target_thread = target.as_single_thread().unwrap(); let target_thread = target.as_single_thread().unwrap();
match op { match op {
&DebugOperation::Continue(single_step) => { &mut DebugOperation::Continue(single_step) => {
// TODO check if it's paused currently // TODO check if it's paused currently
target_thread.resume(single_step); target_thread.resume(single_step);
Ok(()) Ok(())
} }
&mut DebugOperation::SetBreakpoint(address) => target_thread.set_breakpoint(address),
DebugOperation::ReadMemory { address, buffer } => {
target_thread.read_memory(*address, buffer)
}
DebugOperation::WriteMemory { address, buffer } => todo!(),
_ => todo!(), _ => todo!(),
} }
} }

@ -12,7 +12,10 @@ use abi::{
use alloc::sync::Arc; use alloc::sync::Arc;
use libk::{ use libk::{
block, block,
task::{debug::ThreadDebugger, process::Process, runtime, thread::Thread, ThreadId}, task::{
binary::LoadOptions, debug::ThreadDebugger, process::Process, runtime, thread::Thread,
ThreadId,
},
vfs::IoContext, vfs::IoContext,
}; };
use libk_mm::{ use libk_mm::{
@ -89,7 +92,6 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
let process = thread.process(); let process = thread.process();
run_with_io(&process, |mut io| { run_with_io(&process, |mut io| {
// let mut attach_debugger = None;
let attach_debugger = options let attach_debugger = options
.optional .optional
.iter() .iter()
@ -105,14 +107,15 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
})?; })?;
// Setup a new process from the file // Setup a new process from the file
let (child_process, child_main) = proc::load_binary( let load_options = LoadOptions {
io.ioctx_mut(), group_id: process.group_id(),
process.group_id(), parent: Some(Arc::downgrade(&process)),
Some(Arc::downgrade(&process)), path: options.program,
options.program, args: options.arguments,
options.arguments, envs: options.arguments,
options.environment, single_step: attach_debugger.is_some(),
)?; };
let (child_process, child_main) = proc::load_binary(io.ioctx_mut(), &load_options)?;
let pid = child_process.id; let pid = child_process.id;
// Inherit group and session from the creator // Inherit group and session from the creator
@ -166,8 +169,9 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Err
if let Some(debugger) = attach_debugger { if let Some(debugger) = attach_debugger {
child_main.attach_debugger(ThreadDebugger::new(debugger)); child_main.attach_debugger(ThreadDebugger::new(debugger));
} else {
child_main.enqueue();
} }
child_main.enqueue();
Ok(pid as _) Ok(pid as _)
}) })

@ -138,4 +138,4 @@ syscall execve(options: &ExecveOptions<'_>) -> Result<()>;
// Debugging // Debugging
syscall debug_trace(message: &str); syscall debug_trace(message: &str);
syscall debug_control(pid: ProcessId, op: &DebugOperation<'_>) -> Result<()>; syscall debug_control(pid: ProcessId, op: &mut DebugOperation<'_>) -> Result<()>;

@ -1,5 +1,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[repr(C)] #[repr(C)]
@ -10,3 +12,13 @@ pub struct SavedFrame {
pub sp_el0: u64, pub sp_el0: u64,
pub mdscr_el1: u64, pub mdscr_el1: u64,
} }
impl FrameOps for SavedFrame {
fn set_user_ip(&mut self, value: usize) {
self.elr_el1 = value as _;
}
fn user_ip(&self) -> usize {
self.elr_el1 as _
}
}

@ -1,5 +1,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[repr(C)] #[repr(C)]
@ -16,3 +18,13 @@ pub struct SavedFrame {
pub user_sp: u32, pub user_sp: u32,
pub eflags: u32, pub eflags: u32,
} }
impl FrameOps for SavedFrame {
fn set_user_ip(&mut self, value: usize) {
self.user_ip = value as _;
}
fn user_ip(&self) -> usize {
self.user_ip as _
}
}

@ -16,3 +16,8 @@ pub(crate) mod i686;
use i686 as arch_impl; use i686 as arch_impl;
pub use arch_impl::SavedFrame; pub use arch_impl::SavedFrame;
pub trait FrameOps {
fn set_user_ip(&mut self, value: usize);
fn user_ip(&self) -> usize;
}

@ -1,5 +1,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[repr(C)] #[repr(C)]
@ -25,3 +27,13 @@ pub struct SavedFrame {
pub user_sp: u64, pub user_sp: u64,
pub rflags: u64, pub rflags: u64,
} }
impl FrameOps for SavedFrame {
fn set_user_ip(&mut self, value: usize) {
self.user_ip = value as _;
}
fn user_ip(&self) -> usize {
self.user_ip as _
}
}

@ -7,6 +7,7 @@ pub enum DebugOperation<'a> {
Interrupt, Interrupt,
Continue(bool), Continue(bool),
SetBreakpoint(usize),
ReadMemory { ReadMemory {
address: usize, address: usize,
@ -25,11 +26,14 @@ pub enum DebugFrame {
Startup { Startup {
image_base: usize, image_base: usize,
ip_offset: usize, ip_offset: usize,
frame: SavedFrame, ip: usize,
}, },
Step { Step {
frame: SavedFrame, frame: SavedFrame,
}, },
HitBreakpoint {
frame: SavedFrame,
},
// TODO exit status // TODO exit status
Exited, Exited,
} }

7
userspace/Cargo.lock generated

@ -701,6 +701,7 @@ dependencies = [
"iced-x86", "iced-x86",
"libterm", "libterm",
"rangemap", "rangemap",
"rustc-demangle",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -729,6 +730,12 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.34" version = "0.38.34"

@ -14,6 +14,7 @@ serde = { version = "1.0.193", features = ["derive"], default-features = false }
thiserror = "1.0.58" thiserror = "1.0.58"
elf = "0.7.4" elf = "0.7.4"
rangemap = "1.5.1" rangemap = "1.5.1"
rustc-demangle = "0.1.24"
[target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies] [target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
iced-x86 = { version = "1.21.0", default-features = false, features = ["gas", "decoder", "std"] } iced-x86 = { version = "1.21.0", default-features = false, features = ["gas", "decoder", "std"] }

@ -2,6 +2,8 @@ use std::fmt::{self, Display};
use yggdrasil_abi::arch::SavedFrame; use yggdrasil_abi::arch::SavedFrame;
use crate::debugger::SymbolResolver;
use super::{Error, InstructionFormatter, Target}; use super::{Error, InstructionFormatter, Target};
pub struct TargetImpl; pub struct TargetImpl;
@ -38,7 +40,7 @@ impl Target for TargetImpl {
) -> Result<Vec<(usize, Self::Instruction)>, Error> { ) -> Result<Vec<(usize, Self::Instruction)>, Error> {
Ok(vec![]) Ok(vec![])
} }
fn new_instruction_formatter() -> Self::InstructionFormatter { fn new_instruction_formatter(_resolver: SymbolResolver) -> Self::InstructionFormatter {
Unsupported Unsupported
} }
@ -46,7 +48,7 @@ impl Target for TargetImpl {
FlagFormat(frame.spsr_el1) FlagFormat(frame.spsr_el1)
} }
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) { fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) {
for i in 0..30 { for i in 0..10 {
out.push((format!("x{}", i), frame.gp_regs[i])); out.push((format!("x{}", i), frame.gp_regs[i]));
} }
out.push(("pc".into(), frame.elr_el1 as _)); out.push(("pc".into(), frame.elr_el1 as _));

@ -1,4 +1,6 @@
use std::{ use std::{
cell::{RefCell, RefMut},
collections::HashMap,
fmt::Write, fmt::Write,
fs::File, fs::File,
io::{BufReader, Read, Seek, SeekFrom}, io::{BufReader, Read, Seek, SeekFrom},
@ -8,9 +10,10 @@ use std::{
}, },
path::Path, path::Path,
process::{Child, Command}, process::{Child, Command},
sync::Arc,
}; };
use elf::{endian::AnyEndian, segment::ProgramHeader, ElfStream}; use elf::{endian::AnyEndian, segment::ProgramHeader, symbol::Symbol, ElfStream};
use libterm::{Color, Term, TermKey}; use libterm::{Color, Term, TermKey};
use rangemap::RangeMap; use rangemap::RangeMap;
use yggdrasil_rt::{debug::DebugFrame, process::ProcessId}; use yggdrasil_rt::{debug::DebugFrame, process::ProcessId};
@ -19,12 +22,28 @@ use crate::state::State;
use crate::InstructionFormatter; use crate::InstructionFormatter;
use crate::{comm::Comm, Error, Target}; use crate::{comm::Comm, Error, Target};
pub struct SymbolResolver {
image: Arc<ImageInfo>,
ip_offset: usize,
ip: u64,
}
pub struct ImageInfo {
symbol_table: HashMap<String, Symbol>,
segment_headers: RangeMap<usize, ProgramHeader>,
segments: RefCell<RangeMap<usize, Vec<u8>>>,
functions: RangeMap<u64, String>,
}
pub struct Debugger<T: Target> { pub struct Debugger<T: Target> {
comm: Comm, comm: Comm,
term: Term, term: Term,
file: BufReader<File>, file: BufReader<File>,
segment_headers: RangeMap<usize, ProgramHeader>,
segments: RangeMap<usize, Vec<u8>>, image: Arc<ImageInfo>,
command: Option<String>,
status: Option<String>,
term_fd: RawFd, term_fd: RawFd,
comm_fd: RawFd, comm_fd: RawFd,
@ -40,7 +59,7 @@ impl<T: Target> Debugger<T> {
pub fn from_command<P: AsRef<Path>>(image: P, mut command: Command) -> Result<Self, Error> { pub fn from_command<P: AsRef<Path>>(image: P, mut command: Command) -> Result<Self, Error> {
let image = image.as_ref(); let image = image.as_ref();
let segment_headers = extract_segments(image)?; let image_info = read_image(image)?;
let file = BufReader::new(File::open(image)?); let file = BufReader::new(File::open(image)?);
let comm = Comm::open("rdb-1")?; let comm = Comm::open("rdb-1")?;
@ -64,8 +83,10 @@ impl<T: Target> Debugger<T> {
comm, comm,
term, term,
segment_headers, image: image_info.into(),
segments: RangeMap::new(),
command: None,
status: None,
term_fd, term_fd,
comm_fd, comm_fd,
@ -79,11 +100,20 @@ impl<T: Target> Debugger<T> {
} }
fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> { fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> {
self.status = None;
match frame { match frame {
DebugFrame::Startup { image_base, frame, ip_offset } => { DebugFrame::Startup {
image_base,
ip_offset,
ip,
} => {
let pid = unsafe { ProcessId::from_raw(self.child.id()) }; let pid = unsafe { ProcessId::from_raw(self.child.id()) };
let mut state = State::new(image_base, ip_offset, pid); let mut state = State::new(image_base, ip_offset, pid);
state.update(&frame, true)?; if ip >= ip_offset {
state.update_ip(ip - ip_offset);
}
self.status = Some(format!("Attached to #{} @ {:#x}", pid, state.current_ip));
self.state = Some(state); self.state = Some(state);
Ok(()) Ok(())
} }
@ -92,6 +122,12 @@ impl<T: Target> Debugger<T> {
state.update(&frame, true)?; state.update(&frame, true)?;
Ok(()) Ok(())
} }
DebugFrame::HitBreakpoint { frame } => {
let state = self.state.as_mut().unwrap();
state.update(&frame, true)?;
self.status = Some(format!("Hit breakpoint @ {:#x}", state.current_ip));
Ok(())
}
DebugFrame::Exited => { DebugFrame::Exited => {
self.child_exited = true; self.child_exited = true;
Ok(()) Ok(())
@ -100,22 +136,99 @@ impl<T: Target> Debugger<T> {
} }
fn handle_key(&mut self, key: TermKey) -> Result<(), Error> { fn handle_key(&mut self, key: TermKey) -> Result<(), Error> {
match key { if let Some(command) = self.command.as_mut() {
TermKey::Char('q') => { match key {
// TODO send exit to the child TermKey::Char('\x7F') => {
// self.child.kill()?; if !command.is_empty() {
todo!(); command.pop();
}
Ok(())
}
TermKey::Char('\n') => {
let command = command.clone();
self.command = None;
self.run_command(&command)?;
Ok(())
}
TermKey::Char(ch) if ch.is_ascii_graphic() || ch == ' ' => {
command.push(ch);
Ok(())
}
_ => Ok(()),
} }
TermKey::Char('s') => { } else {
// Send resume to the debugee match key {
if let Some(state) = self.state.as_mut() { TermKey::Char(':') => {
state.resume(true)?; self.command = Some(String::new());
Ok(())
}
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 run_command(&mut self, command: &str) -> Result<(), Error> {
let words: Vec<&str> = command.split(' ').collect();
if words.is_empty() {
return Ok(());
}
let Some(state) = self.state.as_mut() else {
return Ok(());
};
match words[0] {
"break" | "b" if words.len() == 2 => {
let address = Self::parse_location(&self.image, state.ip_offset, words[1])?;
if let Err(error) = state.set_breakpoint(address) {
self.status = Some(format!(
"Couldn't set breakpoint @ {:#x}: {:?}",
address, error
));
} }
Ok(()) Ok(())
} }
TermKey::Char('c') => { "read" | "r" if words.len() == 3 => {
if let Some(state) = self.state.as_mut() { let ty = match words[1] {
state.resume(false)?; "b" => 1,
"w" => 2,
"d" => 4,
"q" => 8,
_ => return Ok(()),
};
let address = convert_address(words[2])?;
let mut buf = [0; 8];
if let Ok(_) = state.read_memory(address, &mut buf[..ty]) {
let value = match ty {
1 => buf[0] as u64,
2 => u16::from_ne_bytes([buf[0], buf[1]]) as u64,
4 => u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]) as u64,
8 => u64::from_ne_bytes(buf),
_ => unreachable!(),
};
self.status = Some(format!("*{:#x} -> {:#x}", address, value));
} else {
self.status = Some(format!("Could not read memory at {:#x}", address));
} }
Ok(()) Ok(())
} }
@ -128,10 +241,12 @@ impl<T: Target> Debugger<T> {
return Ok(vec![]); return Ok(vec![]);
}; };
let mut segments = self.image.segments.borrow_mut();
// Find segment // Find segment
let (range, segment) = match self.segments.get_key_value(&state.current_ip) { let (range, segment) = match segments.get_key_value(&state.current_ip) {
Some(seg) => seg, Some(seg) => seg,
None if let Some(header) = self.segment_headers.get(&state.current_ip) => { None if let Some(header) = self.image.segment_headers.get(&state.current_ip) => {
let start = header.p_vaddr as usize; let start = header.p_vaddr as usize;
let end = (header.p_vaddr + header.p_memsz) as usize; let end = (header.p_vaddr + header.p_memsz) as usize;
let mut buffer = vec![0; header.p_memsz as usize]; let mut buffer = vec![0; header.p_memsz as usize];
@ -139,18 +254,19 @@ impl<T: Target> Debugger<T> {
self.file.seek(SeekFrom::Start(header.p_offset))?; self.file.seek(SeekFrom::Start(header.p_offset))?;
self.file.read_exact(&mut buffer)?; self.file.read_exact(&mut buffer)?;
self.segments.insert(start..end, buffer); segments.insert(start..end, buffer);
self.segments.get_key_value(&state.current_ip).unwrap() segments.get_key_value(&state.current_ip).unwrap()
} }
// Outside of any segments // Outside of any segments
None => return Ok(vec![]), None => return Ok(vec![]),
}; };
let offset_within_segment = state.current_ip - range.start; let offset_within_segment = state.current_ip - range.start;
let upper_limit = std::cmp::min(segment.len(), offset_within_segment + amount * 8);
T::disassemble( T::disassemble(
&segment[offset_within_segment..offset_within_segment + amount * 8], &segment[offset_within_segment..upper_limit],
state.current_ip + state.ip_offset, state.current_ip + state.ip_offset,
amount, amount,
) )
@ -163,7 +279,7 @@ impl<T: Target> Debugger<T> {
return Ok(0); return Ok(0);
}; };
let columns = (width + REG_WIDTH - 3) / REG_WIDTH; let columns = core::cmp::max((width - 2) / REG_WIDTH, 1);
let mut gpregs = vec![]; let mut gpregs = vec![];
T::register_list(&state.last_frame, &mut gpregs); T::register_list(&state.last_frame, &mut gpregs);
let rows = 1 + (gpregs.len() + columns - 1) / columns; let rows = 1 + (gpregs.len() + columns - 1) / columns;
@ -213,22 +329,31 @@ impl<T: Target> Debugger<T> {
} }
fn redraw(&mut self) -> Result<(), Error> { fn redraw(&mut self) -> Result<(), Error> {
let resolver = self.symbol_resolver();
let (width, height) = self.term.size()?; let (width, height) = self.term.size()?;
self.term.clear(libterm::Clear::All)?; self.term.clear(libterm::Clear::All)?;
// Show register block // Show register block
let regs_rows = self.print_registers(width)?; let regs_rows = self.print_registers(width)?;
let disassembly = self.disassembly(height - regs_rows - 1)?; if let Some((symbol, offset)) = resolver
.as_ref()
.and_then(SymbolResolver::resolve_current_function)
{
self.term.set_cursor_position(regs_rows, 1).ok();
write!(self.term, "<{}> + {}", symbol, offset).ok();
}
let disassembly = self.disassembly(height - regs_rows - 2)?;
if !disassembly.is_empty() { if !disassembly.is_empty() {
let mut formatter = T::new_instruction_formatter(); let mut formatter = T::new_instruction_formatter(resolver.unwrap());
let mut buffer = String::new(); let mut buffer = String::new();
// Show disassembly block // Show disassembly block
for (i, (ip, insn)) in disassembly.into_iter().enumerate() { for (i, (ip, insn)) in disassembly.into_iter().enumerate() {
let is_current = i == 0; let is_current = i == 0;
self.term.set_cursor_position(i + regs_rows, 0)?; self.term.set_cursor_position(i + regs_rows + 1, 0)?;
buffer.clear(); buffer.clear();
formatter.format_instruction(&insn, &mut buffer); formatter.format_instruction(&insn, &mut buffer);
if is_current { if is_current {
@ -256,7 +381,13 @@ impl<T: Target> Debugger<T> {
write!(self.term, " Waiting for inferior process").ok(); write!(self.term, " Waiting for inferior process").ok();
} }
// TODO deconflict status and command
self.term.set_cursor_position(height - 1, 0)?; self.term.set_cursor_position(height - 1, 0)?;
if let Some(command) = self.command.as_ref() {
write!(self.term, ":{}", command).ok();
} else if let Some(status) = self.status.as_ref() {
write!(self.term, ">> {}", status).ok();
}
self.term.reset_style()?; self.term.reset_style()?;
self.term.flush()?; self.term.flush()?;
@ -287,13 +418,75 @@ impl<T: Target> Debugger<T> {
println!("Program exited with status {:?}", status); println!("Program exited with status {:?}", status);
Ok(()) Ok(())
} }
pub fn symbol_resolver(&self) -> Option<SymbolResolver> {
let state = self.state.as_ref()?;
Some(SymbolResolver {
image: self.image.clone(),
ip_offset: state.ip_offset,
ip: state.current_ip as _,
})
}
fn parse_location(image: &ImageInfo, ip_offset: usize, string: &str) -> Result<u64, Error> {
// TODO validate that the breakpoint is within .text segment (or maybe delegate this
// validation to the kernel?)
if let Some(sym) = image.symbol_table.get(string) {
Ok(sym.st_value + ip_offset as u64)
} else {
convert_address(string)
}
}
} }
fn extract_segments<P: AsRef<Path>>(path: P) -> Result<RangeMap<usize, ProgramHeader>, Error> { impl SymbolResolver {
fn resolve_symbol_inner(image: &ImageInfo, ip: u64) -> Option<(&str, usize)> {
if let Some((range, function)) = image.functions.get_key_value(&ip) {
assert!(range.start <= ip);
return Some((function.as_str(), (ip - range.start).try_into().unwrap()));
}
None
}
pub fn resolve_symbol(&self, ip: u64) -> Option<(&str, usize)> {
Self::resolve_symbol_inner(&self.image, ip)
}
pub fn resolve_current_function(&self) -> Option<(&str, usize)> {
Self::resolve_symbol_inner(&self.image, self.ip)
}
pub fn to_image_address(&self, address: u64) -> Option<u64> {
address.checked_sub(self.ip_offset as _)
}
}
fn read_image<P: AsRef<Path>>(path: P) -> Result<ImageInfo, Error> {
let file = BufReader::new(File::open(path)?); let file = BufReader::new(File::open(path)?);
let elf = ElfStream::<AnyEndian, _>::open_stream(file).unwrap(); let mut elf = ElfStream::<AnyEndian, _>::open_stream(file).unwrap();
let mut ranges = RangeMap::new(); let mut ranges = RangeMap::new();
let mut symbols = HashMap::new();
let mut functions = RangeMap::new();
let (symtab, strtab) = elf.symbol_table().unwrap().unwrap();
#[cfg(any(not(target_arch = "aarch64"), rust_analyzer))]
for sym in symtab {
let raw_name = strtab.get(sym.st_name as _).unwrap();
let demangled_name = rustc_demangle::demangle(raw_name).to_string();
if sym.st_symtype() == elf::abi::STT_FUNC && sym.st_size != 0 {
functions.insert(
sym.st_value..sym.st_value + sym.st_size,
demangled_name.clone(),
);
}
symbols.insert(demangled_name, sym);
}
for seg in elf.segments() { for seg in elf.segments() {
if seg.p_type != elf::abi::PT_LOAD { if seg.p_type != elf::abi::PT_LOAD {
continue; continue;
@ -305,5 +498,17 @@ fn extract_segments<P: AsRef<Path>>(path: P) -> Result<RangeMap<usize, ProgramHe
ranges.insert(start..end, *seg); ranges.insert(start..end, *seg);
} }
Ok(ranges) Ok(ImageInfo {
functions,
symbol_table: symbols,
segment_headers: ranges,
segments: RangeMap::new().into(),
})
}
fn convert_address(s: &str) -> Result<u64, Error> {
if let Some(v) = s.strip_prefix("0x") {
return u64::from_str_radix(v, 16).map_err(|_| Error::InvalidAddress(s.into()));
}
todo!()
} }

@ -2,7 +2,7 @@
use std::{fmt::{LowerHex, Display}, io, path::PathBuf, process::Command}; use std::{fmt::{LowerHex, Display}, io, path::PathBuf, process::Command};
use clap::Parser; use clap::Parser;
use debugger::Debugger; use debugger::{Debugger, SymbolResolver};
use imp::TargetImpl; use imp::TargetImpl;
use yggdrasil_abi::arch::SavedFrame; use yggdrasil_abi::arch::SavedFrame;
@ -26,6 +26,8 @@ pub enum Error {
TermError(#[from] libterm::Error), TermError(#[from] libterm::Error),
#[error("Debug control error: {0:?}")] #[error("Debug control error: {0:?}")]
DebugError(yggdrasil_rt::Error), DebugError(yggdrasil_rt::Error),
#[error("Invalid address: {0:?}")]
InvalidAddress(String)
} }
pub trait Target { pub trait Target {
@ -38,7 +40,7 @@ pub trait Target {
ip: usize, ip: usize,
limit: usize, limit: usize,
) -> Result<Vec<(usize, Self::Instruction)>, Error>; ) -> Result<Vec<(usize, Self::Instruction)>, Error>;
fn new_instruction_formatter() -> Self::InstructionFormatter; fn new_instruction_formatter(resolver: SymbolResolver) -> Self::InstructionFormatter;
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>); fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>);
fn flags_register_as_display(frame: &SavedFrame) -> impl Display; fn flags_register_as_display(frame: &SavedFrame) -> impl Display;

@ -33,15 +33,35 @@ impl<T: Target> State<T> {
pub fn resume(&mut self, step: bool) -> Result<(), Error> { pub fn resume(&mut self, step: bool) -> Result<(), Error> {
unsafe { unsafe {
yggdrasil_rt::sys::debug_control(self.pid, &DebugOperation::Continue(step)) yggdrasil_rt::sys::debug_control(self.pid, &mut DebugOperation::Continue(step))
.map_err(Error::DebugError) .map_err(Error::DebugError)
} }
} }
pub fn set_breakpoint(&mut self, address: u64) -> Result<(), Error> {
unsafe {
yggdrasil_rt::sys::debug_control(self.pid, &mut DebugOperation::SetBreakpoint(address.try_into().unwrap())).map_err(Error::DebugError)
}
}
pub fn read_memory(&mut self, address: u64, buffer: &mut [u8]) -> Result<(), Error> {
let mut op = DebugOperation::ReadMemory { address: address.try_into().unwrap(), buffer };
unsafe {
yggdrasil_rt::sys::debug_control(
self.pid,
&mut op
).map_err(Error::DebugError)
}
}
pub fn update_ip(&mut self, ip: usize) {
self.current_ip = ip;
}
pub fn update(&mut self, frame: &SavedFrame, _refresh: bool) -> Result<(), Error> { pub fn update(&mut self, frame: &SavedFrame, _refresh: bool) -> Result<(), Error> {
let ip = T::real_ip(frame) - self.ip_offset; let ip = T::real_ip(frame) - self.ip_offset;
self.update_ip(ip);
self.last_frame = frame.clone(); self.last_frame = frame.clone();
self.current_ip = ip;
Ok(()) Ok(())
} }

@ -3,7 +3,7 @@ use std::fmt::{self, Display};
use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction}; use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction};
use yggdrasil_abi::arch::SavedFrame; use yggdrasil_abi::arch::SavedFrame;
use crate::{InstructionFormatter, Target}; use crate::{debugger::SymbolResolver, InstructionFormatter, Target};
#[cfg(any(target_pointer_width = "32", rust_analyzer))] #[cfg(any(target_pointer_width = "32", rust_analyzer))]
const BITNESS: u32 = 32; const BITNESS: u32 = 32;
@ -44,6 +44,28 @@ impl InstructionFormatter<Instruction> for GasFormatter {
} }
} }
impl iced_x86::SymbolResolver for SymbolResolver {
fn symbol(
&mut self,
_instruction: &Instruction,
_operand: u32,
_instruction_operand: Option<u32>,
address: u64,
_address_size: u32,
) -> Option<iced_x86::SymbolResult<'_>> {
let image_addr = self.to_image_address(address)?;
let (symbol, offset) = self.resolve_symbol(image_addr)?;
let string = if offset == 0 {
format!("{:#x} <{}>", address, symbol)
} else {
format!("{:#x} <{}+{:#x}>", address, symbol, offset)
};
Some(iced_x86::SymbolResult::with_string(address, string))
}
}
impl Target for TargetImpl { impl Target for TargetImpl {
type Instruction = Instruction; type Instruction = Instruction;
type InstructionFormatter = GasFormatter; type InstructionFormatter = GasFormatter;
@ -62,15 +84,14 @@ impl Target for TargetImpl {
instructions.push((insn.ip() as usize, insn)); instructions.push((insn.ip() as usize, insn));
} }
debug_trace!("{}", instructions.len());
Ok(instructions) Ok(instructions)
} }
fn new_instruction_formatter() -> Self::InstructionFormatter { fn new_instruction_formatter(resolver: SymbolResolver) -> Self::InstructionFormatter {
let mut formatter = GasFormatter::new(); let mut formatter = GasFormatter::with_options(Some(Box::new(resolver)), None);
formatter.options_mut().set_uppercase_hex(false); formatter.options_mut().set_uppercase_hex(false);
formatter.options_mut().set_branch_leading_zeros(false); formatter.options_mut().set_branch_leading_zeros(false);
formatter formatter
} }
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) { fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) {

@ -94,3 +94,7 @@ path = "src/chmod.rs"
[[bin]] [[bin]]
name = "sysmon" name = "sysmon"
path = "src/sysmon.rs" path = "src/sysmon.rs"
[[bin]]
name = "tst"
path = "src/tst.rs"

@ -0,0 +1,35 @@
use std::time::Duration;
fn f(v: f64, s : bool) {
let mut c = 0;
loop {
let mut x = core::hint::black_box(1.0);
let y = core::hint::black_box(2.0);
for _ in 0..10 {
x *= y;
}
let z = core::hint::black_box(v);
let v = core::hint::black_box(core::hint::black_box(x * y) + z);
if s {
if v != 2050.000 {
println!("{:.3} ", v);
}
}
// if c == 100 {
// println!();
// c = 0;
// }
}
}
fn main() {
std::thread::spawn(|| f(1.5, false));
std::thread::spawn(|| f(3.5, false));
std::thread::spawn(|| f(0.75, false));
std::thread::spawn(|| f(0.75, false));
std::thread::spawn(|| f(0.75, false));
std::thread::spawn(|| f(0.75, false));
// std::thread::spawn(f);
f(2.0, true)
}

@ -34,6 +34,7 @@ const PROGRAMS: &[(&str, &str)] = &[
("chmod", "bin/chmod"), ("chmod", "bin/chmod"),
// ("sha256sum", "bin/sha256sum"), // ("sha256sum", "bin/sha256sum"),
("sysmon", "bin/sysmon"), ("sysmon", "bin/sysmon"),
("tst", "bin/tst"),
// netutils // netutils
("netconf", "sbin/netconf"), ("netconf", "sbin/netconf"),
("dhcp-client", "sbin/dhcp-client"), ("dhcp-client", "sbin/dhcp-client"),