use {
crate::error::{DebuggerError, DebuggerResult},
gimli::{EndianSlice, RunTimeEndian, SectionId},
object::{Object, ObjectSection},
sbpf_vm::memory::Memory,
std::{borrow::Cow, collections::HashMap, path::PathBuf},
};
#[derive(Debug, Clone)]
pub struct RODataSymbol {
pub name: String,
pub address: u64,
pub content: String,
}
pub fn rodata_from_section(
section: &sbpf_disassembler::rodata::RodataSection,
) -> Vec<RODataSymbol> {
section
.items
.iter()
.map(|item| RODataSymbol {
name: item.label.clone(),
address: Memory::RODATA_START + item.offset,
content: item.data_type.to_asm(),
})
.collect()
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
_address: u64,
}
#[derive(Clone)]
pub struct LineMap {
address_to_line: HashMap<u64, usize>,
line_to_addresses: HashMap<usize, Vec<u64>>,
source_locations: HashMap<u64, SourceLocation>,
labels: HashMap<u64, String>,
files: Vec<String>,
text_offset: u64,
}
impl Default for LineMap {
fn default() -> Self {
Self::new()
}
}
impl LineMap {
pub fn new() -> Self {
Self {
address_to_line: HashMap::new(),
line_to_addresses: HashMap::new(),
source_locations: HashMap::new(),
labels: HashMap::new(),
files: Vec::new(),
text_offset: 0,
}
}
pub fn from_elf_file(file_path: &str) -> DebuggerResult<Self> {
let file_data = std::fs::read(file_path)?;
Self::from_elf_data(&file_data)
}
pub fn from_elf_data(file_data: &[u8]) -> DebuggerResult<Self> {
let object = object::File::parse(file_data)?;
let mut line_map = Self::new();
line_map.text_offset = object
.section_by_name(".text")
.map(|section| section.address())
.unwrap_or(0);
line_map.parse_debug_info_from_object(&object)?;
Ok(line_map)
}
fn parse_debug_info_from_object(&mut self, obj_file: &object::File) -> DebuggerResult<()> {
let endian = if obj_file.is_little_endian() {
RunTimeEndian::Little
} else {
RunTimeEndian::Big
};
let load_section = |id: SectionId| -> Result<Cow<[u8]>, gimli::Error> {
match obj_file.section_by_name(id.name()) {
Some(section) => match section.uncompressed_data() {
Ok(data) => Ok(data),
Err(_) => Ok(Cow::Borrowed(&[])),
},
None => Ok(Cow::Borrowed(&[])),
}
};
let borrow_section = |section| EndianSlice::new(Cow::as_ref(section), endian);
let dwarf_sections =
gimli::DwarfSections::load(&load_section).map_err(DebuggerError::Dwarf)?;
let dwarf = dwarf_sections.borrow(borrow_section);
let mut iter = dwarf.units();
while let Some(header) = iter.next().map_err(DebuggerError::Dwarf)? {
let unit = dwarf.unit(header).map_err(DebuggerError::Dwarf)?;
let unit = unit.unit_ref(&dwarf);
if let Some(program) = unit.line_program.clone() {
let comp_dir = if let Some(ref dir) = unit.comp_dir {
PathBuf::from(dir.to_string_lossy().into_owned())
} else {
PathBuf::new()
};
let mut rows = program.rows();
while let Some((header, row)) = rows.next_row().map_err(DebuggerError::Dwarf)? {
if !row.end_sequence() {
let mut file_path = String::new();
if let Some(file) = row.file(header) {
let mut path = PathBuf::new();
path.clone_from(&comp_dir);
if file.directory_index() != 0
&& let Some(dir) = file.directory(header)
{
path.push(
unit.attr_string(dir)
.map_err(DebuggerError::Dwarf)?
.to_string_lossy()
.as_ref(),
);
}
path.push(
unit.attr_string(file.path_name())
.map_err(DebuggerError::Dwarf)?
.to_string_lossy()
.as_ref(),
);
file_path = path.to_string_lossy().to_string();
}
let line = match row.line() {
Some(line) => line.get() as u32,
None => 0,
};
let column = match row.column() {
gimli::ColumnType::LeftEdge => 0,
gimli::ColumnType::Column(column) => column.get() as u32,
};
let address = row.address();
self.address_to_line.insert(address, line as usize);
self.line_to_addresses
.entry(line as usize)
.or_default()
.push(address);
let source_loc = SourceLocation {
file: file_path.clone(),
line,
column,
_address: address,
};
self.source_locations.insert(address, source_loc);
if !file_path.is_empty() && !self.files.contains(&file_path) {
self.files.push(file_path);
}
}
}
}
let mut entries = unit.entries();
while let Some(entry) = entries.next_dfs().map_err(DebuggerError::Dwarf)? {
if entry.tag() == gimli::DW_TAG_label
&& let (Some(name_attr), Some(pc_attr)) = (
entry.attr_value(gimli::DW_AT_name),
entry.attr_value(gimli::DW_AT_low_pc),
)
&& let Ok(name) = unit.attr_string(name_attr)
&& let gimli::AttributeValue::Addr(addr) = pc_attr
{
self.labels.insert(addr, name.to_string_lossy().to_string());
}
}
}
Ok(())
}
pub fn get_label_for_address(&self, address: u64) -> Option<&str> {
self.labels.get(&address).map(|s| s.as_str())
}
pub fn get_text_offset(&self) -> u64 {
self.text_offset
}
pub fn get_line_for_address(&self, address: u64) -> Option<usize> {
self.address_to_line.get(&address).copied()
}
pub fn get_addresses_for_line(&self, line: usize) -> Option<&[u64]> {
self.line_to_addresses.get(&line).map(|v| v.as_slice())
}
pub fn get_line_for_pc(&self, pc: u64) -> Option<usize> {
let address = pc.saturating_add(self.text_offset);
self.get_line_for_address(address)
}
pub fn get_pcs_for_line(&self, line: usize) -> Vec<u64> {
if let Some(addresses) = self.get_addresses_for_line(line) {
addresses
.iter()
.map(|addr| addr.saturating_sub(self.text_offset))
.collect()
} else {
Vec::new()
}
}
pub fn get_source_location(&self, pc: u64) -> Option<&SourceLocation> {
let address = pc.saturating_add(self.text_offset);
self.source_locations.get(&address)
}
pub fn get_line_to_addresses(&self) -> &HashMap<usize, Vec<u64>> {
&self.line_to_addresses
}
pub fn get_line_to_pcs(&self) -> HashMap<usize, Vec<u64>> {
self.line_to_addresses
.iter()
.map(|(line, addrs)| {
(
*line,
addrs
.iter()
.map(|addr| addr.saturating_sub(self.text_offset))
.collect(),
)
})
.collect()
}
}