#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
use core::arch::asm;
use core::ops::Range;
use bit_field::BitField;
use bitflags::bitflags;
pub trait DebugAddressRegister {
const NUM: DebugAddressRegisterNumber;
#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
fn read() -> u64;
#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
fn write(addr: u64);
}
macro_rules! debug_address_register {
($Dr:ident, $name:literal) => {
#[derive(Debug)]
pub struct $Dr;
impl DebugAddressRegister for $Dr {
const NUM: DebugAddressRegisterNumber = DebugAddressRegisterNumber::$Dr;
#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
#[inline]
fn read() -> u64 {
let addr;
unsafe {
asm!(concat!("mov {}, ", $name), out(reg) addr, options(nomem, nostack, preserves_flags));
}
addr
}
#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
#[inline]
fn write(addr: u64) {
unsafe {
asm!(concat!("mov ", $name, ", {}"), in(reg) addr, options(nomem, nostack, preserves_flags));
}
}
}
};
}
debug_address_register!(Dr0, "dr0");
debug_address_register!(Dr1, "dr1");
debug_address_register!(Dr2, "dr2");
debug_address_register!(Dr3, "dr3");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugAddressRegisterNumber {
Dr0,
Dr1,
Dr2,
Dr3,
}
impl DebugAddressRegisterNumber {
pub const fn new(n: u8) -> Option<Self> {
match n {
0 => Some(Self::Dr0),
1 => Some(Self::Dr1),
2 => Some(Self::Dr2),
3 => Some(Self::Dr3),
_ => None,
}
}
pub const fn get(self) -> u8 {
match self {
Self::Dr0 => 0,
Self::Dr1 => 1,
Self::Dr2 => 2,
Self::Dr3 => 3,
}
}
}
#[derive(Debug)]
pub struct Dr6;
bitflags! {
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Dr6Flags: u64 {
const TRAP0 = 1;
const TRAP1 = 1 << 1;
const TRAP2 = 1 << 2;
const TRAP3 = 1 << 3;
const TRAP = Self::TRAP0.bits() | Self::TRAP1.bits() | Self::TRAP2.bits() | Self::TRAP3.bits();
const ACCESS_DETECTED = 1 << 13;
const STEP = 1 << 14;
const SWITCH = 1 << 15;
const RTM = 1 << 16;
}
}
impl Dr6Flags {
pub fn trap(n: DebugAddressRegisterNumber) -> Self {
match n {
DebugAddressRegisterNumber::Dr0 => Self::TRAP0,
DebugAddressRegisterNumber::Dr1 => Self::TRAP1,
DebugAddressRegisterNumber::Dr2 => Self::TRAP2,
DebugAddressRegisterNumber::Dr3 => Self::TRAP3,
}
}
}
bitflags! {
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Dr7Flags: u64 {
const LOCAL_BREAKPOINT_0_ENABLE = 1;
const LOCAL_BREAKPOINT_1_ENABLE = 1 << 2;
const LOCAL_BREAKPOINT_2_ENABLE = 1 << 4;
const LOCAL_BREAKPOINT_3_ENABLE = 1 << 6;
const GLOBAL_BREAKPOINT_0_ENABLE = 1 << 1;
const GLOBAL_BREAKPOINT_1_ENABLE = 1 << 3;
const GLOBAL_BREAKPOINT_2_ENABLE = 1 << 5;
const GLOBAL_BREAKPOINT_3_ENABLE = 1 << 7;
const LOCAL_EXACT_BREAKPOINT_ENABLE = 1 << 8;
const GLOBAL_EXACT_BREAKPOINT_ENABLE = 1 << 9;
const RESTRICTED_TRANSACTIONAL_MEMORY = 1 << 11;
const GENERAL_DETECT_ENABLE = 1 << 13;
}
}
impl Dr7Flags {
pub fn local_breakpoint_enable(n: DebugAddressRegisterNumber) -> Self {
match n {
DebugAddressRegisterNumber::Dr0 => Self::LOCAL_BREAKPOINT_0_ENABLE,
DebugAddressRegisterNumber::Dr1 => Self::LOCAL_BREAKPOINT_1_ENABLE,
DebugAddressRegisterNumber::Dr2 => Self::LOCAL_BREAKPOINT_2_ENABLE,
DebugAddressRegisterNumber::Dr3 => Self::LOCAL_BREAKPOINT_3_ENABLE,
}
}
pub fn global_breakpoint_enable(n: DebugAddressRegisterNumber) -> Self {
match n {
DebugAddressRegisterNumber::Dr0 => Self::GLOBAL_BREAKPOINT_0_ENABLE,
DebugAddressRegisterNumber::Dr1 => Self::GLOBAL_BREAKPOINT_1_ENABLE,
DebugAddressRegisterNumber::Dr2 => Self::GLOBAL_BREAKPOINT_2_ENABLE,
DebugAddressRegisterNumber::Dr3 => Self::GLOBAL_BREAKPOINT_3_ENABLE,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum BreakpointCondition {
InstructionExecution = 0b00,
DataWrites = 0b01,
IoReadsWrites = 0b10,
DataReadsWrites = 0b11,
}
impl BreakpointCondition {
pub const fn from_bits(bits: u64) -> Option<Self> {
match bits {
0b00 => Some(Self::InstructionExecution),
0b01 => Some(Self::DataWrites),
0b10 => Some(Self::IoReadsWrites),
0b11 => Some(Self::DataReadsWrites),
_ => None,
}
}
const fn bit_range(n: DebugAddressRegisterNumber) -> Range<usize> {
let lsb = (16 + 4 * n.get()) as usize;
lsb..lsb + 2
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum BreakpointSize {
Length1B = 0b00,
Length2B = 0b01,
Length8B = 0b10,
Length4B = 0b11,
}
impl BreakpointSize {
pub const fn new(size: usize) -> Option<Self> {
match size {
1 => Some(Self::Length1B),
2 => Some(Self::Length2B),
8 => Some(Self::Length8B),
4 => Some(Self::Length4B),
_ => None,
}
}
pub const fn from_bits(bits: u64) -> Option<Self> {
match bits {
0b00 => Some(Self::Length1B),
0b01 => Some(Self::Length2B),
0b10 => Some(Self::Length8B),
0b11 => Some(Self::Length4B),
_ => None,
}
}
const fn bit_range(n: DebugAddressRegisterNumber) -> Range<usize> {
let lsb = (18 + 4 * n.get()) as usize;
lsb..lsb + 2
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Dr7Value {
bits: u64,
}
impl From<Dr7Flags> for Dr7Value {
fn from(dr7_flags: Dr7Flags) -> Self {
Self::from_bits_truncate(dr7_flags.bits())
}
}
impl Dr7Value {
const fn valid_bits() -> u64 {
let field_valid_bits = (1 << 32) - (1 << 16);
let flag_valid_bits = Dr7Flags::all().bits();
field_valid_bits | flag_valid_bits
}
#[inline]
pub const fn from_bits(bits: u64) -> Option<Self> {
if (bits & !Self::valid_bits()) == 0 {
Some(Self { bits })
} else {
None
}
}
#[inline]
pub const fn from_bits_truncate(bits: u64) -> Self {
Self {
bits: bits & Self::valid_bits(),
}
}
#[inline]
pub const unsafe fn from_bits_unchecked(bits: u64) -> Self {
Self { bits }
}
#[inline]
pub const fn bits(&self) -> u64 {
self.bits
}
#[inline]
pub const fn flags(self) -> Dr7Flags {
Dr7Flags::from_bits_truncate(self.bits)
}
#[inline]
pub fn insert_flags(&mut self, flags: Dr7Flags) {
self.bits |= flags.bits();
}
#[inline]
pub fn remove_flags(&mut self, flags: Dr7Flags) {
self.bits &= !flags.bits();
}
#[inline]
pub fn toggle_flags(&mut self, flags: Dr7Flags) {
self.bits ^= flags.bits();
}
#[inline]
pub fn set_flags(&mut self, flags: Dr7Flags, value: bool) {
if value {
self.insert_flags(flags);
} else {
self.remove_flags(flags);
}
}
pub fn condition(&self, n: DebugAddressRegisterNumber) -> BreakpointCondition {
let condition = self.bits.get_bits(BreakpointCondition::bit_range(n));
BreakpointCondition::from_bits(condition).expect("condition should be always valid")
}
pub fn set_condition(&mut self, n: DebugAddressRegisterNumber, condition: BreakpointCondition) {
self.bits
.set_bits(BreakpointCondition::bit_range(n), condition as u64);
}
pub fn size(&self, n: DebugAddressRegisterNumber) -> BreakpointSize {
let size = self.bits.get_bits(BreakpointSize::bit_range(n));
BreakpointSize::from_bits(size).expect("condition should be always valid")
}
pub fn set_size(&mut self, n: DebugAddressRegisterNumber, size: BreakpointSize) {
self.bits
.set_bits(BreakpointSize::bit_range(n), size as u64);
}
}
#[derive(Debug)]
pub struct Dr7;
#[cfg(all(feature = "instructions", target_arch = "x86_64"))]
mod x86_64 {
use super::*;
impl Dr6 {
#[inline]
pub fn read() -> Dr6Flags {
Dr6Flags::from_bits_truncate(Self::read_raw())
}
#[inline]
pub fn read_raw() -> u64 {
let value;
unsafe {
asm!("mov {}, dr6", out(reg) value, options(nomem, nostack, preserves_flags));
}
value
}
}
impl Dr7 {
#[inline]
pub fn read() -> Dr7Value {
Dr7Value::from_bits_truncate(Self::read_raw())
}
#[inline]
pub fn read_raw() -> u64 {
let value;
unsafe {
asm!("mov {}, dr7", out(reg) value, options(nomem, nostack, preserves_flags));
}
value
}
#[inline]
pub fn write(value: Dr7Value) {
let old_value = Self::read_raw();
let reserved = old_value & !Dr7Value::valid_bits();
let new_value = reserved | value.bits();
Self::write_raw(new_value)
}
#[inline]
pub fn write_raw(value: u64) {
unsafe {
asm!("mov dr7, {}", in(reg) value, options(nomem, nostack, preserves_flags));
}
}
#[inline]
pub fn update<F>(f: F)
where
F: FnOnce(&mut Dr7Value),
{
let mut value = Self::read();
f(&mut value);
Self::write(value);
}
}
}