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
View File

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

View File

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

View File

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

View File

@ -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();
}

View File

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

View File

@ -1,5 +1,5 @@
//! 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::{
mem::{KernelTableManager, PhysicalMemoryAllocator},
@ -29,6 +29,12 @@ struct TaskContextInner {
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
#[allow(unused)]
pub struct TaskContextImpl<
@ -36,6 +42,7 @@ pub struct TaskContextImpl<
PA: PhysicalMemoryAllocator<Address = PhysicalAddress>,
> {
inner: UnsafeCell<TaskContextInner>,
fp_context: UnsafeCell<FpContext>,
stack_base_phys: PhysicalAddress,
stack_size: usize,
@ -45,6 +52,22 @@ pub struct TaskContextImpl<
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 {
fn store(&self) -> SavedFrame {
SavedFrame {
@ -151,6 +174,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
Ok(Self {
inner: UnsafeCell::new(TaskContextInner { sp }),
fp_context: UnsafeCell::new(FpContext::new()),
stack_base_phys,
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 mdscr_el1 = if context.single_step { 1 << 0 } else { 0 };
stack.push(context.entry);
stack.push(context.argument);
stack.push(0);
stack.push(mdscr_el1);
stack.push(context.stack_pointer);
setup_common_context(
@ -182,6 +208,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
Ok(Self {
inner: UnsafeCell::new(TaskContextInner { sp }),
fp_context: UnsafeCell::new(FpContext::new()),
stack_base_phys,
stack_size: USER_TASK_PAGES * 0x1000,
@ -191,14 +218,28 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
}
unsafe fn enter(&self) -> ! {
FpContext::restore(self.fp_context.get());
__aarch64_enter_task(self.inner.get())
}
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 ()) {
FpContext::restore(self.fp_context.get());
__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_task_enter_kernel();
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);

View File

@ -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

View File

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

View File

@ -101,8 +101,13 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let stack_base = stack_base_phys.raw_virtualize::<K>();
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.stack_pointer);
@ -372,7 +377,7 @@ impl TaskFrame for ExceptionFrame {
}
fn user_ip(&self) -> usize {
todo!()
self.eip as _
}
fn argument(&self) -> u64 {

View File

@ -48,6 +48,9 @@ extern "C" fn idle_task(_: usize) -> ! {
impl Architecture for ArchitectureImpl {
type PerCpuData = PerCpuData;
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) {
use alloc::boxed::Box;

View File

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

View File

@ -443,13 +443,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
})
}
fn user(
context: UserContextInfo, // entry: usize,
// arg: usize,
// cr3: u64,
// user_stack_sp: usize,
// fs_base: usize,
) -> Result<Self, Error> {
fn user(context: UserContextInfo) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 8;
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);
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.argument);
stack.push(context.stack_pointer);

View File

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

View File

@ -37,6 +37,15 @@ pub mod elf;
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> {
buffer: &'a mut [u8],
virtual_offset: usize,
@ -122,12 +131,16 @@ fn setup_program_env(
Ok(in_user as *const _ as usize)
}
fn setup_context(
fn setup_context<P>(
options: &LoadOptions<P>,
space: &ProcessAddressSpace,
image: &ProcessImage,
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<TaskContextImpl, Error> {
) -> Result<TaskContextImpl, Error>
where
P: AsRef<Path>,
{
const USER_STACK_PAGES: usize = 32;
let virt_stack_base = 0x3000000;
@ -174,14 +187,13 @@ fn setup_context(
stack_pointer: ptr.addr(),
tls: tls_address,
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,
group_id: ProcessGroupId,
parent: Option<Weak<Process>>,
space: ProcessAddressSpace,
image: ProcessImage,
args: &Vec<String>,
@ -189,12 +201,13 @@ fn setup_binary<S>(
) -> Result<LoadedProcess, Error>
where
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(
name,
group_id,
parent,
options.group_id,
options.parent.clone(),
Arc::new(space),
context,
Some(image),
@ -263,24 +276,16 @@ fn xxx_load_program<P: AsRef<Path>>(
} else {
Err(Error::UnrecognizedExecutable)
}
// let image = load_binary(head, file, space)?;
// Ok((image, args, envs))
}
/// Loads a program from given `path`
pub fn load<P: AsRef<Path>>(
ioctx: &mut IoContext,
group_id: ProcessGroupId,
parent: Option<Weak<Process>>,
path: P,
args: &[&str],
envs: &[&str],
options: &LoadOptions<P>,
) -> Result<LoadedProcess, Error> {
let path = path.as_ref();
let args = args.iter().map(|&s| s.to_owned()).collect();
let envs = envs.iter().map(|&s| s.to_owned()).collect();
let path = options.path.as_ref();
let args = options.args.iter().map(|&s| s.to_owned()).collect();
let envs = options.envs.iter().map(|&s| s.to_owned()).collect();
let space = ProcessAddressSpace::new()?;
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,
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>>(
process: &Process,
path: P,
options: &LoadOptions<P>,
args: Vec<String>,
envs: Vec<String>,
) -> Result<(TaskContextImpl, ProcessImage), Error> {
let process = options.parent.as_ref().unwrap().upgrade().unwrap();
let mut io = process.io.lock();
// 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();
space.clear()?;
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))
}

View File

@ -27,6 +27,17 @@ pub trait ForeignPointer: Sized {
/// 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 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:
///
/// * Checks if the pointer has proper alignment for the type.
@ -80,6 +91,15 @@ pub trait ForeignPointer: Sized {
impl<T> ForeignPointer for 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
let addr = self as usize;
let start_page = addr & !0xFFF;
@ -90,12 +110,30 @@ impl<T> ForeignPointer for T {
todo!("Foreign pointer write crossed a page boundary");
}
let phys_page = space
.translate(start_page)
.expect("Address is not mapped in the target address space");
let phys_page = space.translate(start_page)?;
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>(

View File

@ -1,7 +1,7 @@
use core::{cell::Cell, mem::size_of, ops::Deref};
use alloc::{
collections::BTreeMap,
collections::{btree_map, BTreeMap, BTreeSet},
string::String,
sync::{Arc, Weak},
};
@ -9,7 +9,7 @@ use crossbeam_queue::SegQueue;
use futures_util::task::ArcWake;
use kernel_arch::{
task::{Scheduler, TaskContext, TaskFrame},
CpuImpl,
Architecture, ArchitectureImpl, CpuImpl,
};
use libk_mm::process::ProcessAddressSpace;
use libk_util::{
@ -24,7 +24,7 @@ use yggdrasil_abi::{
};
use crate::task::{
mem::ForeignPointer,
mem::{self, ForeignPointer},
sched::CpuQueue,
types::{ThreadAffinity, ThreadId, ThreadState},
TaskContextImpl,
@ -32,6 +32,9 @@ use crate::task::{
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
pub struct ThreadSchedulingInfo {
/// Current state
@ -45,8 +48,10 @@ pub struct ThreadSchedulingInfo {
pub struct ThreadDebuggingInfo {
pub single_step: bool,
pub restore_breakpoint: Option<usize>,
pub debugger: Option<ThreadDebugger>,
pub saved_frame: Option<SavedFrame>,
pub breakpoints: BTreeMap<usize, BreakpointType>,
}
struct SignalEntry {
@ -115,10 +120,13 @@ impl Thread {
in_queue: false,
queue: None,
}),
// TODO lazy initialization for debugging info
debug: IrqSafeSpinlock::new(ThreadDebuggingInfo {
single_step: false,
restore_breakpoint: None,
debugger: None,
saved_frame: None,
breakpoints: BTreeMap::new(),
}),
context: Cell::new(context),
process,
@ -243,6 +251,46 @@ impl Thread {
debug.debugger = Some(debugger);
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) {
@ -453,23 +501,77 @@ impl CurrentThread {
pub fn handle_single_step<F: TaskFrame>(&self, frame: &mut F) {
{
let mut debug = self.debug.lock();
let space = self.address_space();
// Single step cleared
if !debug.single_step {
log::debug!("Clear single step ({} {:?})", self.id, self.name);
frame.set_single_step(false);
return;
if let Some(original) = debug.restore_breakpoint.take() {
let brk_range = original..original + size_of::<BreakpointType>();
assert!(!brk_range.contains(&frame.user_ip()));
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();
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();
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.
@ -485,26 +587,18 @@ impl CurrentThread {
if let Some(signal) = self.signal_queue.pop() {
if signal == Signal::Debug {
log::info!("Entered debug signal");
frame.set_single_step(true);
let frame = frame.store();
let mut debug = self.debug.lock();
debug.single_step = true;
debug.saved_frame = Some(frame.clone());
let debugger = debug.debugger.as_ref().unwrap();
let process = self.process();
frame.set_single_step(true);
// 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();
debugger.send(&DebugFrame::Step { frame }).ok();
return;
}
@ -558,3 +652,28 @@ impl Deref for CurrentThread {
&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),
}
}
}

View File

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

View File

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

View File

@ -149,6 +149,16 @@ impl Entry {
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) -> ! {
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) {
let thread = Thread::current();
if kind != ExceptionKind::Debug {
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 {
let dump = match kind {
ExceptionKind::PageFault => {
let cr2 = CR2.get();
warnln!("CR2 = {:#x}", cr2);
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
ExceptionKind::GeneralProtectionFault => {
thread.raise_signal(Signal::MemoryAccessViolation);
if thread.handle_breakpoint(frame) {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
}
ExceptionKind::InvalidOpcode => {
thread.raise_signal(Signal::Aborted);
true
}
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);
false
}
_ => {
todo!()
}
};
if dump {
dump_user_exception(kind, frame);
}
}

View File

@ -113,45 +113,54 @@ impl Entry {
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) {
let thread = Thread::current();
let cr3 = CR3.get();
if kind != ExceptionKind::Debug {
warnln!("{:?} in {} {:?}", kind, thread.id, thread.name);
// XXX
// frame.dump(debug::LogLevel::Warning);
warnln!("CR3 = {:#x}", cr3);
}
log::warn!("{:#x?}", frame);
match kind {
let dump = 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);
false
}
ExceptionKind::PageFault => {
let cr2 = CR2.get();
warnln!("CR2 = {:#x}", cr2);
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
ExceptionKind::GeneralProtectionFault => {
thread.raise_signal(Signal::MemoryAccessViolation);
if thread.handle_breakpoint(frame) {
false
} else {
thread.raise_signal(Signal::MemoryAccessViolation);
true
}
}
ExceptionKind::FpuException => {
todo!()
}
ExceptionKind::InvalidOpcode => {
// TODO handle ud2 as breakpoint? (it's 2 bytes)
thread.raise_signal(Signal::Aborted);
true
}
ExceptionKind::Breakpoint => {
todo!()
}
_ => todo!("No handler for exception: {:?}", kind),
};
if dump {
dump_user_exception(kind, frame);
}
}

View File

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

View File

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

View File

@ -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_thread = target.as_single_thread().unwrap();
match op {
&DebugOperation::Continue(single_step) => {
&mut DebugOperation::Continue(single_step) => {
// TODO check if it's paused currently
target_thread.resume(single_step);
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!(),
}
}

View File

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

View File

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

View File

@ -1,5 +1,7 @@
#![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
@ -10,3 +12,13 @@ pub struct SavedFrame {
pub sp_el0: 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 _
}
}

View File

@ -1,5 +1,7 @@
#![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
@ -16,3 +18,13 @@ pub struct SavedFrame {
pub user_sp: 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 _
}
}

View File

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

View File

@ -1,5 +1,7 @@
#![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
@ -25,3 +27,13 @@ pub struct SavedFrame {
pub user_sp: 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 _
}
}

View File

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

7
userspace/Cargo.lock generated
View File

@ -701,6 +701,7 @@ dependencies = [
"iced-x86",
"libterm",
"rangemap",
"rustc-demangle",
"serde",
"serde_json",
"thiserror",
@ -729,6 +730,12 @@ dependencies = [
"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]]
name = "rustix"
version = "0.38.34"

View File

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

View File

@ -2,6 +2,8 @@ use std::fmt::{self, Display};
use yggdrasil_abi::arch::SavedFrame;
use crate::debugger::SymbolResolver;
use super::{Error, InstructionFormatter, Target};
pub struct TargetImpl;
@ -38,7 +40,7 @@ impl Target for TargetImpl {
) -> Result<Vec<(usize, Self::Instruction)>, Error> {
Ok(vec![])
}
fn new_instruction_formatter() -> Self::InstructionFormatter {
fn new_instruction_formatter(_resolver: SymbolResolver) -> Self::InstructionFormatter {
Unsupported
}
@ -46,7 +48,7 @@ impl Target for TargetImpl {
FlagFormat(frame.spsr_el1)
}
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(("pc".into(), frame.elr_el1 as _));

View File

@ -1,4 +1,6 @@
use std::{
cell::{RefCell, RefMut},
collections::HashMap,
fmt::Write,
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
@ -8,9 +10,10 @@ use std::{
},
path::Path,
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 rangemap::RangeMap;
use yggdrasil_rt::{debug::DebugFrame, process::ProcessId};
@ -19,12 +22,28 @@ use crate::state::State;
use crate::InstructionFormatter;
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> {
comm: Comm,
term: Term,
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,
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> {
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 comm = Comm::open("rdb-1")?;
@ -64,8 +83,10 @@ impl<T: Target> Debugger<T> {
comm,
term,
segment_headers,
segments: RangeMap::new(),
image: image_info.into(),
command: None,
status: None,
term_fd,
comm_fd,
@ -79,11 +100,20 @@ impl<T: Target> Debugger<T> {
}
fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> {
self.status = None;
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 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);
Ok(())
}
@ -92,6 +122,12 @@ impl<T: Target> Debugger<T> {
state.update(&frame, true)?;
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 => {
self.child_exited = true;
Ok(())
@ -100,22 +136,99 @@ impl<T: Target> Debugger<T> {
}
fn handle_key(&mut self, key: TermKey) -> Result<(), Error> {
match key {
TermKey::Char('q') => {
// TODO send exit to the child
// self.child.kill()?;
todo!();
if let Some(command) = self.command.as_mut() {
match key {
TermKey::Char('\x7F') => {
if !command.is_empty() {
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') => {
// Send resume to the debugee
if let Some(state) = self.state.as_mut() {
state.resume(true)?;
} else {
match key {
TermKey::Char(':') => {
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(())
}
TermKey::Char('c') => {
if let Some(state) = self.state.as_mut() {
state.resume(false)?;
"read" | "r" if words.len() == 3 => {
let ty = match words[1] {
"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(())
}
@ -128,10 +241,12 @@ impl<T: Target> Debugger<T> {
return Ok(vec![]);
};
let mut segments = self.image.segments.borrow_mut();
// 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,
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 end = (header.p_vaddr + 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.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
None => return Ok(vec![]),
};
let offset_within_segment = state.current_ip - range.start;
let upper_limit = std::cmp::min(segment.len(), offset_within_segment + amount * 8);
T::disassemble(
&segment[offset_within_segment..offset_within_segment + amount * 8],
&segment[offset_within_segment..upper_limit],
state.current_ip + state.ip_offset,
amount,
)
@ -163,7 +279,7 @@ impl<T: Target> Debugger<T> {
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![];
T::register_list(&state.last_frame, &mut gpregs);
let rows = 1 + (gpregs.len() + columns - 1) / columns;
@ -213,22 +329,31 @@ impl<T: Target> Debugger<T> {
}
fn redraw(&mut self) -> Result<(), Error> {
let resolver = self.symbol_resolver();
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 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() {
let mut formatter = T::new_instruction_formatter();
let mut formatter = T::new_instruction_formatter(resolver.unwrap());
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)?;
self.term.set_cursor_position(i + regs_rows + 1, 0)?;
buffer.clear();
formatter.format_instruction(&insn, &mut buffer);
if is_current {
@ -256,7 +381,13 @@ impl<T: Target> Debugger<T> {
write!(self.term, " Waiting for inferior process").ok();
}
// TODO deconflict status and command
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.flush()?;
@ -287,13 +418,75 @@ impl<T: Target> Debugger<T> {
println!("Program exited with status {:?}", status);
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 elf = ElfStream::<AnyEndian, _>::open_stream(file).unwrap();
let mut elf = ElfStream::<AnyEndian, _>::open_stream(file).unwrap();
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() {
if seg.p_type != elf::abi::PT_LOAD {
continue;
@ -305,5 +498,17 @@ fn extract_segments<P: AsRef<Path>>(path: P) -> Result<RangeMap<usize, ProgramHe
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!()
}

View File

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

View File

@ -33,15 +33,35 @@ impl<T: Target> State<T> {
pub fn resume(&mut self, step: bool) -> Result<(), Error> {
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)
}
}
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> {
let ip = T::real_ip(frame) - self.ip_offset;
self.update_ip(ip);
self.last_frame = frame.clone();
self.current_ip = ip;
Ok(())
}

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Display};
use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction};
use yggdrasil_abi::arch::SavedFrame;
use crate::{InstructionFormatter, Target};
use crate::{debugger::SymbolResolver, InstructionFormatter, Target};
#[cfg(any(target_pointer_width = "32", rust_analyzer))]
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 {
type Instruction = Instruction;
type InstructionFormatter = GasFormatter;
@ -62,15 +84,14 @@ impl Target for TargetImpl {
instructions.push((insn.ip() as usize, insn));
}
debug_trace!("{}", instructions.len());
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 new_instruction_formatter(resolver: SymbolResolver) -> Self::InstructionFormatter {
let mut formatter = GasFormatter::with_options(Some(Box::new(resolver)), None);
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, Self::Register)>) {

View File

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

View File

@ -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)
}

View File

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