use crate::debugger::Debugger;
use crate::debugger::address::{Address, RelocatedAddress};
use crate::debugger::debugee::Debugee;
use crate::debugger::debugee::dwarf::DebugInformation;
use crate::debugger::debugee::dwarf::unit::PlaceDescriptorOwned;
use crate::debugger::error::Error;
use crate::debugger::error::Error::{NoDebugInformation, NoSuitablePlace, PlaceNotFound};
use nix::libc::c_void;
use nix::sys;
use nix::unistd::Pid;
use std::borrow::Cow;
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::mem;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
enum BrkptsToAddRequest {
Init(Vec<Breakpoint>),
Uninit(Vec<UninitBreakpoint>),
}
pub enum CreateTransparentBreakpointRequest {
Line(String, u64, Rc<dyn Fn(&mut Debugger)>),
Function(String, Rc<dyn Fn(&mut Debugger)>),
}
impl CreateTransparentBreakpointRequest {
pub fn function(f: impl ToString, cb: impl Fn(&mut Debugger) + 'static) -> Self {
Self::Function(f.to_string(), Rc::new(cb))
}
pub fn line(file: impl ToString, line: u64, cb: impl Fn(&mut Debugger) + 'static) -> Self {
Self::Line(file.to_string(), line, Rc::new(cb))
}
fn callback(&self) -> Rc<dyn Fn(&mut Debugger)> {
match self {
CreateTransparentBreakpointRequest::Line(_, _, cb) => cb.clone(),
CreateTransparentBreakpointRequest::Function(_, cb) => cb.clone(),
}
}
}
impl Debugger {
pub fn set_breakpoint_at_addr(
&mut self,
addr: RelocatedAddress,
) -> Result<BreakpointView<'_>, Error> {
if self.debugee.is_in_progress() {
let dwarf = self
.debugee
.debug_info(addr)
.map_err(|_| NoDebugInformation("current place"))?;
let global_addr = addr.into_global(&self.debugee)?;
let place = dwarf
.find_place_from_pc(global_addr)?
.map(|p| p.to_owned())
.ok_or(PlaceNotFound(global_addr))?;
return self.breakpoints.add_and_enable(Breakpoint::new(
dwarf.pathname(),
addr,
self.process.pid(),
Some(place),
));
}
Ok(self.breakpoints.add_uninit(UninitBreakpoint::new(
None::<PathBuf>,
Address::Relocated(addr),
self.process.pid(),
None,
)))
}
pub fn remove_breakpoint(
&mut self,
addr: Address,
) -> Result<Option<BreakpointView<'_>>, Error> {
self.breakpoints.remove_by_addr(addr)
}
pub fn remove_breakpoint_by_number(
&mut self,
number: u32,
) -> Result<Option<BreakpointView<'_>>, Error> {
self.breakpoints.remove_by_num(number)
}
fn create_breakpoint_at_places(
&self,
places: Vec<(&DebugInformation, Vec<PlaceDescriptorOwned>)>,
) -> Result<BrkptsToAddRequest, Error> {
let brkpts_to_add = if self.debugee.is_in_progress() {
let mut to_add = Vec::new();
for (dwarf, places) in places {
for place in places {
let addr = place.address.relocate_to_segment(&self.debugee, dwarf)?;
to_add.push(Breakpoint::new(
dwarf.pathname(),
addr,
self.process.pid(),
Some(place),
));
}
}
BrkptsToAddRequest::Init(to_add)
} else {
let mut to_add = Vec::new();
for (dwarf, places) in places {
for place in places {
to_add.push(UninitBreakpoint::new(
Some(dwarf.pathname()),
Address::Global(place.address),
self.process.pid(),
Some(place),
));
}
}
BrkptsToAddRequest::Uninit(to_add)
};
Ok(brkpts_to_add)
}
fn add_breakpoints(
&mut self,
brkpts_to_add: BrkptsToAddRequest,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let result: Vec<_> = match brkpts_to_add {
BrkptsToAddRequest::Init(init_brkpts) => {
let mut result_addrs = Vec::with_capacity(init_brkpts.len());
for brkpt in init_brkpts {
let addr = brkpt.addr;
self.breakpoints.add_and_enable(brkpt)?;
result_addrs.push(addr);
}
result_addrs
.iter()
.map(|addr| {
BreakpointView::from(
self.breakpoints
.get_enabled(*addr)
.expect("breakpoint must exists"),
)
})
.collect()
}
BrkptsToAddRequest::Uninit(uninit_brkpts) => {
let mut result_addrs = Vec::with_capacity(uninit_brkpts.len());
for brkpt in uninit_brkpts {
let addr = self.breakpoints.add_uninit(brkpt).addr;
result_addrs.push(addr);
}
result_addrs
.iter()
.map(|addr| {
BreakpointView::from(
self.breakpoints
.get_disabled(*addr)
.expect("breakpoint must exists"),
)
})
.collect()
}
};
Ok(result)
}
fn addresses_for_breakpoints_at_places(
&self,
places: &[(&DebugInformation, Vec<PlaceDescriptorOwned>)],
) -> Result<impl Iterator<Item = Address> + use<>, Error> {
let mut init_addresses_to_remove: Vec<Address> = vec![];
if self.debugee.is_in_progress() {
for (dwarf, places) in places.iter() {
for place in places {
let addr = place.address.relocate_to_segment(&self.debugee, dwarf)?;
init_addresses_to_remove.push(Address::Relocated(addr));
}
}
};
let uninit_addresses_to_remove: Vec<_> = places
.iter()
.flat_map(|(_, places)| places.iter().map(|place| Address::Global(place.address)))
.collect();
Ok(init_addresses_to_remove
.into_iter()
.chain(uninit_addresses_to_remove))
}
pub fn remove_breakpoints_at_addresses(
&mut self,
addresses: impl Iterator<Item = Address>,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let mut result = vec![];
for to_rem in addresses {
if let Some(view) = self.breakpoints.remove_by_addr(to_rem)? {
result.push(view)
}
}
Ok(result)
}
fn search_functions(
&self,
tpl: &str,
) -> Result<Vec<(&DebugInformation, Vec<PlaceDescriptorOwned>)>, Error> {
let dwarfs = self.debugee.debug_info_all();
dwarfs
.iter()
.filter(|dwarf| dwarf.has_debug_info() && dwarf.tpl_in_pub_names(tpl) != Some(false))
.map(|&dwarf| {
let places = dwarf.search_places_for_fn_tpl(tpl)?;
Ok((dwarf, places))
})
.collect()
}
pub fn set_breakpoint_at_fn(
&mut self,
template: &str,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let places = self.search_functions(template)?;
if places.iter().all(|(_, places)| places.is_empty()) {
return Err(NoSuitablePlace);
}
let brkpts = self.create_breakpoint_at_places(places)?;
self.add_breakpoints(brkpts)
}
pub fn remove_breakpoint_at_fn(
&mut self,
template: &str,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let places = self.search_functions(template)?;
let addresses = self.addresses_for_breakpoints_at_places(&places)?;
self.remove_breakpoints_at_addresses(addresses)
}
fn search_lines_in_file(
&self,
debug_info: &DebugInformation,
fine_tpl: &str,
line: u64,
) -> Result<Vec<PlaceDescriptorOwned>, Error> {
let places = debug_info.find_closest_place(fine_tpl, line)?;
Ok(places.into_iter().map(|p| p.to_owned()).collect())
}
fn search_lines(
&self,
fine_tpl: &str,
line: u64,
) -> Result<Vec<(&DebugInformation, Vec<PlaceDescriptorOwned>)>, Error> {
let dwarfs = self.debugee.debug_info_all();
dwarfs
.iter()
.filter(|dwarf| dwarf.has_debug_info())
.map(|&dwarf| {
let places = self.search_lines_in_file(dwarf, fine_tpl, line)?;
Ok((dwarf, places))
})
.collect()
}
pub fn set_breakpoint_at_line(
&mut self,
fine_path_tpl: &str,
line: u64,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let places = self.search_lines(fine_path_tpl, line)?;
if places.iter().all(|(_, places)| places.is_empty()) {
return Err(NoSuitablePlace);
}
let brkpts = self.create_breakpoint_at_places(places)?;
self.add_breakpoints(brkpts)
}
pub fn remove_breakpoint_at_line(
&mut self,
fine_name_tpl: &str,
line: u64,
) -> Result<Vec<BreakpointView<'_>>, Error> {
let places = self.search_lines(fine_name_tpl, line)?;
let addresses = self.addresses_for_breakpoints_at_places(&places)?;
self.remove_breakpoints_at_addresses(addresses)
}
pub fn set_transparent_breakpoint(
&mut self,
request: CreateTransparentBreakpointRequest,
) -> Result<(), Error> {
let debug_info = self.debugee.program_debug_info()?;
let places: Vec<_> = match &request {
CreateTransparentBreakpointRequest::Line(file, line, _) => {
self.search_lines_in_file(debug_info, file, *line)?
}
CreateTransparentBreakpointRequest::Function(tpl, _) => {
if debug_info.has_debug_info() && debug_info.tpl_in_pub_names(tpl) != Some(false) {
debug_info.search_places_for_fn_tpl(tpl)?
} else {
vec![]
}
}
};
if places.is_empty() {
return Err(NoSuitablePlace);
}
let callback = request.callback();
let breakpoints: Vec<_> = places
.into_iter()
.flat_map(|place| {
let addr = place
.address
.relocate_to_segment(&self.debugee, debug_info)
.ok()?;
Some(Breakpoint::new_transparent(
debug_info.pathname(),
addr,
self.process.pid(),
callback.clone(),
))
})
.collect();
for brkpt in breakpoints {
self.breakpoints.add_and_enable(brkpt)?;
}
Ok(())
}
pub fn breakpoints_snapshot(&self) -> Vec<BreakpointView<'_>> {
self.breakpoints.snapshot()
}
pub fn add_deferred_at_addr(&mut self, addr: RelocatedAddress) {
self.breakpoints
.deferred_breakpoints
.push(DeferredBreakpoint::at_address(addr));
}
pub fn add_deferred_at_function(&mut self, function: &str) {
self.breakpoints
.deferred_breakpoints
.push(DeferredBreakpoint::at_function(function));
}
pub fn add_deferred_at_line(&mut self, file: &str, line: u64) {
self.breakpoints
.deferred_breakpoints
.push(DeferredBreakpoint::at_line(file, line));
}
pub fn refresh_deferred(&mut self) -> Vec<Error> {
let mut errors = vec![];
let mut deferred_brkpts = mem::take(&mut self.breakpoints.deferred_breakpoints);
deferred_brkpts.retain(|brkpt| {
let mb_error = match &brkpt {
DeferredBreakpoint::Address(addr) => self.set_breakpoint_at_addr(*addr).err(),
DeferredBreakpoint::Line(file, line) => {
self.set_breakpoint_at_line(file, *line).err()
}
DeferredBreakpoint::Function(function) => self.set_breakpoint_at_fn(function).err(),
};
match mb_error {
None => false,
Some(NoSuitablePlace) => true,
Some(err) => {
errors.push(err);
true
}
}
});
self.breakpoints.deferred_breakpoints = deferred_brkpts;
errors
}
}
#[derive(Clone)]
pub enum BrkptType {
EntryPoint,
UserDefined,
WatchpointCompanion(Vec<u32>),
Temporary,
TemporaryAsync,
LinkerMapFn,
Transparent(Rc<dyn Fn(&mut Debugger)>),
}
impl Debug for BrkptType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BrkptType::EntryPoint => f.write_str("entry-point"),
BrkptType::UserDefined => f.write_str("user-defined"),
BrkptType::Temporary => f.write_str("temporary"),
BrkptType::LinkerMapFn => f.write_str("linker-map"),
BrkptType::Transparent(_) => f.write_str("transparent"),
BrkptType::WatchpointCompanion(_) => f.write_str("watchpoint-companion"),
BrkptType::TemporaryAsync => f.write_str("temporary-async"),
}
}
}
impl PartialEq for BrkptType {
fn eq(&self, other: &Self) -> bool {
match self {
BrkptType::EntryPoint => {
matches!(other, BrkptType::EntryPoint)
}
BrkptType::UserDefined => {
matches!(other, BrkptType::UserDefined)
}
BrkptType::Temporary => {
matches!(other, BrkptType::Temporary)
}
BrkptType::TemporaryAsync => {
matches!(other, BrkptType::TemporaryAsync)
}
BrkptType::LinkerMapFn => {
matches!(other, BrkptType::LinkerMapFn)
}
BrkptType::Transparent(_) => {
matches!(other, BrkptType::Transparent(_))
}
BrkptType::WatchpointCompanion(nums) => {
matches!(other, BrkptType::WatchpointCompanion(other_nums) if nums == other_nums)
}
}
}
}
static GLOBAL_BP_COUNTER: AtomicU32 = AtomicU32::new(1);
#[derive(Debug, Clone)]
pub struct Breakpoint {
pub addr: RelocatedAddress,
pub pid: Pid,
number: u32,
place: Option<PlaceDescriptorOwned>,
pub saved_data: Cell<u8>,
enabled: Cell<bool>,
r#type: BrkptType,
pub debug_info_file: PathBuf,
}
impl Breakpoint {
pub(crate) fn is_enabled(&self) -> bool {
self.enabled.get()
}
}
impl Breakpoint {
const INT3: u64 = 0xCC_u64;
#[inline(always)]
fn new_inner(
addr: RelocatedAddress,
pid: Pid,
number: u32,
place: Option<PlaceDescriptorOwned>,
r#type: BrkptType,
debug_info_file: PathBuf,
) -> Self {
Self {
addr,
number,
pid,
place,
enabled: Default::default(),
saved_data: Default::default(),
r#type,
debug_info_file,
}
}
#[inline(always)]
pub fn new(
debug_info_file: impl Into<PathBuf>,
addr: RelocatedAddress,
pid: Pid,
place: Option<PlaceDescriptorOwned>,
) -> Self {
Self::new_inner(
addr,
pid,
GLOBAL_BP_COUNTER.fetch_add(1, Ordering::Relaxed),
place,
BrkptType::UserDefined,
debug_info_file.into(),
)
}
#[inline(always)]
pub fn new_entry_point(
debug_info_file: impl Into<PathBuf>,
addr: RelocatedAddress,
pid: Pid,
) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::EntryPoint,
debug_info_file.into(),
)
}
#[inline(always)]
pub fn new_temporary(
debug_info_file: impl Into<PathBuf>,
addr: RelocatedAddress,
pid: Pid,
) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::Temporary,
debug_info_file.into(),
)
}
#[inline(always)]
pub fn new_temporary_async(
debug_info_file: impl Into<PathBuf>,
addr: RelocatedAddress,
pid: Pid,
) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::TemporaryAsync,
debug_info_file.into(),
)
}
#[inline(always)]
pub fn new_linker_map(addr: RelocatedAddress, pid: Pid) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::LinkerMapFn,
PathBuf::default(),
)
}
#[inline(always)]
pub fn new_transparent(
debug_info_file: impl Into<PathBuf>,
addr: RelocatedAddress,
pid: Pid,
callback: Rc<dyn Fn(&mut Debugger)>,
) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::Transparent(callback),
debug_info_file.into(),
)
}
#[inline(always)]
pub(super) fn new_watchpoint_companion(
registry: &BreakpointRegistry,
wp_num: u32,
addr: RelocatedAddress,
pid: Pid,
) -> Self {
let (wp_nums, brkpt_num) = if let Some(Breakpoint {
r#type: BrkptType::WatchpointCompanion(nums),
number,
..
}) = registry.get_enabled(addr)
{
let mut nums = nums.to_vec();
nums.push(wp_num);
(nums, *number)
} else {
(
vec![wp_num],
GLOBAL_BP_COUNTER.fetch_add(1, Ordering::Relaxed),
)
};
Self::new_inner(
addr,
pid,
brkpt_num,
None,
BrkptType::WatchpointCompanion(wp_nums),
PathBuf::default(),
)
}
#[inline(always)]
pub fn number(&self) -> u32 {
self.number
}
pub fn place(&self) -> Option<&PlaceDescriptorOwned> {
match self.r#type {
BrkptType::UserDefined => self.place.as_ref(),
BrkptType::EntryPoint
| BrkptType::Temporary
| BrkptType::TemporaryAsync
| BrkptType::LinkerMapFn
| BrkptType::WatchpointCompanion(_)
| BrkptType::Transparent(_) => {
panic!("only user defined breakpoint has a place attribute")
}
}
}
pub fn is_entry_point(&self) -> bool {
self.r#type == BrkptType::EntryPoint
}
pub fn is_wp_companion(&self) -> bool {
matches!(self.r#type, BrkptType::WatchpointCompanion(_))
}
#[inline(always)]
pub fn r#type(&self) -> &BrkptType {
&self.r#type
}
#[inline(always)]
pub fn is_temporary(&self) -> bool {
matches!(self.r#type, BrkptType::Temporary)
}
#[inline(always)]
pub fn is_temporary_async(&self) -> bool {
matches!(self.r#type, BrkptType::TemporaryAsync)
}
pub fn enable(&self) -> Result<(), Error> {
let addr = self.addr.as_usize() as *mut c_void;
let data = sys::ptrace::read(self.pid, addr).map_err(Error::Ptrace)?;
self.saved_data.set((data & 0xff) as u8);
let data_with_pb = (data & !0xff) as u64 | Self::INT3;
unsafe {
sys::ptrace::write(self.pid, addr, data_with_pb as *mut c_void)
.map_err(Error::Ptrace)?;
}
self.enabled.set(true);
Ok(())
}
pub fn disable(&self) -> Result<(), Error> {
let addr = self.addr.as_usize() as *mut c_void;
let data = sys::ptrace::read(self.pid, addr).map_err(Error::Ptrace)? as u64;
let restored: u64 = (data & !0xff) | self.saved_data.get() as u64;
unsafe {
sys::ptrace::write(self.pid, addr, restored as *mut c_void).map_err(Error::Ptrace)?;
}
self.enabled.set(false);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct UninitBreakpoint {
addr: Address,
pid: Pid,
number: u32,
place: Option<PlaceDescriptorOwned>,
r#type: BrkptType,
debug_info_file: Option<PathBuf>,
}
impl UninitBreakpoint {
fn new_inner(
addr: Address,
pid: Pid,
number: u32,
place: Option<PlaceDescriptorOwned>,
r#type: BrkptType,
debug_info_file: Option<PathBuf>,
) -> Self {
Self {
addr,
pid,
number,
place,
r#type,
debug_info_file,
}
}
pub fn new(
debug_info_file: Option<impl Into<PathBuf>>,
addr: Address,
pid: Pid,
place: Option<PlaceDescriptorOwned>,
) -> Self {
Self::new_inner(
addr,
pid,
GLOBAL_BP_COUNTER.fetch_add(1, Ordering::Relaxed),
place,
BrkptType::UserDefined,
debug_info_file.map(|path| path.into()),
)
}
pub fn new_inherited(addr: Address, brkpt: Breakpoint) -> Self {
Self::new_inner(
addr,
brkpt.pid,
brkpt.number,
brkpt.place,
BrkptType::UserDefined,
Some(brkpt.debug_info_file),
)
}
pub fn new_entry_point(
debug_info_file: Option<impl Into<PathBuf>>,
addr: Address,
pid: Pid,
) -> Self {
Self::new_inner(
addr,
pid,
0,
None,
BrkptType::EntryPoint,
debug_info_file.map(|path| path.into()),
)
}
pub fn try_into_brkpt(self, debugee: &Debugee) -> Result<Breakpoint, Error> {
debug_assert!(
self.r#type == BrkptType::EntryPoint || self.r#type == BrkptType::UserDefined
);
let (global_addr, rel_addr) = match self.addr {
Address::Relocated(addr) => (addr.into_global(debugee)?, Some(addr)),
Address::Global(addr) => (addr, None),
};
let dwarf = match self.debug_info_file {
None if self.r#type == BrkptType::EntryPoint => debugee.program_debug_info().ok(),
None => rel_addr.and_then(|addr| debugee.debug_info(addr).ok()),
Some(path) => debugee.debug_info_from_file(&path).ok(),
}
.ok_or(NoDebugInformation("breakpoint"))?;
let place = if self.r#type == BrkptType::UserDefined {
if self.place.is_some() {
self.place
} else {
Some(
dwarf
.find_place_from_pc(global_addr)?
.ok_or(PlaceNotFound(global_addr))?
.to_owned(),
)
}
} else {
None
};
Ok(Breakpoint::new_inner(
global_addr.relocate_to_segment(debugee, dwarf)?,
self.pid,
self.number,
place,
self.r#type,
dwarf.pathname().into(),
))
}
}
pub struct BreakpointView<'a> {
pub addr: Address,
pub number: u32,
pub place: Option<Cow<'a, PlaceDescriptorOwned>>,
}
impl From<Breakpoint> for BreakpointView<'_> {
fn from(brkpt: Breakpoint) -> Self {
Self {
addr: Address::Relocated(brkpt.addr),
number: brkpt.number,
place: brkpt.place.map(Cow::Owned),
}
}
}
impl<'a> From<&'a Breakpoint> for BreakpointView<'a> {
fn from(brkpt: &'a Breakpoint) -> Self {
Self {
addr: Address::Relocated(brkpt.addr),
number: brkpt.number,
place: brkpt.place.as_ref().map(Cow::Borrowed),
}
}
}
impl From<UninitBreakpoint> for BreakpointView<'_> {
fn from(brkpt: UninitBreakpoint) -> Self {
Self {
addr: brkpt.addr,
number: brkpt.number,
place: brkpt.place.map(Cow::Owned),
}
}
}
impl<'a> From<&'a UninitBreakpoint> for BreakpointView<'a> {
fn from(brkpt: &'a UninitBreakpoint) -> Self {
Self {
addr: brkpt.addr,
number: brkpt.number,
place: brkpt.place.as_ref().map(Cow::Borrowed),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BreakpointViewOwned {
pub addr: Address,
pub number: u32,
pub place: Option<PlaceDescriptorOwned>,
}
impl BreakpointView<'_> {
pub fn to_owned(&self) -> BreakpointViewOwned {
BreakpointViewOwned {
addr: self.addr,
number: self.number,
place: self.place.clone().map(|p| p.into_owned()),
}
}
}
pub enum DeferredBreakpoint {
Address(RelocatedAddress),
Line(String, u64),
Function(String),
}
impl DeferredBreakpoint {
pub fn at_address(addr: RelocatedAddress) -> DeferredBreakpoint {
DeferredBreakpoint::Address(addr)
}
pub fn at_line(file: &str, line: u64) -> DeferredBreakpoint {
DeferredBreakpoint::Line(file.to_string(), line)
}
pub fn at_function(function: &str) -> DeferredBreakpoint {
DeferredBreakpoint::Function(function.to_string())
}
}
#[derive(Default)]
pub struct BreakpointRegistry {
breakpoints: HashMap<RelocatedAddress, Breakpoint>,
disabled_breakpoints: HashMap<Address, UninitBreakpoint>,
deferred_breakpoints: Vec<DeferredBreakpoint>,
}
impl BreakpointRegistry {
pub fn add_and_enable(&mut self, brkpt: Breakpoint) -> Result<BreakpointView<'_>, Error> {
if let Some(existed) = self.breakpoints.get(&brkpt.addr) {
existed.disable()?;
}
brkpt.enable()?;
let addr = brkpt.addr;
self.breakpoints.insert(addr, brkpt);
Ok((&self.breakpoints[&addr]).into())
}
pub fn get_enabled(&self, addr: RelocatedAddress) -> Option<&Breakpoint> {
self.breakpoints.get(&addr)
}
pub fn get_disabled(&self, addr: Address) -> Option<&UninitBreakpoint> {
self.disabled_breakpoints.get(&addr)
}
pub fn add_uninit(&mut self, brkpt: UninitBreakpoint) -> BreakpointView<'_> {
let addr = brkpt.addr;
self.disabled_breakpoints.insert(addr, brkpt);
(&self.disabled_breakpoints[&addr]).into()
}
pub fn decrease_companion_rc(&mut self, num: u32, target_wp_num: u32) -> Result<(), Error> {
let Some(companion) = self
.breakpoints
.values_mut()
.find(|brkpt| brkpt.number == num)
else {
return Ok(());
};
let BrkptType::WatchpointCompanion(wps) = companion.r#type() else {
panic!("not a watchpoint companion");
};
debug_assert!(wps.contains(&target_wp_num));
if wps.len() == 1 && wps[0] == target_wp_num {
self.remove_by_num(num)?;
} else {
let new_wps: Vec<_> = wps
.iter()
.filter(|&&wp_num| wp_num != target_wp_num)
.copied()
.collect();
companion.r#type = BrkptType::WatchpointCompanion(new_wps);
};
Ok(())
}
pub fn remove_by_addr(
&mut self,
addr: Address,
) -> Result<Option<BreakpointView<'static>>, Error> {
if let Some(brkpt) = self.disabled_breakpoints.remove(&addr) {
return Ok(Some(brkpt.into()));
}
if let Address::Relocated(addr) = addr
&& let Some(brkpt) = self.breakpoints.remove(&addr)
{
if brkpt.is_enabled() {
brkpt.disable()?;
}
return Ok(Some(brkpt.into()));
}
Ok(None)
}
pub fn remove_by_num(&mut self, number: u32) -> Result<Option<BreakpointView<'static>>, Error> {
if let Some(addr) = self.disabled_breakpoints.iter().find_map(|(addr, brkpt)| {
if brkpt.number == number {
return Some(addr);
}
None
}) {
return self.remove_by_addr(*addr);
}
if let Some(addr) = self.breakpoints.iter().find_map(|(addr, brkpt)| {
if brkpt.number == number {
return Some(addr);
}
None
}) {
return self.remove_by_addr(Address::Relocated(*addr));
}
Ok(None)
}
pub fn enable_all_breakpoints(&mut self, debugee: &Debugee) -> Vec<Error> {
let mut errors = vec![];
let mut disabled_breakpoints = mem::take(&mut self.disabled_breakpoints);
for (_, uninit_brkpt) in disabled_breakpoints.drain() {
let brkpt = match uninit_brkpt.try_into_brkpt(debugee) {
Ok(b) => b,
Err(e) => {
errors.push(e);
continue;
}
};
if let Err(e) = self.add_and_enable(brkpt) {
errors.push(e);
}
}
errors
}
pub fn enable_entry_breakpoint(&mut self, debugee: &Debugee) -> Result<(), Error> {
let Some((&key, _)) = self
.disabled_breakpoints
.iter()
.find(|(_, brkpt)| brkpt.r#type == BrkptType::EntryPoint)
else {
return Ok(());
};
let uninit_entry_point_brkpt = self.disabled_breakpoints.remove(&key).unwrap();
let brkpt = uninit_entry_point_brkpt.try_into_brkpt(debugee)?;
self.add_and_enable(brkpt)?;
Ok(())
}
pub fn disable_all_breakpoints(&mut self, debugee: &Debugee) -> Result<Vec<Error>, Error> {
let mut errors = vec![];
let mut breakpoints = std::mem::take(&mut self.breakpoints);
for (_, brkpt) in breakpoints.drain() {
if let Err(e) = brkpt.disable() {
errors.push(e);
}
let addr = Address::Global(brkpt.addr.into_global(debugee)?);
match brkpt.r#type {
BrkptType::EntryPoint => {
self.add_uninit(UninitBreakpoint::new_entry_point(
Some(brkpt.debug_info_file),
addr,
brkpt.pid,
));
}
BrkptType::UserDefined => {
self.add_uninit(UninitBreakpoint::new_inherited(addr, brkpt));
}
BrkptType::Temporary
| BrkptType::TemporaryAsync
| BrkptType::LinkerMapFn
| BrkptType::Transparent(_)
| BrkptType::WatchpointCompanion(_) => {}
}
}
Ok(errors)
}
pub fn update_pid(&mut self, new_pid: Pid) {
self.breakpoints
.iter_mut()
.for_each(|(_, brkpt)| brkpt.pid = new_pid);
self.disabled_breakpoints
.iter_mut()
.for_each(|(_, brkpt)| brkpt.pid = new_pid);
}
pub fn active_breakpoints(&self) -> Vec<&Breakpoint> {
self.breakpoints.values().collect()
}
pub fn snapshot(&self) -> Vec<BreakpointView<'_>> {
let active_bps = self
.breakpoints
.values()
.filter(|&bp| bp.r#type() == &BrkptType::UserDefined)
.map(BreakpointView::from);
let disabled_brkpts = self
.disabled_breakpoints
.values()
.filter(|&bp| bp.r#type == BrkptType::UserDefined)
.map(BreakpointView::from);
let mut snap = active_bps.chain(disabled_brkpts).collect::<Vec<_>>();
snap.sort_by(|a, b| a.number.cmp(&b.number));
snap
}
}