use anyhow::{bail, ensure, Context as _};
use gimli::{
BaseAddresses, DebugFrame, LittleEndian, UninitializedUnwindContext, UnwindSection as _,
};
use probe_rs::{config::RamRegion, Core};
use crate::{
cortexm,
registers::{self, Registers},
stacked::Stacked,
Outcome, VectorTable,
};
static MISSING_DEBUG_INFO: &str = "debug information is missing. Likely fixes:
1. compile the Rust code with `debug = 1` or higher. This is configured in the `profile.{release,bench}` sections of Cargo.toml (`profile.{dev,test}` default to `debug = 2`)
2. use a recent version of the `cortex-m` crates (e.g. cortex-m 0.6.3 or newer). Check versions in Cargo.lock
3. if linking to C code, compile the C code with the `-g` flag";
pub(crate) fn target(
core: &mut Core,
debug_frame: &[u8],
vector_table: &VectorTable,
sp_ram_region: &Option<RamRegion>,
) -> anyhow::Result<Output> {
let mut debug_frame = DebugFrame::new(debug_frame, LittleEndian);
debug_frame.set_address_size(cortexm::ADDRESS_SIZE);
let mut pc = core.read_core_reg(registers::PC)?;
let sp = core.read_core_reg(registers::SP)?;
let lr = core.read_core_reg(registers::LR)?;
let base_addresses = BaseAddresses::default();
let mut unwind_context = UninitializedUnwindContext::new();
let mut outcome = Outcome::Ok;
let mut registers = Registers::new(lr, sp, core);
let mut raw_frames = vec![];
let mut corrupted = true;
loop {
if cortexm::is_hard_fault(pc, vector_table) {
assert!(
raw_frames.is_empty(),
"when present HardFault handler must be the first frame we unwind but wasn't"
);
outcome = if overflowed_stack(sp, sp_ram_region) {
Outcome::StackOverflow
} else {
Outcome::HardFault
};
}
raw_frames.push(RawFrame::Subroutine { pc });
let uwt_row = debug_frame
.unwind_info_for_address(
&base_addresses,
&mut unwind_context,
pc.into(),
DebugFrame::cie_from_offset,
)
.with_context(|| MISSING_DEBUG_INFO)?;
let cfa_changed = registers.update_cfa(uwt_row.cfa())?;
for (reg, rule) in uwt_row.registers() {
registers.update(reg, rule)?;
}
let lr = registers.get(registers::LR)?;
log::debug!("LR={:#010X} PC={:#010X}", lr, pc);
if lr == registers::LR_END {
break;
}
let exception_entry = lr >= cortexm::EXC_RETURN_MARKER;
let program_counter_changed = !cortexm::subroutine_eq(lr, pc);
corrupted = !cfa_changed && !program_counter_changed;
if corrupted {
break;
}
if exception_entry {
raw_frames.push(RawFrame::Exception);
let fpu = match lr {
0xFFFFFFF1 | 0xFFFFFFF9 | 0xFFFFFFFD => false,
0xFFFFFFE1 | 0xFFFFFFE9 | 0xFFFFFFED => true,
_ => bail!("LR contains invalid EXC_RETURN value {:#010X}", lr),
};
let sp = registers.get(registers::SP)?;
let ram_bounds = sp_ram_region
.as_ref()
.map(|ram_region| ram_region.range.clone())
.unwrap_or(cortexm::VALID_RAM_ADDRESS);
let stacked = if let Some(stacked) = Stacked::read(registers.core, sp, fpu, ram_bounds)?
{
stacked
} else {
corrupted = true;
break;
};
registers.insert(registers::LR, stacked.lr);
registers.insert(registers::SP, sp + stacked.size());
pc = stacked.pc;
} else {
ensure!(
cortexm::is_thumb_bit_set(lr),
"bug? LR ({:#010x}) didn't have the Thumb bit set",
lr
);
pc = cortexm::clear_thumb_bit(lr);
}
}
Ok(Output {
corrupted,
outcome,
raw_frames,
})
}
#[derive(Debug)]
pub struct Output {
pub(crate) corrupted: bool,
pub(crate) outcome: Outcome,
pub(crate) raw_frames: Vec<RawFrame>,
}
#[derive(Debug)]
pub(crate) enum RawFrame {
Subroutine { pc: u32 },
Exception,
}
impl RawFrame {
pub(crate) fn is_exception(&self) -> bool {
matches!(self, Self::Exception)
}
}
fn overflowed_stack(sp: u32, sp_ram_region: &Option<RamRegion>) -> bool {
if let Some(sp_ram_region) = sp_ram_region {
let range = sp_ram_region.range.start..=sp_ram_region.range.end;
!range.contains(&sp)
} else {
log::warn!("no RAM region appears to contain the stack; cannot determine if this was a stack overflow");
false
}
}