use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
}
#[derive(Clone, Debug)]
pub struct DwarfSourceMap {
pub pc_map: HashMap<usize, Vec<SourceLocation>>,
pub fn_map: HashMap<usize, (String, SourceLocation)>,
pub executable_lines: HashMap<String, HashSet<u32>>,
}
pub fn build_dwarf_source_map(debug_binary: &[u8]) -> Option<DwarfSourceMap> {
use object::{Object, ObjectSection};
let object_file = object::File::parse(debug_binary).ok()?;
if object_file.section_by_name(".debug_info").is_none() {
return None;
}
let text_section = object_file.section_by_name(".text")?;
let text_vaddr = text_section.address();
let text_size = text_section.size();
let max_pc = (text_size / 8) as usize;
let load_section = |id: gimli::SectionId| -> Result<gimli::EndianSlice<'_, gimli::LittleEndian>, gimli::Error> {
let data = object_file
.section_by_name(id.name())
.and_then(|s| s.data().ok())
.unwrap_or(&[]);
Ok(gimli::EndianSlice::new(data, gimli::LittleEndian))
};
let dwarf = gimli::Dwarf::load(&load_section).ok()?;
let context = addr2line::Context::from_dwarf(dwarf).ok()?;
let source_root: Option<String> = std::env::var("FUZZ_SYMBOLS")
.ok()
.and_then(|p| p.find("/target/").map(|idx| p[..idx].to_string()));
let resolve_path = |file: &str| -> String {
if let Ok(abs) = std::fs::canonicalize(file) {
return abs.to_string_lossy().into_owned();
}
if let Some(ref root) = source_root {
let full = format!("{}/{}", root, file);
if let Ok(abs) = std::fs::canonicalize(&full) {
return abs.to_string_lossy().into_owned();
}
}
file.to_string()
};
let mut pc_map: HashMap<usize, Vec<SourceLocation>> = HashMap::new();
let mut fn_map = HashMap::new();
let mut executable_lines: HashMap<String, HashSet<u32>> = HashMap::new();
for pc in 0..max_pc {
let elf_addr = text_vaddr + (pc as u64) * 8;
let mut frames = match context.find_frames(elf_addr).skip_all_loads() {
Ok(frames) => frames,
Err(_) => continue,
};
let mut locations = Vec::new();
let mut first_function_name: Option<String> = None;
loop {
match frames.next() {
Ok(Some(frame)) => {
if first_function_name.is_none() {
if let Some(ref function) = frame.function {
first_function_name = Some(
function
.demangle()
.map(|d: std::borrow::Cow<'_, str>| d.into_owned())
.unwrap_or_else(|_| {
function
.raw_name()
.map(|r: std::borrow::Cow<'_, str>| r.into_owned())
.unwrap_or_else(|_| format!("fn_{}", pc))
}),
);
}
}
if let Some(loc) = frame.location {
if let (Some(file), Some(line)) = (loc.file, loc.line) {
let file_path = resolve_path(file);
executable_lines
.entry(file_path.clone())
.or_default()
.insert(line);
locations.push(SourceLocation {
file: file_path,
line,
});
}
}
}
Ok(None) => break,
Err(_) => break,
}
}
if !locations.is_empty() {
if let Some(name) = first_function_name {
let outermost = locations.last().unwrap();
fn_map
.entry(pc)
.or_insert_with(|| (name, outermost.clone()));
}
pc_map.insert(pc, locations);
}
}
if let Ok(locs) = context.find_location_range(text_vaddr, text_vaddr + text_size) {
for (addr, _len, loc) in locs {
if let (Some(file), Some(line)) = (loc.file, loc.line) {
if addr < text_vaddr || addr >= text_vaddr + text_size {
continue;
}
let pc = ((addr - text_vaddr) / 8) as usize;
let file_path = resolve_path(file);
executable_lines
.entry(file_path.clone())
.or_default()
.insert(line);
let locations = pc_map.entry(pc).or_default();
if !locations
.iter()
.any(|l| l.file == file_path && l.line == line)
{
locations.push(SourceLocation {
file: file_path,
line,
});
}
}
}
}
let symbol_map = object_file.symbol_map();
for sym in symbol_map.symbols() {
let addr = sym.address();
if addr >= text_vaddr && addr < text_vaddr + text_size {
let pc = ((addr - text_vaddr) / 8) as usize;
if !fn_map.contains_key(&pc) {
if let Some(locs) = pc_map.get(&pc) {
if let Some(loc) = locs.last() {
fn_map.insert(pc, (sym.name().to_string(), loc.clone()));
}
}
}
}
}
Some(DwarfSourceMap {
pc_map,
fn_map,
executable_lines,
})
}