use anyhow::{anyhow, Result};
use log::error;
use crate::utils::get_current_unit;
use gimli::{ColumnType, DebuggingInformationEntry, Dwarf, Reader, Unit};
use std::num::NonZeroU64;
#[derive(Debug, Clone)]
pub struct SourceInformation {
pub directory: Option<String>,
pub file: Option<String>,
pub line: Option<NonZeroU64>,
pub column: Option<NonZeroU64>,
}
impl SourceInformation {
pub fn get_die_source_information<R: Reader<Offset = usize>>(
dwarf: &Dwarf<R>,
unit: &Unit<R>,
die: &DebuggingInformationEntry<'_, '_, R>,
cwd: &str,
) -> Result<SourceInformation> {
let (file, directory) = match die.attr_value(gimli::DW_AT_decl_file)? {
Some(gimli::AttributeValue::FileIndex(v)) => match &unit.line_program {
Some(lp) => {
let header = lp.header();
match header.file(v) {
Some(file_entry) => {
let (file, directory) = match file_entry.directory(header) {
Some(dir_av) => {
let mut dir_raw =
dwarf.attr_string(unit, dir_av)?.to_string()?.to_string();
let file_raw = dwarf
.attr_string(unit, file_entry.path_name())?
.to_string()?
.to_string();
let file = file_raw.trim_start_matches(&dir_raw).to_string();
if !dir_raw.starts_with('/') {
dir_raw = format!("{}/{}", cwd, dir_raw);
}
(file, Some(dir_raw))
}
None => (
dwarf
.attr_string(unit, file_entry.path_name())?
.to_string()?
.to_string(),
None,
),
};
(Some(file), directory)
}
None => (None, None),
}
}
None => (None, None),
},
None => (None, None),
Some(v) => {
error!("Unimplemented {:?}", v);
return Err(anyhow!("Unimplemented {:?}", v));
}
};
let line = match die.attr_value(gimli::DW_AT_decl_line)? {
Some(gimli::AttributeValue::Udata(v)) => NonZeroU64::new(v),
None => None,
Some(v) => {
error!("Unimplemented {:?}", v);
return Err(anyhow!("Unimplemented {:?}", v));
}
};
let column = match die.attr_value(gimli::DW_AT_decl_column)? {
Some(gimli::AttributeValue::Udata(v)) => NonZeroU64::new(v),
None => None,
Some(v) => {
error!("Unimplemented {:?}", v);
return Err(anyhow!("Unimplemented {:?}", v));
}
};
Ok(SourceInformation {
directory,
file,
line,
column,
})
}
pub fn get_from_address<R: Reader<Offset = usize>>(
dwarf: &Dwarf<R>,
address: u64,
cwd: &str,
) -> Result<SourceInformation> {
let unit = get_current_unit(dwarf, address as u32)?;
let mut nearest = None;
match unit.line_program.clone() {
Some(line_program) => {
let (program, sequences) = line_program.sequences()?;
let mut in_range_seqs = vec![];
for seq in sequences {
if address >= seq.start && address < seq.end {
in_range_seqs.push(seq);
}
}
let mut result = vec![];
for seq in in_range_seqs {
let mut sm = program.resume_from(&seq);
while let Some((header, row)) = sm.next_row()? {
if row.address() <= address {
let (file, directory) = match row.file(header) {
Some(file_entry) => match file_entry.directory(header) {
Some(dir_av) => {
let mut dir_raw = dwarf
.attr_string(&unit, dir_av)?
.to_string()?
.to_string();
let file_raw = dwarf
.attr_string(&unit, file_entry.path_name())?
.to_string()?
.to_string();
let file =
file_raw.trim_start_matches(&dir_raw).to_string();
if !dir_raw.starts_with('/') {
dir_raw = format!("{}/{}", cwd, dir_raw);
}
(Some(file), Some(dir_raw))
}
None => (None, None),
},
None => (None, None),
};
let si = SourceInformation {
directory,
file,
line: row.line(),
column: match row.column() {
ColumnType::LeftEdge => NonZeroU64::new(1),
ColumnType::Column(n) => Some(n),
},
};
match nearest {
Some((addr, _)) => {
if row.address() > addr {
nearest = Some((row.address(), si));
}
}
None => nearest = Some((row.address(), si)),
};
}
if row.address() == address {
result.push(row.line());
}
}
}
match nearest {
Some((_, si)) => Ok(si),
None => {
error!("Could not find source informaitno");
Err(anyhow!("Could not find source informaitno"))
}
}
}
None => {
error!("Unit has no line program");
Err(anyhow!("Unit has no line program"))
}
}
}
}
pub fn find_breakpoint_location<'a, R: Reader<Offset = usize>>(
dwarf: &'a Dwarf<R>,
cwd: &str,
path: &str,
line: NonZeroU64,
column: Option<NonZeroU64>,
) -> Result<Option<u64>> {
let mut locations = vec![];
let mut units = dwarf.units();
while let Some(unit_header) = units.next()? {
let unit = dwarf.unit(unit_header)?;
if let Some(ref line_program) = unit.line_program {
let lp_header = line_program.header();
for file_entry in lp_header.file_names() {
let directory = match file_entry.directory(lp_header) {
Some(dir_av) => {
let dir_raw = dwarf.attr_string(&unit, dir_av)?;
dir_raw.to_string()?.to_string()
}
None => continue,
};
let file_raw = dwarf.attr_string(&unit, file_entry.path_name())?;
let mut file_path = format!("{}/{}", directory, file_raw.to_string()?);
if !file_path.starts_with('/') {
file_path = format!("{}/{}", cwd, file_path);
}
if path == file_path {
let mut rows = line_program.clone().rows();
while let Some((header, row)) = rows.next_row()? {
let file_entry = match row.file(header) {
Some(v) => v,
None => continue,
};
let directory = match file_entry.directory(header) {
Some(dir_av) => {
let dir_raw = dwarf.attr_string(&unit, dir_av)?;
dir_raw.to_string()?.to_string()
}
None => continue,
};
let file_raw = dwarf.attr_string(&unit, file_entry.path_name())?;
let mut file_path = format!("{}/{}", directory, file_raw.to_string()?);
if !file_path.starts_with('/') {
file_path = format!("{}/{}", cwd, file_path);
}
if path == file_path {
if let Some(l) = row.line() {
if line == l {
locations.push((row.column(), row.address()));
}
}
}
}
}
}
}
}
match locations.len() {
0 => Ok(None),
len => {
let search = match column {
Some(v) => gimli::ColumnType::Column(v),
None => gimli::ColumnType::LeftEdge,
};
let mut res = locations[0];
for location in locations.iter().take(len).skip(1) {
if location.0 <= search && location.0 > res.0 {
res = *location;
}
}
Ok(Some(res.1))
}
}
}