diff --git a/debian/changelog b/debian/changelog index 5373b1906dfedaade4e740bdab0aab068fb433e3..f991897ef9590e9599a4fe3c41fc1751168c3bf3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +dwarf2sources (0.2.4) apertis; urgency=medium + + * Collect project-external files from debug line info + + -- Ryan Gonzalez <ryan.gonzalez@collabora.com> Thu, 21 Sep 2023 15:50:00 -0500 + dwarf2sources (0.2.3) apertis; urgency=medium * Add support for reading DWARF-5 info diff --git a/src/main.rs b/src/main.rs index 52cbd098adea484ac7cdea3a776cf19830538a04..bc2bc979adbe33580c6103fce9ef488fe2df6aa6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,32 @@ // Based on the gimli 0.16.1 dwarfdump.rs example -use anyhow::{anyhow, Error, Result}; +use anyhow::{anyhow, bail, Error, Result}; use fallible_iterator::{convert, FallibleIterator}; use gimli::{AttributeValue, Endianity, Reader}; use object::{Object, ObjectSection}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; use std::borrow::{Borrow, Cow}; +use std::collections::BTreeSet; use std::fs; use std::path::PathBuf; use structopt::StructOpt; use typed_arena::Arena; -fn list_file<E: Endianity>(file: &object::File, endian: E) -> Result<Vec<Unit>> { +#[derive(Debug, Serialize)] +struct Unit { + comp_dir: String, + comp_name: String, +} + +#[derive(Debug, Default, Serialize)] +struct ObjectInfo { + units: Vec<Unit>, + // Note that we use a BTreeSet so that the output order is consistent across + // runs. + external_files: BTreeSet<String>, +} + +fn list_file<E: Endianity>(file: &object::File, endian: E) -> Result<ObjectInfo> { let arena = Arena::new(); fn load_section<'a, 'file, 'input, S, E>( @@ -39,33 +54,61 @@ fn list_file<E: Endianity>(file: &object::File, endian: E) -> Result<Vec<Unit>> let debug_info = &load_section(&arena, file, endian); let debug_str = &load_section(&arena, file, endian); let debug_line_str = &load_section(&arena, file, endian); + let debug_line = &load_section(&arena, file, endian); - list_info(debug_info, debug_abbrev, debug_str, debug_line_str) + list_info( + debug_info, + debug_abbrev, + debug_str, + debug_line_str, + debug_line, + ) } fn list_info<R: Reader>( debug_info: &gimli::DebugInfo<R>, debug_abbrev: &gimli::DebugAbbrev<R>, debug_str: &gimli::DebugStr<R>, + debug_line: &gimli::DebugLine<R>, debug_line_str: &gimli::DebugLineStr<R>, -) -> Result<Vec<Unit>> { - let mut v = Vec::new(); - let units = debug_info.units().collect::<Vec<_>>().unwrap(); - for u in units { +) -> Result<ObjectInfo> { + let mut info = ObjectInfo::default(); + let dw_units = debug_info.units().collect::<Vec<_>>().unwrap(); + for u in dw_units { let abbrevs = u.abbreviations(debug_abbrev)?; - v.append(&mut list_entries( + info.units.append(&mut list_entries( + &mut info.external_files, u.entries(&abbrevs), + u.address_size(), debug_str, debug_line_str, + debug_line, )?); } - Ok(v) + Ok(info) } -#[derive(Debug, Serialize)] -struct Unit { - comp_dir: String, - comp_name: String, +fn get_attr_string<R: Reader>( + dw_attr_value: &gimli::AttributeValue<R>, + debug_str: &gimli::DebugStr<R>, + debug_line_str: &gimli::DebugLineStr<R>, +) -> Result<(R, String)> { + let reader = if let Some(r) = dw_attr_value.string_value(debug_str) { + r + } else { + match dw_attr_value { + AttributeValue::DebugLineStrRef(offset) => debug_line_str + .get_str(*offset) + .expect("attribute value should be set"), + AttributeValue::DebugStrRefSup(_) => { + bail!("attribute in supplemental file"); + } + _ => bail!("attribute with unexpected type"), + } + }; + + let string = reader.to_string()?.into_owned(); + Ok((reader, string)) } fn entry_attr_value<R: Reader>( @@ -73,36 +116,72 @@ fn entry_attr_value<R: Reader>( attr_name: gimli::constants::DwAt, debug_str: &gimli::DebugStr<R>, debug_line_str: &gimli::DebugLineStr<R>, -) -> Result<Option<String>> { +) -> Result<Option<(R, String)>> { let dw_attr = if let Some(it) = entry.attr(attr_name)? { it } else { return Ok(None); }; - if let Some(r) = dw_attr.string_value(debug_str) { - Ok(Some(r.to_string()?.into_owned())) + get_attr_string(&dw_attr.value(), debug_str, debug_line_str).map(Some) +} + +fn collect_external_files<R: Reader>( + external_files: &mut BTreeSet<String>, + entry: &gimli::DebuggingInformationEntry<R>, + address_size: u8, + debug_str: &gimli::DebugStr<R>, + debug_line_str: &gimli::DebugLineStr<R>, + debug_line: &gimli::DebugLine<R>, + comp_dir_reader: R, + comp_name_reader: R, +) -> Result<()> { + let program = if let Some(gimli::AttributeValue::DebugLineRef(offs)) = + entry.attr_value(gimli::DW_AT_stmt_list)? + { + debug_line.program( + offs, + address_size, + Some(comp_dir_reader), + Some(comp_name_reader), + )? } else { - match dw_attr.raw_value() { - AttributeValue::DebugLineStrRef(offset) => Ok(Some( - debug_line_str - .get_str(offset) - .expect("attribute value should be set") - .to_string()? - .into_owned(), - )), - AttributeValue::DebugStrRefSup(_) => { - Err(anyhow!("attribute {} in supplemental file", attr_name)) - } - _ => Err(anyhow!("attribute {} with unexpected type", attr_name)), + return Ok(()); + }; + + let header = program.header(); + for file_entry in header.file_names() { + if file_entry.directory_index() == 0 { + // Directory index of 0 means it's from the current CU's directory, + // so skip it. + continue; + } + + let mut name = + PathBuf::from(get_attr_string(&file_entry.path_name(), debug_str, debug_line_str)?.1); + + if let Some(dir_attr) = file_entry.directory(header) { + let (_, dir) = get_attr_string(&dir_attr, debug_str, debug_line_str)?; + name = PathBuf::from(dir).join(name); + } + + if name.is_absolute() { + // This shouldn't be invalid UTF-8, because it just came from some + // String instances already. + external_files.insert(name.into_os_string().into_string().unwrap()); } } + + Ok(()) } fn list_entries<R: Reader>( + external_files: &mut BTreeSet<String>, mut entries: gimli::EntriesCursor<R>, + address_size: u8, debug_str: &gimli::DebugStr<R>, debug_line_str: &gimli::DebugLineStr<R>, + debug_line: &gimli::DebugLine<R>, ) -> Result<Vec<Unit>> { let mut depth = 0; let mut v = Vec::new(); @@ -114,13 +193,13 @@ fn list_entries<R: Reader>( } if entry.tag() == gimli::DW_TAG_compile_unit || entry.tag() == gimli::DW_TAG_type_unit { - let comp_dir = + let (comp_dir_reader, comp_dir) = entry_attr_value(entry, gimli::DW_AT_comp_dir, debug_str, debug_line_str)? .ok_or_else(|| anyhow!("Unable to parse or missing DW_AT_comp_dir"))?; let at_name = entry_attr_value(entry, gimli::DW_AT_name, debug_str, debug_line_str); - let comp_name = match at_name { - Ok(Some(comp_name)) => comp_name.to_string(), + let (comp_name_reader, comp_name) = match at_name { + Ok(Some(result)) => result, Ok(None) => { eprintln!("Warning: unit without name, skipping it"); continue; @@ -139,16 +218,24 @@ fn list_entries<R: Reader>( comp_name, }); } + + if let Err(err) = collect_external_files( + external_files, + entry, + address_size, + debug_str, + debug_line_str, + debug_line, + comp_dir_reader, + comp_name_reader, + ) { + eprintln!("Warning: collecting external files: {}", err); + } } } Ok(v) } -#[derive(Debug, Serialize)] -struct Data { - units: Vec<Unit>, -} - #[derive(StructOpt, Debug)] struct Opt { #[structopt(short, long, parse(from_os_str))] @@ -164,28 +251,17 @@ fn process_file<E: Endianity>( file: &object::File, endian: E, strip_prefix: Option<&String>, -) -> Result<Vec<Unit>> { - let units = list_file(file, endian)?; - let units = if let Some(strip) = strip_prefix { - units - .iter() - .map(|u| { - let comp_dir = if u.comp_dir.starts_with(strip) { - (&u.comp_dir[strip.len()..]).to_string() - } else { - u.comp_dir.clone() - }; - Unit { - comp_dir, - comp_name: u.comp_name.clone(), - } - }) - .collect() - } else { - units - }; +) -> Result<ObjectInfo> { + let mut info = list_file(file, endian)?; + if let Some(strip) = strip_prefix { + for u in info.units.iter_mut() { + if u.comp_dir.starts_with(strip) { + u.comp_dir = (&u.comp_dir[strip.len()..]).to_string(); + } + } + } - Ok(units) + Ok(info) } fn serialize<S, K, V, I>(s: S, mut iter: I, len: Option<usize>) -> Result<()> @@ -218,8 +294,8 @@ fn main() -> Result<()> { gimli::RunTimeEndian::Big }; - let units = process_file(&file, endian, opt.strip_prefix.as_ref())?; - Ok((f.clone(), Data { units })) + let info = process_file(&file, endian, opt.strip_prefix.as_ref())?; + Ok((f.clone(), info)) })); if let Some(output) = &opt.output {