mod basic_type;
mod config;
mod disassemble;
mod probe;
mod return_value;
mod timer;
mod util;
mod value_writer;
pub use config::*;
use disassemble::*;
pub(crate) use probe::*;
use return_value::*;
use util::*;
pub use value_writer::*;
use crate::{
value::{read_str, RVal, Val},
version::rustc_version,
ActiveFrame, Breakpoint, BreakpointType, Bytes, EventStream, SourceFile, UnionType,
VariableCapture, WriteErr, BREAKPOINT_STREAM, EVENT_STREAM, FILE_STREAM, INFO_STREAM,
};
use anyhow::{Context, Result};
use firedbg_protocol::{
event::Reason,
info::{InfoMessage, ProgExitInfo},
};
use firedbg_rust_parser::FunctionDef;
use lldb::{
IsValid, ProcessState, SBAddress, SBBreakpoint, SBData, SBDebugger, SBFunctionId, SBProcess,
SBSymbolId, SBTarget, SBThread, SBType, SBTypeId, SBValue, StopReason, VariableOptions,
};
use rustc_hash::{FxHashMap, FxHashSet};
use sea_streamer::{Producer, SeaProducer, SeaStreamerBackend, StreamKey};
use std::{
path::Path,
sync::{Arc, Mutex},
time::SystemTime,
};
static mut SB_TARGET: Option<SBTarget> = None;
static mut SB_PROCESS: Option<SBProcess> = None;
lazy_static::lazy_static! {
static ref ENUM_CACHE: Mutex<FxHashMap<SBTypeId, Option<Arc<UnionType>>>> = Mutex::new(Default::default());
static ref TYPE_CACHE: Mutex<FxHashMap<String, Option<SBType>>> = Mutex::new(Default::default());
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DebuggerParams {
pub binary: String,
pub files: Vec<SourceFile>,
pub breakpoints: Vec<Breakpoint>,
pub arguments: Vec<String>,
}
#[derive(Debug)]
struct Thread {
frame: Frame,
active_frames: Vec<ActiveFrame>,
allocating: Option<String>,
}
#[derive(Debug)]
struct Frame {
frame_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Debugger {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct BpId(pub u32);
pub const FIREDBG_SOURCE_FILE_ID: u32 = 0;
pub const RUST_PANIC_BP_ID: BpId = BpId(1);
pub const FIREDBG_TRACE_BP_ID: BpId = BpId(2);
pub const EXCHANGE_MALLOC: BpId = BpId(3);
const RET: &str = {
#[cfg(target_arch = "x86_64")]
{
"retq"
}
#[cfg(target_arch = "aarch64")]
{
"ret"
}
};
pub const SUPPORTED_RUSTC_VERSION: &str = env!("RUSTC_VERSION");
#[derive(Default)]
struct FunctionCache {
cache: FxHashMap<SBFunctionId, SBType>,
}
struct FuncAsm {
extended_prologue: u16,
}
impl FunctionCache {
fn get(&self, sb_fn_id: &SBFunctionId) -> Option<&SBType> {
self.cache.get(sb_fn_id)
}
fn insert<'a, F>(&'a mut self, sb_fn_id: &SBFunctionId, get_type: F)
where
F: FnOnce() -> SBType,
{
if !self.cache.contains_key(sb_fn_id) {
let ty = get_type();
self.cache.insert(*sb_fn_id, ty);
}
}
}
impl Debugger {
#[inline]
pub fn run(params: DebuggerParams, producer: SeaProducer) {
run(params, producer).expect("Fail to start debugger");
}
}
fn run(mut params: DebuggerParams, mut producer: SeaProducer) -> Result<()> {
let mut process_timer = timer::ProcessTimer::default();
let t_global = process_timer.global.span();
let t_init = process_timer.init.span();
load_config();
SBDebugger::initialize();
let sb_debugger = SBDebugger::create(false);
sb_debugger.set_async_mode(false);
log::debug!("{:?}", sb_debugger);
let path = Path::new(¶ms.binary);
let sb_target = sb_debugger
.create_target(path, None, None, false)
.with_context(|| format!("Fail to create target: `{}`", path.display()))?;
let sb_target = unsafe {
SB_TARGET = Some(sb_target);
SB_TARGET.as_ref().expect("Always")
};
log::debug!("{:?}", sb_target);
let target_basename = match producer.get_file() {
Some(file) => get_target_basename(file.file_id().path()),
None => panic!("Not SeaProducerBackend::File"),
};
let stdout_path = &format!("{target_basename}.stdout");
let stderr_path = &format!("{target_basename}.stderr");
let mut threads: FxHashMap<u64, Thread> = Default::default();
let mut breakpoints = vec![Default::default()];
let mut functions_disassembled: FxHashMap<u64, FuncAsm> = Default::default();
let mut breakpoint_addresses: FxHashMap<u64, BpId> = Default::default();
let mut allocation: FxHashMap<u64, String> = Default::default();
let mut fn_cache: FunctionCache = Default::default();
let event_stream = StreamKey::new(EVENT_STREAM)
.with_context(|| format!("Fail to create StreamKey: `{EVENT_STREAM}`"))?;
let bp_stream = StreamKey::new(BREAKPOINT_STREAM)
.with_context(|| format!("Fail to create StreamKey: `{BREAKPOINT_STREAM}`"))?;
{
params.files[0] = get_rust_panic_source_file();
let file_stream = StreamKey::new(FILE_STREAM)
.with_context(|| format!("Fail to create StreamKey: `{FILE_STREAM}`"))?;
for (i, file) in params.files.iter().enumerate() {
assert_eq!(i as u32, file.id);
let content = serde_json::to_string(&file).context("Fail to serialize JSON")?;
producer
.send_to(&file_stream, content)
.context("Fail to stream file")?;
}
}
std::mem::drop(t_init);
let t_set_breakpoint = process_timer.set_breakpoint.span();
let sb_bp = sb_target.breakpoint_create_by_name("rust_panic");
let breakpoint = get_rust_panic_breakpoint();
send_breakpoint(&producer, &bp_stream, &breakpoint)?;
register_breakpoint(&mut breakpoint_addresses, &sb_bp);
breakpoints.push(breakpoint);
let sb_bp = sb_target.breakpoint_create_by_regex("firedbg_lib::__firedbg_trace__");
let breakpoint = get_firedbg_trace_breakpoint();
send_breakpoint(&producer, &bp_stream, &breakpoint)?;
register_breakpoint(&mut breakpoint_addresses, &sb_bp);
breakpoints.push(breakpoint);
if !*DONT_TRACE_ALLOCATION {
let sb_bp = sb_target.breakpoint_create_by_name("alloc::alloc::exchange_malloc");
let breakpoint = get_exchange_malloc_breakpoint();
send_breakpoint(&producer, &bp_stream, &breakpoint)?;
register_breakpoint(&mut breakpoint_addresses, &sb_bp);
breakpoints.push(breakpoint);
}
for (i, mut bp) in params.breakpoints.into_iter().enumerate().skip(1) {
let crate_name = format!("{}::", ¶ms.files[bp.file_id as usize].crate_name);
assert_eq!(i as u32, bp.id);
let src = Path::new(¶ms.files[bp.file_id as usize].path);
let mut sb_bp = sb_target.breakpoint_create_by_location(&src, bp.loc.line, bp.loc.column);
log::debug!("{:#?}", sb_bp);
let mut try_loc_end = false;
for sb_bp_loc in sb_bp.locations() {
let desc = format!("{:#?}", sb_bp_loc);
log::debug!("{}", desc);
if let Some(fn_full_name) = get_bp_fn_full_name(&desc) {
if let BreakpointType::FunctionCall { fn_name } = &bp.breakpoint_type {
if (fn_full_name.starts_with(&crate_name) || fn_full_name.contains(&crate_name))
&& fn_full_name.ends_with(fn_name)
{
continue;
}
if is_executable_fn(&desc) && fn_full_name.ends_with(fn_name) {
continue;
}
try_loc_end = true;
break;
}
}
}
if sb_bp.num_locations() == 0 {
try_loc_end = true;
}
if try_loc_end {
sb_target.breakpoint_delete(breakpoints.len() as u32);
if let Some(loc_end) = &bp.loc_end {
sb_bp = sb_target.breakpoint_create_by_location(&src, loc_end.line, loc_end.column);
log::debug!("{:#?}", sb_bp);
if log::log_enabled!(log::Level::Debug) {
for sb_bp_loc in sb_bp.locations() {
log::debug!("{:#?}", sb_bp_loc);
}
}
breakpoints.push(Default::default());
}
}
register_breakpoint(&mut breakpoint_addresses, &sb_bp);
let mut map = FxHashSet::<SBSymbolId>::default();
for sb_bp_loc in sb_bp.locations() {
let addr = sb_bp_loc.address();
let symbol = addr.symbol().expect("Fail to fetch symbol by address");
let symbol_id = symbol.id();
if map.contains(&symbol_id) {
sb_bp_loc.set_enabled(false);
} else {
map.insert(symbol_id);
}
}
bp.id = breakpoints.len() as u32;
if sb_bp.num_locations() > 0 {
send_breakpoint(&producer, &bp_stream, &bp)?;
}
breakpoints.push(bp);
}
std::mem::drop(t_set_breakpoint);
let mut handle_breakpoint = |breakpoint_addresses: &mut FxHashMap<u64, BpId>,
sb_process: &SBProcess,
sb_thread: SBThread,
bp_id: BpId|
-> Result<()> {
let _t = process_timer.handle_breakpoint.span();
let bp_origin = &breakpoints[bp_id.0 as usize];
assert_eq!(bp_origin.id, bp_id.0);
let bp_file_id = bp_origin.file_id;
let bp_event_type = &bp_origin.breakpoint_type;
let bp_capture = bp_origin.capture.clone();
let thread_id = sb_thread.thread_id();
let mut sb_frame = sb_thread.selected_frame();
let mut frame_idx = 1;
while sb_frame.is_inlined() && frame_idx < sb_thread.num_frames() {
sb_frame = sb_thread.set_selected_frame(frame_idx);
frame_idx += 1;
}
let mut rwriter = RValueWriter::new(&allocation);
let rwriter = &mut rwriter;
let Thread {
frame,
active_frames,
allocating,
} = threads.entry(thread_id).or_default();
log::trace!("= Active Frame (thread={thread_id}) =");
for af in active_frames.iter() {
log::trace!("sp = {}", af.stack_pointer);
}
log::trace!(" --> {}", sb_frame.sp());
let mut unwind_last_active_frame = |active_frame: &ActiveFrame| {
for sb_fra in sb_thread.frames() {
if sb_fra.sp() > active_frame.stack_pointer {
break;
}
sb_frame = sb_fra;
if sb_frame.sp() == active_frame.stack_pointer {
break;
}
}
log::warn!(
"panic! active frame = {}",
sb_frame.function_name().unwrap_or_default()
);
};
let is_function_return = matches!(bp_event_type, BreakpointType::FunctionReturn);
let mut return_immediately = false;
if matches!(
bp_event_type,
BreakpointType::Breakpoint | BreakpointType::FunctionCall { .. }
) {
let mut event = if matches!(bp_event_type, BreakpointType::Breakpoint) {
let (active_frame_id, reason) = active_frames
.last()
.map(|active_frame| {
let reason = match bp_id {
RUST_PANIC_BP_ID => {
unwind_last_active_frame(active_frame);
Reason::Panic
}
_ => Reason::Breakpoint,
};
(active_frame.frame_id, reason)
})
.unwrap_or_default();
EventStream::breakpoint(bp_id, thread_id, active_frame_id, reason)
} else if matches!(bp_event_type, BreakpointType::FunctionCall { .. }) {
let sb_function = sb_frame.function();
let get_return_type = || {
let ty = sb_function.type_().function_return_type();
log::debug!("{}() -> {}", sb_function.name(), ty.name());
ty
};
fn_cache.insert(&sb_function.id(), get_return_type);
if let Some(active_frame) = active_frames.last() {
if sb_frame.pc() == active_frame.program_counter
&& sb_frame.sp() == active_frame.stack_pointer
{
return Ok(());
}
assert!(sb_frame.sp() <= active_frame.stack_pointer);
}
frame.frame_id += 1;
active_frames.push(ActiveFrame {
frame_id: frame.frame_id,
stack_pointer: sb_frame.sp(),
program_counter: sb_frame.pc(),
function_name: sb_function.name().to_owned(),
function_id: sb_function.id(),
});
if !functions_disassembled.contains_key(&sb_frame.pc()) {
let prologue_byte_size = sb_function.prologue_byte_size() as u64;
let mut prologue_address =
sb_function.start_address().file_address() as u64 + prologue_byte_size;
let pc_file_address = sb_frame.pc_address().file_address() as u64;
let mut try_extend_prologue = pc_file_address == prologue_address
&& sb_frame
.variables(&VariableOptions {
arguments: true,
locals: false,
statics: false,
in_scope_only: true,
})
.len()
> 0;
if try_extend_prologue {
prologue_address = sb_frame.pc() - pc_file_address + prologue_address;
}
let mut extended_prologue = 0;
#[allow(unused_variables)]
let mut ret_loc = 0;
for line in sb_frame.disassemble().unwrap_or_default().lines() {
let (inst, operand) = if let Some(inst) = line.split(": ").nth(1) {
parse_asm(inst.split("; ").nth(0).expect("asm stmt").trim())
} else {
continue;
};
if try_extend_prologue {
let current_addr = parse_addr(line)?;
if current_addr >= prologue_address {
#[cfg(target_arch = "x86_64")]
let is_prolog = inst.starts_with("mov")
&& (operand.contains("(%rsp)") || operand.contains("(%rbp)"));
#[cfg(target_arch = "aarch64")]
let is_prolog = matches!(inst, "str" | "stur")
&& (operand.contains("[sp,") || operand.contains("[x29,")); if !is_prolog {
if inst == RET {
} else {
extended_prologue =
(current_addr - prologue_address).try_into()?;
}
try_extend_prologue = false;
}
}
}
if inst == RET {
if line.split("0x").nth(0).expect("Before addr").contains("->") {
return_immediately = true;
} else {
let addr = parse_addr(line)?;
log::debug!("Set breakpoint {}", breakpoints.len());
let sb_addr = SBAddress::from_load_address(addr, sb_target);
let sb_bp = sb_target.breakpoint_create_by_address(&sb_addr);
log::debug!("{:#?}", sb_bp);
let sb_bp_loc = sb_bp.location_at_index(0);
log::debug!("{:#?}", sb_bp_loc);
register_breakpoint(breakpoint_addresses, &sb_bp);
let bp = Breakpoint {
id: breakpoints.len() as u32,
file_id: bp_file_id,
breakpoint_type: BreakpointType::FunctionReturn,
capture: VariableCapture::None,
..Default::default()
};
send_breakpoint(&producer, &bp_stream, &bp)?;
breakpoints.push(bp);
ret_loc += 1;
}
}
}
functions_disassembled.insert(sb_frame.pc(), FuncAsm { extended_prologue });
}
let func_info = functions_disassembled.get(&sb_frame.pc()).expect("Cached");
if func_info.extended_prologue > 0 {
log::debug!(
"{} extended_prologue = {}",
sb_function.name(),
func_info.extended_prologue
);
let addr = sb_frame.pc() + func_info.extended_prologue as u64;
sb_thread
.run_to_address(addr)
.with_context(|| format!("Fail to run till address: `{addr}`"))?;
}
EventStream::function_call(bp_id, thread_id, active_frames.last().expect("Pushed"))
} else {
unreachable!();
};
let mut write_variable = |var: SBValue| event.write_sb_value(rwriter, &var);
match bp_capture {
VariableCapture::Arguments if bp_id == FIREDBG_TRACE_BP_ID => {
if let Some(name_sb_val) = sb_frame.find_variable("name") {
let name_bytes = read_str(&name_sb_val)?;
let name = std::str::from_utf8(&name_bytes)?;
if let Some(v_sb_val) = sb_frame.find_variable("v") {
event.write_sb_value_renamed(rwriter, name, &v_sb_val);
}
}
}
VariableCapture::Arguments if bp_id == EXCHANGE_MALLOC => {
let mut frame_idx = 1;
while frame_idx < sb_thread.num_frames() {
if let Some(fn_name) = sb_frame.display_function_name() {
if fn_name == "alloc::boxed::Box<T>::new" {
let var = sb_frame.find_variable("x");
if var.is_none() {
break;
}
let var = var.unwrap();
if !functions_disassembled.contains_key(&sb_frame.pc()) {
let extended_prologue = 0;
functions_disassembled
.insert(sb_frame.pc(), FuncAsm { extended_prologue });
log::trace!("exchange_malloc at {}", sb_frame.pc());
if var.byte_size() == 0 {
break;
}
for line in sb_frame.disassemble().unwrap_or_default().lines() {
if line.split("0x").nth(0).expect("Addr").contains("->") {
let addr = parse_addr(line)?;
if !breakpoint_addresses.contains_key(&addr) {
log::debug!("Set breakpoint {}", breakpoints.len());
let sb_addr =
SBAddress::from_load_address(addr, sb_target);
let sb_bp = sb_target
.breakpoint_create_by_address(&sb_addr);
log::debug!("{:#?}", sb_bp);
register_breakpoint(breakpoint_addresses, &sb_bp);
let bp = Breakpoint {
id: breakpoints.len() as u32,
file_id: FIREDBG_SOURCE_FILE_ID,
breakpoint_type: BreakpointType::Breakpoint,
capture: VariableCapture::Only(vec![
"exchange_malloc".to_owned(),
]),
..Default::default()
};
send_breakpoint(&producer, &bp_stream, &bp)?;
breakpoints.push(bp);
}
break;
}
}
}
if let Some(ty) = var.type_name() {
if ty.contains('{') {
} else {
match ty {
"alloc::sync::ArcInner<std::thread::Packet<()>>" => {
}
_ => {
log::debug!("Allocating for {ty}");
*allocating = Some(ty.to_owned());
}
}
}
}
break;
}
} else {
break;
}
sb_frame = sb_thread.set_selected_frame(frame_idx);
frame_idx += 1;
}
return Ok(());
}
VariableCapture::Only(only) if bp_file_id == FIREDBG_SOURCE_FILE_ID => {
if only.len() == 1 && only[0] == "exchange_malloc" {
let rax = sb_frame.find_register({
#[cfg(target_arch = "x86_64")]
{
"rax"
}
#[cfg(target_arch = "aarch64")]
{
"x0"
}
});
let addr = read_u64(&rax)?;
if allocating.is_some() {
let fmt_addr = crate::Addr::new(&addr.to_ne_bytes());
log::debug!(
"Allocation {} -> {}",
fmt_addr,
allocating.as_ref().unwrap()
);
allocation.insert(addr, allocating.take().unwrap());
}
} else {
log::warn!("Unhandled breakpoint VariableCapture::Only({only:?})");
}
return Ok(());
}
VariableCapture::Arguments | VariableCapture::Locals => {
for var in sb_frame
.variables(&VariableOptions {
arguments: matches!(bp_capture, VariableCapture::Arguments),
locals: matches!(bp_capture, VariableCapture::Locals),
statics: false,
in_scope_only: true,
})
.iter()
{
write_variable(var);
}
}
VariableCapture::Only(vars) => {
for var in vars {
if let Some(var) = sb_frame.find_variable(&var) {
write_variable(var);
}
}
}
VariableCapture::None => (),
}
producer
.send_to(&event_stream, event)
.context("Fail to stream event")?;
}
*allocating = None;
if is_function_return || return_immediately {
assert!(!active_frames.is_empty());
if is_function_return {
let active_frame = active_frames.last().unwrap();
if sb_frame.pc() == active_frame.program_counter
&& sb_frame.sp() == active_frame.stack_pointer
{
return Ok(());
}
}
let last_frame = active_frames.pop().expect("Not empty");
if !return_immediately {
assert!(sb_frame.sp() >= last_frame.stack_pointer);
}
let mut event = EventStream::function_return(bp_id, thread_id, &last_frame);
if write_return_value(
&mut event,
rwriter,
sb_target,
sb_process,
&sb_frame,
fn_cache.get(&last_frame.function_id).expect("Cached"),
)
.is_err()
{
event.write_value(rwriter, RETVAL, rwriter.opaque_v().as_bytes());
}
producer
.send_to(&event_stream, event)
.context("Fail to stream return value")?;
}
Ok(())
};
let sb_process = process_timer
.debugger_launch
.time(|| sb_target.launch_redirected(stdout_path, stderr_path, params.arguments))
.context("Fail to launch debugger")?;
let sb_process = unsafe {
SB_PROCESS = Some(sb_process);
SB_PROCESS.as_ref().expect("Some")
};
let t_debugger_run = process_timer.debugger_run.span();
loop {
match sb_process.state() {
ProcessState::Stopped => {}
ProcessState::Unloaded
| ProcessState::Crashed
| ProcessState::Detached
| ProcessState::Exited => break,
ProcessState::Invalid
| ProcessState::Connected
| ProcessState::Attaching
| ProcessState::Launching
| ProcessState::Running
| ProcessState::Stepping
| ProcessState::Suspended => continue,
}
let sb_thread = sb_process.selected_thread();
if matches!(sb_thread.stop_reason(), StopReason::Breakpoint) {
let stop_reason_data_count = sb_thread.stop_reason_data_count();
assert!(stop_reason_data_count >= 2);
let breakpoint_id = BpId(sb_thread.stop_reason_data_at_index(0) as u32);
log::debug!("Stop at Breakpoint {breakpoint_id:?}");
let selected_thread_id = sb_thread.thread_id();
let bp_addrs = &mut breakpoint_addresses;
handle_breakpoint(bp_addrs, sb_process, sb_thread, breakpoint_id)
.context("Fail to handle breakpoint")?;
for i in 0..sb_process.num_threads() {
let sb_thread = sb_process.thread_at_index(i);
if sb_thread.thread_id() == selected_thread_id {
continue;
}
let sb_frame = sb_thread.selected_frame();
let pc_file_address = sb_frame.pc_address().file_address() as u64;
if let Some(bp_id) = breakpoint_addresses.get(&pc_file_address).copied() {
log::debug!("Handle extra breakpoint for {bp_id:?}");
let bp_addrs = &mut breakpoint_addresses;
handle_breakpoint(bp_addrs, sb_process, sb_thread, bp_id)
.context("Fail to handle breakpoint")?;
}
}
} else {
log::debug!("Stop for some reason {:?}", sb_thread.stop_reason());
}
log::trace!("sb_process.resume");
process_timer
.process_resume
.time(|| sb_process.resume())
.context("Fail to resume debugger")?;
}
let exit_code = sb_process.exit_status();
producer.send_to(
&StreamKey::new(INFO_STREAM)
.with_context(|| format!("Fail to create StreamKey: `{INFO_STREAM}`"))?,
serde_json::to_string(&InfoMessage::Exit(ProgExitInfo { exit_code }))?.as_str(),
)?;
if exit_code == -1 {
panic!("Fail to start LLDB: {}", sb_process.exit_description());
}
std::mem::drop(t_debugger_run);
process_timer.debugger_cleanup.time(SBDebugger::terminate);
log::debug!("Debugger Terminated");
std::mem::drop(t_global);
log::debug!("{:#?}", process_timer);
log::info!(
"Finished. target: {} total: {}",
process_timer.debugger_run,
process_timer.global
);
unsafe {
SB_PROCESS = None;
SB_TARGET = None;
}
Ok(())
}
pub(crate) fn get_union_type(ty: &SBType) -> Option<Arc<UnionType>> {
let mut cache = ENUM_CACHE
.try_lock()
.expect("There should be no concurrent access");
let type_id = ty.id();
if let Some(info) = cache.get(&type_id) {
return info.clone();
}
let ty_desc = format!("{:?}", ty);
let info = if ty_desc.starts_with("enum ") {
let mut lines = ty_desc.split_once("enum ").expect("checked").1.lines();
let enum_name = lines.next().expect("enum variants").trim_end_matches(" {");
Some(Arc::new(UnionType {
name: trim_type_name(enum_name),
variants: extract_variants(lines),
}))
} else if ty_desc.contains("\nStatic:\nenum ") {
let mut lines = ty_desc
.split_once("\nStatic:\nenum ")
.expect("checked")
.1
.lines();
let enum_name = lines
.next()
.expect("static enum variants")
.trim_end_matches(" {");
Some(Arc::new(UnionType {
name: trim_type_name(enum_name),
variants: extract_variants(lines),
}))
} else {
None
};
cache.insert(type_id, info.clone());
info
}
pub(crate) fn get_sb_type(name: &str) -> Option<SBType> {
let sb_target = unsafe { SB_TARGET.as_ref().expect("Always") };
let mut cache = TYPE_CACHE
.try_lock()
.expect("There should be no concurrent access");
if !cache.contains_key(name) {
cache.insert(name.to_owned(), sb_target.find_first_type(name));
}
cache.get(name).expect("Cached").to_owned()
}
pub(crate) fn cache_sb_type(ty: SBType) {
let mut cache = TYPE_CACHE
.try_lock()
.expect("There should be no concurrent access");
let name = ty.name();
if !cache.contains_key(name) {
cache.insert(name.to_owned(), Some(ty));
}
}
pub(crate) fn sb_value_from_addr(
name: &str,
addr: u64,
sb_type: &SBType,
) -> Result<SBValue, WriteErr> {
let sb_target = unsafe { SB_TARGET.as_ref().expect("Always") };
let addr = SBAddress::from_load_address(addr, sb_target);
let value = sb_target.create_value_from_address(name, &addr, sb_type);
if value.is_valid() {
Ok(value)
} else {
Err(WriteErr)
}
}
pub(crate) fn sb_value_from_data(
name: &str,
data: &[u64],
sb_type: &SBType,
) -> Result<SBValue, WriteErr> {
let sb_target = unsafe { SB_TARGET.as_ref().expect("Always") };
let sb_data = SBData::from_u64(
data,
sb_target.byte_order(),
sb_target.address_byte_size() as u32,
);
if !sb_data.is_valid() {
return Err(WriteErr);
}
let value = sb_target.create_value_from_data(name, &sb_data, sb_type);
if value.is_valid() {
Ok(value)
} else {
Err(WriteErr)
}
}
pub(crate) fn read_process_memory(addr: u64, len: usize) -> Result<Vec<u8>, WriteErr> {
let sb_process = unsafe { SB_PROCESS.as_ref().expect("Some") };
let mut buf = vec![0u8; len];
sb_process
.read_memory(addr, &mut buf)
.map_err(|_| WriteErr)?;
Ok(buf)
}
fn extract_variants(lines: std::str::Lines) -> Vec<String> {
lines
.map_while(|line| {
if line == "}" {
None
} else {
Some(match line.trim_start().split_once(':') {
Some((var, _)) => var.to_owned(),
None => line.to_owned(),
})
}
})
.collect()
}
impl Bytes {
fn write_sb_value(&mut self, rwriter: &mut RValueWriter, value: &SBValue) {
let name = value.name().unwrap_or("<>");
self.write_sb_value_renamed(rwriter, name, value);
}
fn write_sb_value_renamed(&mut self, rwriter: &mut RValueWriter, name: &str, value: &SBValue) {
self.identifier(name);
self.push_str("name");
self.space();
self.write_inner_value(rwriter, value);
self.space();
}
fn write_inner_value(&mut self, rwriter: &mut RValueWriter, value: &SBValue) {
if let Ok(val) = rwriter.write_value(value) {
let env = rwriter.emit_env();
self.push_bytes(env);
self.push_bytes(val);
} else {
self.push_bytes(rwriter.opaque_v());
}
}
fn write_value(&mut self, rwriter: &mut RValueWriter, name: &str, val: &[u8]) {
self.identifier(name);
self.push_str("name");
self.space();
let env = rwriter.emit_env();
self.push_bytes(env);
self.push_slice(val);
self.space();
}
pub fn write_unit_v(&mut self, name: &str) {
let temp = Default::default();
let mut rwriter = RValueWriter::new(&temp);
let bytes = rwriter.unit_v();
self.write_value(&mut rwriter, name, bytes.as_bytes());
}
pub fn write_opaque_v(&mut self, name: &str) {
let temp = Default::default();
let mut rwriter = RValueWriter::new(&temp);
let bytes = rwriter.opaque_v();
self.write_value(&mut rwriter, name, bytes.as_bytes());
}
fn write_result(
&mut self,
rwriter: &mut RValueWriter,
name: &str,
ty: &str,
ok: bool,
val: Bytes,
) {
let result_enum = UnionType {
name: trim_type_name(ty),
variants: vec!["Ok".to_owned(), "Err".to_owned()],
};
let bytes = rwriter.union_v(
&result_enum,
if ok { 0 } else { 1 },
[("0".to_owned(), val)].into_iter(),
);
self.write_value(rwriter, name, bytes.as_bytes());
}
fn write_option(
&mut self,
rwriter: &mut RValueWriter,
name: &str,
ty: &str,
val: Option<Bytes>,
) {
let option_enum = UnionType {
name: trim_type_name(ty),
variants: vec!["None".to_owned(), "Some".to_owned()],
};
let bytes = if let Some(val) = val {
rwriter.union_v(&option_enum, 1, [("0".to_owned(), val)].into_iter())
} else {
rwriter.union_v(&option_enum, 0, [].into_iter())
};
self.write_value(rwriter, name, bytes.as_bytes());
}
}
impl Default for Thread {
fn default() -> Self {
Self {
frame: Frame { frame_id: 0 },
active_frames: Vec::new(),
allocating: None,
}
}
}
#[doc(hidden)]
pub fn new_breakpoint(id: u32, file_id: u32, func: FunctionDef) -> Breakpoint {
Breakpoint {
id,
file_id,
loc: func.loc.start,
loc_end: Some(func.loc.end),
breakpoint_type: BreakpointType::FunctionCall {
fn_name: func.ty.fn_name().into(),
},
capture: VariableCapture::Arguments,
}
}
fn register_breakpoint(breakpoint_addresses: &mut FxHashMap<u64, BpId>, sb_bp: &SBBreakpoint) {
for sb_bp_loc in sb_bp.locations() {
breakpoint_addresses.insert(sb_bp_loc.address().file_address() as u64, BpId(sb_bp.id()));
}
}
fn send_breakpoint(producer: &SeaProducer, key: &StreamKey, bp: &Breakpoint) -> Result<()> {
producer
.send_to(key, serde_json::to_string(bp)?)
.context("Fail to steam breakpoint")?;
Ok(())
}
fn get_rust_panic_source_file() -> SourceFile {
SourceFile {
id: FIREDBG_SOURCE_FILE_ID,
path: "FireDBG.Internal".into(),
crate_name: "FireDBG.Internal".into(),
modified: SystemTime::now(),
}
}
fn get_rust_panic_breakpoint() -> Breakpoint {
Breakpoint {
id: RUST_PANIC_BP_ID.0,
file_id: FIREDBG_SOURCE_FILE_ID,
loc: Default::default(),
loc_end: Default::default(),
breakpoint_type: BreakpointType::Breakpoint,
capture: VariableCapture::Locals,
}
}
fn get_firedbg_trace_breakpoint() -> Breakpoint {
Breakpoint {
id: FIREDBG_TRACE_BP_ID.0,
file_id: FIREDBG_SOURCE_FILE_ID,
loc: Default::default(),
loc_end: Default::default(),
breakpoint_type: BreakpointType::Breakpoint,
capture: VariableCapture::Arguments,
}
}
fn get_exchange_malloc_breakpoint() -> Breakpoint {
Breakpoint {
id: EXCHANGE_MALLOC.0,
file_id: FIREDBG_SOURCE_FILE_ID,
loc: Default::default(),
loc_end: Default::default(),
breakpoint_type: BreakpointType::Breakpoint,
capture: VariableCapture::Arguments,
}
}
fn parse_addr(line: &str) -> Result<u64> {
let addr = line
.split("0x")
.nth(1)
.expect("Starts with `0x`")
.split_ascii_whitespace()
.nth(0)
.expect("Read until space");
let res =
u64::from_str_radix(addr, 16).with_context(|| format!("Invalid address: `{addr}`"))?;
Ok(res)
}
fn read_u64(v: &SBValue) -> Result<u64, WriteErr> {
v.data().read_u64(0).map_err(|_| WriteErr)
}
#[doc(hidden)]
pub fn get_target_basename(output: &str) -> &str {
output.trim_end_matches(".firedbg.ss")
}
pub fn check_rustc_version() {
let rustc_version = rustc_version();
if rustc_version != SUPPORTED_RUSTC_VERSION {
panic!("Mismatched `rustc` version: expect `{SUPPORTED_RUSTC_VERSION}`, found `{rustc_version}`. Please install a matching FireDBG version.");
}
}