i686: add single-step debugging

This commit is contained in:
Mark Poliakov 2024-10-18 18:29:14 +03:00
parent 6c30bd062a
commit 0daf7c677c
19 changed files with 308 additions and 133 deletions

View File

@ -108,12 +108,13 @@ __i686_switch_and_drop:
// %esp + 0: return
// %esp + 4: destination
// %esp + 8: thread to drop
mov 8(%esp), %ecx
mov 4(%esp), %eax
// Switch to stack
mov (%eax), %esp
LOAD_TASK_STATE
// TODO actually drop the thread
ret

View File

@ -328,8 +328,12 @@ impl TaskFrame for InterruptFrame {
self.eax = value as u32;
}
fn set_single_step(&mut self, _step: bool) {
todo!()
fn set_single_step(&mut self, step: bool) {
if step {
self.eflags |= 1 << 8;
} else {
self.eflags &= !(1 << 8);
}
}
fn set_return_value(&mut self, value: u64) {
@ -392,8 +396,12 @@ impl TaskFrame for ExceptionFrame {
self.eax = value as u32;
}
fn set_single_step(&mut self, _step: bool) {
todo!()
fn set_single_step(&mut self, step: bool) {
if step {
self.eflags |= 1 << 8;
} else {
self.eflags &= !(1 << 8);
}
}
fn set_return_value(&mut self, value: u64) {

View File

@ -179,7 +179,7 @@ pub fn load_elf_from_file<F: Read + Seek>(
// Find out image size
let (vaddr_min, vaddr_max) = elf_virtual_range(&elf);
let image_load_base = elf_load_address(elf.ehdr.e_type, vaddr_min);
let (image_load_base, ip_offset) = elf_load_address(elf.ehdr.e_type, vaddr_min);
let image_load_size = vaddr_max - vaddr_min;
log::debug!(
@ -213,6 +213,7 @@ pub fn load_elf_from_file<F: Read + Seek>(
Ok(ProcessImage {
entry,
ip_offset,
tls,
load_base: image_load_base,
})
@ -299,13 +300,14 @@ pub fn elf_virtual_range<F: Read + Seek>(
(vaddr_min, vaddr_max)
}
fn elf_load_address(elf_type: u16, virtual_address: usize) -> usize {
fn elf_load_address(elf_type: u16, virtual_address: usize) -> (usize, usize) {
match elf_type {
elf::abi::ET_EXEC => virtual_address,
elf::abi::ET_EXEC => (virtual_address, 0),
// TODO ASLR through random?
elf::abi::ET_DYN => {
let index = random::range(0x5000, 0x20000);
(index as usize) * 0x1000
let base = (index as usize) * 0x1000;
(base, base)
}
// Handled in load_elf_from_file()
_ => unreachable!(),

View File

@ -51,6 +51,7 @@ pub struct ProcessManager {
#[derive(Clone)]
pub struct ProcessImage {
pub load_base: usize,
pub ip_offset: usize,
/// Entry point address
pub entry: usize,
/// Thread-local storage information
@ -234,8 +235,12 @@ impl Process {
self.inner.read().space.clone().unwrap()
}
pub fn map_image<T, F: FnOnce(&ProcessImage) -> T>(&self, f: F) -> Option<T> {
self.inner.read().image.as_ref().map(f)
}
pub fn image_base(&self) -> Option<usize> {
self.inner.read().image.as_ref().map(|img| img.load_base)
self.map_image(|image| image.load_base)
}
pub fn as_single_thread(&self) -> Option<Arc<Thread>> {
@ -329,9 +334,10 @@ impl Process {
let inner = proc.inner.read();
if !proc.has_exited() && inner.group_id == group_id {
log::debug!(
"Deliver group ({}) signal to {}: {:?}",
"Deliver group ({}) signal to {} ({:?}): {:?}",
group_id,
proc.id,
proc.name,
signal
);
drop(inner);

View File

@ -456,6 +456,7 @@ impl CurrentThread {
// Single step cleared
if !debug.single_step {
log::debug!("Clear single step ({} {:?})", self.id, self.name);
frame.set_single_step(false);
return;
}
@ -494,9 +495,13 @@ impl CurrentThread {
// 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: process.image_base().unwrap(),
image_base,
ip_offset,
frame: saved_frame,
})
.ok();

View File

@ -162,13 +162,16 @@ fn kernel_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) -> ! {
panic!("Irrecoverable exception")
}
fn user_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 {
ExceptionKind::PageFault => {
@ -183,6 +186,11 @@ fn user_exception_inner(kind: ExceptionKind, frame: &ExceptionFrame) {
ExceptionKind::InvalidOpcode => {
thread.raise_signal(Signal::Aborted);
}
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);
}
_ => {
todo!()
}

View File

@ -121,7 +121,7 @@ impl DisplayConsole for TextFramebuffer {
}
fn text_dimensions(&self) -> (usize, usize) {
(self.width, self.height)
(self.height, self.width)
}
}

View File

@ -24,6 +24,7 @@ pub enum DebugOperation<'a> {
pub enum DebugFrame {
Startup {
image_base: usize,
ip_offset: usize,
frame: SavedFrame,
},
Step {

View File

@ -94,10 +94,12 @@ where
current += 1;
node = bucket.next;
}
panic!(
"BucketList index out of range: contains {} buckets, tried to index {}",
current, index
);
loop {}
// panic!(
// "BucketList index out of range: contains {} buckets, tried to index {}",
// current, index
// );
}
}

106
userspace/Cargo.lock generated
View File

@ -16,12 +16,55 @@ dependencies = [
name = "abi-lib"
version = "0.1.0"
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -109,8 +152,10 @@ version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
@ -131,6 +176,12 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "colors"
version = "0.1.0"
@ -178,6 +229,12 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "elf"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -282,6 +339,15 @@ dependencies = [
"libm",
]
[[package]]
name = "iced-x86"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
dependencies = [
"lazy_static",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -311,6 +377,12 @@ dependencies = [
"yggdrasil-rt",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.10"
@ -602,6 +674,12 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "rangemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
[[package]]
name = "raqote"
version = "0.8.3"
@ -614,6 +692,22 @@ dependencies = [
"typed-arena",
]
[[package]]
name = "rdb"
version = "0.1.0"
dependencies = [
"clap",
"elf",
"iced-x86",
"libterm",
"rangemap",
"serde",
"serde_json",
"thiserror",
"yggdrasil-abi",
"yggdrasil-rt",
]
[[package]]
name = "red"
version = "0.1.0"
@ -749,6 +843,12 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "sw-composite"
version = "0.7.16"
@ -957,6 +1057,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.4"

View File

@ -13,6 +13,6 @@ members = [
"netutils",
"netutils",
# "dyn-loader",
# "rdb"
"rdb"
]
exclude = ["dynload-program", "test-kernel-module"]

View File

@ -15,5 +15,8 @@ thiserror = "1.0.58"
elf = "0.7.4"
rangemap = "1.5.1"
[target.'cfg(target_arch = "x86_64")'.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"] }
[dev-dependencies]
iced-x86 = { version = "1.21.0", default-features = false, features = ["gas", "decoder", "std"] }

View File

@ -29,6 +29,7 @@ impl InstructionFormatter<()> for Unsupported {
impl Target for TargetImpl {
type Instruction = ();
type InstructionFormatter = Unsupported;
type Register = usize;
fn disassemble(
_window: &[u8],
@ -44,11 +45,11 @@ impl Target for TargetImpl {
fn flags_register_as_display(frame: &SavedFrame) -> impl Display {
FlagFormat(frame.spsr_el1)
}
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, u64)>) {
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) {
for i in 0..30 {
out.push((format!("x{}", i), frame.gp_regs[i]));
}
out.push(("pc".into(), frame.elr_el1));
out.push(("pc".into(), frame.elr_el1 as _));
out.push(("lr".into(), frame.gp_regs[30]));
}
fn real_ip(frame: &SavedFrame) -> usize {

View File

@ -43,8 +43,8 @@ impl<T: Target> Debugger<T> {
let segment_headers = extract_segments(image)?;
let file = BufReader::new(File::open(image)?);
let term = Term::open().unwrap();
let comm = Comm::open("rdb-1")?;
let term = Term::open().unwrap();
let mut poll = PollChannel::new()?;
let comm_fd = comm.as_raw_fd();
@ -80,9 +80,9 @@ impl<T: Target> Debugger<T> {
fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> {
match frame {
DebugFrame::Startup { image_base, frame } => {
DebugFrame::Startup { image_base, frame, ip_offset } => {
let pid = unsafe { ProcessId::from_raw(self.child.id()) };
let mut state = State::new(image_base, pid);
let mut state = State::new(image_base, ip_offset, pid);
state.update(&frame, true)?;
self.state = Some(state);
Ok(())
@ -151,7 +151,7 @@ impl<T: Target> Debugger<T> {
T::disassemble(
&segment[offset_within_segment..offset_within_segment + amount * 8],
state.current_ip + state.image_base,
state.current_ip + state.ip_offset,
amount,
)
}
@ -163,7 +163,7 @@ impl<T: Target> Debugger<T> {
return Ok(0);
};
let columns = (width - 2) / REG_WIDTH;
let columns = (width + REG_WIDTH - 3) / REG_WIDTH;
let mut gpregs = vec![];
T::register_list(&state.last_frame, &mut gpregs);
let rows = 1 + (gpregs.len() + columns - 1) / columns;

View File

@ -1,16 +1,15 @@
#![feature(yggdrasil_os, if_let_guard)]
use std::{fmt::Display, io, path::PathBuf, process::Command};
use std::{fmt::{LowerHex, Display}, io, path::PathBuf, process::Command};
use clap::Parser;
use debugger::Debugger;
use imp::TargetImpl;
use yggdrasil_abi::arch::SavedFrame;
#[cfg(target_arch = "x86_64")]
#[path = "x86_64.rs"]
#[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
#[path = "x86.rs"]
pub mod imp;
#[cfg(target_arch = "aarch64")]
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
#[path = "aarch64.rs"]
pub mod imp;
@ -32,6 +31,7 @@ pub enum Error {
pub trait Target {
type Instruction;
type InstructionFormatter: InstructionFormatter<Self::Instruction>;
type Register: LowerHex;
fn disassemble(
window: &[u8],
@ -40,7 +40,7 @@ pub trait Target {
) -> Result<Vec<(usize, Self::Instruction)>, Error>;
fn new_instruction_formatter() -> Self::InstructionFormatter;
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, u64)>);
fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>);
fn flags_register_as_display(frame: &SavedFrame) -> impl Display;
fn real_ip(frame: &SavedFrame) -> usize;
}

View File

@ -12,18 +12,20 @@ pub struct State<T: Target> {
pub last_frame: SavedFrame,
pub image_base: usize,
pub ip_offset: usize,
_pd: PhantomData<T>,
}
impl<T: Target> State<T> {
pub fn new(image_base: usize, pid: ProcessId) -> Self {
pub fn new(image_base: usize, ip_offset: usize, pid: ProcessId) -> Self {
Self {
current_ip: 0,
last_frame: SavedFrame::default(),
pid,
image_base,
ip_offset,
_pd: PhantomData,
}
@ -37,7 +39,7 @@ impl<T: Target> State<T> {
}
pub fn update(&mut self, frame: &SavedFrame, _refresh: bool) -> Result<(), Error> {
let ip = T::real_ip(frame) - self.image_base;
let ip = T::real_ip(frame) - self.ip_offset;
self.last_frame = frame.clone();
self.current_ip = ip;

123
userspace/rdb/src/x86.rs Normal file
View File

@ -0,0 +1,123 @@
use std::fmt::{self, Display};
use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction};
use yggdrasil_abi::arch::SavedFrame;
use crate::{InstructionFormatter, Target};
#[cfg(any(target_pointer_width = "32", rust_analyzer))]
const BITNESS: u32 = 32;
#[cfg(any(target_pointer_width = "64", rust_analyzer))]
const BITNESS: u32 = 64;
pub struct FlagsDisplay(usize);
pub struct TargetImpl;
pub struct NoopFormatter;
impl fmt::Display for FlagsDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const FLAGS: &[(usize, &str)] = &[
(0, "CF"),
(2, "PF"),
(4, "AF"),
(5, "ZF"),
(6, "SF"),
(9, "IF"),
(10, "DF"),
(11, "OF"),
];
write!(f, " {: <#8x} [ ", self.0)?;
for (bit, flag) in FLAGS {
if self.0 & (1 << bit) != 0 {
write!(f, "{} ", flag)?;
}
}
write!(f, "]")
}
}
impl InstructionFormatter<Instruction> for GasFormatter {
fn format_instruction(&mut self, insn: &Instruction, out: &mut String) {
self.format(insn, out)
}
}
impl Target for TargetImpl {
type Instruction = Instruction;
type InstructionFormatter = GasFormatter;
type Register = usize;
fn disassemble(
window: &[u8],
ip: usize,
limit: usize,
) -> Result<Vec<(usize, Self::Instruction)>, crate::Error> {
let mut instructions = vec![];
let mut decoder = Decoder::with_ip(BITNESS, window, ip as _, DecoderOptions::NONE);
while decoder.can_decode() && instructions.len() < limit {
let insn = decoder.decode();
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 register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) {
#[cfg(any(target_arch = "x86", rust_analyzer))]
{
out.push(("eax".into(), frame.eax as _));
out.push(("ecx".into(), frame.ecx as _));
out.push(("edx".into(), frame.edx as _));
out.push(("ebx".into(), frame.ebx as _));
out.push(("esp".into(), frame.user_sp as _));
out.push(("ebp".into(), frame.ebp as _));
out.push(("esi".into(), frame.esi as _));
out.push(("edi".into(), frame.edi as _));
}
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
{
out.push(("rax".into(), frame.rax as _));
out.push(("rcx".into(), frame.rcx as _));
out.push(("rdx".into(), frame.rdx as _));
out.push(("rbx".into(), frame.rbx as _));
out.push(("rdi".into(), frame.rdi as _));
out.push(("rsi".into(), frame.rsi as _));
out.push(("rsp".into(), frame.user_sp as _));
out.push(("rbp".into(), frame.rbp as _));
out.push(("r8".into(), frame.r8 as _));
out.push(("r9".into(), frame.r9 as _));
out.push(("r10".into(), frame.r10 as _));
out.push(("r11".into(), frame.r11 as _));
out.push(("r12".into(), frame.r12 as _));
out.push(("r13".into(), frame.r13 as _));
out.push(("r14".into(), frame.r14 as _));
out.push(("r15".into(), frame.r15 as _));
}
}
fn flags_register_as_display(frame: &SavedFrame) -> impl Display {
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
{
FlagsDisplay(frame.rflags as _)
}
#[cfg(any(target_arch = "x86", rust_analyzer))]
{
FlagsDisplay(frame.eflags as _)
}
}
fn real_ip(frame: &SavedFrame) -> usize {
frame.user_ip as _
}
}

View File

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

View File

@ -47,7 +47,7 @@ const PROGRAMS: &[(&str, &str)] = &[
// red
("red", "bin/red"),
// rdb
// ("rdb", "bin/rdb"),
("rdb", "bin/rdb"),
// ("dyn-loader", "libexec/dyn-loader"),
];