From 3c58759bd929602dc5ef506714b142a21b24c12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 16 Jul 2021 14:21:46 +0200 Subject: [PATCH 1/5] Remove gdb-protocol-based debugging infrastructure --- Cargo.lock | 72 +- Cargo.toml | 4 - src/arch/mod.rs | 2 - src/arch/x86.rs | 125 --- src/debug_manager.rs | 111 --- src/gdb_parser.rs | 2043 -------------------------------------- src/lib.rs | 4 - src/linux/gdb.rs | 791 --------------- src/linux/i386-64bit.xml | 221 ----- src/linux/mod.rs | 1 - src/linux/uhyve.rs | 9 - src/linux/vcpu.rs | 20 +- src/macos/gdb.rs | 777 --------------- src/macos/i386-64bit.xml | 221 ----- src/macos/mod.rs | 1 - src/macos/uhyve.rs | 9 - src/macos/vcpu.rs | 19 +- 17 files changed, 12 insertions(+), 4418 deletions(-) delete mode 100644 src/arch/mod.rs delete mode 100644 src/arch/x86.rs delete mode 100644 src/debug_manager.rs delete mode 100644 src/gdb_parser.rs delete mode 100644 src/linux/gdb.rs delete mode 100644 src/linux/i386-64bit.xml delete mode 100644 src/macos/gdb.rs delete mode 100644 src/macos/i386-64bit.xml diff --git a/Cargo.lock b/Cargo.lock index 60d5c09f..a98bcef8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.4.0", + "memchr", ] [[package]] @@ -62,7 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "lazy_static", - "memchr 2.4.0", + "memchr", "regex-automata", "serde", ] @@ -241,7 +241,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ - "memchr 2.4.0", + "memchr", ] [[package]] @@ -279,15 +279,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50045aa8931ae01afbc5d72439e8f57f326becb8c70d07dfc816778eff3d167" -[[package]] -name = "gdb-protocol" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8b88b222a91266bb192222d46d0da29addbc423d0a9910aec233dce875eb6e" -dependencies = [ - "memchr 2.4.0", -] - [[package]] name = "goblin" version = "0.4.2" @@ -311,15 +302,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -435,15 +417,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "memchr" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -dependencies = [ - "libc 0.2.101", -] - [[package]] name = "memchr" version = "2.4.0" @@ -471,15 +444,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nom" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" -dependencies = [ - "memchr 1.0.2", -] - [[package]] name = "num-traits" version = "0.2.14" @@ -598,7 +562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", - "memchr 2.4.0", + "memchr", "regex-syntax", ] @@ -735,24 +699,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "strum" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syn" version = "1.0.74" @@ -835,7 +781,6 @@ dependencies = [ "either", "env_logger", "envmnt", - "gdb-protocol", "goblin", "kvm-bindings", "kvm-ioctls", @@ -844,13 +789,10 @@ dependencies = [ "log", "mac_address", "nix", - "nom", "raw-cpuid", "rftrace", "rftrace-frontend", "rustc-serialize", - "strum", - "strum_macros", "thiserror", "tun-tap", "virtio-bindings", @@ -860,12 +802,6 @@ dependencies = [ "xhypervisor", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-width" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 9782e370..a52daeca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,16 +45,12 @@ core_affinity = "0.5" either = "1.6" env_logger = "0.9" envmnt = "0.9" -gdb-protocol = "0.1" goblin = { version = "0.4", default-features = false, features = ["elf64", "elf32", "endian_fd", "std"] } lazy_static = "1.4" libc = "0.2" log = "0.4" -nom = "3.2" raw-cpuid = "10.2" rustc-serialize = "0.3" -strum = "0.21" -strum_macros = "0.21" thiserror = "1.0" rftrace = { version = "0.1", optional = true } diff --git a/src/arch/mod.rs b/src/arch/mod.rs deleted file mode 100644 index 06ae8e6e..00000000 --- a/src/arch/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(target_arch = "x86_64")] -pub mod x86; diff --git a/src/arch/x86.rs b/src/arch/x86.rs deleted file mode 100644 index 0c79fa45..00000000 --- a/src/arch/x86.rs +++ /dev/null @@ -1,125 +0,0 @@ -/// Describe Hardware Break/Watchpoints in a format easily convertible into raw addrs and bits in Dr7 register -use log::error; - -#[derive(Copy, Clone)] -pub struct HWBreakpoint { - pub addr: u64, - pub is_local: bool, - pub is_global: bool, - pub trigger: BreakTrigger, - pub size: BreakSize, -} - -#[derive(Copy, Clone, PartialEq)] -pub enum BreakTrigger { - Ex = 0b00, - W = 0b01, - RW = 0b11, -} - -#[derive(Copy, Clone, PartialEq)] -pub enum BreakSize { - B1 = 0b00, - B2 = 0b01, - B4 = 0b10, - B8 = 0b11, -} - -impl Default for HWBreakpoint { - fn default() -> Self { - HWBreakpoint { - addr: 0, - is_local: false, - is_global: false, - trigger: BreakTrigger::Ex, - size: BreakSize::B1, - } - } -} - -#[derive(Copy, Clone, Default)] -pub struct HWBreakpoints(pub [HWBreakpoint; 4]); - -/// See https://stackoverflow.com/a/40820763 -/// https://www.intel.com/content/dam/support/us/en/documents/processors/pentium4/sb/253669.pdf page 5-7 -impl HWBreakpoints { - /// Return address of breakpoint i - pub fn get_addr(&self, i: usize) -> Option { - self.0.get(i).map(|bp| bp.addr) - } - - /// Return debug control register DR7 - pub fn get_dr7(&self) -> u64 { - let mut out = 0; - - /* local and global exact breakpoint enable (bits 8,9) - This feature is not supported in the P6 family processors, later IA-32 processors, - and Intel 64 processors. When set, these flags cause the processor to detect the - exact instruction that caused a data breakpoint condition. For backward and forward - compatibility with other Intel processors, we recommend that the LE and GE flags be - set to 1 if exact breakpoints are required - */ - out |= (1 << 8) | (1 << 9); - - // Bits 11,12,14,15 are reserved 0 - // Bit 10 is reserved, must be set to 1 - // GD (general detect enable) flag (bit 13), protects debug regs from being changed. Not needed by us - out |= 1 << 10; - - for (n, bp) in self.0.iter().enumerate() { - if bp.trigger == BreakTrigger::Ex && bp.size != BreakSize::B1 { - error!("Instruction breakpoint addresses must have a length specification of 1 byte (the LENn field is set to 00). \ - Code breakpoints for other operand sizes are undefined"); - } - - out |= (bp.trigger as u64) << (4 * n) << 16; - out |= (bp.size as u64) << 2 << (4 * n) << 16; - - out |= (bp.is_local as u64) << (2 * n); - out |= (bp.is_global as u64) << 1 << (2 * n); - } - - out - } -} - -#[allow(clippy::unusual_byte_groupings)] -#[test] -fn test_hwbreakpoints_dr7() { - let br = HWBreakpoints([ - HWBreakpoint { - addr: 0, - is_local: false, - is_global: true, - trigger: BreakTrigger::W, - size: BreakSize::B8, - }, - HWBreakpoint { - addr: 0, - is_local: false, - is_global: true, - trigger: BreakTrigger::RW, - size: BreakSize::B4, - }, - HWBreakpoint { - addr: 0, - is_local: true, - is_global: true, - trigger: BreakTrigger::Ex, - size: BreakSize::B1, - }, - HWBreakpoint { - addr: 0, - is_local: true, - is_global: false, - trigger: BreakTrigger::W, - size: BreakSize::B2, - }, - ]); - assert_eq!( - br.get_dr7(), - 0b_0101_0000_1011_1101_00000111_01_11_10_10, - "dr7 wrong: {:#034b}", - br.get_dr7() - ); -} diff --git a/src/debug_manager.rs b/src/debug_manager.rs deleted file mode 100644 index f076ab8d..00000000 --- a/src/debug_manager.rs +++ /dev/null @@ -1,111 +0,0 @@ -use gdb_protocol::{ - io::GdbServer, - packet::{CheckedPacket, Kind}, -}; - -use crate::gdb_parser::{self, handle_packet, Handler, Response, StopReason, VCont}; -use std::cell::RefCell; -use std::io::BufReader; -use std::net::TcpStream; - -/// GDBStub Implementation Details: -/// -/// REMOTE_GDB <--> gdb-protocol <--> gdb_parser <--> Handler <--> vCpu -/// ----- Arch-independent ----- | -- Arch-dependent -- -/// -/// - Create 'global' DebugManager, store in VM -/// - Manages gdb-protocol connection & global state -/// - Handler will get reference to global state, which might be handy if we implement multithreading support. -/// - Pass manager in an Arc to all virtual cpus -/// - on start/trap, vcpu calls `gdb_handle_exception`, which creates a `Handler`. This Handler is only responsible for the current trap on the current CPU -/// - Handler gets passed to the `DebugManager` event-loop -/// - use gdb-procotol to tell REMOTE we have stopped, wait for commands -/// - parses commands with gdb_parser, which calls the corrosponsing Handler functions -/// - Handler interacts with vcpu, eg reading/writing regs/memory -/// - Handler generates response enum. It gets turned to bytes by gdb_parser and send by gdb-protocol -/// -/// - To be Host-OS/Arch flexible, both Handler and State are defined in eg `linux/gdb.rs` -/// -use crate::os::gdb; - -use log::{debug, info}; - -pub type State = gdb::State; - -pub struct DebugManager { - server: RefCell, TcpStream>>, - pub state: RefCell, -} - -impl DebugManager { - pub fn new(port: u32) -> Result { - println!("Waiting for debugger to attach on port {}...", port); - let server = GdbServer::listen(format!("0.0.0.0:{}", port))?; - info!("Connected!"); - - let state = State::new(); - - Ok(DebugManager { - server: RefCell::new(server), - state: RefCell::new(state), - }) - } - - /// main event-loop. Called from vcpu trap, loops and executes commands until debugger tells us to continue. - /// Do not borrow state in this func, since handler is expected to borrow/mutate it. - pub fn handle_commands( - &self, - handler: &mut H, - signal: Option, - ) -> Result - where - H: Handler, - { - let mut server = self.server.borrow_mut(); - if let Some(signal) = signal { - let resp = Response::Stopped(signal); - let resp = CheckedPacket::from_data(Kind::Packet, resp.into()); - let mut bytes = Vec::new(); - resp.encode(&mut bytes).unwrap(); - debug!("OUT {:?}", std::str::from_utf8(&bytes)); - server.dispatch(&resp)?; - } - - while let Some(packet) = server.next_packet()? { - debug!( - " IN {:?} {:?}", - packet.kind, - std::str::from_utf8(&packet.data) - ); - - let resp = match handle_packet(&packet.data, handler) { - Ok(resp) => resp, - Err(e) => { - info!( - "Could not execute command: {:?} ({:?})", - std::str::from_utf8(&packet.data), - e - ); - match e { - gdb_parser::Error::Unimplemented => Response::Empty, - gdb_parser::Error::Error(e) => Response::Error(e), - } - } - }; - - // Early abort if we are continuing. Response gets send next time the handler is entered! - if let Some(vcont) = handler.should_cont() { - return Ok(vcont); - } - - let resp = CheckedPacket::from_data(Kind::Packet, resp.into()); - let mut bytes = Vec::new(); - resp.encode(&mut bytes).unwrap(); - debug!("OUT {:?}", std::str::from_utf8(&bytes)); - server.dispatch(&resp)?; - } - - info!("No next packet! Has GDB exited?"); - Ok(VCont::Continue) - } -} diff --git a/src/gdb_parser.rs b/src/gdb_parser.rs deleted file mode 100644 index a5f1b871..00000000 --- a/src/gdb_parser.rs +++ /dev/null @@ -1,2043 +0,0 @@ -//! An implementation of the server side of the GDB Remote Serial -//! Protocol -- the protocol used by GDB and LLDB to talk to remote -//! targets. -//! -//! This library attempts to hide many of the protocol warts from -//! server implementations. It is also mildly opinionated, in that it -//! implements certain features itself and requires users of the -//! library to conform. For example, it unconditionally implements -//! the multiprocess and non-stop modes. -//! -//! ## Protocol Documentation -//! -//! * [Documentation of the protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html) -//! * [LLDB extensions](https://github.com/llvm-mirror/lldb/blob/master/docs/lldb-gdb-remote.txt) -// from https://github.com/luser/rust-gdb-remote-protocol/blob/master/src/lib.rs - -//#![deny(missing_docs)] -//#![allow(dead_code)] - -use gdb_protocol::io::BUF_SIZE; -use log::{debug, info, trace}; -use nom::IResult::*; -use nom::{ - alt, alt_complete, call, complete, do_parse, error_position, flat_map, is_not, is_not_s, many0, - many1, map, map_res, named, one_of, opt, preceded, separated_list, separated_list_complete, - separated_nonempty_list, separated_nonempty_list_complete, separated_pair, tag, take, - take_till, take_while1, try_parse, tuple, tuple_parser, -}; -use nom::{IResult, Needed}; -use rustc_serialize::hex::ToHex; -use std::borrow::Cow; -use std::convert::From; -use std::ops::Range; -use std::str::{self, FromStr}; -use strum_macros::EnumString; - -/// returns subslice of s at given offset of at most given length. If offset OOB, return empty slice -pub fn get_max_subslice(s: &str, offset: usize, length: usize) -> &str { - let large = s.get(offset..s.len()).unwrap_or(""); - if large.len() > length { - &large[0..length] - } else { - large - } -} - -#[allow(non_camel_case_types)] -#[derive(Copy, Clone, Debug, EnumString, PartialEq)] -enum GDBFeature { - multiprocess, - xmlRegisters, - qRelocInsn, - swbreak, - hwbreak, - #[strum(serialize = "fork-events")] - fork_events, - #[strum(serialize = "vfork-events")] - vfork_events, - #[strum(serialize = "exec-events")] - exec_events, - vContSupported, - // these are not listed in the docs but GDB sends them - #[strum(serialize = "no-resumed")] - no_resumed, - QThreadEvents, -} - -#[derive(Clone, Debug, PartialEq)] -enum Known<'a> { - Yes(GDBFeature), - No(&'a str), -} - -#[derive(Clone, Debug, PartialEq)] -struct GDBFeatureSupported<'a>(Known<'a>, FeatureSupported<'a>); - -#[derive(Clone, Debug, PartialEq)] -enum FeatureSupported<'a> { - Yes, - No, - #[allow(unused)] - Maybe, - Value(&'a str), -} - -#[derive(Clone, Debug, PartialEq)] -enum Query<'a> { - /// Return the attached state of the indicated process. - // FIXME the PID only needs to be optional in the - // non-multi-process case, which we aren't supporting; but we - // don't send multiprocess+ in the feature response yet. - Attached(Option), - /// Return the current thread ID. - CurrentThread, - /// Search memory for some bytes. - SearchMemory { - address: u64, - length: u64, - bytes: Vec, - }, - /// Compute the CRC checksum of a block of memory. - // Uncomment this when qC is implemented. - // #[allow(unused)] - // CRC { addr: u64, length: u64 }, - /// Tell the remote stub about features supported by gdb, and query the stub for features - /// it supports. - SupportedFeatures(Vec>), - /// Disable acknowledgments. - StartNoAckMode, - /// Invoke a command on the server. The server defines commands - /// and how to parse them. - Invoke(Vec), - /// Enable or disable address space randomization. - AddressRandomization(bool), - /// Enable or disable catching of syscalls. - CatchSyscalls(Option>), - /// Set the list of pass signals. - PassSignals(Vec), - /// Set the list of program signals. - ProgramSignals(Vec), - /// Get a string description of a thread. - ThreadInfo(ThreadId), - /// Get a list of all active threads - ThreadList(bool), - /// Get a list of all active processes (LLDB ex) - ProcessList(bool), - /// Get Target triple etc. - HostInfo, - /// Read features (target.xml) - FeatureRead { - name: String, - offset: u64, - length: u64, - }, -} - -/// Part of a process id. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Id { - /// A process or thread id. This value may not be 0 or -1. - Id(u32), - /// A special form meaning all processes or all threads of a given - /// process. - All, - /// A special form meaning any process or any thread of a given - /// process. - Any, -} - -/// A thread identifier. In the RSP this is just a numeric handle -/// that is passed across the wire. It needn't correspond to any real -/// thread or process id (though obviously it may be more convenient -/// when it does). -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct ThreadId { - /// The process id. - pub pid: Id, - /// The thread id. - pub tid: Id, -} - -/// A process identifier for LLDBs qfProcessInfo. -#[derive(Clone, Debug, PartialEq)] -pub struct ProcessInfo { - pub name: String, - pub pid: Id, - pub triple: String, -} - -/// A descriptor for a watchpoint. The particular semantics of the watchpoint -/// (watching memory for read or write access) are addressed elsewhere. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Watchpoint { - /// The address. - pub addr: u64, - - /// The number of bytes covered. - pub n_bytes: u64, -} - -impl Watchpoint { - fn new(addr: u64, n_bytes: u64) -> Watchpoint { - Watchpoint { addr, n_bytes } - } -} - -/// Target-specific bytecode. -#[derive(Clone, Debug, PartialEq)] -pub struct Bytecode { - /// The bytecodes. - pub bytecode: Vec, -} - -/// A descriptor for a breakpoint. The particular implementation technique -/// of the breakpoint, hardware or software, is handled elsewhere. -#[derive(Clone, Debug, PartialEq)] -pub struct Breakpoint { - /// The address. - pub addr: u64, - - /// The kind of breakpoint. This field is generally 0 and its - /// interpretation is target-specific. A typical use of it is for - /// targets that support multiple execution modes (e.g. ARM/Thumb); - /// different values for this field would identify the kind of code - /// region in which the breakpoint is being inserted. - pub kind: u64, - - /// An optional list of target-specific bytecodes representing - /// conditions. Each condition should be evaluated by the target when - /// the breakpoint is hit to determine whether the hit should be reported - /// back to the debugger. - pub conditions: Option>, - - /// An optional list of target-specific bytecodes representing commands. - /// These commands should be evaluated when a breakpoint is hit; any - /// results are not reported back to the debugger. - pub commands: Option>, -} - -impl Breakpoint { - fn new( - addr: u64, - kind: u64, - conditions: Option>, - commands: Option>, - ) -> Breakpoint { - Breakpoint { - addr, - kind, - conditions, - commands, - } - } -} - -/// A descriptor for a region of memory. -#[derive(Clone, Debug, PartialEq)] -pub struct MemoryRegion { - /// The base address. - pub address: u64, - /// The length. - pub length: u64, -} - -impl MemoryRegion { - fn new(address: u64, length: u64) -> MemoryRegion { - MemoryRegion { address, length } - } -} - -/// The name of certain vCont features to be addressed when queried -/// for which are supported. -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum VContFeature { - /// Indicate that you support continuing until breakpoint - Continue = b'c', - /// Indicate that you support continuing with a signal - ContinueWithSignal = b'C', - /// Indicate that you support singlestepping one instruction - Step = b's', - /// Indicate that you support singlestepping with a signal - StepWithSignal = b'S', - /// Indicate that you support stopping a thread - Stop = b't', - /// Indicate that you support singlestepping while inside of a range - RangeStep = b'r', -} - -/// vCont commands -#[derive(Clone, Debug, PartialEq)] -pub enum VCont { - /// Continue until breakpoint, signal or exit - Continue, - /// Like `Continue`, but replace any current signal with a - /// specified one - ContinueWithSignal(u8), - /// Step one machine instruction - Step, - /// Like `Step`, but replace any current signal with a specified - /// one - StepWithSignal(u8), - /// Only relevant in non-stop mode. Stop a thread and when - /// queried, indicate a stop with signal 0 - Stop, - /// Keep stepping until instruction pointer is outside of - /// specified range. May also spuriously stop, such as when a - /// breakpoint is reached. - RangeStep(Range), -} - -/// GDB remote protocol commands, as defined in (the GDB documentation)[1] -/// [1]: https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets -#[derive(Clone, Debug, PartialEq)] -enum Command<'a> { - /// Detach from a process or from all processes. - Detach(Option), - /// Enable extended mode. - EnableExtendedMode, - /// Indicate the reason the target halted. - TargetHaltReason, - // Read general registers. - ReadGeneralRegisters, - // Write general registers. - WriteGeneralRegisters(Vec), - // Read a single register. - ReadRegister(u64), - // Write a single register. - WriteRegister(u64, Vec), - // Kill request. The argument is the optional PID, provided when the vKill - // packet was used, and None when the k packet was used. - Kill(Option), - // Read specified region of memory. - ReadMemory(MemoryRegion), - // Write specified region of memory. - WriteMemory(MemoryRegion, Vec), - Query(Query<'a>), - Reset, - PingThread(ThreadId), - CtrlC, - UnknownV, - /// Set the current thread for future commands, such as `ReadRegister`. - SetCurrentThread(ThreadId), - /// Insert a software breakpoint. - InsertSoftwareBreakpoint(Breakpoint), - /// Insert a hardware breakpoint - InsertHardwareBreakpoint(Breakpoint), - /// Insert a write watchpoint. - InsertWriteWatchpoint(Watchpoint), - /// Insert a read watchpoint. - InsertReadWatchpoint(Watchpoint), - /// Insert an access watchpoint. - InsertAccessWatchpoint(Watchpoint), - /// Remove a software breakpoint. - RemoveSoftwareBreakpoint(Breakpoint), - /// Remove a hardware breakpoint. - RemoveHardwareBreakpoint(Breakpoint), - /// Remove a write watchpoint. - RemoveWriteWatchpoint(Watchpoint), - /// Remove a read watchpoint. - RemoveReadWatchpoint(Watchpoint), - /// Remove an access watchpoint. - RemoveAccessWatchpoint(Watchpoint), - /// Query for a list of supported vCont features. - VContSupported, - /// Resume with different actions for each thread. Choose the - /// first matching thread in the list. - VCont(Vec<(VCont, Option)>), -} - -named!( - gdbfeature>, - map!(map_res!(is_not_s!(";="), str::from_utf8), |s| { - match GDBFeature::from_str(s) { - Ok(f) => Known::Yes(f), - Err(_) => Known::No(s), - } - }) -); - -fn gdbfeaturesupported<'a>(i: &'a [u8]) -> IResult<&'a [u8], GDBFeatureSupported<'a>> { - flat_map!(i, is_not!(";"), |f: &'a [u8]| { - match f.split_last() { - None => IResult::Incomplete(Needed::Size(2)), - Some((&b'+', first)) => map!(first, gdbfeature, |feat| GDBFeatureSupported( - feat, - FeatureSupported::Yes - )), - Some((&b'-', first)) => map!(first, gdbfeature, |feat| GDBFeatureSupported( - feat, - FeatureSupported::No - )), - Some((_, _)) => map!( - f, - separated_pair!( - gdbfeature, - tag!("="), - map_res!(is_not!(";"), str::from_utf8) - ), - |(feat, value)| GDBFeatureSupported(feat, FeatureSupported::Value(value)) - ), - } - }) -} - -named!(q_search_memory<&[u8], (u64, u64, Vec)>, - complete!(do_parse!( - tag!("qSearch:memory:") >> - address: hex_value >> - tag!(";") >> - length: hex_value >> - tag!(";") >> - data: hex_byte_sequence >> - (address, length, data)))); - -named!(q_read_feature<&[u8], (&str, u64, u64)>, - complete!(do_parse!( - tag!("qXfer:features:read:") >> - filename: map!(is_not_s!(":"), |s| std::str::from_utf8(s).unwrap()) >> - tag!(":") >> - offset: hex_value >> - tag!(",") >> - length: hex_value >> - (filename, offset, length)))); - -fn query<'a>(i: &'a [u8]) -> IResult<&'a [u8], Query<'a>> { - alt_complete!(i, - tag!("qC") => { |_| Query::CurrentThread } - | preceded!(tag!("qSupported"), - preceded!(tag!(":"), - separated_list_complete!(tag!(";"), - gdbfeaturesupported))) => { - |features: Vec>| Query::SupportedFeatures(features) - } - | q_read_feature => { - |(filename, offset, length): (&str, u64, u64)| Query::FeatureRead { - name: filename.to_string(), - offset, length, - } - } - | preceded!(tag!("qRcmd,"), hex_byte_sequence) => { - |bytes| Query::Invoke(bytes) - } - | q_search_memory => { - |(address, length, bytes)| Query::SearchMemory { address, length, bytes } - } - | tag!("QStartNoAckMode") => { |_| Query::StartNoAckMode } - | preceded!(tag!("qAttached:"), hex_value) => { - |value| Query::Attached(Some(value)) - } - | tag!("qAttached") => { |_| Query::Attached(None) } - | tag!("qfThreadInfo") => { |_| Query::ThreadList(true) } - | tag!("qsThreadInfo") => { |_| Query::ThreadList(false) } - | tag!("QDisableRandomization:0") => { |_| Query::AddressRandomization(true) } - | tag!("QDisableRandomization:1") => { |_| Query::AddressRandomization(false) } - | tag!("QCatchSyscalls:0") => { |_| Query::CatchSyscalls(None) } - | preceded!(tag!("QCatchSyscalls:1"), - many0!(preceded!(tag!(";"), hex_value))) => { - |syscalls| Query::CatchSyscalls(Some(syscalls)) - } - | preceded!(tag!("QPassSignals:"), - separated_list_complete!(tag!(";"), hex_value)) => { - |signals| Query::PassSignals(signals) - } - | preceded!(tag!("QProgramSignals:"), - separated_nonempty_list_complete!(tag!(";"), hex_value)) => { - |signals| Query::ProgramSignals(signals) - } - | preceded!(tag!("qThreadExtraInfo,"), parse_thread_id) => { - |thread_id| Query::ThreadInfo(thread_id) - } - | tag!("qfProcessInfo") => { |_| Query::ProcessList(true) } - | tag!("qsProcessInfo") => { |_| Query::ProcessList(false) } - | tag!("qHostInfo") => { |_| Query::HostInfo } - ) -} - -// TODO: should the caller be responsible for determining whether they actually -// wanted a u32, or should we provide different versions of this function with -// extra checking? -named!(hex_value<&[u8], u64>, -map!(take_while1!(&nom::is_hex_digit), - |hex| { - let s = str::from_utf8(hex).unwrap(); - let r = u64::from_str_radix(s, 16); - r.unwrap() - })); - -named!(hex_digit<&[u8], char>, - one_of!("0123456789abcdefABCDEF")); - -named!(hex_byte<&[u8], u8>, - do_parse!( - digit0: hex_digit >> - digit1: hex_digit >> - ((16 * digit0.to_digit(16).unwrap() + digit1.to_digit(16).unwrap()) as u8) - ) -); - -named!(hex_byte_sequence<&[u8], Vec>, - many1!(hex_byte)); - -named!(write_memory<&[u8], (u64, u64, Vec)>, - complete!(do_parse!( - tag!("M") >> - address: hex_value >> - tag!(",") >> - length: hex_value >> - tag!(":") >> - data: hex_byte_sequence >> - (address, length, data)))); - -named!(binary_byte<&[u8], u8>, - alt_complete!( - preceded!(tag!("}"), take!(1)) => { |b: &[u8]| b[0] ^ 0x20 } | - take!(1) => { |b: &[u8]| b[0] })); - -named!(binary_byte_sequence<&[u8], Vec>, - many1!(binary_byte)); - -named!(write_memory_binary<&[u8], (u64, u64, Vec)>, - complete!(do_parse!( - tag!("X") >> - address: hex_value >> - tag!(",") >> - length: hex_value >> - tag!(":") >> - data: binary_byte_sequence >> - (address, length, data)))); - -named!(read_memory<&[u8], (u64, u64)>, - preceded!(tag!("m"), - separated_pair!(hex_value, - tag!(","), - hex_value))); - -named!(read_register<&[u8], u64>, - preceded!(tag!("p"), hex_value)); - -named!(write_register<&[u8], (u64, Vec)>, - preceded!(tag!("P"), - separated_pair!(hex_value, - tag!("="), - hex_byte_sequence))); - -named!(write_general_registers<&[u8], Vec>, - preceded!(tag!("G"), hex_byte_sequence)); - -// Helper for parse_thread_id that parses a single thread-id element. -named!(parse_thread_id_element<&[u8], Id>, - alt_complete!(tag!("0") => { |_| Id::Any } - | tag!("-1") => { |_| Id::All } - | hex_value => { |val: u64| Id::Id(val as u32) })); - -// Parse a thread-id. -named!(parse_thread_id<&[u8], ThreadId>, -alt_complete!(parse_thread_id_element => { |pid| ThreadId { pid, tid: Id::Any } } - | preceded!(tag!("p"), - separated_pair!(parse_thread_id_element, - tag!("."), - parse_thread_id_element)) => { - |pair: (Id, Id)| ThreadId { pid: pair.0, tid: pair.1 } - } - | preceded!(tag!("p"), parse_thread_id_element) => { - |id: Id| ThreadId { pid: id, tid: Id::All } - })); - -// Parse the T packet. -named!(parse_ping_thread<&[u8], ThreadId>, - preceded!(tag!("T"), parse_thread_id)); - -fn v_command(i: &[u8]) -> IResult<&[u8], Command<'_>> { - alt_complete!(i, - tag!("vCtrlC") => { |_| Command::CtrlC } - | preceded!(tag!("vCont"), - alt_complete!(tag!("?") => { |_| Command::VContSupported } - | many0!(do_parse!( - tag!(";") >> - action: alt_complete!(tag!("c") => { |_| VCont::Continue } - | preceded!(tag!("C"), hex_byte) => { |sig| VCont::ContinueWithSignal(sig) } - | tag!("s") => { |_| VCont::Step } - | preceded!(tag!("S"), hex_byte) => { |sig| VCont::StepWithSignal(sig) } - | tag!("t") => { |_| VCont::Stop } - | do_parse!(tag!("r") >> - start: hex_value >> - tag!(",") >> - end: hex_value >> - (start, end)) => { |(start, end)| VCont::RangeStep(start..end) } - ) >> - thread: opt!(complete!(preceded!(tag!(":"), parse_thread_id))) >> - (action, thread) - )) => { |actions| Command::VCont(actions) } - )) => { - |c| c - } - | preceded!(tag!("vKill;"), hex_value) => { - |pid| Command::Kill(Some(pid)) - } - // TODO: log the unknown command for debugging purposes. - | preceded!(tag!("v"), take_till!(|_| { false })) => { - |_| Command::UnknownV - }) -} - -// Parse the H packet. -named!(parse_h_packet<&[u8], ThreadId>, - preceded!(tag!("Hg"), parse_thread_id)); - -// Parse the D packet. -named!(parse_d_packet<&[u8], Option>, - alt_complete!(preceded!(tag!("D;"), hex_value) => { - |pid| Some(pid) - } - | tag!("D") => { |_| None })); - -#[derive(Copy, Clone)] -enum ZAction { - Insert, - Remove, -} - -named!(parse_z_action<&[u8], ZAction>, - alt_complete!(tag!("z") => { |_| ZAction::Remove } | - tag!("Z") => { |_| ZAction::Insert })); - -#[derive(Copy, Clone)] -enum ZType { - SoftwareBreakpoint, - HardwareBreakpoint, - WriteWatchpoint, - ReadWatchpoint, - AccessWatchpoint, -} - -named!(parse_z_type<&[u8], ZType>, - alt_complete!(tag!("0") => { |_| ZType::SoftwareBreakpoint } | - tag!("1") => { |_| ZType::HardwareBreakpoint } | - tag!("2") => { |_| ZType::WriteWatchpoint } | - tag!("3") => { |_| ZType::ReadWatchpoint } | - tag!("4") => { |_| ZType::AccessWatchpoint })); - -named!(parse_cond_or_command_expression<&[u8], Bytecode>, - do_parse!(tag!("X") >> - len: hex_value >> - tag!(",") >> - expr: take!(len) >> - (Bytecode { bytecode: expr.to_vec() }))); - -named!(parse_condition_list<&[u8], Vec>, - do_parse!(tag!(";") >> - list: many1!(parse_cond_or_command_expression) >> - (list))); - -fn maybe_condition_list(i: &[u8]) -> IResult<&[u8], Option>> { - // An Incomplete here really means "not enough input to match a - // condition list", and that's OK. An Error is *probably* that the - // input contains a command list rather than a condition list; the - // two are identical in their first character. So just ignore that - // FIXME. - match parse_condition_list(i) { - Done(rest, v) => Done(rest, Some(v)), - Incomplete(_i) => Done(i, None), - Error(_) => Done(i, None), - } -} - -named!(parse_command_list<&[u8], Vec>, - // FIXME we drop the persistence flag here. - do_parse!(tag!(";cmds") >> - list: alt_complete!(do_parse!(persist_flag: hex_value >> - tag!(",") >> - cmd_list: many1!(parse_cond_or_command_expression) >> - (cmd_list)) | - many1!(parse_cond_or_command_expression)) >> - (list))); - -fn maybe_command_list(i: &[u8]) -> IResult<&[u8], Option>> { - // An Incomplete here really means "not enough input to match a - // command list", and that's OK. - match parse_command_list(i) { - Done(rest, v) => Done(rest, Some(v)), - Incomplete(_i) => Done(i, None), - Error(e) => Error(e), - } -} - -named!(parse_cond_and_command_list<&[u8], (Option>, - Option>)>, - do_parse!(cond_list: maybe_condition_list >> - cmd_list: maybe_command_list >> - (cond_list, cmd_list))); - -fn parse_z_packet(i: &[u8]) -> IResult<&[u8], Command<'_>> { - let (rest, (action, type_, addr, kind)) = try_parse!( - i, - do_parse!( - action: parse_z_action - >> type_: parse_z_type - >> tag!(",") >> addr: hex_value - >> tag!(",") >> kind: hex_value - >> (action, type_, addr, kind) - ) - ); - - return match action { - ZAction::Insert => insert_command(rest, type_, addr, kind), - ZAction::Remove => Done(rest, remove_command(type_, addr, kind)), - }; - - fn insert_command( - rest: &[u8], - type_: ZType, - addr: u64, - kind: u64, - ) -> IResult<&[u8], Command<'_>> { - match type_ { - // Software and hardware breakpoints both permit optional condition - // lists and commands that are evaluated on the target when - // breakpoints are hit. - ZType::SoftwareBreakpoint | ZType::HardwareBreakpoint => { - let (rest, (cond_list, cmd_list)) = parse_cond_and_command_list(rest).unwrap(); - let c = (match type_ { - ZType::SoftwareBreakpoint => Command::InsertSoftwareBreakpoint, - ZType::HardwareBreakpoint => Command::InsertHardwareBreakpoint, - // Satisfy rustc's checking - _ => panic!("cannot get here"), - })(Breakpoint::new(addr, kind, cond_list, cmd_list)); - Done(rest, c) - } - ZType::WriteWatchpoint => Done( - rest, - Command::InsertWriteWatchpoint(Watchpoint::new(addr, kind)), - ), - ZType::ReadWatchpoint => Done( - rest, - Command::InsertReadWatchpoint(Watchpoint::new(addr, kind)), - ), - ZType::AccessWatchpoint => Done( - rest, - Command::InsertAccessWatchpoint(Watchpoint::new(addr, kind)), - ), - } - } - - fn remove_command<'a>(type_: ZType, addr: u64, kind: u64) -> Command<'a> { - match type_ { - ZType::SoftwareBreakpoint => { - Command::RemoveSoftwareBreakpoint(Breakpoint::new(addr, kind, None, None)) - } - ZType::HardwareBreakpoint => { - Command::RemoveHardwareBreakpoint(Breakpoint::new(addr, kind, None, None)) - } - ZType::WriteWatchpoint => Command::RemoveWriteWatchpoint(Watchpoint::new(addr, kind)), - ZType::ReadWatchpoint => Command::RemoveReadWatchpoint(Watchpoint::new(addr, kind)), - ZType::AccessWatchpoint => Command::RemoveAccessWatchpoint(Watchpoint::new(addr, kind)), - } - } -} - -fn command(i: &[u8]) -> IResult<&[u8], Command<'_>> { - alt!(i, - tag!("!") => { |_| Command::EnableExtendedMode } - | tag!("?") => { |_| Command::TargetHaltReason } - | tag!("c") => { |_| Command::VCont(vec![(VCont::Continue, None)]) } // simluate c as VCont;c - | parse_d_packet => { |pid| Command::Detach(pid) } - | tag!("g") => { |_| Command::ReadGeneralRegisters } - | write_general_registers => { |bytes| Command::WriteGeneralRegisters(bytes) } - | parse_h_packet => { |thread_id| Command::SetCurrentThread(thread_id) } - | tag!("k") => { |_| Command::Kill(None) } - | read_memory => { |(addr, length)| Command::ReadMemory(MemoryRegion::new(addr, length)) } - | write_memory => { |(addr, length, bytes)| Command::WriteMemory(MemoryRegion::new(addr, length), bytes) } - | read_register => { |regno| Command::ReadRegister(regno) } - | write_register => { |(regno, bytes)| Command::WriteRegister(regno, bytes) } - | query => { |q| Command::Query(q) } - | tag!("r") => { |_| Command::Reset } - | preceded!(tag!("R"), take!(2)) => { |_| Command::Reset } - | parse_ping_thread => { |thread_id| Command::PingThread(thread_id) } - | v_command => { |command| command } - | write_memory_binary => { |(addr, length, bytes)| Command::WriteMemory(MemoryRegion::new(addr, length), bytes) } - | parse_z_packet => { |command| command } - ) -} - -/// An error as returned by a `Handler` method. -#[allow(dead_code)] -#[derive(Debug)] -pub enum Error { - /// A plain error. The meaning of the value is not defined by the - /// protocol. Different values can therefore be used by a handler - /// for debugging purposes. - Error(u8), - /// The request is not implemented. Note that, in some cases, the - /// protocol implementation tells the client that a feature is implemented; - /// if the handler method then returns `Unimplemented`, the client will - /// be confused. So, normally it is best either to not implement - /// a `Handler` method, or to return `Error` from implementations. - Unimplemented, -} - -/// simple wrapper around File contents. TODO: extend, so we can return errors as well. -#[derive(Clone, Debug)] -pub struct FileData(pub String); - -/// The `qAttached` packet lets the client distinguish between -/// attached and created processes, so that it knows whether to send a -/// detach request when disconnecting. -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] -pub enum ProcessType { - /// The process already existed and was attached to. - Attached, - /// The process was created by the server. - Created, -} - -/// The possible reasons for a thread to stop. -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] -pub enum StopReason { - /// Process stopped due to a signal. - Signal(u8), - /// The process with the given PID exited with the given status. - Exited(u64, u8), - /// The process with the given PID terminated due to the given - /// signal. - ExitedWithSignal(u64, u8), - /// The indicated thread exited with the given status. - ThreadExited(ThreadId, u64), - /// There are no remaining resumed threads. - // FIXME we should report the 'no-resumed' feature in response to - // qSupports before emitting this; and we should also check that - // the client knows about it. - NoMoreThreads, - // FIXME implement these as well. These are used by the T packet, - // which can also send along registers. - // Watchpoint(u64), - // ReadWatchpoint(u64), - // AccessWatchpoint(u64), - // SyscallEntry(u8), - // SyscallExit(u8), - // LibraryChange, - // ReplayLogStart, - // ReplayLogEnd, - // SoftwareBreakpoint, - // HardwareBreakpoint, - // Fork(ThreadId), - // VFork(ThreadId), - // VForkDone, - // Exec(String), - // NewThread(ThreadId), -} - -/// This trait should be implemented by servers. Methods in the trait -/// generally default to returning `Error::Unimplemented`; but some -/// exceptions are noted below. Methods that must be implemented in -/// order for the server to work at all do not have a default -/// implementation. -pub trait Handler { - /// Return a vector of additional features supported by this handler. - /// Note that there currently is no way to override the built-in - /// features that are always handled by the protocol - /// implementation. - fn query_supported_features(&self) -> Vec { - vec![] - } - - /// Indicate whether the process in question already existed, and - /// was attached to; or whether it was created by this server. - fn attached(&self, _pid: Option) -> Result; - - /// Detach from the process. - fn detach(&self, _pid: Option) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Kill the indicated process. If no process is given, then the - /// precise effect is unspecified; but killing any or all - /// processes, or even rebooting an entire bare-metal target, - /// would be appropriate. - fn kill(&self, _pid: Option) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Check whether the indicated thread is alive. If alive, return - /// `()`. Otherwise, return an error. - fn ping_thread(&self, _id: ThreadId) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Read a memory region. - fn read_memory(&self, _region: MemoryRegion) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Write the provided bytes to memory at the given address. - fn write_memory(&self, _address: u64, _bytes: &[u8]) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Read the contents of the indicated register. The results - /// should be in target byte order. Note that a value-based API - /// is not provided here because on some architectures, there are - /// registers wider than ordinary integer types. - fn read_register(&self, _register: u64) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Set the contents of the indicated register to the given - /// contents. The contents are in target byte order. Note that a - /// value-based API is not provided here because on some - /// architectures, there are registers wider than ordinary integer - /// types. - fn write_register(&self, _register: u64, _contents: &[u8]) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Return the general registers. The registers are returned as a - /// vector of bytes, with the registers appearing contiguously in - /// a target-specific order, with the bytes laid out in the target - /// byte order. - fn read_general_registers(&self) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Write the general registers. The registers are specified as a - /// vector of bytes, with the registers appearing contiguously in - /// a target-specific order, with the bytes laid out in the target - /// byte order. - fn write_general_registers(&self, _contents: &[u8]) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Return the identifier of the current thread. - fn current_thread(&self) -> Result, Error> { - Ok(None) - } - - /// Set the current thread for future operations. - fn set_current_thread(&self, _id: ThreadId) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Search memory. The search begins at the given address, and - /// ends after length bytes have been searched. If the provided - /// bytes are not seen, `None` should be returned; otherwise, the - /// address at which the bytes were found should be returned. - fn search_memory( - &self, - _address: u64, - _length: u64, - _bytes: &[u8], - ) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Return the reason that the inferior has halted. - fn halt_reason(&self) -> Result; - - /// Invoke a command. The command is just a sequence of bytes - /// (typically ASCII characters), to be interpreted by the server - /// in any way it likes. The result is output to send back to the - /// client. This is used to implement gdb's `monitor` command. - fn invoke(&self, _: &[u8]) -> Result { - Err(Error::Unimplemented) - } - - /// Enable or disable address space randomization. This setting - /// should be used when launching a new process. - fn set_address_randomization(&self, _enable: bool) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Start or stop catching syscalls. If the argument is `None`, then - /// stop catching syscalls. Otherwise, start catching syscalls. - /// If any syscalls are specified, then only those need be caught; - /// however, it is ok to report syscall stops that aren't in the - /// list if that is convenient. - fn catch_syscalls(&self, _syscalls: Option>) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Set the list of "pass signals". A signal marked as a pass - /// signal can be delivered to the inferior. No stopping or - /// notification of the client is required. - fn set_pass_signals(&self, _signals: Vec) -> Result<(), Error> { - Ok(()) - } - - /// Set the list of "program signals". A signal marked as a - /// program signal can be delivered to the inferior; other signals - /// should be silently discarded. - fn set_program_signals(&self, _signals: Vec) -> Result<(), Error> { - Ok(()) - } - - /// Return information about a given thread. The returned - /// information is just a string description that can be presented - /// to the user. - fn thread_info(&self, _thread: ThreadId) -> Result { - Err(Error::Unimplemented) - } - - /// Return a list of all active thread IDs. GDB will call this in - /// a paging fashion: First query has `reset` set to true and - /// should reply with the first chunk of threads. Further queries - /// have `reset` set to false and should respond with a chunk of - /// remaining threads, until completion which should return an - /// empty list to signify it's the end. - /// - /// Each initial GDB connection will query this and the very first - /// thread ID will be stopped - so ensure the first ID is ready to - /// be stopped and inspected by GDB. - fn thread_list(&self, _reset: bool) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Return a list of all active processes for LLDB. Called in - /// a paging fashion: First query has `reset` set to true and - /// should reply with the first chunk of threads. Further queries - /// have `reset` set to false and should respond with a chunk of - /// remaining threads, until completion which should return an - /// empty list to signify it's the end. - fn process_list(&self, _reset: bool) -> Result, Error> { - Err(Error::Unimplemented) - } - - fn read_feature(&self, _name: String, _offset: u64, _length: u64) -> Result { - Err(Error::Unimplemented) - } - - /// Insert a software breakpoint. - fn insert_software_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Insert a hardware breakpoint. - fn insert_hardware_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Insert a write watchpoint. - fn insert_write_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Insert a read watchpoint. - fn insert_read_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Insert an access watchpoint. - fn insert_access_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Remove a software breakpoint. - fn remove_software_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Remove a hardware breakpoint. - fn remove_hardware_breakpoint(&self, _breakpoint: Breakpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Remove a write watchpoint. - fn remove_write_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Remove a read watchpoint. - fn remove_read_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Remove an access watchpoint. - fn remove_access_watchpoint(&self, _watchpoint: Watchpoint) -> Result<(), Error> { - Err(Error::Unimplemented) - } - - /// Query for a list of supported vCont features. - fn query_supported_vcont(&self) -> Result, Error> { - Err(Error::Unimplemented) - } - - /// Resume with different actions for each thread. Choose the - /// first matching thread in the list. - fn vcont(&self, _request: Vec<(VCont, Option)>) -> Result { - Err(Error::Unimplemented) - } - - /// Get target triple etc. - fn host_info(&self) -> Result { - Err(Error::Unimplemented) - } - - /// Return true when the gdb-event-loop should be exited. - fn should_cont(&self) -> Option; -} - -#[derive(Debug)] -pub enum Response<'a> { - Empty, - Ok, - Error(u8), - String(Cow<'a, str>), - Output(String), - Bytes(Vec), - CurrentThread(Option), - ProcessType(ProcessType), - Stopped(StopReason), - SearchResult(Option), - VContFeatures(Cow<'static, [VContFeature]>), - ThreadList(Vec), - ProcessList(Vec), - File(FileData), -} - -impl<'a, T> From> for Response<'a> -where - Response<'a>: From, -{ - fn from(result: Result) -> Self { - match result { - Result::Ok(val) => val.into(), - Result::Err(Error::Error(val)) => Response::Error(val), - Result::Err(Error::Unimplemented) => { - println!("Unimplemented!"); - Response::Empty - } - } - } -} - -impl<'a> From<()> for Response<'a> { - fn from(_: ()) -> Self { - Response::Ok - } -} - -impl<'a> From> for Response<'a> { - fn from(response: Vec) -> Self { - Response::Bytes(response) - } -} - -impl<'a> From for Response<'a> { - fn from(response: FileData) -> Self { - Response::File(response) - } -} - -impl<'a> From> for Response<'a> { - fn from(response: Option) -> Self { - Response::CurrentThread(response) - } -} - -// This seems a bit specific -- what if some other handler method -// wants to return an Option? -impl<'a> From> for Response<'a> { - fn from(response: Option) -> Self { - Response::SearchResult(response) - } -} - -impl<'a> From for Response<'a> { - fn from(process_type: ProcessType) -> Self { - Response::ProcessType(process_type) - } -} - -impl<'a> From for Response<'a> { - fn from(reason: StopReason) -> Self { - Response::Stopped(reason) - } -} - -impl<'a> From for Response<'a> { - fn from(reason: String) -> Self { - Response::String(Cow::Owned(reason) as Cow<'_, str>) - } -} - -impl<'a> From> for Response<'a> { - fn from(features: Cow<'static, [VContFeature]>) -> Self { - Response::VContFeatures(features) - } -} - -impl<'a> From> for Response<'a> { - fn from(threads: Vec) -> Self { - Response::ThreadList(threads) - } -} - -impl<'a> From> for Response<'a> { - fn from(procs: Vec) -> Self { - Response::ProcessList(procs) - } -} - -fn get_thread_id(thread_id: ThreadId) -> String { - let mut tid = String::new(); - // LLDB does not support multiprocess syntax! - // Always just send thread - /*tid.push_str("p"); - match thread_id.pid { - Id::All => tid.push_str("-1"), - Id::Any => tid.push_str( "0"), - Id::Id(num) => tid.push_str(&format!("{:x}", num)), - }; - tid.push_str(".");*/ - match thread_id.tid { - Id::All => tid.push_str("-1"), - Id::Any => tid.push('0'), - Id::Id(num) => tid.push_str(&format!("{:x}", num)), - }; - tid -} - -fn get_process_info(p: &ProcessInfo) -> String { - let mut out = String::new(); - out.push_str("pid:"); - match p.pid { - Id::All => out.push_str("-1"), - Id::Any => out.push('0'), - Id::Id(num) => out.push_str(&format!("{:x}", num)), - }; - out.push_str(&format!("name:{}", p.name)); - out.push_str(&format!("triple:{}", p.triple)); - out -} - -/// get a byte vector we can send to remote from a response -impl<'a> From> for Vec { - fn from(response: Response<'_>) -> Vec { - trace!("Response: {:?}", response); - - let mut rsp = String::new(); - match response { - Response::Ok => "OK".into(), - Response::Empty => "".into(), - Response::Error(val) => format!("E{:02x}", val), - Response::String(s) => format!("{}", s), - Response::Output(s) => format!("O{}", s.as_bytes().to_hex()), - Response::Bytes(bytes) => bytes.to_hex(), - Response::File(data) => { - if data.0.is_empty() { - "l".into() - } else { - // LLDB is weird and does not decode our hex. - format!("m{}", data.0 /*.as_bytes().to_hex()*/) - } - } - Response::CurrentThread(tid) => { - // This is incorrect if multiprocess hasn't yet been enabled. - match tid { - None => "OK".into(), - Some(thread_id) => format!("QC{}", get_thread_id(thread_id)), - } - } - Response::ProcessType(process_type) => match process_type { - ProcessType::Attached => "1".into(), - ProcessType::Created => "0".into(), - }, - Response::SearchResult(maybe_addr) => match maybe_addr { - Some(addr) => format!("1,{:x}", addr), - None => "0".into(), - }, - Response::Stopped(stop_reason) => { - match stop_reason { - StopReason::Signal(signo) => format!("S{:02x}", signo), - StopReason::Exited(pid, status) => { - // Non-multi-process gdb only accepts 2 hex digits - // for the status. - format!("W{:02x};process:{:x}", status, pid) - } - StopReason::ExitedWithSignal(pid, status) => { - // Non-multi-process gdb only accepts 2 hex digits - // for the status. - format!("X{:x};process:{:x}", status, pid) - } - StopReason::ThreadExited(thread_id, status) => { - format!("w{:x}{};", status, get_thread_id(thread_id)) - } - StopReason::NoMoreThreads => "N".to_string(), - } - } - Response::VContFeatures(features) => { - rsp.push_str("vCont"); - for &feature in &*features { - rsp.push_str(&format!(";{}", feature as u8 as char)); - } - rsp - } - Response::ThreadList(threads) => { - if threads.is_empty() { - "l".into() - } else { - rsp.push('m'); - for (i, &id) in threads.iter().enumerate() { - // Write separator - if i != 0 { - rsp.push(','); - } - rsp.push_str(&get_thread_id(id)); - } - rsp - } - } - Response::ProcessList(procs) => { - if procs.is_empty() { - "E00".into() // lldb spec just says error Exx where xx is hex - } else { - rsp.push('m'); - for (i, p) in procs.iter().enumerate() { - // Write separator - if i != 0 { - rsp.push(','); - } - rsp.push_str(&get_process_info(p)); - } - rsp - } - } - } - .as_bytes() - .to_vec() - } -} - -fn handle_supported_features<'a, H>( - handler: &H, - _features: &[GDBFeatureSupported<'a>], -) -> Response<'static> -where - H: Handler, -{ - let mut features = vec![ - format!("PacketSize={}", BUF_SIZE), - //"QStartNoAckMode+".to_string(), gdb-protocol crate does not support no ack mode - //"multiprocess+".to_string(), llvm does not support multiprocess syntax - //"QDisableRandomization+".to_string(), - //"QCatchSyscalls+".to_string(), - //"QPassSignals+".to_string(), - //"QProgramSignals+".to_string(), - ]; - let mut new_features = handler.query_supported_features(); - features.append(&mut new_features); - Response::String(Cow::Owned(features.join(";")) as Cow<'_, str>) -} - -/// Handle a single packet `data` with `handler` and return response -pub fn handle_packet<'a, H>(data: &[u8], handler: &'a H) -> Result, Error> -where - H: Handler, -{ - let mut _no_ack_mode = false; - let response = if let Done(_, command) = command(data) { - debug!( - "Successfully parsed command: {} into {:?}", - String::from_utf8_lossy(data), - command - ); - match command { - // We unconditionally support extended mode. - Command::EnableExtendedMode => Response::Ok, - Command::TargetHaltReason => handler.halt_reason()?.into(), - Command::ReadGeneralRegisters => handler.read_general_registers()?.into(), - Command::WriteGeneralRegisters(bytes) => { - handler.write_general_registers(&bytes[..])?.into() - } - Command::Kill(None) => { - // The k packet requires no response, so purposely - // ignore the result. - drop(handler.kill(None)); - Response::Empty - } - Command::Kill(pid) => handler.kill(pid)?.into(), - Command::Reset => Response::Empty, - Command::ReadRegister(regno) => handler.read_register(regno)?.into(), - Command::WriteRegister(regno, bytes) => { - handler.write_register(regno, &bytes[..])?.into() - } - Command::ReadMemory(region) => handler.read_memory(region)?.into(), - Command::WriteMemory(region, bytes) => { - // The docs don't really say what to do if the given - // length disagrees with the number of bytes sent, so - // just error if they disagree. - if region.length as usize != bytes.len() { - Response::Error(1) - } else { - handler.write_memory(region.address, &bytes[..])?.into() - } - } - Command::SetCurrentThread(thread_id) => handler.set_current_thread(thread_id)?.into(), - Command::Detach(pid) => handler.detach(pid)?.into(), - - Command::Query(Query::Attached(pid)) => handler.attached(pid)?.into(), - Command::Query(Query::CurrentThread) => handler.current_thread()?.into(), - Command::Query(Query::Invoke(cmd)) => match handler.invoke(&cmd[..]) { - Result::Ok(val) => { - if val.is_empty() { - Response::Ok - } else { - Response::Output(val) - } - } - Result::Err(Error::Error(val)) => Response::Error(val), - Result::Err(Error::Unimplemented) => Response::Empty, - }, - Command::Query(Query::SearchMemory { - address, - length, - bytes, - }) => handler.search_memory(address, length, &bytes[..])?.into(), - Command::Query(Query::SupportedFeatures(features)) => { - handle_supported_features(handler, &features) - } - Command::Query(Query::StartNoAckMode) => { - _no_ack_mode = true; - Response::Empty // gdb-protocol crate does not support no ack mode! - } - Command::Query(Query::AddressRandomization(randomize)) => { - handler.set_address_randomization(randomize)?.into() - } - Command::Query(Query::CatchSyscalls(calls)) => handler.catch_syscalls(calls)?.into(), - Command::Query(Query::PassSignals(signals)) => { - handler.set_pass_signals(signals)?.into() - } - Command::Query(Query::ProgramSignals(signals)) => { - handler.set_program_signals(signals)?.into() - } - Command::Query(Query::ThreadInfo(thread_info)) => { - handler.thread_info(thread_info)?.into() - } - Command::Query(Query::ThreadList(reset)) => handler.thread_list(reset)?.into(), - Command::Query(Query::ProcessList(reset)) => handler.process_list(reset)?.into(), - Command::Query(Query::HostInfo) => handler.host_info()?.into(), - Command::Query(Query::FeatureRead { - name, - offset, - length, - }) => handler.read_feature(name, offset, length)?.into(), - Command::PingThread(thread_id) => handler.ping_thread(thread_id)?.into(), - // Empty means "not implemented". - Command::CtrlC => Response::Empty, - - // Unknown v commands are required to give an empty - // response. - Command::UnknownV => Response::Empty, - - Command::InsertSoftwareBreakpoint(bp) => handler.insert_software_breakpoint(bp)?.into(), - Command::InsertHardwareBreakpoint(bp) => handler.insert_hardware_breakpoint(bp)?.into(), - Command::InsertWriteWatchpoint(wp) => handler.insert_write_watchpoint(wp)?.into(), - Command::InsertReadWatchpoint(wp) => handler.insert_read_watchpoint(wp)?.into(), - Command::InsertAccessWatchpoint(wp) => handler.insert_access_watchpoint(wp)?.into(), - Command::RemoveSoftwareBreakpoint(bp) => handler.remove_software_breakpoint(bp)?.into(), - Command::RemoveHardwareBreakpoint(bp) => handler.remove_hardware_breakpoint(bp)?.into(), - Command::RemoveWriteWatchpoint(wp) => handler.remove_write_watchpoint(wp)?.into(), - Command::RemoveReadWatchpoint(wp) => handler.remove_read_watchpoint(wp)?.into(), - Command::RemoveAccessWatchpoint(wp) => handler.remove_access_watchpoint(wp)?.into(), - Command::VContSupported => handler.query_supported_vcont()?.into(), - Command::VCont(list) => handler.vcont(list)?.into(), - } - } else { - info!( - "Command could not be parsed: {}", - String::from_utf8_lossy(data) - ); - Response::Empty - }; - //Ok(no_ack_mode) - Ok(response) -} - -#[test] -fn test_gdbfeaturesupported() { - assert_eq!( - gdbfeaturesupported(&b"multiprocess+"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::Yes(GDBFeature::multiprocess), FeatureSupported::Yes) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"xmlRegisters=i386"[..]), - Done( - &b""[..], - GDBFeatureSupported( - Known::Yes(GDBFeature::xmlRegisters), - FeatureSupported::Value("i386") - ) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"qRelocInsn-"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::Yes(GDBFeature::qRelocInsn), FeatureSupported::No) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"vfork-events+"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::Yes) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"vfork-events-"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::No) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"unknown-feature+"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::No("unknown-feature"), FeatureSupported::Yes) - ) - ); - assert_eq!( - gdbfeaturesupported(&b"unknown-feature-"[..]), - Done( - &b""[..], - GDBFeatureSupported(Known::No("unknown-feature"), FeatureSupported::No) - ) - ); -} - -#[test] -fn test_gdbfeature() { - assert_eq!( - gdbfeature(&b"multiprocess"[..]), - Done(&b""[..], Known::Yes(GDBFeature::multiprocess)) - ); - assert_eq!( - gdbfeature(&b"fork-events"[..]), - Done(&b""[..], Known::Yes(GDBFeature::fork_events)) - ); - assert_eq!( - gdbfeature(&b"some-unknown-feature"[..]), - Done(&b""[..], Known::No("some-unknown-feature")) - ); -} - -#[test] -fn test_query() { - // From a gdbserve packet capture. - let b = concat!( - "qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;", - "vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;", - "xmlRegisters=i386" - ); - assert_eq!( - query(b.as_bytes()), - Done( - &b""[..], - Query::SupportedFeatures(vec![ - GDBFeatureSupported(Known::Yes(GDBFeature::multiprocess), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::swbreak), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::hwbreak), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::qRelocInsn), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::fork_events), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::vfork_events), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::exec_events), FeatureSupported::Yes), - GDBFeatureSupported( - Known::Yes(GDBFeature::vContSupported), - FeatureSupported::Yes - ), - GDBFeatureSupported(Known::Yes(GDBFeature::QThreadEvents), FeatureSupported::Yes), - GDBFeatureSupported(Known::Yes(GDBFeature::no_resumed), FeatureSupported::Yes), - GDBFeatureSupported( - Known::Yes(GDBFeature::xmlRegisters), - FeatureSupported::Value("i386") - ), - ]) - ) - ); -} - -#[test] -fn test_hex_value() { - assert_eq!(hex_value(&b""[..]), Incomplete(Needed::Size(1))); - assert_eq!(hex_value(&b","[..]), Error(nom::ErrorKind::TakeWhile1)); - assert_eq!(hex_value(&b"a"[..]), Done(&b""[..], 0xa)); - assert_eq!(hex_value(&b"10,"[..]), Done(&b","[..], 0x10)); - assert_eq!(hex_value(&b"ff"[..]), Done(&b""[..], 0xff)); -} - -#[test] -fn test_parse_thread_id_element() { - assert_eq!(parse_thread_id_element(&b"0"[..]), Done(&b""[..], Id::Any)); - assert_eq!(parse_thread_id_element(&b"-1"[..]), Done(&b""[..], Id::All)); - assert_eq!( - parse_thread_id_element(&b"23"[..]), - Done(&b""[..], Id::Id(0x23)) - ); -} - -#[test] -fn test_parse_thread_id() { - assert_eq!( - parse_thread_id(&b"0"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::Any, - tid: Id::Any - } - ) - ); - assert_eq!( - parse_thread_id(&b"-1"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::All, - tid: Id::Any - } - ) - ); - assert_eq!( - parse_thread_id(&b"23"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::Id(0x23), - tid: Id::Any - } - ) - ); - - assert_eq!( - parse_thread_id(&b"p23"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::Id(0x23), - tid: Id::All - } - ) - ); - - assert_eq!( - parse_thread_id(&b"p0.0"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::Any, - tid: Id::Any - } - ) - ); - assert_eq!( - parse_thread_id(&b"p-1.23"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::All, - tid: Id::Id(0x23) - } - ) - ); - assert_eq!( - parse_thread_id(&b"pff.23"[..]), - Done( - &b""[..], - ThreadId { - pid: Id::Id(0xff), - tid: Id::Id(0x23) - } - ) - ); -} - -#[test] -fn test_parse_v_commands() { - assert_eq!( - v_command(&b"vKill;33"[..]), - Done(&b""[..], Command::Kill(Some(0x33))) - ); - assert_eq!(v_command(&b"vCtrlC"[..]), Done(&b""[..], Command::CtrlC)); - assert_eq!( - v_command(&b"vMustReplyEmpty"[..]), - Done(&b""[..], Command::UnknownV) - ); - assert_eq!( - v_command(&b"vFile:close:0"[..]), - Done(&b""[..], Command::UnknownV) - ); - - assert_eq!( - v_command(&b"vCont?"[..]), - Done(&b""[..], Command::VContSupported) - ); - assert_eq!( - v_command(&b"vCont"[..]), - Done(&b""[..], Command::VCont(Vec::new())) - ); - assert_eq!( - v_command(&b"vCont;c"[..]), - Done(&b""[..], Command::VCont(vec![(VCont::Continue, None)])) - ); - assert_eq!( - v_command(&b"vCont;r1,2:p34.56;SAD:-1;c"[..]), - Done( - &b""[..], - Command::VCont(vec![ - ( - VCont::RangeStep(1..2), - Some(ThreadId { - pid: Id::Id(0x34), - tid: Id::Id(0x56) - }) - ), - ( - VCont::StepWithSignal(0xAD), - Some(ThreadId { - pid: Id::All, - tid: Id::Any - }) - ), - (VCont::Continue, None) - ]) - ) - ); -} - -#[test] -fn test_parse_d_packets() { - assert_eq!(parse_d_packet(&b"D"[..]), Done(&b""[..], None)); - assert_eq!(parse_d_packet(&b"D;f0"[..]), Done(&b""[..], Some(240))); -} - -#[test] -fn test_parse_write_memory() { - assert_eq!( - write_memory(&b"Mf0,3:ff0102"[..]), - Done(&b""[..], (240, 3, vec![255, 1, 2])) - ); -} - -#[test] -fn test_parse_write_memory_binary() { - assert_eq!( - write_memory_binary(&b"Xf0,1: "[..]), - Done(&b""[..], (240, 1, vec![0x20])) - ); - assert_eq!( - write_memory_binary(&b"X90,10:}\x5d"[..]), - Done(&b""[..], (144, 16, vec![0x7d])) - ); - assert_eq!( - write_memory_binary(&b"X5,100:}\x5d}\x03"[..]), - Done(&b""[..], (5, 256, vec![0x7d, 0x23])) - ); - assert_eq!( - write_memory_binary(&b"Xff,2:}\x04\x9a"[..]), - Done(&b""[..], (255, 2, vec![0x24, 0x9a])) - ); - assert_eq!( - write_memory_binary(&b"Xff,2:\xce}\x0a\x9a"[..]), - Done(&b""[..], (255, 2, vec![0xce, 0x2a, 0x9a])) - ); -} - -#[test] -fn test_parse_qrcmd() { - assert_eq!( - query(&b"qRcmd,736f6d657468696e67"[..]), - Done(&b""[..], Query::Invoke(b"something".to_vec())) - ); -} - -#[test] -fn test_parse_randomization() { - assert_eq!( - query(&b"QDisableRandomization:0"[..]), - Done(&b""[..], Query::AddressRandomization(true)) - ); - assert_eq!( - query(&b"QDisableRandomization:1"[..]), - Done(&b""[..], Query::AddressRandomization(false)) - ); -} - -#[test] -fn test_parse_syscalls() { - assert_eq!( - query(&b"QCatchSyscalls:0"[..]), - Done(&b""[..], Query::CatchSyscalls(None)) - ); - assert_eq!( - query(&b"QCatchSyscalls:1"[..]), - Done(&b""[..], Query::CatchSyscalls(Some(vec![]))) - ); - assert_eq!( - query(&b"QCatchSyscalls:1;0;1;ff"[..]), - Done(&b""[..], Query::CatchSyscalls(Some(vec![0, 1, 255]))) - ); -} - -#[test] -fn test_parse_signals() { - assert_eq!( - query(&b"QPassSignals:"[..]), - Done(&b""[..], Query::PassSignals(vec![])) - ); - assert_eq!( - query(&b"QPassSignals:0"[..]), - Done(&b""[..], Query::PassSignals(vec![0])) - ); - assert_eq!( - query(&b"QPassSignals:1;2;ff"[..]), - Done(&b""[..], Query::PassSignals(vec![1, 2, 255])) - ); - assert_eq!( - query(&b"QProgramSignals:0"[..]), - Done(&b""[..], Query::ProgramSignals(vec![0])) - ); - assert_eq!( - query(&b"QProgramSignals:1;2;ff"[..]), - Done(&b""[..], Query::ProgramSignals(vec![1, 2, 255])) - ); -} - -#[test] -fn test_thread_info() { - assert_eq!( - query(&b"qThreadExtraInfo,ffff"[..]), - Done( - &b""[..], - Query::ThreadInfo(ThreadId { - pid: Id::Id(65535), - tid: Id::Any - }) - ) - ); -} - -#[test] -fn test_thread_list() { - assert_eq!( - query(&b"qfThreadInfo"[..]), - Done(&b""[..], Query::ThreadList(true)) - ); - assert_eq!( - query(&b"qsThreadInfo"[..]), - Done(&b""[..], Query::ThreadList(false)) - ); -} - -#[test] -fn test_parse_write_register() { - assert_eq!( - write_register(&b"Pff=1020"[..]), - Done(&b""[..], (255, vec![16, 32])) - ); -} - -#[test] -fn test_parse_write_general_registers() { - assert_eq!( - write_general_registers(&b"G0001020304"[..]), - Done(&b""[..], vec![0, 1, 2, 3, 4]) - ); -} - -#[cfg(test)] -macro_rules! bytecode { - ($elem:expr; $n:expr) => (Bytecode { bytecode: vec![$elem; $n] }); - ($($x:expr),*) => (Bytecode { bytecode: vec![$($x),*] }) -} - -#[test] -fn test_breakpoints() { - assert_eq!( - parse_z_packet(&b"Z0,1ff,0"[..]), - Done( - &b""[..], - Command::InsertSoftwareBreakpoint(Breakpoint::new(0x1ff, 0, None, None)) - ) - ); - assert_eq!( - parse_z_packet(&b"z0,1fff,0"[..]), - Done( - &b""[..], - Command::RemoveSoftwareBreakpoint(Breakpoint::new(0x1fff, 0, None, None)) - ) - ); - assert_eq!( - parse_z_packet(&b"Z1,ae,0"[..]), - Done( - &b""[..], - Command::InsertHardwareBreakpoint(Breakpoint::new(0xae, 0, None, None)) - ) - ); - assert_eq!( - parse_z_packet(&b"z1,aec,0"[..]), - Done( - &b""[..], - Command::RemoveHardwareBreakpoint(Breakpoint::new(0xaec, 0, None, None)) - ) - ); - assert_eq!( - parse_z_packet(&b"Z2,4cc,2"[..]), - Done( - &b""[..], - Command::InsertWriteWatchpoint(Watchpoint::new(0x4cc, 2)) - ) - ); - assert_eq!( - parse_z_packet(&b"z2,4ccf,4"[..]), - Done( - &b""[..], - Command::RemoveWriteWatchpoint(Watchpoint::new(0x4ccf, 4)) - ) - ); - assert_eq!( - parse_z_packet(&b"Z3,7777,4"[..]), - Done( - &b""[..], - Command::InsertReadWatchpoint(Watchpoint::new(0x7777, 4)) - ) - ); - assert_eq!( - parse_z_packet(&b"z3,77778,8"[..]), - Done( - &b""[..], - Command::RemoveReadWatchpoint(Watchpoint::new(0x77778, 8)) - ) - ); - assert_eq!( - parse_z_packet(&b"Z4,7777,10"[..]), - Done( - &b""[..], - Command::InsertAccessWatchpoint(Watchpoint::new(0x7777, 16)) - ) - ); - assert_eq!( - parse_z_packet(&b"z4,77778,20"[..]), - Done( - &b""[..], - Command::RemoveAccessWatchpoint(Watchpoint::new(0x77778, 32)) - ) - ); - - assert_eq!( - parse_z_packet(&b"Z0,1ff,2;X1,0"[..]), - Done( - &b""[..], - Command::InsertSoftwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - Some(vec![bytecode!(b'0')]), - None - )) - ) - ); - assert_eq!( - parse_z_packet(&b"Z1,1ff,2;X1,0"[..]), - Done( - &b""[..], - Command::InsertHardwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - Some(vec![bytecode!(b'0')]), - None - )) - ) - ); - - assert_eq!( - parse_z_packet(&b"Z0,1ff,2;cmdsX1,z"[..]), - Done( - &b""[..], - Command::InsertSoftwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - None, - Some(vec![bytecode!(b'z')]) - )) - ) - ); - assert_eq!( - parse_z_packet(&b"Z1,1ff,2;cmdsX1,z"[..]), - Done( - &b""[..], - Command::InsertHardwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - None, - Some(vec![bytecode!(b'z')]) - )) - ) - ); - - assert_eq!( - parse_z_packet(&b"Z0,1ff,2;X1,0;cmdsX1,a"[..]), - Done( - &b""[..], - Command::InsertSoftwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - Some(vec![bytecode!(b'0')]), - Some(vec![bytecode!(b'a')]) - )) - ) - ); - assert_eq!( - parse_z_packet(&b"Z1,1ff,2;X1,0;cmdsX1,a"[..]), - Done( - &b""[..], - Command::InsertHardwareBreakpoint(Breakpoint::new( - 0x1ff, - 2, - Some(vec![bytecode!(b'0')]), - Some(vec![bytecode!(b'a')]) - )) - ) - ); -} - -#[test] -fn test_cond_or_command_list() { - assert_eq!( - parse_condition_list(&b";X1,a"[..]), - Done(&b""[..], vec![bytecode!(b'a')]) - ); - assert_eq!( - parse_condition_list(&b";X2,ab"[..]), - Done(&b""[..], vec![bytecode!(b'a', b'b')]) - ); - assert_eq!( - parse_condition_list(&b";X1,zX1,y"[..]), - Done(&b""[..], vec![bytecode!(b'z'), bytecode!(b'y')]) - ); - assert_eq!( - parse_condition_list(&b";X1,zX10,yyyyyyyyyyyyyyyy"[..]), - Done(&b""[..], vec![bytecode!(b'z'), bytecode![b'y'; 16]]) - ); - - assert_eq!( - parse_command_list(&b";cmdsX1,a"[..]), - Done(&b""[..], vec![bytecode!(b'a')]) - ); - assert_eq!( - parse_command_list(&b";cmdsX2,ab"[..]), - Done(&b""[..], vec![bytecode!(b'a', b'b')]) - ); - assert_eq!( - parse_command_list(&b";cmdsX1,zX1,y"[..]), - Done(&b""[..], vec![bytecode!(b'z'), bytecode!(b'y')]) - ); - assert_eq!( - parse_command_list(&b";cmdsX1,zX10,yyyyyyyyyyyyyyyy"[..]), - Done(&b""[..], vec![bytecode!(b'z'), bytecode![b'y'; 16]]) - ); -} diff --git a/src/lib.rs b/src/lib.rs index 261f7ce0..b99fff6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,7 @@ mod macros; #[macro_use] extern crate log; -pub mod arch; pub mod consts; -pub mod debug_manager; -pub mod gdb_parser; #[cfg(target_os = "linux")] pub mod linux; #[cfg(target_os = "linux")] @@ -26,5 +23,4 @@ pub mod shared_queue; pub mod utils; pub mod vm; -pub use arch::*; pub use os::uhyve::Uhyve; diff --git a/src/linux/gdb.rs b/src/linux/gdb.rs deleted file mode 100644 index 1ad8cbd6..00000000 --- a/src/linux/gdb.rs +++ /dev/null @@ -1,791 +0,0 @@ -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use kvm_bindings::*; -use kvm_ioctls::{VcpuExit, VcpuFd}; -use libc::ioctl; -use rustc_serialize::hex::ToHex; -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::HashMap; -use std::os::unix::io::AsRawFd; -use std::{io, slice}; - -use crate::arch::x86; -use crate::gdb_parser::{ - get_max_subslice, Breakpoint, Error, FileData, Handler, Id, MemoryRegion, ProcessInfo, - ProcessType, StopReason, ThreadId, VCont, VContFeature, Watchpoint, -}; -use crate::linux::vcpu::UhyveCPU; -use crate::vm::VirtualCPU; -use log::{debug, error, info}; - -/// Debugging Stub for linux/x64 -/// Currently supported features: -/// - Register read/write -/// - Memory read/write -/// - Software breakpoints (int3) -/// - Hardware breakpoints -/// - Execute / Write / Read-Write -/// - Singlestepping / Continue -/// - LLDB support (transmit info about arch/reg layout) -/// - read of feature target.xml [i386-64bit.xml] -/// - qHostInfo triple sends x86_64-unknown-hermit - -const INT3: &[u8] = &[0xcc]; - -impl UhyveCPU { - /// Called on Trap. Creates Handler. - /// Enter gdb-event-loop until gdb tells us to continue. Set singlestep mode if necessary and return - pub fn gdb_handle_exception(&mut self, signal: Option>) { - debug!("Handling debug exception!"); - if let Some(dbg) = &mut self.dbg { - let dbgarc = dbg.clone(); - let dbg = dbgarc.lock().expect("No gdb available!"); - - let (mut cmdhandler, signal) = if let Some(signal) = signal { - // send signal with which we are stopped. Hardcoded to 5 for now (TODO) - ( - CmdHandler::new(self, &dbg.state, signal), - Some(StopReason::Signal(5)), - ) - } else { - // target stopped on boot. No signal recv'd yet. Pretend debug signal? Not used rn anyways. - (CmdHandler::new(self, &dbg.state, VcpuExit::Debug), None) - }; - - // enter command-handler, stay there until we receive a continue signal - let vcont = dbg - .handle_commands(&mut cmdhandler, signal) - .unwrap_or_else(|error| { - error!("Cannot handle debugging commands: {:?}", error); - // always continue - VCont::Continue - }); - - let hwbr = dbg.state.borrow().get_hardware_breakpoints(); - - // handler returned with a continuation command, - // determine if we should continue single-stepped or until next trap - match vcont { - VCont::Continue | VCont::ContinueWithSignal(_) => { - info!("Continuing execution.."); - self.kvm_change_guestdbg(false, hwbr.as_ref()); // TODO: optimize this, don't call too often? - } - VCont::Step | VCont::StepWithSignal(_) => { - info!("Starting Single Stepping.."); - self.kvm_change_guestdbg(true, hwbr.as_ref()); // TODO: optimize this, don't call too often? - } - _ => error!("Unknown Handler exit reason!"), - } - } else { - info!("Debugging disabled, ignoring exception {:?}.", signal); - }; - } - - unsafe fn read_mem(&self, guest_addr: usize, len: usize) -> &[u8] { - let phys = self.virt_to_phys(guest_addr); - let host = self.host_address(phys); - - slice::from_raw_parts(host as *mut u8, len) - } - - unsafe fn write_mem(&self, guest_addr: usize, data: &[u8]) { - let phys = self.virt_to_phys(guest_addr); - let host = self.host_address(phys); - - let mem: &mut [u8] = slice::from_raw_parts_mut(host as *mut u8, data.len()); - - mem.copy_from_slice(data); - } - - fn kvm_change_guestdbg( - &mut self, - single_step: bool, - hwbr: Option<&x86::HWBreakpoints>, /*&HashMap*/ - ) { - debug!("KVM: Enable guest debug. SS:{}", single_step); - let mut dbg = kvm_guest_debug { - control: KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP, // KVM_GUESTDBG_USE_HW_BP - pad: 0, - arch: kvm_guest_debug_arch { debugreg: [0; 8] }, - }; - - if single_step { - dbg.control |= KVM_GUESTDBG_SINGLESTEP; - } - - error!("Setting guestdbg"); - if let Some(hwbr) = hwbr { - // arch.debugreg has 4 address registers (0-3), a control reg (7) and status reg (6). 4-5 are unused. - dbg.control |= KVM_GUESTDBG_USE_HW_BP; - for i in 0..4 { - dbg.arch.debugreg[i] = hwbr.get_addr(i).unwrap(); - } - dbg.arch.debugreg[7] = hwbr.get_dr7(); - } - - let ret = unsafe { - ioctl( - self.get_vcpu().as_raw_fd(), - 0x4048_ae9b, /* KVM_SET_GUEST_DEBUG, from https://android.googlesource.com/platform/system/sepolicy/+/master/public/ioctl_defines */ - &dbg, - ) - }; - if ret < 0 { - panic!( - "Could not change KVM debugging state: {:?}", - io::Error::last_os_error() - ) - } - } -} - -pub struct State { - breakpoints: HashMap, - breakpoints_hw: HashMap, -} - -#[derive(Debug)] -enum BreakpointKind { - Breakpoint, - WatchWrite, - WatchAccess, -} - -#[derive(Debug)] -struct SWBreakpoint { - bp: Breakpoint, - insn: u8, -} - -#[derive(Debug)] -struct HWBreakpoint { - kind: BreakpointKind, - addr: u64, - n_bytes: u64, -} - -impl State { - pub(crate) fn new() -> Self { - Self { - breakpoints: HashMap::new(), - breakpoints_hw: HashMap::new(), - } - } - - pub fn get_hardware_breakpoints(&self) -> Option { - if self.breakpoints_hw.is_empty() { - return None; - } - - if self.breakpoints_hw.len() > 4 { - error!("Cannot set more than 4 hardware breakpoints!") - } - - let mut hwbr = x86::HWBreakpoints::default(); - - for (i, (addr, bp)) in self.breakpoints_hw.iter().take(4).enumerate() { - hwbr.0[i].addr = *addr as _; - hwbr.0[i].is_local = true; - hwbr.0[i].is_global = true; - hwbr.0[i].trigger = match bp.kind { - BreakpointKind::Breakpoint => x86::BreakTrigger::Ex, - BreakpointKind::WatchWrite => x86::BreakTrigger::W, - BreakpointKind::WatchAccess => x86::BreakTrigger::RW, - }; - hwbr.0[i].size = match bp.n_bytes { - 1 => x86::BreakSize::B1, - 2 => x86::BreakSize::B2, - 4 => x86::BreakSize::B4, - 8 => x86::BreakSize::B8, - _ => { - error!("Unknown watchpoint size!"); - x86::BreakSize::B1 - } - }; - } - - Some(hwbr) - } -} - -pub struct CmdHandler<'a> { - // use RefCells to not break existing api of gdb_parser (no mutability in handler) - resume: RefCell>, - current_cpu: RefCell<&'a mut UhyveCPU>, - state: &'a RefCell, - _current_signal: VcpuExit<'a>, -} - -impl<'a> CmdHandler<'a> { - pub fn new( - cpu: &'a mut UhyveCPU, - state: &'a RefCell, - signal: VcpuExit<'a>, - ) -> CmdHandler<'a> { - CmdHandler { - resume: RefCell::new(None), - current_cpu: RefCell::new(cpu), - _current_signal: signal, - state, - } - } - - pub fn continue_execution(&self, reason: VCont) { - debug!("Continuing.."); - *self.resume.borrow_mut() = Some(reason); - } - - fn register_hardware_trap(&self, bp: HWBreakpoint) -> Result<(), Error> { - { - let brhw = &self.state.borrow().breakpoints_hw; - // bail if breakpoint already exists - if brhw.contains_key(&(bp.addr as _)) { - return Err(Error::Error(6)); - } - - if brhw.len() >= 4 { - error!("Cannot set more than 4 hardware breakpoints!"); - return Err(Error::Error(6)); - } - } - - // HW BREAKPOINTS get set/removed during KVM update on cmd-loop exit! (kvm_change_guestdbg) - - self.state - .borrow_mut() - .breakpoints_hw - .insert(bp.addr as _, bp); - info!( - "Add breakpoints_hw: {:?}", - self.state.borrow().breakpoints_hw - ); - Ok(()) - } - - fn deregister_hardware_trap(&self, breakpoint: HWBreakpoint) -> Result<(), Error> { - info!( - "Remove breakpoints_hw: {:?}", - self.state.borrow().breakpoints_hw - ); - if let Some(_bp) = self - .state - .borrow_mut() - .breakpoints_hw - .remove(&(breakpoint.addr as _)) - { - // HW BREAKPOINTS get set/removed during KVM update on cmd-loop exit! (kvm_change_guestdbg) - - Ok(()) - } else { - Err(Error::Error(4)) - } - } -} - -impl<'a> Handler for CmdHandler<'a> { - fn should_cont(&self) -> Option { - self.resume.borrow().clone() - } - - fn attached(&self, _pid: Option) -> Result { - Ok(ProcessType::Attached) - } - - fn halt_reason(&self) -> Result { - //Ok(StopReason::Exited(23, 0)) - // TODO make this dynamic based on VcpuExit reason. - Ok(StopReason::Signal(5)) - } - - fn query_supported_features(&self) -> Vec { - vec!["qXfer:features:read+".to_string()] - } - - fn query_supported_vcont(&self) -> Result, Error> { - Ok(Cow::Borrowed(&[ - VContFeature::Continue, - VContFeature::ContinueWithSignal, - VContFeature::Step, - VContFeature::StepWithSignal, - //VContFeature::RangeStep, - ])) - } - - /// TODO: actually implement thread switching for multithread support - fn set_current_thread(&self, id: ThreadId) -> Result<(), Error> { - info!("Setting current thread to {:?}", id); - Ok(()) - } - - /// Return the identifier of the current thread. - fn current_thread(&self) -> Result, Error> { - Ok(Some(ThreadId { - pid: Id::Id(1), - tid: Id::Id(1), - })) - } - - fn read_general_registers(&self) -> Result, Error> { - let out = Registers::from_kvm(self.current_cpu.borrow().get_vcpu()).encode(); - Ok(out) - } - - fn write_general_registers(&self, contents: &[u8]) -> Result<(), Error> { - let regs = Registers::decode(contents); - regs.to_kvm(self.current_cpu.borrow_mut().get_vcpu_mut()); - Ok(()) - } - - fn read_memory(&self, mem: MemoryRegion) -> Result, Error> { - Ok(unsafe { - self.current_cpu - .borrow() - .read_mem(mem.address as _, mem.length as _) - } - .to_vec()) - } - - fn write_memory(&self, address: u64, bytes: &[u8]) -> Result<(), Error> { - unsafe { self.current_cpu.borrow().write_mem(address as _, bytes) } - Ok(()) - } - - fn insert_software_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - // bail if breakpoint already exists - if self - .state - .borrow() - .breakpoints - .contains_key(&(bp.addr as _)) - { - return Err(Error::Error(6)); - } - - // save original instruction byte - let insn = unsafe { self.current_cpu.borrow().read_mem(bp.addr as _, 1) }[0]; - // overwrite with int3 - unsafe { self.current_cpu.borrow().write_mem(bp.addr as _, INT3) } - - let bp = SWBreakpoint { bp, insn }; - self.state - .borrow_mut() - .breakpoints - .insert(bp.bp.addr as _, bp); - info!("Add breakpoints: {:?}", self.state.borrow().breakpoints); - Ok(()) - } - - fn remove_software_breakpoint(&self, breakpoint: Breakpoint) -> Result<(), Error> { - info!("Remove breakpoints: {:?}", self.state.borrow().breakpoints); - if let Some(bp) = self - .state - .borrow_mut() - .breakpoints - .remove(&(breakpoint.addr as _)) - { - // restore original instruction byte - unsafe { - self.current_cpu - .borrow() - .write_mem(breakpoint.addr as _, &[bp.insn]) - }; - Ok(()) - } else { - Err(Error::Error(4)) - } - } - - fn insert_hardware_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::Breakpoint, - addr: bp.addr as _, - n_bytes: 1, - }) - } - - fn remove_hardware_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::Breakpoint, - addr: bp.addr as _, - n_bytes: 1, - }) - } - - /// Insert a write watchpoint. - fn insert_write_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchWrite, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Insert a read watchpoint. - fn insert_read_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Insert an access watchpoint. - fn insert_access_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove a write watchpoint. - fn remove_write_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchWrite, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove a read watchpoint. - fn remove_read_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove an access watchpoint. - fn remove_access_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// TODO: currently ignores tid/pid, and just continues/steps currently running cpu according to first command - /// At most apply one action per thread. GDB likes to send default action for other threads, - /// even if it knows only about 1: "vCont;s:1;c" (step thread 1, continue others) - fn vcont(&self, actions: Vec<(VCont, Option)>) -> Result { - if !actions.is_empty() { - let (cmd, id) = &actions[0]; - - let _id = id.unwrap_or(ThreadId { - pid: Id::All, - tid: Id::All, - }); - //debug!("{:?}", id); - //println!(self.tracee.pid()); - /*match (id.pid, id.tid) { - (Id::Id(pid), _) if pid != self.tracee.pid() => continue, - (_, Id::Id(tid)) if tid != self.tracee.pid() => continue, - (_, _) => (), - }*/ - debug!("vcont: {:?}", *cmd); - // need to clone, since std::ops::Range should probably also be Copy, but it isn't. - self.continue_execution(cmd.clone()); - } - - // This reason should not matter, since we don't send it when continuing. - Ok(StopReason::Signal(0)) - } - - /// TODO: return actual number of threads, not just one - fn thread_list(&self, reset: bool) -> Result, Error> { - if reset { - Ok(vec![ - ThreadId { - pid: Id::Id(1), - tid: Id::Id(1), - }, - /*ThreadId{pid: Id::Id(1), tid: Id::Id(2)}, - ThreadId{pid: Id::Id(1), tid: Id::Id(3)}, - ThreadId{pid: Id::Id(1), tid: Id::Id(4)},*/ - ]) - } else { - Ok(Vec::new()) - } - } - - fn process_list(&self, reset: bool) -> Result, Error> { - if reset { - Ok(vec![ProcessInfo { - pid: Id::Id(1), - name: "hermitcore app".to_string(), - triple: "x86_64-unknown-hermit".to_string(), - }]) - } else { - Ok(Vec::new()) - } - } - - fn read_feature(&self, name: String, offset: u64, length: u64) -> Result { - let targetxml = include_str!("i386-64bit.xml"); - match name.as_ref() { - "target.xml" => Ok(FileData( - get_max_subslice(targetxml, offset as _, length as _).to_string(), - )), - _ => { - info!( - "Error: emote tried to read {}, which is unimplemented", - name - ); - Err(Error::Unimplemented) - } - } - } - - fn host_info(&self) -> Result { - Ok(format!("triple:{};", b"x86_64-unknown-hermit".to_hex())) - } -} - -#[derive(Default)] -pub struct Registers { - // Gotten from gnu-binutils/gdb/regformats/i386/amd64-linux.dat - pub rax: Option, - pub rbx: Option, - pub rcx: Option, - pub rdx: Option, - pub rsi: Option, - pub rdi: Option, - pub rbp: Option, - pub rsp: Option, - pub r8: Option, - pub r9: Option, - pub r10: Option, - pub r11: Option, - pub r12: Option, - pub r13: Option, - pub r14: Option, - pub r15: Option, - pub rip: Option, - pub eflags: Option, - pub cs: Option, - pub ss: Option, - pub ds: Option, - pub es: Option, - pub fs: Option, - pub gs: Option, - /*pub st0: Option, - pub st1: Option, - pub st2: Option, - pub st3: Option, - pub st4: Option, - pub st5: Option, - pub st6: Option, - pub st7: Option, - pub fctrl: Option, - pub fstat: Option, - pub ftag: Option, - pub fiseg: Option, - pub fioff: Option, - pub foseg: Option, - pub fooff: Option, - pub fop: Option, - pub xmm0: Option, - pub xmm1: Option, - pub xmm2: Option, - pub xmm3: Option, - pub xmm4: Option, - pub xmm5: Option, - pub xmm6: Option, - pub xmm7: Option, - pub xmm8: Option, - pub xmm9: Option, - pub xmm10: Option, - pub xmm11: Option, - pub xmm12: Option, - pub xmm13: Option, - pub xmm14: Option, - pub xmm15: Option, - pub mxcsr: Option, - pub orig_rax: Option, - pub fs_base: Option, - pub gs_base: Option,*/ -} - -impl Registers { - /// Loads the register set from kvm into the register struct - pub fn from_kvm(cpu: &VcpuFd) -> Self { - let regs = cpu.get_regs().expect("Can't get regs from kvm!"); - let sregs = cpu.get_sregs().expect("Can't get sregs from kvm!"); - - /*registers.fctrl = Some(float.cwd as _); - registers.fop = Some(float.fop as _); - - registers.st0 = Some(float.st_space[0] as _); - registers.st1 = Some(float.st_space[1] as _); - registers.st2 = Some(float.st_space[2] as _); - registers.st3 = Some(float.st_space[3] as _); - registers.st4 = Some(float.st_space[4] as _); - registers.st5 = Some(float.st_space[5] as _); - registers.st6 = Some(float.st_space[6] as _); - registers.st7 = Some(float.st_space[7] as _); - - registers.xmm0 = Some(float.xmm_space[0] as _); - registers.xmm1 = Some(float.xmm_space[1] as _); - registers.xmm2 = Some(float.xmm_space[2] as _); - registers.xmm3 = Some(float.xmm_space[3] as _); - registers.xmm4 = Some(float.xmm_space[4] as _); - registers.xmm5 = Some(float.xmm_space[5] as _); - registers.xmm6 = Some(float.xmm_space[6] as _); - registers.xmm7 = Some(float.xmm_space[7] as _); - registers.xmm8 = Some(float.xmm_space[8] as _); - registers.xmm9 = Some(float.xmm_space[9] as _); - registers.xmm10 = Some(float.xmm_space[10] as _); - registers.xmm11 = Some(float.xmm_space[11] as _); - registers.xmm12 = Some(float.xmm_space[12] as _); - registers.xmm13 = Some(float.xmm_space[13] as _); - registers.xmm14 = Some(float.xmm_space[14] as _); - registers.xmm15 = Some(float.xmm_space[15] as _); - - registers.mxcsr = Some(float.mxcsr); - registers.fs_base = Some(sregs.fs.base as _); - registers.gs_base = Some(sregs.gs.base as _);*/ - - Self { - r15: Some(regs.r15), - r14: Some(regs.r14), - r13: Some(regs.r13), - r12: Some(regs.r12), - r11: Some(regs.r11), - r10: Some(regs.r10), - r9: Some(regs.r9), - r8: Some(regs.r8), - rax: Some(regs.rax), - rbx: Some(regs.rbx), - rcx: Some(regs.rcx), - rdx: Some(regs.rdx), - rsi: Some(regs.rsi), - rdi: Some(regs.rdi), - rsp: Some(regs.rsp), - rbp: Some(regs.rbp), - rip: Some(regs.rip), - eflags: Some(regs.rflags as _), - cs: Some(sregs.cs.base as _), - ss: Some(sregs.ss.base as _), - ds: Some(sregs.ds.base as _), - es: Some(sregs.es.base as _), - fs: Some(sregs.fs.base as _), - gs: Some(sregs.gs.base as _), - } - } - - /// Saves a register struct (only where non-None values are) into kvm. - pub fn to_kvm(&self, cpu: &mut VcpuFd) { - let mut regs = cpu.get_regs().expect("Can't get regs from kvm!"); - let mut sregs = cpu.get_sregs().expect("Can't get sregs from kvm!"); - - regs.r15 = self.r15.unwrap_or(regs.r15); - regs.r14 = self.r14.unwrap_or(regs.r14); - regs.r13 = self.r13.unwrap_or(regs.r13); - regs.r12 = self.r12.unwrap_or(regs.r12); - regs.r11 = self.r11.unwrap_or(regs.r11); - regs.r10 = self.r10.unwrap_or(regs.r10); - regs.r9 = self.r9.unwrap_or(regs.r9); - regs.r8 = self.r8.unwrap_or(regs.r8); - regs.rax = self.rax.unwrap_or(regs.rax); - regs.rbx = self.rbx.unwrap_or(regs.rbx); - regs.rcx = self.rcx.unwrap_or(regs.rcx); - regs.rdx = self.rdx.unwrap_or(regs.rdx); - regs.rsi = self.rsi.unwrap_or(regs.rsi); - regs.rdi = self.rdi.unwrap_or(regs.rdi); - regs.rsp = self.rsp.unwrap_or(regs.rsp); - regs.rbp = self.rbp.unwrap_or(regs.rbp); - regs.rip = self.rip.unwrap_or(regs.rip); - regs.rflags = self.eflags.unwrap_or(regs.rflags as _) as _; - sregs.cs.base = self.cs.unwrap_or(sregs.cs.base as _) as _; - sregs.ss.base = self.ss.unwrap_or(sregs.ss.base as _) as _; - sregs.ds.base = self.ds.unwrap_or(sregs.ds.base as _) as _; - sregs.es.base = self.es.unwrap_or(sregs.es.base as _) as _; - sregs.fs.base = self.fs.unwrap_or(sregs.fs.base as _) as _; - sregs.gs.base = self.gs.unwrap_or(sregs.gs.base as _) as _; - - cpu.set_regs(®s).expect("Can't set regs to kvm!"); - cpu.set_sregs(&sregs).expect("Can't set regs to kvm!"); - } - - /// take the serialized register set send by gdb and decodes it into a register structure. - /// uses little endian, order as specified by gdb arch i386:x86-64 - pub fn decode(mut raw: &[u8]) -> Self { - Self { - rax: raw.read_u64::().ok(), - rbx: raw.read_u64::().ok(), - rcx: raw.read_u64::().ok(), - rdx: raw.read_u64::().ok(), - rsi: raw.read_u64::().ok(), - rdi: raw.read_u64::().ok(), - rbp: raw.read_u64::().ok(), - rsp: raw.read_u64::().ok(), - r8: raw.read_u64::().ok(), - r9: raw.read_u64::().ok(), - r10: raw.read_u64::().ok(), - r11: raw.read_u64::().ok(), - r12: raw.read_u64::().ok(), - r13: raw.read_u64::().ok(), - r14: raw.read_u64::().ok(), - r15: raw.read_u64::().ok(), - rip: raw.read_u64::().ok(), - eflags: raw.read_u32::().ok(), - cs: raw.read_u32::().ok(), - ss: raw.read_u32::().ok(), - ds: raw.read_u32::().ok(), - es: raw.read_u32::().ok(), - fs: raw.read_u32::().ok(), - gs: raw.read_u32::().ok(), - } - } - - /// take the register set and encode it as a u8-vector by concatenating the values - /// uses little endian, order as specified by gdb arch i386:x86-64 - pub fn encode(&self) -> Vec { - let mut out: Vec = vec![]; - - out.write_u64::(self.rax.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rbx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rcx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rdx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rsi.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rdi.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rbp.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rsp.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r8.unwrap_or(0)).unwrap(); - out.write_u64::(self.r9.unwrap_or(0)).unwrap(); - out.write_u64::(self.r10.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r11.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r12.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r13.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r14.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r15.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rip.unwrap_or(0)) - .unwrap(); - - out.write_u32::(self.eflags.unwrap_or(0)) - .unwrap(); - out.write_u32::(self.cs.unwrap_or(0)).unwrap(); - out.write_u32::(self.ss.unwrap_or(0)).unwrap(); - out.write_u32::(self.ds.unwrap_or(0)).unwrap(); - out.write_u32::(self.es.unwrap_or(0)).unwrap(); - out.write_u32::(self.fs.unwrap_or(0)).unwrap(); - out.write_u32::(self.gs.unwrap_or(0)).unwrap(); - - out - } -} diff --git a/src/linux/i386-64bit.xml b/src/linux/i386-64bit.xml deleted file mode 100644 index f19a8ccc..00000000 --- a/src/linux/i386-64bit.xml +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - i386:x86-64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/linux/mod.rs b/src/linux/mod.rs index b2b6adff..bdb4b764 100755 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1,4 +1,3 @@ -pub mod gdb; pub mod uhyve; pub mod vcpu; pub mod virtio; diff --git a/src/linux/uhyve.rs b/src/linux/uhyve.rs index 9db5b2a0..1c65e501 100755 --- a/src/linux/uhyve.rs +++ b/src/linux/uhyve.rs @@ -2,7 +2,6 @@ //! create a Virtual Machine and load the kernel. use crate::consts::*; -use crate::debug_manager::DebugManager; use crate::linux::vcpu::*; use crate::linux::virtio::*; use crate::linux::{MemoryRegion, KVM}; @@ -137,7 +136,6 @@ pub struct Uhyve { mask: Option, uhyve_device: Option, virtio_device: Arc>, - dbg: Option>>, } impl fmt::Debug for Uhyve { @@ -274,11 +272,6 @@ impl Uhyve { _ => None, }; - let dbg = specs - .gdbport - .map(|port| DebugManager::new(port).unwrap()) - .map(|g| Arc::new(Mutex::new(g))); - let hyve = Uhyve { vm, offset: 0, @@ -293,7 +286,6 @@ impl Uhyve { mask, uhyve_device, virtio_device, - dbg, }; hyve.init_guest_mem(); @@ -358,7 +350,6 @@ impl Vm for Uhyve { vm_start, tx, self.virtio_device.clone(), - self.dbg.as_ref().cloned(), )) } diff --git a/src/linux/vcpu.rs b/src/linux/vcpu.rs index 34e5cc7f..c42cf816 100755 --- a/src/linux/vcpu.rs +++ b/src/linux/vcpu.rs @@ -1,5 +1,4 @@ use crate::consts::*; -use crate::debug_manager::DebugManager; use crate::linux::virtio::*; use crate::linux::KVM; use crate::paging::*; @@ -8,7 +7,6 @@ use crate::vm::VcpuStopReason; use crate::vm::VirtualCPU; use kvm_bindings::*; use kvm_ioctls::{VcpuExit, VcpuFd}; -use log::{debug, info}; use std::path::Path; use std::path::PathBuf; use std::sync::{Arc, Mutex}; @@ -29,7 +27,6 @@ pub struct UhyveCPU { tx: Option>, virtio_device: Arc>, pci_addr: Option, - pub dbg: Option>>, } impl UhyveCPU { @@ -40,7 +37,6 @@ impl UhyveCPU { vm_start: usize, tx: Option>, virtio_device: Arc>, - dbg: Option>>, ) -> UhyveCPU { UhyveCPU { id, @@ -50,7 +46,6 @@ impl UhyveCPU { tx, virtio_device, pci_addr: None, - dbg, } } @@ -448,17 +443,10 @@ impl VirtualCPU for UhyveCPU { } fn run(&mut self) -> HypervisorResult> { - // Pause first CPU before first execution, so we have time to attach debugger - if self.id == 0 { - self.gdb_handle_exception(None); - } - - loop { - match self.r#continue()? { - VcpuStopReason::Debug => self.gdb_handle_exception(Some(VcpuExit::Debug)), - VcpuStopReason::Exit(code) => break Ok(Some(code)), - VcpuStopReason::Kick => break Ok(None), - } + match self.r#continue()? { + VcpuStopReason::Debug => todo!(), + VcpuStopReason::Exit(code) => Ok(Some(code)), + VcpuStopReason::Kick => Ok(None), } } diff --git a/src/macos/gdb.rs b/src/macos/gdb.rs deleted file mode 100644 index 3dbeeb64..00000000 --- a/src/macos/gdb.rs +++ /dev/null @@ -1,777 +0,0 @@ -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use log::{debug, error}; -use rustc_serialize::hex::ToHex; -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::HashMap; -use std::slice; -use x86_64::registers::rflags::RFlags; -use xhypervisor::{vCPU, x86Reg}; - -use crate::arch::x86; -use crate::gdb_parser::{ - get_max_subslice, Breakpoint, Error, FileData, Handler, Id, MemoryRegion, ProcessInfo, - ProcessType, StopReason, ThreadId, VCont, VContFeature, Watchpoint, -}; -use crate::macos::vcpu::UhyveCPU; -use crate::vm::VirtualCPU; - -/// Debugging Stub for linux/x64 -/// Currently supported features: -/// - Register read/write -/// - Memory read/write -/// - Software breakpoints (int3) -/// - Hardware breakpoints -/// - Execute / Write / Read-Write -/// - Singlestepping / Continue -/// - LLDB support (transmit info about arch/reg layout) -/// - read of feature target.xml [i386-64bit.xml] -/// - qHostInfo triple sends x86_64-unknown-hermit - -const INT3: &[u8] = &[0xcc]; - -impl UhyveCPU { - /// Called on Trap. Creates Handler. - /// Enter gdb-event-loop until gdb tells us to continue. Set singlestep mode if necessary and return - pub fn gdb_handle_exception(&mut self, signal: bool) { - debug!("Handling debug exception!"); - if let Some(dbg) = &mut self.dbg { - let dbgarc = dbg.clone(); - let dbg = dbgarc.lock().expect("No gdb available!"); - - let (mut cmdhandler, signal) = if signal { - // send signal with which we are stopped. Hardcoded to 5 for now (TODO) - ( - CmdHandler::new(self, &dbg.state), - Some(StopReason::Signal(5)), - ) - } else { - // target stopped on boot. No signal recv'd yet. Pretend debug signal? Not used rn anyways. - (CmdHandler::new(self, &dbg.state), None) - }; - - // enter command-handler, stay there until we receive a continue signal - let vcont = dbg - .handle_commands(&mut cmdhandler, signal) - .unwrap_or_else(|error| { - error!("Cannot handle debugging commands: {:?}", error); - // always continue - VCont::Continue - }); - - let hwbr = dbg.state.borrow().get_hardware_breakpoints(); - - // handler returned with a continuation command, - // determine if we should continue single-stepped or until next trap - match vcont { - VCont::Continue | VCont::ContinueWithSignal(_) => { - debug!("Continuing execution.."); - self.change_guestdbg(false, hwbr.as_ref()); // TODO: optimize this, don't call too often? - } - VCont::Step | VCont::StepWithSignal(_) => { - debug!("Starting Single Stepping.."); - self.change_guestdbg(true, hwbr.as_ref()); // TODO: optimize this, don't call too often? - } - _ => error!("Unknown Handler exit reason!"), - } - } else { - debug!("Debugging disabled, ignoring exception {:?}.", signal); - }; - } - - unsafe fn read_mem(&self, guest_addr: usize, len: usize) -> &[u8] { - let phys = self.virt_to_phys(guest_addr); - let host = self.host_address(phys); - - slice::from_raw_parts(host as *mut u8, len) - } - - unsafe fn write_mem(&self, guest_addr: usize, data: &[u8]) { - let phys = self.virt_to_phys(guest_addr); - let host = self.host_address(phys); - - let mem: &mut [u8] = slice::from_raw_parts_mut(host as *mut u8, data.len()); - - mem.copy_from_slice(data); - } - - pub fn change_guestdbg( - &mut self, - single_step: bool, - hwbr: Option<&x86::HWBreakpoints>, /*&HashMap*/ - ) { - debug!( - "xhypervisor: Enable guest debug. single_step:{}", - single_step - ); - - debug!("Setting guestdbg"); - let vcpu = self.get_vcpu(); - let mut rflags = vcpu.read_register(&x86Reg::RFLAGS).unwrap(); - if single_step { - rflags |= RFlags::TRAP_FLAG.bits(); - } else { - rflags &= !RFlags::TRAP_FLAG.bits(); - } - vcpu.write_register(&x86Reg::RFLAGS, rflags).unwrap(); - - if let Some(hwbr) = hwbr { - vcpu.write_register(&x86Reg::DR0, hwbr.get_addr(0).unwrap()) - .unwrap(); - vcpu.write_register(&x86Reg::DR1, hwbr.get_addr(1).unwrap()) - .unwrap(); - vcpu.write_register(&x86Reg::DR2, hwbr.get_addr(2).unwrap()) - .unwrap(); - vcpu.write_register(&x86Reg::DR3, hwbr.get_addr(3).unwrap()) - .unwrap(); - vcpu.write_register(&x86Reg::DR7, hwbr.get_dr7()).unwrap(); - } - } -} - -#[derive(Default)] -pub struct State { - breakpoints: HashMap, - breakpoints_hw: HashMap, -} - -#[derive(Debug)] -enum BreakpointKind { - Breakpoint, - WatchWrite, - WatchAccess, -} - -#[derive(Debug)] -struct SWBreakpoint { - bp: Breakpoint, - insn: u8, -} - -#[derive(Debug)] -struct HWBreakpoint { - kind: BreakpointKind, - addr: u64, - n_bytes: u64, -} - -impl State { - pub fn new() -> Self { - Self::default() - } - - pub fn get_hardware_breakpoints(&self) -> Option { - if self.breakpoints_hw.is_empty() { - return None; - } - - if self.breakpoints_hw.len() > 4 { - error!("Cannot set more than 4 hardware breakpoints!") - } - - let mut hwbr = x86::HWBreakpoints::default(); - - for (i, (addr, bp)) in self.breakpoints_hw.iter().take(4).enumerate() { - hwbr.0[i].addr = *addr as _; - hwbr.0[i].is_local = true; - hwbr.0[i].is_global = true; - hwbr.0[i].trigger = match bp.kind { - BreakpointKind::Breakpoint => x86::BreakTrigger::Ex, - BreakpointKind::WatchWrite => x86::BreakTrigger::W, - BreakpointKind::WatchAccess => x86::BreakTrigger::RW, - }; - hwbr.0[i].size = match bp.n_bytes { - 1 => x86::BreakSize::B1, - 2 => x86::BreakSize::B2, - 4 => x86::BreakSize::B4, - 8 => x86::BreakSize::B8, - _ => { - error!("Unknown watchpoint size!"); - x86::BreakSize::B1 - } - }; - } - - Some(hwbr) - } -} - -pub struct CmdHandler<'a> { - // use RefCells to not break existing api of gdb_parser (no mutability in handler) - resume: RefCell>, - current_cpu: RefCell<&'a mut UhyveCPU>, - state: &'a RefCell, -} - -impl<'a> CmdHandler<'a> { - pub fn new(cpu: &'a mut UhyveCPU, state: &'a RefCell) -> CmdHandler<'a> { - CmdHandler { - resume: RefCell::new(None), - current_cpu: RefCell::new(cpu), - state, - } - } - - pub fn continue_execution(&self, reason: VCont) { - debug!("Continuing.."); - *self.resume.borrow_mut() = Some(reason); - } - - fn register_hardware_trap(&self, bp: HWBreakpoint) -> Result<(), Error> { - { - let brhw = &self.state.borrow().breakpoints_hw; - // bail if breakpoint already exists - if brhw.contains_key(&(bp.addr as _)) { - return Err(Error::Error(6)); - } - - if brhw.len() >= 4 { - error!("Cannot set more than 4 hardware breakpoints!"); - return Err(Error::Error(6)); - } - } - - // HW BREAKPOINTS get set/removed during KVM update on cmd-loop exit! (kvm_change_guestdbg) - - self.state - .borrow_mut() - .breakpoints_hw - .insert(bp.addr as _, bp); - debug!( - "Add breakpoints_hw: {:?}", - self.state.borrow().breakpoints_hw - ); - Ok(()) - } - - fn deregister_hardware_trap(&self, breakpoint: HWBreakpoint) -> Result<(), Error> { - debug!( - "Remove breakpoints_hw: {:?}", - self.state.borrow().breakpoints_hw - ); - if let Some(_bp) = self - .state - .borrow_mut() - .breakpoints_hw - .remove(&(breakpoint.addr as _)) - { - // HW BREAKPOINTS get set/removed during KVM update on cmd-loop exit! (kvm_change_guestdbg) - - Ok(()) - } else { - Err(Error::Error(4)) - } - } -} - -impl<'a> Handler for CmdHandler<'a> { - fn should_cont(&self) -> Option { - self.resume.borrow().clone() - } - - fn attached(&self, _pid: Option) -> Result { - Ok(ProcessType::Attached) - } - - fn halt_reason(&self) -> Result { - //Ok(StopReason::Exited(23, 0)) - // TODO make this dynamic based on VcpuExit reason. - Ok(StopReason::Signal(5)) - } - - fn query_supported_features(&self) -> Vec { - vec!["qXfer:features:read+".to_string()] - } - - fn query_supported_vcont(&self) -> Result, Error> { - Ok(Cow::Borrowed(&[ - VContFeature::Continue, - VContFeature::ContinueWithSignal, - VContFeature::Step, - VContFeature::StepWithSignal, - //VContFeature::RangeStep, - ])) - } - - /// TODO: actually implement thread switching for multithread support - fn set_current_thread(&self, id: ThreadId) -> Result<(), Error> { - debug!("Setting current thread to {:?}", id); - Ok(()) - } - - /// Return the identifier of the current thread. - fn current_thread(&self) -> Result, Error> { - Ok(Some(ThreadId { - pid: Id::Id(1), - tid: Id::Id(1), - })) - } - - fn read_general_registers(&self) -> Result, Error> { - let out = Registers::from_xhypervisor(self.current_cpu.borrow().get_vcpu()).encode(); - Ok(out) - } - - fn write_general_registers(&self, contents: &[u8]) -> Result<(), Error> { - let regs = Registers::decode(contents); - regs.to_xhypervisor(self.current_cpu.borrow().get_vcpu()); - Ok(()) - } - - fn read_memory(&self, mem: MemoryRegion) -> Result, Error> { - Ok(unsafe { - self.current_cpu - .borrow() - .read_mem(mem.address as _, mem.length as _) - } - .to_vec()) - } - - fn write_memory(&self, address: u64, bytes: &[u8]) -> Result<(), Error> { - unsafe { self.current_cpu.borrow().write_mem(address as _, bytes) } - Ok(()) - } - - fn insert_software_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - // bail if breakpoint already exists - if self - .state - .borrow() - .breakpoints - .contains_key(&(bp.addr as _)) - { - return Err(Error::Error(6)); - } - - // save original instruction byte - let insn = unsafe { self.current_cpu.borrow().read_mem(bp.addr as _, 1) }[0]; - // overwrite with int3 - unsafe { self.current_cpu.borrow().write_mem(bp.addr as _, INT3) } - - let bp = SWBreakpoint { bp, insn }; - self.state - .borrow_mut() - .breakpoints - .insert(bp.bp.addr as _, bp); - debug!("Add breakpoints: {:?}", self.state.borrow().breakpoints); - Ok(()) - } - - fn remove_software_breakpoint(&self, breakpoint: Breakpoint) -> Result<(), Error> { - debug!("Remove breakpoints: {:?}", self.state.borrow().breakpoints); - if let Some(bp) = self - .state - .borrow_mut() - .breakpoints - .remove(&(breakpoint.addr as _)) - { - // restore original instruction byte - unsafe { - self.current_cpu - .borrow() - .write_mem(breakpoint.addr as _, &[bp.insn]) - }; - Ok(()) - } else { - Err(Error::Error(4)) - } - } - - fn insert_hardware_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::Breakpoint, - addr: bp.addr as _, - n_bytes: 1, - }) - } - - fn remove_hardware_breakpoint(&self, bp: Breakpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::Breakpoint, - addr: bp.addr as _, - n_bytes: 1, - }) - } - - /// Insert a write watchpoint. - fn insert_write_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchWrite, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Insert a read watchpoint. - fn insert_read_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Insert an access watchpoint. - fn insert_access_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.register_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove a write watchpoint. - fn remove_write_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchWrite, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove a read watchpoint. - fn remove_read_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// Remove an access watchpoint. - fn remove_access_watchpoint(&self, watchpoint: Watchpoint) -> Result<(), Error> { - self.deregister_hardware_trap(HWBreakpoint { - kind: BreakpointKind::WatchAccess, - addr: watchpoint.addr as _, - n_bytes: watchpoint.n_bytes, - }) - } - - /// TODO: currently ignores tid/pid, and just continues/steps currently running cpu according to first command - /// At most apply one action per thread. GDB likes to send default action for other threads, - /// even if it knows only about 1: "vCont;s:1;c" (step thread 1, continue others) - fn vcont(&self, actions: Vec<(VCont, Option)>) -> Result { - actions.into_iter().take(1).for_each(|(cmd, id)| { - let _id = id.unwrap_or(ThreadId { - pid: Id::All, - tid: Id::All, - }); - //debug!("{:?}", id); - //println!(self.tracee.pid()); - /*match (id.pid, id.tid) { - (Id::Id(pid), _) if pid != self.tracee.pid() => continue, - (_, Id::Id(tid)) if tid != self.tracee.pid() => continue, - (_, _) => (), - }*/ - debug!("vcont: {:?}", cmd); - // need to clone, since std::ops::Range should probably also be Copy, but it isn't. - self.continue_execution(cmd); - }); - - // This reason should not matter, since we don't send it when continuing. - Ok(StopReason::Signal(0)) - } - - /// TODO: return actual number of threads, not just one - fn thread_list(&self, reset: bool) -> Result, Error> { - if reset { - Ok(vec![ - ThreadId { - pid: Id::Id(1), - tid: Id::Id(1), - }, - /*ThreadId{pid: Id::Id(1), tid: Id::Id(2)}, - ThreadId{pid: Id::Id(1), tid: Id::Id(3)}, - ThreadId{pid: Id::Id(1), tid: Id::Id(4)},*/ - ]) - } else { - Ok(Vec::new()) - } - } - - fn process_list(&self, reset: bool) -> Result, Error> { - if reset { - Ok(vec![ProcessInfo { - pid: Id::Id(1), - name: "hermitcore app".to_string(), - triple: "x86_64-unknown-hermit".to_string(), - }]) - } else { - Ok(Vec::new()) - } - } - - fn read_feature(&self, name: String, offset: u64, length: u64) -> Result { - let targetxml = include_str!("i386-64bit.xml"); - match name.as_ref() { - "target.xml" => Ok(FileData( - get_max_subslice(targetxml, offset as _, length as _).to_string(), - )), - _ => { - debug!( - "Error: emote tried to read {}, which is unimplemented", - name - ); - Err(Error::Unimplemented) - } - } - } - - fn host_info(&self) -> Result { - Ok(format!("triple:{};", b"x86_64-unknown-hermit".to_hex())) - } -} - -#[derive(Default)] -pub struct Registers { - // Gotten from gnu-binutils/gdb/regformats/i386/amd64-linux.dat - pub rax: Option, - pub rbx: Option, - pub rcx: Option, - pub rdx: Option, - pub rsi: Option, - pub rdi: Option, - pub rbp: Option, - pub rsp: Option, - pub r8: Option, - pub r9: Option, - pub r10: Option, - pub r11: Option, - pub r12: Option, - pub r13: Option, - pub r14: Option, - pub r15: Option, - pub rip: Option, - pub eflags: Option, - pub cs: Option, - pub ss: Option, - pub ds: Option, - pub es: Option, - pub fs: Option, - pub gs: Option, - /*pub st0: Option, - pub st1: Option, - pub st2: Option, - pub st3: Option, - pub st4: Option, - pub st5: Option, - pub st6: Option, - pub st7: Option, - pub fctrl: Option, - pub fstat: Option, - pub ftag: Option, - pub fiseg: Option, - pub fioff: Option, - pub foseg: Option, - pub fooff: Option, - pub fop: Option, - pub xmm0: Option, - pub xmm1: Option, - pub xmm2: Option, - pub xmm3: Option, - pub xmm4: Option, - pub xmm5: Option, - pub xmm6: Option, - pub xmm7: Option, - pub xmm8: Option, - pub xmm9: Option, - pub xmm10: Option, - pub xmm11: Option, - pub xmm12: Option, - pub xmm13: Option, - pub xmm14: Option, - pub xmm15: Option, - pub mxcsr: Option, - pub orig_rax: Option, - pub fs_base: Option, - pub gs_base: Option,*/ -} - -impl Registers { - /// Loads the register set from xhypervisor into the register struct - pub fn from_xhypervisor(vcpu: &vCPU) -> Self { - Self { - r15: Some(vcpu.read_register(&x86Reg::R15).unwrap()), - r14: Some(vcpu.read_register(&x86Reg::R14).unwrap()), - r13: Some(vcpu.read_register(&x86Reg::R13).unwrap()), - r12: Some(vcpu.read_register(&x86Reg::R12).unwrap()), - r11: Some(vcpu.read_register(&x86Reg::R11).unwrap()), - r10: Some(vcpu.read_register(&x86Reg::R10).unwrap()), - r9: Some(vcpu.read_register(&x86Reg::R9).unwrap()), - r8: Some(vcpu.read_register(&x86Reg::R8).unwrap()), - rax: Some(vcpu.read_register(&x86Reg::RAX).unwrap()), - rbx: Some(vcpu.read_register(&x86Reg::RBX).unwrap()), - rcx: Some(vcpu.read_register(&x86Reg::RCX).unwrap()), - rdx: Some(vcpu.read_register(&x86Reg::RDX).unwrap()), - rsi: Some(vcpu.read_register(&x86Reg::RSI).unwrap()), - rdi: Some(vcpu.read_register(&x86Reg::RDI).unwrap()), - rsp: Some(vcpu.read_register(&x86Reg::RSP).unwrap()), - rbp: Some(vcpu.read_register(&x86Reg::RBP).unwrap()), - rip: Some(vcpu.read_register(&x86Reg::RIP).unwrap()), - eflags: Some(vcpu.read_register(&x86Reg::RFLAGS).unwrap() as u32), - cs: Some(vcpu.read_register(&x86Reg::CS).unwrap() as u32), - ss: Some(vcpu.read_register(&x86Reg::SS).unwrap() as u32), - ds: Some(vcpu.read_register(&x86Reg::DS).unwrap() as u32), - es: Some(vcpu.read_register(&x86Reg::ES).unwrap() as u32), - fs: Some(vcpu.read_register(&x86Reg::FS).unwrap() as u32), - gs: Some(vcpu.read_register(&x86Reg::GS).unwrap() as u32), - } - } - - /// Saves a register struct (only where non-None values are) into xhypervisor. - pub fn to_xhypervisor(&self, vcpu: &vCPU) { - if let Some(r15) = self.r15 { - vcpu.write_register(&x86Reg::R15, r15).unwrap(); - } - if let Some(r14) = self.r14 { - vcpu.write_register(&x86Reg::R14, r14).unwrap(); - } - if let Some(r13) = self.r13 { - vcpu.write_register(&x86Reg::R13, r13).unwrap(); - } - if let Some(r12) = self.r12 { - vcpu.write_register(&x86Reg::R12, r12).unwrap(); - } - if let Some(r11) = self.r11 { - vcpu.write_register(&x86Reg::R11, r11).unwrap(); - } - if let Some(r10) = self.r10 { - vcpu.write_register(&x86Reg::R10, r10).unwrap(); - } - if let Some(r9) = self.r9 { - vcpu.write_register(&x86Reg::R9, r9).unwrap(); - } - if let Some(r8) = self.r8 { - vcpu.write_register(&x86Reg::R8, r8).unwrap(); - } - if let Some(rax) = self.rax { - vcpu.write_register(&x86Reg::RAX, rax).unwrap(); - } - if let Some(rbx) = self.rbx { - vcpu.write_register(&x86Reg::RBX, rbx).unwrap(); - } - if let Some(rcx) = self.rcx { - vcpu.write_register(&x86Reg::RCX, rcx).unwrap(); - } - if let Some(rdx) = self.rdx { - vcpu.write_register(&x86Reg::RDX, rdx).unwrap(); - } - if let Some(rdi) = self.rdi { - vcpu.write_register(&x86Reg::RDI, rdi).unwrap(); - } - if let Some(rsi) = self.rsi { - vcpu.write_register(&x86Reg::RSI, rsi).unwrap(); - } - if let Some(rsp) = self.rsp { - vcpu.write_register(&x86Reg::RSP, rsp).unwrap(); - } - if let Some(rbp) = self.rbp { - vcpu.write_register(&x86Reg::RBP, rbp).unwrap(); - } - if let Some(rip) = self.rip { - vcpu.write_register(&x86Reg::RIP, rip).unwrap(); - } - if let Some(rflags) = self.eflags { - vcpu.write_register(&x86Reg::RFLAGS, rflags as u64).unwrap(); - } - if let Some(cs) = self.cs { - vcpu.write_register(&x86Reg::CS, cs as u64).unwrap(); - } - if let Some(ss) = self.ss { - vcpu.write_register(&x86Reg::SS, ss as u64).unwrap(); - } - if let Some(ds) = self.ds { - vcpu.write_register(&x86Reg::DS, ds as u64).unwrap(); - } - if let Some(es) = self.es { - vcpu.write_register(&x86Reg::ES, es as u64).unwrap(); - } - if let Some(fs) = self.fs { - vcpu.write_register(&x86Reg::FS, fs as u64).unwrap(); - } - if let Some(gs) = self.gs { - vcpu.write_register(&x86Reg::GS, gs as u64).unwrap(); - } - } - - /// take the serialized register set send by gdb and decodes it into a register structure. - /// uses little endian, order as specified by gdb arch i386:x86-64 - pub fn decode(mut raw: &[u8]) -> Self { - Self { - rax: raw.read_u64::().ok(), - rbx: raw.read_u64::().ok(), - rcx: raw.read_u64::().ok(), - rdx: raw.read_u64::().ok(), - rsi: raw.read_u64::().ok(), - rdi: raw.read_u64::().ok(), - rbp: raw.read_u64::().ok(), - rsp: raw.read_u64::().ok(), - r8: raw.read_u64::().ok(), - r9: raw.read_u64::().ok(), - r10: raw.read_u64::().ok(), - r11: raw.read_u64::().ok(), - r12: raw.read_u64::().ok(), - r13: raw.read_u64::().ok(), - r14: raw.read_u64::().ok(), - r15: raw.read_u64::().ok(), - rip: raw.read_u64::().ok(), - eflags: raw.read_u32::().ok(), - cs: raw.read_u32::().ok(), - ss: raw.read_u32::().ok(), - ds: raw.read_u32::().ok(), - es: raw.read_u32::().ok(), - fs: raw.read_u32::().ok(), - gs: raw.read_u32::().ok(), - } - } - - /// take the register set and encode it as a u8-vector by concatenating the values - /// uses little endian, order as specified by gdb arch i386:x86-64 - pub fn encode(&self) -> Vec { - let mut out: Vec = vec![]; - - out.write_u64::(self.rax.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rbx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rcx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rdx.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rsi.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rdi.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rbp.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rsp.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r8.unwrap_or(0)).unwrap(); - out.write_u64::(self.r9.unwrap_or(0)).unwrap(); - out.write_u64::(self.r10.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r11.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r12.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r13.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r14.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.r15.unwrap_or(0)) - .unwrap(); - out.write_u64::(self.rip.unwrap_or(0)) - .unwrap(); - - out.write_u32::(self.eflags.unwrap_or(0)) - .unwrap(); - out.write_u32::(self.cs.unwrap_or(0)).unwrap(); - out.write_u32::(self.ss.unwrap_or(0)).unwrap(); - out.write_u32::(self.ds.unwrap_or(0)).unwrap(); - out.write_u32::(self.es.unwrap_or(0)).unwrap(); - out.write_u32::(self.fs.unwrap_or(0)).unwrap(); - out.write_u32::(self.gs.unwrap_or(0)).unwrap(); - - out - } -} diff --git a/src/macos/i386-64bit.xml b/src/macos/i386-64bit.xml deleted file mode 100644 index f19a8ccc..00000000 --- a/src/macos/i386-64bit.xml +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - i386:x86-64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 7bd4811a..482ff713 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -11,7 +11,6 @@ use crate::{ Uhyve, }; -pub mod gdb; mod ioapic; pub mod uhyve; pub mod vcpu; diff --git a/src/macos/uhyve.rs b/src/macos/uhyve.rs index cd6e53cd..7f425a81 100644 --- a/src/macos/uhyve.rs +++ b/src/macos/uhyve.rs @@ -1,4 +1,3 @@ -use crate::debug_manager::DebugManager; use crate::macos::ioapic::IoApic; use crate::macos::vcpu::*; use crate::vm::HypervisorResult; @@ -24,7 +23,6 @@ pub struct Uhyve { boot_info: *const BootInfo, ioapic: Arc>, verbose: bool, - dbg: Option>>, } impl std::fmt::Debug for Uhyve { @@ -71,11 +69,6 @@ impl Uhyve { )?; } - let dbg = specs - .gdbport - .map(|port| DebugManager::new(port).unwrap()) - .map(|g| Arc::new(Mutex::new(g))); - let hyve = Uhyve { offset: 0, entry_point: 0, @@ -86,7 +79,6 @@ impl Uhyve { boot_info: ptr::null(), ioapic: Arc::new(Mutex::new(IoApic::new())), verbose: specs.verbose, - dbg, }; hyve.init_guest_mem(); @@ -134,7 +126,6 @@ impl Vm for Uhyve { self.path.clone(), self.guest_mem as usize, self.ioapic.clone(), - self.dbg.as_ref().cloned(), )) } diff --git a/src/macos/vcpu.rs b/src/macos/vcpu.rs index 957274ff..29ede1c6 100644 --- a/src/macos/vcpu.rs +++ b/src/macos/vcpu.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] use crate::consts::*; -use crate::debug_manager::DebugManager; use crate::macos::ioapic::IoApic; use crate::paging::*; use crate::vm::HypervisorResult; @@ -71,7 +70,6 @@ pub struct UhyveCPU { vm_start: usize, apic_base: u64, ioapic: Arc>, - pub dbg: Option>>, } impl UhyveCPU { @@ -80,7 +78,6 @@ impl UhyveCPU { kernel_path: PathBuf, vm_start: usize, ioapic: Arc>, - dbg: Option>>, ) -> UhyveCPU { UhyveCPU { id, @@ -89,7 +86,6 @@ impl UhyveCPU { vm_start, apic_base: APIC_DEFAULT_BASE, ioapic, - dbg, } } @@ -728,17 +724,10 @@ impl VirtualCPU for UhyveCPU { } fn run(&mut self) -> HypervisorResult> { - // Pause first CPU before first execution, so we have time to attach debugger - if self.id == 0 { - self.gdb_handle_exception(false); - } - - loop { - match self.r#continue()? { - VcpuStopReason::Debug => self.gdb_handle_exception(true), - VcpuStopReason::Exit(code) => break Ok(Some(code)), - VcpuStopReason::Kick => break Ok(None), - } + match self.r#continue()? { + VcpuStopReason::Debug => unimplemented!(), + VcpuStopReason::Exit(code) => Ok(Some(code)), + VcpuStopReason::Kick => Ok(None), } } From d37f7efc403027f0bd43173bc81b8f90d544e5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 26 Aug 2021 12:11:52 +0200 Subject: [PATCH 2/5] Remove MemoryRegion trait --- src/linux/mod.rs | 7 ------- src/linux/uhyve.rs | 42 ++++++++++++------------------------------ 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/linux/mod.rs b/src/linux/mod.rs index bdb4b764..63b76731 100755 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -30,13 +30,6 @@ lazy_static! { static ref KVM: Kvm = Kvm::new().unwrap(); } -trait MemoryRegion { - fn flags(&self) -> u32; - fn memory_size(&self) -> usize; - fn guest_address(&self) -> usize; - fn host_address(&self) -> usize; -} - /// The signal for kicking vCPUs out of KVM_RUN. /// /// It is used to stop a vCPU from another thread. diff --git a/src/linux/uhyve.rs b/src/linux/uhyve.rs index 1c65e501..b093127c 100755 --- a/src/linux/uhyve.rs +++ b/src/linux/uhyve.rs @@ -4,7 +4,7 @@ use crate::consts::*; use crate::linux::vcpu::*; use crate::linux::virtio::*; -use crate::linux::{MemoryRegion, KVM}; +use crate::linux::KVM; use crate::shared_queue::*; use crate::vm::HypervisorResult; use crate::vm::{BootInfo, Parameter, Vm}; @@ -191,10 +191,10 @@ impl Uhyve { let kvm_mem = kvm_userspace_memory_region { slot: 0, - flags: mem.flags(), + flags: mem.flags, memory_size: sz as u64, - guest_phys_addr: mem.guest_address() as u64, - userspace_addr: mem.host_address() as u64, + guest_phys_addr: mem.guest_address as u64, + userspace_addr: mem.host_address as u64, }; unsafe { vm.set_user_memory_region(kvm_mem) }?; @@ -202,11 +202,11 @@ impl Uhyve { if specs.mem_size > KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE { let kvm_mem = kvm_userspace_memory_region { slot: 1, - flags: mem.flags(), + flags: mem.flags, memory_size: (specs.mem_size - KVM_32BIT_GAP_START - KVM_32BIT_GAP_SIZE) as u64, - guest_phys_addr: (mem.guest_address() + KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) + guest_phys_addr: (mem.guest_address + KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) as u64, - userspace_addr: (mem.host_address() + KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) + userspace_addr: (mem.host_address + KVM_32BIT_GAP_START + KVM_32BIT_GAP_SIZE) as u64, }; @@ -266,7 +266,7 @@ impl Uhyve { Some(UhyveNetwork::new( evtfd, nic.to_string(), - mem.host_address() + SHAREDQUEUE_START, + mem.host_address + SHAREDQUEUE_START, )) } _ => None, @@ -332,7 +332,7 @@ impl Vm for Uhyve { } fn guest_mem(&self) -> (*mut u8, usize) { - (self.mem.host_address() as *mut u8, self.mem.memory_size()) + (self.mem.host_address as *mut u8, self.mem.memory_size) } fn kernel_path(&self) -> &Path { @@ -340,7 +340,7 @@ impl Vm for Uhyve { } fn create_cpu(&self, id: u32) -> HypervisorResult { - let vm_start = self.mem.host_address() as usize; + let vm_start = self.mem.host_address as usize; let tx = self.uhyve_device.as_ref().map(|dev| dev.tx.clone()); Ok(UhyveCPU::new( @@ -431,29 +431,11 @@ impl MmapMemory { } } -impl MemoryRegion for MmapMemory { - fn flags(&self) -> u32 { - self.flags - } - - fn memory_size(&self) -> usize { - self.memory_size - } - - fn guest_address(&self) -> usize { - self.guest_address - } - - fn host_address(&self) -> usize { - self.host_address - } -} - impl Drop for MmapMemory { fn drop(&mut self) { - if self.memory_size() > 0 { + if self.memory_size > 0 { unsafe { - munmap(self.host_address() as *mut c_void, self.memory_size()).unwrap(); + munmap(self.host_address as *mut c_void, self.memory_size).unwrap(); } } } From 864be81dc49931702b7b9b5865ee3ccb70d344cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Tue, 24 Aug 2021 23:12:28 +0200 Subject: [PATCH 3/5] Upgrade kvm-ioctl to 0.10 --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- src/linux/vcpu.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a98bcef8..1c8300dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,18 +363,18 @@ dependencies = [ [[package]] name = "kvm-bindings" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04936b66155d2b23a0df85c411c234b329206e548e1d7f94b8e47c66c87a8a4f" +checksum = "a78c049190826fff959994b7c1d8a2930d0a348f1b8f3aa4f9bb34cd5d7f2952" dependencies = [ "vmm-sys-util", ] [[package]] name = "kvm-ioctls" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2924454e22895c738e43331ae310459c74a11ded9c97dc250129ee10d2f9ca2" +checksum = "48dc14f9047df1873cf6942caccc7431d19c3d496ca7a0d162260c4cf0f64b76" dependencies = [ "kvm-bindings", "libc 0.2.101", diff --git a/Cargo.toml b/Cargo.toml index a52daeca..bb7ec851 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ rftrace = { version = "0.1", optional = true } rftrace-frontend = { version = "0.1", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -kvm-bindings = "0.4" -kvm-ioctls = "0.9" +kvm-bindings = "0.5" +kvm-ioctls = "0.10" mac_address = "1.1" nix = "0.22" tun-tap = { version = "0.1", default-features = false } diff --git a/src/linux/vcpu.rs b/src/linux/vcpu.rs index c42cf816..befcab38 100755 --- a/src/linux/vcpu.rs +++ b/src/linux/vcpu.rs @@ -423,7 +423,7 @@ impl VirtualCPU for UhyveCPU { } } } - VcpuExit::Debug => { + VcpuExit::Debug(_) => { info!("Caught Debug Interrupt!"); return Ok(VcpuStopReason::Debug); } From 00b2af277e1fec542b659e56b9e216f0a508908c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Mon, 6 Sep 2021 16:29:44 +0200 Subject: [PATCH 4/5] Change type of GDB port to u16 --- src/bin/uhyve.rs | 4 ++-- src/vm.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index c10b84f7..6a7891d5 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -244,11 +244,11 @@ fn main() { } let gdbport = matches .value_of("GDB_PORT") - .map(|p| p.parse::().expect("Could not parse gdb port")) + .map(|p| p.parse::().expect("Could not parse gdb port")) .or_else(|| { env::var("HERMIT_GDB_PORT") .ok() - .map(|p| p.parse::().expect("Could not parse gdb port")) + .map(|p| p.parse::().expect("Could not parse gdb port")) }); let params = vm::Parameter { diff --git a/src/vm.rs b/src/vm.rs index f4518747..3c6f524d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -105,7 +105,7 @@ pub struct Parameter<'a> { pub gateway: Option<&'a str>, pub mask: Option<&'a str>, pub nic: Option<&'a str>, - pub gdbport: Option, + pub gdbport: Option, } #[repr(C, packed)] From 06776ee675c3aa1d8feecdd2b52ffc2dfc04454e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sat, 10 Jul 2021 00:09:35 +0200 Subject: [PATCH 5/5] Implement GDB server using gdbstub --- Cargo.lock | 70 +++++++-- Cargo.toml | 5 + src/arch/mod.rs | 2 + src/arch/x86_64/mod.rs | 1 + src/arch/x86_64/registers/debug.rs | 120 +++++++++++++++ src/arch/x86_64/registers/mod.rs | 2 + src/lib.rs | 1 + src/linux/gdb/breakpoints.rs | 121 +++++++++++++++ src/linux/gdb/mod.rs | 146 ++++++++++++++++++ src/linux/gdb/regs.rs | 239 +++++++++++++++++++++++++++++ src/linux/gdb/section_offsets.rs | 16 ++ src/linux/mod.rs | 138 ++++++++++++++++- src/linux/uhyve.rs | 7 + src/linux/vcpu.rs | 16 +- src/macos/mod.rs | 1 + src/macos/uhyve.rs | 2 + src/macos/vcpu.rs | 6 +- src/vm.rs | 3 +- 18 files changed, 871 insertions(+), 25 deletions(-) create mode 100644 src/arch/mod.rs create mode 100644 src/arch/x86_64/mod.rs create mode 100644 src/arch/x86_64/registers/debug.rs create mode 100644 src/arch/x86_64/registers/mod.rs create mode 100644 src/linux/gdb/breakpoints.rs create mode 100644 src/linux/gdb/mod.rs create mode 100644 src/linux/gdb/regs.rs create mode 100644 src/linux/gdb/section_offsets.rs diff --git a/Cargo.lock b/Cargo.lock index 1c8300dc..328abb31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,12 +37,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "bit_field" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" - [[package]] name = "bit_field" version = "0.10.1" @@ -109,6 +103,12 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -184,7 +184,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -194,7 +194,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -205,7 +205,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", "memoffset", @@ -218,7 +218,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -279,6 +279,29 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50045aa8931ae01afbc5d72439e8f57f326becb8c70d07dfc816778eff3d167" +[[package]] +name = "gdbstub" +version = "0.5.0" +source = "git+https://github.com/daniel5151/gdbstub?branch=dev/0.6#9227dfd0b78db5b20859d13c890f6f47e597029e" +dependencies = [ + "bitflags", + "cfg-if 0.1.10", + "log", + "managed", + "num-traits", + "paste", +] + +[[package]] +name = "gdbstub_arch" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e358b9c0e1468eae66099062e47bb502849308b987b74b5e72f1936397c33c16" +dependencies = [ + "gdbstub", + "num-traits", +] + [[package]] name = "goblin" version = "0.4.2" @@ -404,7 +427,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -417,6 +440,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "memchr" version = "2.4.0" @@ -439,7 +468,7 @@ source = "git+https://github.com/nix-rust/nix#dab7332eabed8646f6d01a0d0688b4d143 dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc 0.2.98", "memoffset", ] @@ -469,6 +498,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "paste" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" + [[package]] name = "plain" version = "0.2.3" @@ -781,6 +816,8 @@ dependencies = [ "either", "env_logger", "envmnt", + "gdbstub", + "gdbstub_arch", "goblin", "kvm-bindings", "kvm-ioctls", @@ -865,7 +902,7 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -972,7 +1009,7 @@ version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcf43654d533a571fe7f7116373098a95ebc01279efdc5038fc45d3b220cb5a" dependencies = [ - "bit_field 0.10.1", + "bit_field", "bitflags", "raw-cpuid", ] @@ -980,10 +1017,9 @@ dependencies = [ [[package]] name = "x86_64" version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95947de37ad0d2d9a4a4dd22e0d042e034e5cbd7ab53edbca0d8035e0a6a64d" +source = "git+https://github.com/mkroening/x86_64?branch=debug-stable#6da998935e7ea79a8db33c7149198a67f95696ec" dependencies = [ - "bit_field 0.9.0", + "bit_field", "bitflags", "volatile", ] diff --git a/Cargo.toml b/Cargo.toml index bb7ec851..76d2f53d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,9 @@ default = [] instrument = ["rftrace", "rftrace-frontend"] [patch.crates-io] +gdbstub = { git = "https://github.com/daniel5151/gdbstub", branch = "dev/0.6" } nix = { git = "https://github.com/nix-rust/nix" } +x86_64 = { git = "https://github.com/mkroening/x86_64", branch = "debug-stable" } [dependencies] bitflags = "1.3" @@ -45,6 +47,8 @@ core_affinity = "0.5" either = "1.6" env_logger = "0.9" envmnt = "0.9" +gdbstub = "0.5" +gdbstub_arch = "0.1" goblin = { version = "0.4", default-features = false, features = ["elf64", "elf32", "endian_fd", "std"] } lazy_static = "1.4" libc = "0.2" @@ -52,6 +56,7 @@ log = "0.4" raw-cpuid = "10.2" rustc-serialize = "0.3" thiserror = "1.0" +x86_64 = "0.14" rftrace = { version = "0.1", optional = true } rftrace-frontend = { version = "0.1", optional = true } diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 00000000..832557ef --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_arch = "x86_64")] +pub mod x86_64; diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs new file mode 100644 index 00000000..8993d24a --- /dev/null +++ b/src/arch/x86_64/mod.rs @@ -0,0 +1 @@ +pub mod registers; diff --git a/src/arch/x86_64/registers/debug.rs b/src/arch/x86_64/registers/debug.rs new file mode 100644 index 00000000..3d8355c3 --- /dev/null +++ b/src/arch/x86_64/registers/debug.rs @@ -0,0 +1,120 @@ +//! Functions to read and write debug registers. + +use std::convert::{TryFrom, TryInto}; + +use gdbstub::target::ext::breakpoints::WatchKind; +use x86_64::{ + registers::debug::{ + DebugAddressRegisterNumber, Dr7Flags, Dr7Value, HwBreakpointCondition, HwBreakpointSize, + TryFromIntError, + }, + VirtAddr, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HwBreakpoint { + addr: VirtAddr, + size: HwBreakpointSize, + condition: HwBreakpointCondition, +} + +impl HwBreakpoint { + pub fn new_breakpoint(addr: u64, kind: usize) -> Result { + Ok(Self { + addr: VirtAddr::new(addr), + size: kind.try_into()?, + condition: HwBreakpointCondition::InstructionExecution, + }) + } + + pub fn new_watchpoint(addr: u64, len: u64, kind: WatchKind) -> Option { + let condition = match kind { + WatchKind::Write => Some(HwBreakpointCondition::DataWrites), + WatchKind::Read => None, + WatchKind::ReadWrite => Some(HwBreakpointCondition::DataReadsWrites), + }?; + + let ret = Self { + addr: VirtAddr::new(addr), + size: usize::try_from(len).ok()?.try_into().ok()?, + condition, + }; + + Some(ret) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct HwBreakpoints([Option; 4]); + +#[derive(Debug)] +pub struct CapacityExceededError(()); + +impl HwBreakpoints { + pub const fn new() -> Self { + Self([None; 4]) + } + + pub fn try_insert(&mut self, hw_breakpoint: HwBreakpoint) -> Result<(), CapacityExceededError> { + if let Some(entry) = self.0.iter_mut().find(|entry| entry.is_none()) { + *entry = Some(hw_breakpoint); + Ok(()) + } else { + Err(CapacityExceededError(())) + } + } + + pub fn take(&mut self, hw_breakpoint: &HwBreakpoint) -> Option { + self.0 + .iter_mut() + .find(|slot| slot.as_ref() == Some(hw_breakpoint))? + .take() + } + + fn control_value(&self) -> Dr7Value { + let dr7_flags = Dr7Flags::LOCAL_EXACT_BREAKPOINT_ENABLE + | Dr7Flags::GLOBAL_EXACT_BREAKPOINT_ENABLE + | Dr7Flags::GENERAL_DETECT_ENABLE; + let mut dr7_value = Dr7Value::from(dr7_flags); + + for (i, hw_breakpoint) in + self.0.iter().enumerate().filter_map(|(i, hw_breakpoint)| { + hw_breakpoint.map(|hw_breakpoint| (i, hw_breakpoint)) + }) { + let n = DebugAddressRegisterNumber::new(i.try_into().unwrap()).unwrap(); + + dr7_value + .flags_mut() + .insert(Dr7Flags::global_breakpoint_enable(n)); + dr7_value.set_condition(n, hw_breakpoint.condition); + dr7_value.set_size(n, hw_breakpoint.size); + } + + dr7_value + } + + pub fn registers(self) -> [u64; 8] { + let control_value = self.control_value(); + let address = |hw_breakpoint: Option| { + hw_breakpoint + .map(|hw_breakpoint| hw_breakpoint.addr.as_u64()) + .unwrap_or(0) + }; + [ + address(self.0[0]), + address(self.0[1]), + address(self.0[2]), + address(self.0[3]), + 0, + 0, + 0, + control_value.bits(), + ] + } +} + +impl Default for HwBreakpoints { + fn default() -> Self { + Self::new() + } +} diff --git a/src/arch/x86_64/registers/mod.rs b/src/arch/x86_64/registers/mod.rs new file mode 100644 index 00000000..59ae6e1a --- /dev/null +++ b/src/arch/x86_64/registers/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_os = "linux")] +pub mod debug; diff --git a/src/lib.rs b/src/lib.rs index b99fff6b..edcc1601 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod macros; #[macro_use] extern crate log; +mod arch; pub mod consts; #[cfg(target_os = "linux")] pub mod linux; diff --git a/src/linux/gdb/breakpoints.rs b/src/linux/gdb/breakpoints.rs new file mode 100644 index 00000000..f522608d --- /dev/null +++ b/src/linux/gdb/breakpoints.rs @@ -0,0 +1,121 @@ +use std::collections::{hash_map::Entry, HashMap}; + +use gdbstub::target::{self, ext::breakpoints::WatchKind, TargetResult}; + +use crate::arch::x86_64::registers; + +use super::GdbUhyve; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct SwBreakpoint { + addr: u64, + kind: usize, +} + +impl SwBreakpoint { + const OPCODE: u8 = 0xcc; + + pub fn new(addr: u64, kind: usize) -> Self { + Self { addr, kind } + } +} + +pub type SwBreakpoints = HashMap>; + +impl target::ext::breakpoints::Breakpoints for GdbUhyve { + #[inline(always)] + fn sw_breakpoint(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn hw_breakpoint(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn hw_watchpoint(&mut self) -> Option> { + Some(self) + } +} + +impl target::ext::breakpoints::SwBreakpoint for GdbUhyve { + fn add_sw_breakpoint(&mut self, addr: u64, kind: usize) -> TargetResult { + let sw_breakpoint = SwBreakpoint::new(addr, kind); + + if let Entry::Vacant(entry) = self.sw_breakpoints.entry(sw_breakpoint) { + let instructions = unsafe { self.vcpu.memory(addr, kind) }; + entry.insert(instructions.into()); + instructions.fill(SwBreakpoint::OPCODE); + Ok(true) + } else { + Ok(false) + } + } + + fn remove_sw_breakpoint(&mut self, addr: u64, kind: usize) -> TargetResult { + let sw_breakpoint = SwBreakpoint::new(addr, kind); + + if let Entry::Occupied(entry) = self.sw_breakpoints.entry(sw_breakpoint) { + let instructions = unsafe { self.vcpu.memory(addr, kind) }; + instructions.copy_from_slice(&entry.remove()); + Ok(true) + } else { + Ok(false) + } + } +} + +impl target::ext::breakpoints::HwBreakpoint for GdbUhyve { + fn add_hw_breakpoint(&mut self, addr: u64, kind: usize) -> TargetResult { + let hw_breakpoint = match registers::debug::HwBreakpoint::new_breakpoint(addr, kind) { + Ok(hw_breakpoint) => hw_breakpoint, + Err(_) => return Ok(false), + }; + + let success = self.hw_breakpoints.try_insert(hw_breakpoint).is_ok(); + Ok(success) + } + + fn remove_hw_breakpoint(&mut self, addr: u64, kind: usize) -> TargetResult { + let hw_breakpoint = match registers::debug::HwBreakpoint::new_breakpoint(addr, kind) { + Ok(hw_breakpoint) => hw_breakpoint, + Err(_) => return Ok(false), + }; + + let success = self.hw_breakpoints.take(&hw_breakpoint).is_some(); + Ok(success) + } +} + +impl target::ext::breakpoints::HwWatchpoint for GdbUhyve { + fn add_hw_watchpoint( + &mut self, + addr: u64, + len: u64, + kind: WatchKind, + ) -> TargetResult { + let hw_breakpoint = match registers::debug::HwBreakpoint::new_watchpoint(addr, len, kind) { + Some(hw_breakpoint) => hw_breakpoint, + None => return Ok(false), + }; + + let success = self.hw_breakpoints.try_insert(hw_breakpoint).is_ok(); + Ok(success) + } + + fn remove_hw_watchpoint( + &mut self, + addr: u64, + len: u64, + kind: WatchKind, + ) -> TargetResult { + let hw_breakpoint = match registers::debug::HwBreakpoint::new_watchpoint(addr, len, kind) { + Some(hw_breakpoint) => hw_breakpoint, + None => return Ok(false), + }; + + let success = self.hw_breakpoints.take(&hw_breakpoint).is_some(); + Ok(success) + } +} diff --git a/src/linux/gdb/mod.rs b/src/linux/gdb/mod.rs new file mode 100644 index 00000000..8ebbed16 --- /dev/null +++ b/src/linux/gdb/mod.rs @@ -0,0 +1,146 @@ +mod breakpoints; +mod regs; +mod section_offsets; + +use gdbstub::target::{ + self, + ext::base::singlethread::{GdbInterrupt, ResumeAction, SingleThreadOps, StopReason}, + Target, TargetError, TargetResult, +}; +use gdbstub_arch::x86::reg::X86_64CoreRegs; +use kvm_bindings::{ + kvm_guest_debug, kvm_guest_debug_arch, BP_VECTOR, DB_VECTOR, KVM_GUESTDBG_ENABLE, + KVM_GUESTDBG_SINGLESTEP, KVM_GUESTDBG_USE_HW_BP, KVM_GUESTDBG_USE_SW_BP, +}; +use std::convert::TryInto; +use x86_64::registers::debug::Dr6Flags; + +use crate::linux::vcpu::UhyveCPU; +use crate::vm::{VcpuStopReason, VirtualCPU}; +use crate::{arch::x86_64::registers::debug::HwBreakpoints, Uhyve}; + +use self::breakpoints::SwBreakpoints; + +use super::HypervisorError; + +pub struct GdbUhyve { + vm: Uhyve, + vcpu: UhyveCPU, + hw_breakpoints: HwBreakpoints, + sw_breakpoints: SwBreakpoints, +} + +impl GdbUhyve { + pub fn new(vm: Uhyve, vcpu: UhyveCPU) -> Self { + Self { + vm, + vcpu, + hw_breakpoints: HwBreakpoints::new(), + sw_breakpoints: SwBreakpoints::new(), + } + } +} + +impl Target for GdbUhyve { + type Arch = gdbstub_arch::x86::X86_64_SSE; + type Error = HypervisorError; + + // --------------- IMPORTANT NOTE --------------- + // Always remember to annotate IDET enable methods with `inline(always)`! + // Without this annotation, LLVM might fail to dead-code-eliminate nested IDET + // implementations, resulting in unnecessary binary bloat. + + #[inline(always)] + fn base_ops(&mut self) -> target::ext::base::BaseOps<'_, Self::Arch, Self::Error> { + target::ext::base::BaseOps::SingleThread(self) + } + + #[inline(always)] + fn breakpoints(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn section_offsets( + &mut self, + ) -> Option> { + Some(self) + } +} + +impl GdbUhyve { + fn apply_guest_debug(&mut self, step: bool) -> Result<(), kvm_ioctls::Error> { + let debugreg = self.hw_breakpoints.registers(); + let mut control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP | KVM_GUESTDBG_USE_HW_BP; + if step { + control |= KVM_GUESTDBG_SINGLESTEP; + } + let debug_struct = kvm_guest_debug { + control, + pad: 0, + arch: kvm_guest_debug_arch { debugreg }, + }; + self.vcpu.get_vcpu().set_guest_debug(&debug_struct) + } +} + +impl SingleThreadOps for GdbUhyve { + fn resume( + &mut self, + action: ResumeAction, + gdb_interrupt: GdbInterrupt<'_>, + ) -> Result>, Self::Error> { + let step = matches!(action, ResumeAction::Step | ResumeAction::StepWithSignal(_)); + self.apply_guest_debug(step)?; + let mut gdb_interrupt = gdb_interrupt.no_async(); + match self.vcpu.r#continue()? { + VcpuStopReason::Debug(debug) => match debug.exception { + DB_VECTOR => { + let dr6_flags = Dr6Flags::from_bits_truncate(debug.dr6); + if dr6_flags.contains(Dr6Flags::STEP) { + Ok(Some(StopReason::DoneStep)) + } else if dr6_flags.intersects(Dr6Flags::TRAP) { + Ok(Some(StopReason::HwBreak)) + } else { + unreachable!("could not identify KVM debug exit reason") + } + } + BP_VECTOR => Ok(Some(StopReason::SwBreak)), + vector => unreachable!("unknown KVM exception vector: {}", vector), + }, + VcpuStopReason::Exit(code) => { + let status = if code == 0 { 0 } else { 1 }; + Ok(Some(StopReason::Exited(status))) + } + VcpuStopReason::Kick => { + assert!( + gdb_interrupt.pending(), + "VCPU got kicked without a pending GDB interrupt" + ); + Ok(None) + } + } + } + + fn read_registers(&mut self, regs: &mut X86_64CoreRegs) -> TargetResult<(), Self> { + regs::read(self.vcpu.get_vcpu(), regs) + .map_err(|error| TargetError::Errno(error.errno().try_into().unwrap())) + } + + fn write_registers(&mut self, regs: &X86_64CoreRegs) -> TargetResult<(), Self> { + regs::write(regs, self.vcpu.get_vcpu()) + .map_err(|error| TargetError::Errno(error.errno().try_into().unwrap())) + } + + fn read_addrs(&mut self, start_addr: u64, data: &mut [u8]) -> TargetResult<(), Self> { + let src = unsafe { self.vcpu.memory(start_addr, data.len()) }; + data.copy_from_slice(src); + Ok(()) + } + + fn write_addrs(&mut self, start_addr: u64, data: &[u8]) -> TargetResult<(), Self> { + let mem = unsafe { self.vcpu.memory(start_addr, data.len()) }; + mem.copy_from_slice(data); + Ok(()) + } +} diff --git a/src/linux/gdb/regs.rs b/src/linux/gdb/regs.rs new file mode 100644 index 00000000..2627ce32 --- /dev/null +++ b/src/linux/gdb/regs.rs @@ -0,0 +1,239 @@ +use std::convert::TryInto; + +use gdbstub_arch::x86::reg::{X86SegmentRegs, X86_64CoreRegs, X87FpuInternalRegs, F80}; +use kvm_bindings::{kvm_fpu, kvm_regs, kvm_sregs}; +use kvm_ioctls::VcpuFd; + +/// [`kvm_regs`]-related [`X86_64CoreRegs`] fields. +struct Regs { + regs: [u64; 16], + eflags: u32, + rip: u64, +} + +impl From for Regs { + fn from(kvm_regs: kvm_regs) -> Self { + let regs = [ + kvm_regs.rax, + kvm_regs.rbx, + kvm_regs.rcx, + kvm_regs.rdx, + kvm_regs.rsi, + kvm_regs.rdi, + kvm_regs.rbp, + kvm_regs.rsp, + kvm_regs.r8, + kvm_regs.r9, + kvm_regs.r10, + kvm_regs.r11, + kvm_regs.r12, + kvm_regs.r13, + kvm_regs.r14, + kvm_regs.r15, + ]; + // Truncating does not lose information, as upper half of RFLAGS is reserved. + let eflags = kvm_regs.rflags as _; + let rip = kvm_regs.rip; + Self { regs, eflags, rip } + } +} + +impl From for kvm_regs { + fn from(regs: Regs) -> Self { + kvm_regs { + rax: regs.regs[0], + rbx: regs.regs[1], + rcx: regs.regs[2], + rdx: regs.regs[3], + rsi: regs.regs[4], + rdi: regs.regs[5], + rbp: regs.regs[6], + rsp: regs.regs[7], + r8: regs.regs[8], + r9: regs.regs[9], + r10: regs.regs[10], + r11: regs.regs[11], + r12: regs.regs[12], + r13: regs.regs[13], + r14: regs.regs[14], + r15: regs.regs[15], + rflags: regs.eflags.into(), + rip: regs.rip, + } + } +} + +/// [`kvm_sregs`]-related [`X86_64CoreRegs`] fields. +struct Sregs { + segments: X86SegmentRegs, +} + +impl From for Sregs { + fn from(kvm_sregs: kvm_sregs) -> Self { + let segments = X86SegmentRegs { + cs: kvm_sregs.cs.selector.into(), + ss: kvm_sregs.ss.selector.into(), + ds: kvm_sregs.ds.selector.into(), + es: kvm_sregs.es.selector.into(), + fs: kvm_sregs.fs.selector.into(), + gs: kvm_sregs.gs.selector.into(), + }; + Self { segments } + } +} + +impl Sregs { + fn update(self, kvm_sregs: &mut kvm_sregs) { + kvm_sregs.cs.selector = self.segments.cs.try_into().unwrap(); + kvm_sregs.ss.selector = self.segments.ss.try_into().unwrap(); + kvm_sregs.ds.selector = self.segments.ds.try_into().unwrap(); + kvm_sregs.es.selector = self.segments.es.try_into().unwrap(); + kvm_sregs.fs.selector = self.segments.fs.try_into().unwrap(); + kvm_sregs.gs.selector = self.segments.gs.try_into().unwrap(); + } +} + +/// [`kvm_fpu`]-related [`X86_64CoreRegs`] fields. +struct Fpu { + st: [F80; 8], + fpu: X87FpuInternalRegs, + xmm: [u128; 16], + mxcsr: u32, +} + +impl From for Fpu { + fn from(kvm_fpu: kvm_fpu) -> Self { + // For details on `kvm_fpu` see: + // * https://elixir.bootlin.com/linux/v5.13.1/source/arch/x86/include/uapi/asm/kvm.h#L163 + // * https://elixir.bootlin.com/linux/v5.13.1/source/arch/x86/kvm/x86.c#L10181 + // * https://elixir.bootlin.com/linux/v5.13.1/source/arch/x86/include/asm/fpu/types.h#L34 + + let st = kvm_fpu.fpr.map(|fpr| fpr[..10].try_into().unwrap()); + + let fpu = X87FpuInternalRegs { + fctrl: kvm_fpu.fcw.into(), + fstat: kvm_fpu.fsw.into(), + ftag: kvm_fpu.ftwx.into(), + fiseg: kvm_fpu.last_ip as _, + fioff: (kvm_fpu.last_ip >> u32::BITS) as _, + foseg: kvm_fpu.last_dp as _, + fooff: (kvm_fpu.last_dp >> u32::BITS) as _, + fop: kvm_fpu.last_opcode.into(), + }; + + let xmm = kvm_fpu.xmm.map(u128::from_ne_bytes); + + let mxcsr = kvm_fpu.mxcsr; + + Self { + st, + fpu, + xmm, + mxcsr, + } + } +} + +impl From for kvm_fpu { + fn from(fpu: Fpu) -> Self { + let fpr = fpu + .st + .iter() + .map(|fpr| [&fpr[..], &[0; 6][..]].concat().try_into().unwrap()) + .collect::>() + .try_into() + .unwrap(); + + let last_ip = { + let mut last_ip = fpu.fpu.fiseg.into(); + last_ip |= u64::from(fpu.fpu.fioff) << u32::BITS; + last_ip + }; + + let last_dp = { + let mut last_dp = fpu.fpu.foseg.into(); + last_dp |= u64::from(fpu.fpu.fooff) << u32::BITS; + last_dp + }; + + let xmm = IntoIterator::into_iter(fpu.xmm) + .map(u128::to_ne_bytes) + .collect::>() + .try_into() + .unwrap(); + + kvm_fpu { + fpr, + fcw: fpu.fpu.fctrl.try_into().unwrap(), + fsw: fpu.fpu.fstat.try_into().unwrap(), + ftwx: fpu.fpu.ftag.try_into().unwrap(), + pad1: 0, + last_opcode: fpu.fpu.fop.try_into().unwrap(), + last_ip, + last_dp, + xmm, + mxcsr: fpu.mxcsr, + pad2: 0, + } + } +} + +pub fn read(vcpu: &VcpuFd, regs: &mut X86_64CoreRegs) -> Result<(), kvm_ioctls::Error> { + // TODO: Rewrite using destructuring assignment once stabilized + + let Regs { + regs: gp_regs, + eflags, + rip, + } = vcpu.get_regs()?.into(); + regs.regs = gp_regs; + regs.eflags = eflags; + regs.rip = rip; + + let Sregs { segments } = vcpu.get_sregs()?.into(); + regs.segments = segments; + + let Fpu { + st, + fpu, + xmm, + mxcsr, + } = vcpu.get_fpu()?.into(); + regs.st = st; + regs.fpu = fpu; + regs.xmm = xmm; + regs.mxcsr = mxcsr; + + Ok(()) +} + +pub fn write(regs: &X86_64CoreRegs, vcpu: &VcpuFd) -> Result<(), kvm_ioctls::Error> { + let X86_64CoreRegs { + regs, + eflags, + rip, + segments, + st, + fpu, + xmm, + mxcsr, + } = regs.clone(); + + let kvm_regs = Regs { regs, eflags, rip }.into(); + vcpu.set_regs(&kvm_regs)?; + + let mut kvm_sregs = vcpu.get_sregs()?; + Sregs { segments }.update(&mut kvm_sregs); + vcpu.set_sregs(&kvm_sregs)?; + + let kvm_fpu = Fpu { + st, + fpu, + xmm, + mxcsr, + } + .into(); + vcpu.set_fpu(&kvm_fpu)?; + + Ok(()) +} diff --git a/src/linux/gdb/section_offsets.rs b/src/linux/gdb/section_offsets.rs new file mode 100644 index 00000000..c5af9d04 --- /dev/null +++ b/src/linux/gdb/section_offsets.rs @@ -0,0 +1,16 @@ +use gdbstub::target::{self, ext::section_offsets::Offsets}; + +use crate::vm::Vm; + +use super::GdbUhyve; + +impl target::ext::section_offsets::SectionOffsets for GdbUhyve { + fn get_section_offsets(&mut self) -> Result, Self::Error> { + let offset = self.vm.get_offset(); + Ok(Offsets::Sections { + text: offset, + data: offset, + bss: Some(offset), + }) + } +} diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 63b76731..94de284e 100755 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1,27 +1,39 @@ +pub mod gdb; pub mod uhyve; pub mod vcpu; pub mod virtio; pub mod virtqueue; pub type HypervisorError = kvm_ioctls::Error; +pub type DebugExitInfo = kvm_bindings::kvm_debug_exit_arch; use std::{ - hint, mem, + hint, + io::{self, Read}, + mem, + net::{TcpListener, TcpStream}, os::unix::prelude::JoinHandleExt, sync::{Arc, Barrier}, thread, + time::Duration, }; use core_affinity::CoreId; +use gdbstub::{ + state_machine::Event, + target::{ext::base::multithread::ThreadStopReason, Target}, + ConnectionExt, DisconnectReason, GdbStub, GdbStubStateMachine, +}; use kvm_ioctls::Kvm; use lazy_static::lazy_static; use libc::{SIGRTMAX, SIGRTMIN}; use nix::sys::{ - pthread::{pthread_kill, Pthread}, + pthread::{pthread_kill, pthread_self, Pthread}, signal::{signal, SigHandler, Signal}, }; use crate::{ + linux::gdb::GdbUhyve, vm::{VirtualCPU, Vm}, Uhyve, }; @@ -74,6 +86,14 @@ impl Uhyve { self.load_kernel().expect("Unabled to load the kernel"); } + if self.gdb_port.is_none() { + self.run_no_gdb(cpu_affinity) + } else { + self.run_gdb(cpu_affinity) + } + } + + fn run_no_gdb(self, cpu_affinity: Option>) -> i32 { // After spinning up all vCPU threads, the main thread waits for any vCPU to end execution. let barrier = Arc::new(Barrier::new(2)); @@ -141,4 +161,118 @@ impl Uhyve { ); code[0] } + + fn run_gdb(self, cpu_affinity: Option>) -> i32 { + let cpu_id = 0; + + let local_cpu_affinity = cpu_affinity + .as_ref() + .map(|core_ids| core_ids.get(cpu_id as usize).copied()) + .flatten(); + + match local_cpu_affinity { + Some(core_id) => { + debug!("Trying to pin thread {} to CPU {}", cpu_id, core_id.id); + core_affinity::set_for_current(core_id); // This does not return an error if it fails :( + } + None => debug!("No affinity specified, not binding thread"), + } + + let mut cpu = self.create_cpu(cpu_id).unwrap(); + cpu.init(self.get_entry_point()).unwrap(); + + let connection = wait_for_gdb_connection(self.gdb_port.unwrap()).unwrap(); + + let debugger = gdbstub::GdbStub::new(connection.try_clone().unwrap()); + let mut debuggable_vcpu = GdbUhyve::new(self, cpu); + + match run_debugger(&mut debuggable_vcpu, debugger, connection).unwrap() { + DisconnectReason::TargetExited(code) => code.into(), + DisconnectReason::TargetTerminated(_) => unreachable!(), + DisconnectReason::Disconnect => { + eprintln!("Debugger disconnected."); + 0 + } + DisconnectReason::Kill => { + eprintln!("Kill command received."); + 0 + } + } + } +} + +fn run_debugger( + target: &mut T, + gdb: GdbStub<'_, T, C>, + mut tcp_stream: TcpStream, +) -> Result> { + let parent_thread = pthread_self(); + thread::spawn(move || { + loop { + // Block on TCP stream without consuming any data. + Read::read(&mut tcp_stream, &mut []).unwrap(); + + // Kick VCPU out of KVM_RUN + KickSignal::pthread_kill(parent_thread).unwrap(); + + // Wait for all inputs to be processed and for VCPU to be running again + thread::sleep(Duration::from_millis(20)); + } + }); + + let mut gdb = gdb.run_state_machine()?; + loop { + gdb = match gdb { + GdbStubStateMachine::Pump(mut gdb) => { + let byte = gdb + .borrow_conn() + .read() + .map_err(gdbstub::GdbStubError::ConnectionRead)?; + + let (gdb, disconnect_reason) = gdb.pump(target, byte)?; + if let Some(disconnect_reason) = disconnect_reason { + break Ok(disconnect_reason); + } + gdb + } + GdbStubStateMachine::DeferredStopReason(mut gdb) => { + let byte = gdb + .borrow_conn() + .read() + .map_err(gdbstub::GdbStubError::ConnectionRead)?; + + let (gdb, event) = gdb.pump(target, byte)?; + match event { + Event::None => gdb, + Event::Disconnect(disconnect_reason) => break Ok(disconnect_reason), + Event::CtrlCInterrupt => { + // when an interrupt is received, report the `GdbInterrupt` stop reason. + if let GdbStubStateMachine::DeferredStopReason(gdb) = gdb { + match gdb + .deferred_stop_reason(target, ThreadStopReason::GdbInterrupt)? + { + (_, Some(disconnect_reason)) => break Ok(disconnect_reason), + (gdb, None) => gdb, + } + } else { + gdb + } + } + } + } + } + } +} + +fn wait_for_gdb_connection(port: u16) -> io::Result { + let sockaddr = format!("localhost:{}", port); + eprintln!("Waiting for a GDB connection on {:?}...", sockaddr); + let sock = TcpListener::bind(sockaddr)?; + let (stream, addr) = sock.accept()?; + + // Blocks until a GDB client connects via TCP. + // i.e: Running `target remote localhost:` from the GDB prompt. + + eprintln!("Debugger connected from {}", addr); + Ok(stream) // `TcpStream` implements `gdbstub::Connection` } diff --git a/src/linux/uhyve.rs b/src/linux/uhyve.rs index b093127c..57dc6cd0 100755 --- a/src/linux/uhyve.rs +++ b/src/linux/uhyve.rs @@ -136,6 +136,7 @@ pub struct Uhyve { mask: Option, uhyve_device: Option, virtio_device: Arc>, + pub(super) gdb_port: Option, } impl fmt::Debug for Uhyve { @@ -272,6 +273,11 @@ impl Uhyve { _ => None, }; + assert!( + specs.gdbport.is_none() || specs.num_cpus == 1, + "gdbstub is only supported with one CPU" + ); + let hyve = Uhyve { vm, offset: 0, @@ -286,6 +292,7 @@ impl Uhyve { mask, uhyve_device, virtio_device, + gdb_port: specs.gdbport, }; hyve.init_guest_mem(); diff --git a/src/linux/vcpu.rs b/src/linux/vcpu.rs index befcab38..5535fcd9 100755 --- a/src/linux/vcpu.rs +++ b/src/linux/vcpu.rs @@ -7,8 +7,10 @@ use crate::vm::VcpuStopReason; use crate::vm::VirtualCPU; use kvm_bindings::*; use kvm_ioctls::{VcpuExit, VcpuFd}; +use std::convert::TryInto; use std::path::Path; use std::path::PathBuf; +use std::slice; use std::sync::{Arc, Mutex}; use x86_64::registers::control::{Cr0Flags, Cr4Flags}; @@ -30,6 +32,12 @@ pub struct UhyveCPU { } impl UhyveCPU { + pub unsafe fn memory(&mut self, start_addr: u64, len: usize) -> &mut [u8] { + let phys = self.virt_to_phys(start_addr.try_into().unwrap()); + let host = self.host_address(phys); + slice::from_raw_parts_mut(host as *mut u8, len) + } + pub fn new( id: u32, kernel_path: PathBuf, @@ -423,9 +431,9 @@ impl VirtualCPU for UhyveCPU { } } } - VcpuExit::Debug(_) => { + VcpuExit::Debug(debug) => { info!("Caught Debug Interrupt!"); - return Ok(VcpuStopReason::Debug); + return Ok(VcpuStopReason::Debug(debug)); } VcpuExit::InternalError => { panic!("{:?}", VcpuExit::InternalError) @@ -444,7 +452,9 @@ impl VirtualCPU for UhyveCPU { fn run(&mut self) -> HypervisorResult> { match self.r#continue()? { - VcpuStopReason::Debug => todo!(), + VcpuStopReason::Debug(_) => { + unreachable!("reached debug exit without running in debugging mode") + } VcpuStopReason::Exit(code) => Ok(Some(code)), VcpuStopReason::Kick => Ok(None), } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 482ff713..f615299c 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -16,6 +16,7 @@ pub mod uhyve; pub mod vcpu; pub type HypervisorError = xhypervisor::Error; +pub type DebugExitInfo = (); impl Uhyve { /// Runs the VM. diff --git a/src/macos/uhyve.rs b/src/macos/uhyve.rs index 7f425a81..2a89a080 100644 --- a/src/macos/uhyve.rs +++ b/src/macos/uhyve.rs @@ -69,6 +69,8 @@ impl Uhyve { )?; } + assert!(specs.gdbport.is_none(), "gdbstub is not supported on macos"); + let hyve = Uhyve { offset: 0, entry_point: 0, diff --git a/src/macos/vcpu.rs b/src/macos/vcpu.rs index 29ede1c6..a4e0de05 100644 --- a/src/macos/vcpu.rs +++ b/src/macos/vcpu.rs @@ -609,7 +609,7 @@ impl VirtualCPU for UhyveCPU { irq_vec ); debug!("Handle breakpoint exception"); - return Ok(VcpuStopReason::Debug); + return Ok(VcpuStopReason::Debug(())); } vmx_exit::VMX_REASON_CPUID => { self.emulate_cpuid(rip)?; @@ -725,7 +725,9 @@ impl VirtualCPU for UhyveCPU { fn run(&mut self) -> HypervisorResult> { match self.r#continue()? { - VcpuStopReason::Debug => unimplemented!(), + VcpuStopReason::Debug(_) => { + unreachable!("reached debug exit without running in debugging mode") + } VcpuStopReason::Exit(code) => Ok(Some(code)), VcpuStopReason::Kick => Ok(None), } diff --git a/src/vm.rs b/src/vm.rs index 3c6f524d..6af73e8f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -19,6 +19,7 @@ use thiserror::Error; use crate::consts::*; use crate::os::vcpu::UhyveCPU; +use crate::os::DebugExitInfo; use crate::os::HypervisorError; const MHZ_TO_HZ: u64 = 1000000; @@ -191,7 +192,7 @@ pub type LoadKernelResult = Result; /// Reasons for vCPU exits. pub enum VcpuStopReason { /// The vCPU stopped for debugging. - Debug, + Debug(DebugExitInfo), /// The vCPU exited with the specified exit code. Exit(i32),