Files
yggdrasil/userspace/tools/rdb/src/debugger.rs
T

516 lines
16 KiB
Rust

use std::{
cell::RefCell,
collections::HashMap,
fmt::Write,
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
os::{
fd::{AsRawFd, RawFd},
yggdrasil::{io::poll::PollChannel, process::CommandExt},
},
path::Path,
process::{Child, Command},
sync::Arc,
};
use elf::{ElfStream, endian::AnyEndian, segment::ProgramHeader, symbol::Symbol};
use libterm::{Color, Term, TermKey};
use rangemap::RangeMap;
use yggdrasil_rt::{debug::DebugFrame, process::ProcessId};
use crate::InstructionFormatter;
use crate::state::State;
use crate::{Error, Target, comm::Comm};
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>,
image: Arc<ImageInfo>,
command: Option<String>,
status: Option<String>,
term_fd: RawFd,
comm_fd: RawFd,
poll: PollChannel,
child: Child,
child_exited: bool,
state: Option<State<T>>,
}
impl<T: Target> Debugger<T> {
pub fn from_command<P: AsRef<Path>>(image: P, mut command: Command) -> Result<Self, Error> {
let image = image.as_ref();
let image_info = read_image(image)?;
let file = BufReader::new(File::open(image)?);
let comm = Comm::open("rdb-1")?;
let term = Term::open().unwrap();
let mut poll = PollChannel::new()?;
let comm_fd = comm.as_raw_fd();
let term_fd = term.input_fd();
poll.add(comm_fd)?;
poll.add(term_fd)?;
unsafe {
command.attach_debugger(comm_fd);
}
let child = command.spawn()?;
Ok(Self {
file,
comm,
term,
image: image_info.into(),
command: None,
status: None,
term_fd,
comm_fd,
poll,
child,
child_exited: false,
state: None,
})
}
fn handle_frame(&mut self, frame: DebugFrame) -> Result<(), Error> {
self.status = None;
match frame {
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);
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(())
}
DebugFrame::Step { frame } => {
let state = self.state.as_mut().unwrap();
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(())
}
}
}
fn handle_key(&mut self, key: TermKey) -> Result<(), Error> {
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(()),
}
} 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(())
}
"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 state.read_memory(address, &mut buf[..ty]).is_ok() {
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(()),
}
}
fn disassembly(&mut self, amount: usize) -> Result<Vec<(usize, T::Instruction)>, Error> {
let Some(state) = self.state.as_ref() else {
return Ok(vec![]);
};
let mut segments = self.image.segments.borrow_mut();
// Find segment
let (range, segment) = match segments.get_key_value(&state.current_ip) {
Some(seg) => seg,
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];
self.file.seek(SeekFrom::Start(header.p_offset))?;
self.file.read_exact(&mut buffer)?;
segments.insert(start..end, buffer);
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..upper_limit],
state.current_ip + state.ip_offset,
amount,
)
}
fn print_registers(&mut self, width: usize) -> Result<usize, Error> {
const REG_WIDTH: usize = 32;
let Some(state) = self.state.as_ref() else {
return Ok(0);
};
let columns = 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().div_ceil(columns);
self.term.set_cursor_position(0, 0)?;
write!(self.term, "+ Registers ").ok();
for _ in 13..width {
write!(self.term, "-").ok();
}
write!(self.term, "+").ok();
for (i, (name, reg)) in gpregs.into_iter().enumerate() {
let row = i / columns;
let col = i % columns;
self.term
.set_cursor_position(row + 1, col * REG_WIDTH + 1)?;
write!(self.term, " {:<4} = {: >#18x}", name, reg).ok();
}
self.term.set_cursor_position(rows, 0)?;
write!(
self.term,
" flags = {}",
T::flags_register_as_display(&state.last_frame)
)
.ok();
// 1 extra row for RFLAGS
let rows = rows + 1;
for i in 1..rows {
self.term.set_cursor_position(i, 0)?;
write!(self.term, "|").ok();
self.term.set_cursor_position(i, width - 1)?;
write!(self.term, "|").ok();
}
self.term.set_cursor_position(rows, 0)?;
write!(self.term, "+").ok();
for _ in 2..width {
write!(self.term, "-").ok();
}
write!(self.term, "+").ok();
Ok(rows + 1)
}
fn redraw(&mut self) -> Result<(), Error> {
let 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)?;
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(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 + 1, 0)?;
buffer.clear();
formatter.format_instruction(&insn, &mut buffer);
if is_current {
write!(self.term, ">").ok();
self.term.set_bright(true)?;
self.term.set_foreground(Color::Black)?;
self.term.set_background(Color::Green)?;
write!(self.term, "{:016x} {}", ip, buffer).ok();
} else {
write!(self.term, " ").ok();
self.term.set_foreground(Color::Blue)?;
write!(self.term, "{:016x} ", ip).ok();
self.term.set_foreground(Color::Yellow)?;
write!(self.term, "{}", buffer).ok();
}
self.term.reset_style()?;
}
}
if self.state.is_none() {
self.term.set_foreground(Color::Yellow)?;
write!(self.term, " Waiting for inferior process").ok();
}
// 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()?;
Ok(())
}
pub fn run(mut self) -> Result<(), Error> {
while !self.child_exited {
self.redraw()?;
let (fd, result) = self.poll.wait(None, true)?.unwrap();
result?;
match fd {
_ if fd == self.comm_fd => {
let frame = self.comm.recv()?;
self.handle_frame(frame)?;
}
_ if fd == self.term_fd => {
let key = self.term.read_key()?;
self.handle_key(key)?;
}
_ => unreachable!(),
}
}
let status = self.child.wait()?;
println!("Program exited with status {:?}", status);
Ok(())
}
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)
}
}
}
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 _)
}
}
#[allow(unused)]
fn read_image<P: AsRef<Path>>(path: P) -> Result<ImageInfo, Error> {
let file = BufReader::new(File::open(path)?);
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;
}
let start = seg.p_vaddr as usize;
let end = (seg.p_vaddr + seg.p_memsz) as usize;
ranges.insert(start..end, *seg);
}
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!()
}