use super::{unit_info::UnitInfo, DebugError, DebugInfo};
use gimli::{ColumnType, LineSequence};
use num_traits::Zero;
use std::{
fmt::{Debug, Formatter},
num::NonZeroU64,
ops::Range,
};
pub struct SourceStatements {
pub(crate) statements: Vec<SourceStatement>,
}
impl Debug for SourceStatements {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for statement in &self.statements {
writeln!(f, "{:?}", statement)?;
}
Ok(())
}
}
impl SourceStatements {
pub(crate) fn new(
debug_info: &DebugInfo,
program_unit: &UnitInfo,
program_counter: u64,
) -> Result<Self, DebugError> {
let mut source_statements = SourceStatements {
statements: Vec::new(),
};
let (complete_line_program, active_sequence) =
get_program_info_at_pc(debug_info, program_unit, program_counter)?;
let mut sequence_rows = complete_line_program.resume_from(&active_sequence);
let mut prologue_completed = false;
let mut source_statement: Option<SourceStatement> = None;
while let Ok(Some((_, row))) = sequence_rows.next_row() {
if let Some(source_row) = source_statement.as_mut() {
if source_row.line.is_none()
&& row.line().is_some()
&& row.file_index() == source_row.file_index
&& row.column() == source_row.column
{
source_row.line = row.line();
}
} else {
source_statement = Some(SourceStatement::from(row));
}
if row.prologue_end() {
prologue_completed = true;
}
if !prologue_completed {
log_row_eval(&active_sequence, program_counter, row, " inside prologue>");
continue;
} else {
log_row_eval(&active_sequence, program_counter, row, " after prologue>");
}
if let Some(source_row) = source_statement.as_mut() {
source_row.instruction_range = source_row.low_pc()..row.address();
if row.end_sequence()
|| (row.is_stmt() && row.address() > source_row.low_pc())
|| !(row.file_index() == source_row.file_index
&& (row.line() == source_row.line || row.line().is_none())
&& row.column() == source_row.column)
{
if source_row.low_pc() >= program_counter {
source_row.sequence_range = program_counter..active_sequence.end;
source_statements.add(source_row.clone());
}
if row.end_sequence() {
break;
}
source_statement = Some(SourceStatement::from(row));
} else if row.address() == program_counter {
source_row.instruction_range = row.address()..row.address();
}
}
}
if source_statements.len().is_zero() {
Err(DebugError::NoValidHaltLocation{
message: "Could not find valid source statements for this address. Consider using instruction level stepping.".to_string(),
pc_at_error: program_counter,
})
} else {
tracing::trace!(
"Source statements for pc={:#010x}\n{:?}",
program_counter,
source_statements
);
Ok(source_statements)
}
}
pub(crate) fn add(&mut self, statement: SourceStatement) {
self.statements.push(statement);
}
pub(crate) fn len(&self) -> usize {
self.statements.len()
}
}
#[derive(Clone)]
pub(crate) struct SourceStatement {
pub(crate) is_stmt: bool,
pub(crate) file_index: u64,
pub(crate) line: Option<NonZeroU64>,
pub(crate) column: ColumnType,
pub(crate) instruction_range: Range<u64>,
pub(crate) sequence_range: Range<u64>,
}
impl Debug for SourceStatement {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"\tStatement={:05} on line={:04} col={:05} f={:02}, Range: {:#010x}-{:#010x} --> Sequence Range: {:#010x}-{:#010x}",
&self.is_stmt,
match &self.line {
Some(line) => line.get(),
None => 0,
},
match &self.column {
gimli::ColumnType::LeftEdge => 0,
gimli::ColumnType::Column(column) => column.get(),
},
&self.file_index,
&self.instruction_range.start,
&self.instruction_range.end,
&self.sequence_range.start,
&self.sequence_range.end,
)?;
Ok(())
}
}
impl SourceStatement {
pub(crate) fn get_first_halt_address(&self, address: u64) -> Option<u64> {
if self.instruction_range.start == address
|| (self.sequence_range.start..self.instruction_range.end).contains(&address)
{
Some(self.low_pc())
} else {
None
}
}
pub(crate) fn low_pc(&self) -> u64 {
self.instruction_range.start
}
}
impl From<&gimli::LineRow> for SourceStatement {
fn from(line_row: &gimli::LineRow) -> Self {
SourceStatement {
is_stmt: line_row.is_stmt(),
file_index: line_row.file_index(),
line: line_row.line(),
column: line_row.column(),
instruction_range: line_row.address()..line_row.address(),
sequence_range: line_row.address()..line_row.address(),
}
}
}
#[allow(clippy::type_complexity)]
fn get_program_info_at_pc(
debug_info: &DebugInfo,
program_unit: &UnitInfo,
program_counter: u64,
) -> Result<
(
gimli::CompleteLineProgram<
gimli::EndianReader<gimli::LittleEndian, std::rc::Rc<[u8]>>,
usize,
>,
gimli::LineSequence<gimli::EndianReader<gimli::LittleEndian, std::rc::Rc<[u8]>>>,
),
DebugError,
> {
let (offset, address_size) = if let Some(line_program) = program_unit.unit.line_program.clone()
{
(
line_program.header().offset(),
line_program.header().address_size(),
)
} else {
return Err(DebugError::NoValidHaltLocation{
message: "The specified source location does not have any line_program information available. Please consider using instruction level stepping.".to_string(),
pc_at_error: program_counter,
});
};
let incomplete_line_program =
debug_info
.debug_line_section
.program(offset, address_size, None, None)?;
let (complete_line_program, line_sequences) = incomplete_line_program.sequences()?;
if let Some(active_sequence) = line_sequences.iter().find(|line_sequence| {
line_sequence.start <= program_counter && program_counter < line_sequence.end
}) {
Ok((complete_line_program, active_sequence.clone()))
} else {
Err(DebugError::NoValidHaltLocation{
message: "The specified source location does not have any line information available. Please consider using instruction level stepping.".to_string(),
pc_at_error: program_counter,
})
}
}
fn log_row_eval(
active_sequence: &LineSequence<super::GimliReader>,
pc: u64,
row: &gimli::LineRow,
status: &str,
) {
tracing::trace!("Sequence row {:#010X}<={:#010X}<{:#010X}: addr={:#010X} stmt={:5} ep={:5} es={:5} line={:04} col={:05} f={:02} : {}",
active_sequence.start,
pc,
active_sequence.end,
row.address(),
row.is_stmt(),
row.prologue_end(),
row.end_sequence(),
match row.line() {
Some(line) => line.get(),
None => 0,
},
match row.column() {
gimli::ColumnType::LeftEdge => 0,
gimli::ColumnType::Column(column) => column.get(),
},
row.file_index(),
status);
}