use super::{
canonical_path_eq,
unit_info::{self, UnitInfo},
ColumnType, DebugError, DebugInfo, GimliReader,
};
use gimli::LineSequence;
use serde::Serialize;
use std::{
fmt::{Debug, Formatter},
num::NonZeroU64,
ops::Range,
};
use typed_path::{TypedPath, TypedPathBuf};
#[derive(Clone, Debug)]
pub struct VerifiedBreakpoint {
pub address: u64,
pub source_location: SourceLocation,
}
impl VerifiedBreakpoint {
pub(crate) fn for_address(
debug_info: &DebugInfo,
address: u64,
) -> Result<VerifiedBreakpoint, DebugError> {
let instruction_sequence = InstructionSequence::from_address(debug_info, address)?;
if let Some(verified_breakpoint) = match_address(&instruction_sequence, address, debug_info)
{
tracing::debug!(
"Found valid breakpoint for address: {:#010x} : {verified_breakpoint:?}",
&address
);
return Ok(verified_breakpoint);
}
let message = format!("Could not identify a valid breakpoint for address: {address:#010x}. Please consider using instruction level stepping.");
Err(DebugError::WarnAndContinue { message })
}
pub(crate) fn for_source_location(
debug_info: &DebugInfo,
path: TypedPath,
line: u64,
column: Option<u64>,
) -> Result<Self, DebugError> {
for program_unit in &debug_info.unit_infos {
let Some(ref line_program) = program_unit.unit.line_program else {
continue;
};
let mut num_files = line_program.header().file_names().len();
if program_unit.unit.header.version() <= 4 {
num_files += 1;
}
let matching_file_indices: Vec<_> = (0..num_files)
.filter_map(|file_index| {
let file_index = file_index as u64;
debug_info
.get_path(&program_unit.unit, file_index)
.and_then(|combined_path: TypedPathBuf| {
if canonical_path_eq(path, combined_path.to_path()) {
tracing::debug!(
"Found matching file index: {file_index} for path: {path}",
file_index = file_index,
path = path.display()
);
Some(file_index)
} else {
None
}
})
})
.collect();
if matching_file_indices.is_empty() {
continue;
}
let Ok((complete_line_program, line_sequences)) = line_program.clone().sequences()
else {
tracing::debug!("Failed to get line sequences for line program");
continue;
};
for line_sequence in line_sequences {
let instruction_sequence = InstructionSequence::from_line_sequence(
debug_info,
program_unit,
&complete_line_program,
&line_sequence,
);
for matching_file_index in &matching_file_indices {
if let Some(verified_breakpoint) = match_file_line_column(
&instruction_sequence,
*matching_file_index,
line,
column,
debug_info,
program_unit,
) {
return Ok(verified_breakpoint);
}
if let Some(verified_breakpoint) = match_file_line_first_available_column(
&instruction_sequence,
*matching_file_index,
line,
debug_info,
program_unit,
) {
return Ok(verified_breakpoint);
}
}
}
}
Err(DebugError::Other(format!("No valid breakpoint information found for file: {}, line: {line:?}, column: {column:?}", path.display())))
}
}
fn match_address(
instruction_sequence: &InstructionSequence<'_>,
address: u64,
debug_info: &DebugInfo,
) -> Option<VerifiedBreakpoint> {
if instruction_sequence.address_range.contains(&address) {
let instruction_location =
instruction_sequence
.instructions
.iter()
.find(|instruction_location| {
instruction_location.instruction_type == InstructionType::HaltLocation
&& instruction_location.address >= address
})?;
let source_location = SourceLocation::from_instruction_location(
debug_info,
instruction_sequence.program_unit,
instruction_location,
)?;
Some(VerifiedBreakpoint {
address: instruction_location.address,
source_location,
})
} else {
None
}
}
fn match_file_line_column(
instruction_sequence: &InstructionSequence<'_>,
matching_file_index: u64,
line: u64,
column: Option<u64>,
debug_info: &DebugInfo,
program_unit: &UnitInfo,
) -> Option<VerifiedBreakpoint> {
let instruction_location =
instruction_sequence
.instructions
.iter()
.find(|instruction_location| {
instruction_location.instruction_type == InstructionType::HaltLocation
&& matching_file_index == instruction_location.file_index
&& NonZeroU64::new(line) == instruction_location.line
&& column
.map(ColumnType::Column)
.is_some_and(|col| col == instruction_location.column)
})?;
let source_location =
SourceLocation::from_instruction_location(debug_info, program_unit, instruction_location)?;
Some(VerifiedBreakpoint {
address: instruction_location.address,
source_location,
})
}
fn match_file_line_first_available_column(
instruction_sequence: &InstructionSequence<'_>,
matching_file_index: u64,
line: u64,
debug_info: &DebugInfo,
program_unit: &UnitInfo,
) -> Option<VerifiedBreakpoint> {
let instruction_location =
instruction_sequence
.instructions
.iter()
.find(|instruction_location| {
instruction_location.instruction_type == InstructionType::HaltLocation
&& matching_file_index == instruction_location.file_index
&& NonZeroU64::new(line) == instruction_location.line
})?;
let source_location =
SourceLocation::from_instruction_location(debug_info, program_unit, instruction_location)?;
Some(VerifiedBreakpoint {
address: instruction_location.address,
source_location,
})
}
fn serialize_typed_path<S>(path: &TypedPathBuf, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&path.to_string_lossy())
}
#[derive(Clone, PartialEq, Eq, Serialize)]
pub struct SourceLocation {
#[serde(serialize_with = "serialize_typed_path")]
pub path: TypedPathBuf,
pub line: Option<u64>,
pub column: Option<ColumnType>,
}
impl Debug for SourceLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{:?}:{:?}",
self.path.to_path().display(),
self.line,
self.column
)
}
}
impl SourceLocation {
fn from_instruction_location(
debug_info: &DebugInfo,
program_unit: &unit_info::UnitInfo,
instruction_location: &InstructionLocation,
) -> Option<SourceLocation> {
debug_info
.find_file_and_directory(&program_unit.unit, instruction_location.file_index)
.map(|path| SourceLocation {
line: instruction_location.line.map(std::num::NonZeroU64::get),
column: Some(instruction_location.column),
path,
})
}
pub fn file_name(&self) -> Option<String> {
self.path
.file_name()
.map(|name| String::from_utf8_lossy(name).to_string())
}
}
struct InstructionSequence<'debug_info> {
address_range: Range<u64>,
instructions: Vec<InstructionLocation>,
debug_info: &'debug_info DebugInfo,
program_unit: &'debug_info UnitInfo,
}
impl Debug for InstructionSequence<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Instruction Sequence with address range: {:#010x} - {:#010x}",
self.address_range.start, self.address_range.end
)?;
for instruction_location in &self.instructions {
writeln!(
f,
"\t{instruction_location:?} - {}",
self.debug_info
.get_path(&self.program_unit.unit, instruction_location.file_index)
.map(|file_path| file_path.to_string_lossy().to_string())
.unwrap_or("<unknown file>".to_string())
)?;
}
Ok(())
}
}
impl<'debug_info> InstructionSequence<'debug_info> {
fn from_address(
debug_info: &'debug_info DebugInfo,
program_counter: u64,
) -> Result<Self, DebugError> {
let program_unit = debug_info.compile_unit_info(program_counter)?;
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 {
let message = "The specified source location does not have any line_program information available. Please consider using instruction level stepping.".to_string();
return Err(DebugError::WarnAndContinue { message });
};
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()?;
let Some(line_sequence) = line_sequences.iter().find(|line_sequence| {
line_sequence.start <= program_counter && program_counter < line_sequence.end
}) else {
let message = "The specified source location does not have any line information available. Please consider using instruction level stepping.".to_string();
return Err(DebugError::WarnAndContinue { message });
};
let instruction_sequence = Self::from_line_sequence(
debug_info,
program_unit,
&complete_line_program,
line_sequence,
);
if instruction_sequence.len() == 0 {
let message = "Could not find valid instruction locations for this address. Consider using instruction level stepping.".to_string();
Err(DebugError::WarnAndContinue { message })
} else {
tracing::trace!(
"Instruction location for pc={:#010x}\n{:?}",
program_counter,
instruction_sequence
);
Ok(instruction_sequence)
}
}
fn from_line_sequence(
debug_info: &'debug_info DebugInfo,
program_unit: &'debug_info UnitInfo,
complete_line_program: &gimli::CompleteLineProgram<GimliReader>,
line_sequence: &LineSequence<GimliReader>,
) -> Self {
let program_language = program_unit.get_language();
let mut sequence_rows = complete_line_program.resume_from(line_sequence);
let mut instruction_sequence = InstructionSequence {
address_range: line_sequence.start..line_sequence.end,
instructions: Vec::new(),
debug_info,
program_unit,
};
let mut prologue_completed = false;
let mut previous_row: Option<gimli::LineRow> = None;
while let Ok(Some((_, row))) = sequence_rows.next_row() {
if row.prologue_end() {
prologue_completed = true;
}
if !prologue_completed
&& matches!(
program_language,
gimli::DW_LANG_C99 | gimli::DW_LANG_C11 | gimli::DW_LANG_C17
)
{
if let Some(prev_row) = previous_row {
if row.end_sequence()
|| (row.is_stmt()
&& (row.file_index() == prev_row.file_index()
&& (row.line() != prev_row.line() || row.line().is_none())))
{
prologue_completed = true;
}
}
}
if !prologue_completed {
log_row_eval(line_sequence, row, " inside prologue>");
} else {
log_row_eval(line_sequence, row, " after prologue>");
}
if row.end_sequence() {
break;
}
instruction_sequence.add(prologue_completed, row, previous_row.as_ref());
previous_row = Some(*row);
}
instruction_sequence
}
fn add(
&mut self,
prologue_completed: bool,
row: &gimli::LineRow,
previous_row: Option<&gimli::LineRow>,
) {
let mut instruction_line = row.line();
if let Some(prev_row) = previous_row {
if row.line().is_none()
&& prev_row.line().is_some()
&& row.file_index() == prev_row.file_index()
&& prev_row.column() == row.column()
{
instruction_line = prev_row.line();
}
}
let instruction_location = InstructionLocation {
address: row.address(),
file_index: row.file_index(),
line: instruction_line,
column: row.column().into(),
instruction_type: if !prologue_completed {
InstructionType::Prologue
} else if row.epilogue_begin() || row.is_stmt() {
InstructionType::HaltLocation
} else {
InstructionType::Unspecified
},
};
self.instructions.push(instruction_location);
}
fn len(&self) -> usize {
self.instructions.len()
}
}
#[derive(Debug, Clone, PartialEq)]
enum InstructionType {
Prologue,
HaltLocation,
Unspecified,
}
#[derive(Clone)]
struct InstructionLocation {
address: u64,
file_index: u64,
line: Option<NonZeroU64>,
column: ColumnType,
instruction_type: InstructionType,
}
impl Debug for InstructionLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Instruction @ {:010x}, on line={:04} col={:05} f={:02}, type={:?}",
&self.address,
match &self.line {
Some(line) => line.get(),
None => 0,
},
match &self.column {
ColumnType::LeftEdge => 0,
ColumnType::Column(column) => column.to_owned(),
},
&self.file_index,
&self.instruction_type,
)?;
Ok(())
}
}
fn log_row_eval(
active_sequence: &LineSequence<super::GimliReader>,
row: &gimli::LineRow,
status: &str,
) {
tracing::trace!("Sequence: line={:04} col={:05} f={:02} stmt={:5} ep={:5} es={:5} eb={:5} : {:#010X}<={:#010X}<{:#010X} : {}",
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(),
row.is_stmt(),
row.prologue_end(),
row.end_sequence(),
row.epilogue_begin(),
active_sequence.start,
row.address(),
active_sequence.end,
status);
}