Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ranges with the GNU DWO extension and other improvements #568

Merged
merged 3 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 135 additions & 28 deletions examples/dwarfdump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::iter::Iterator;
use std::mem;
use std::process;
use std::result;
use std::slice;
use std::sync::{Condvar, Mutex};
use typed_arena::Arena;

Expand Down Expand Up @@ -358,20 +359,11 @@ struct Flags {
pubtypes: bool,
aranges: bool,
dwo: bool,
dwo_parent: Option<object::File<'static>>,
raw: bool,
match_units: Option<Regex>,
}

impl Flags {
fn section_name(&self, id: gimli::SectionId) -> Option<&'static str> {
if self.dwo {
id.dwo_name()
} else {
Some(id.name())
}
}
}

fn print_usage(opts: &getopts::Options) -> ! {
let brief = format!("Usage: {} <options> <file>", env::args().next().unwrap());
write!(&mut io::stderr(), "{}", opts.usage(&brief)).ok();
Expand All @@ -396,6 +388,12 @@ fn main() {
"dwo",
"print the .dwo versions of the selected sections",
);
opts.optopt(
"",
"dwo-parent",
"use the specified file as the parent of the dwo (e.g. for .debug_addr)",
"library path",
);
opts.optflag("", "raw", "print raw data values");
opts.optopt(
"u",
Expand Down Expand Up @@ -472,32 +470,43 @@ fn main() {
None
};

let sup_mmap;
let sup_file = if let Some(sup_path) = matches.opt_str("sup") {
let file = match fs::File::open(&sup_path) {
let load_file = |path| {
let file = match fs::File::open(&path) {
Ok(file) => file,
Err(err) => {
eprintln!("Failed to open file '{}': {}", sup_path, err);
eprintln!("Failed to open file '{}': {}", path, err);
process::exit(1);
}
};
sup_mmap = match unsafe { memmap::Mmap::map(&file) } {
let mmap = match unsafe { memmap::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
eprintln!("Failed to map file '{}': {}", sup_path, err);
eprintln!("Failed to map file '{}': {}", path, err);
process::exit(1);
}
};
match object::File::parse(&*sup_mmap) {
let mmap_ptr = mmap.as_ptr();
let mmap_len = mmap.len();
mem::forget(mmap);
match object::File::parse(unsafe { slice::from_raw_parts(mmap_ptr, mmap_len) }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but an arena would be nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk what this means in practice.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok(file) => Some(file),
Err(err) => {
eprintln!("Failed to parse file '{}': {}", sup_path, err);
eprintln!("Failed to parse file '{}': {}", path, err);
process::exit(1);
}
}
};

let sup_file = if let Some(sup_path) = matches.opt_str("sup") {
load_file(sup_path)
} else {
None
};
flags.dwo_parent = matches.opt_str("dwo-parent").and_then(load_file);
if flags.dwo_parent.is_some() && !flags.dwo {
eprintln!("--dwo-parent also requires --dwo");
process::exit(1);
}

for file_path in &matches.free {
if matches.free.len() != 1 {
Expand Down Expand Up @@ -544,16 +553,21 @@ fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
id: gimli::SectionId,
file: &object::File<'input>,
endian: Endian,
flags: &Flags,
is_dwo: bool,
arena_data: &'arena Arena<Cow<'input, [u8]>>,
arena_relocations: &'arena Arena<RelocationMap>,
) -> Result<Relocate<'arena, gimli::EndianSlice<'arena, Endian>>> {
let mut relocations = RelocationMap::default();
let name = flags.section_name(id);
let name = if is_dwo {
id.dwo_name()
} else {
Some(id.name())
};

let data = match name.and_then(|name| file.section_by_name(&name)) {
Some(ref section) => {
// DWO sections never have relocations, so don't bother.
if !flags.dwo {
if !is_dwo {
add_relocations(&mut relocations, file, section);
}
section.uncompressed_data()?
Expand Down Expand Up @@ -585,18 +599,39 @@ where
let arena_relocations = Arena::new();

let mut load_section = |id: gimli::SectionId| -> Result<_> {
load_file_section(id, file, endian, flags, &arena_data, &arena_relocations)
load_file_section(id, file, endian, flags.dwo, &arena_data, &arena_relocations)
};
let mut dwarf = gimli::Dwarf::load(&mut load_section)?;
let mut load_dwo_parent_section = None;
if flags.dwo {
dwarf.file_type = gimli::DwarfFileType::Dwo;

if let Some(dwo_parent_file) = flags.dwo_parent.as_ref() {
let arena_data = &arena_data;
let arena_relocations = &arena_relocations;
load_dwo_parent_section = Some(move |id: gimli::SectionId| -> Result<_> {
load_file_section(
id,
dwo_parent_file,
endian,
false,
arena_data,
arena_relocations,
)
});
dwarf.debug_addr = gimli::Section::load(load_dwo_parent_section.as_mut().unwrap())?;
dwarf.ranges = gimli::RangeLists::new(
gimli::Section::load(load_dwo_parent_section.as_mut().unwrap())?,
gimli::Section::load(load_dwo_parent_section.as_mut().unwrap())?,
);
}
}

if let Some(sup_file) = sup_file {
let mut load_sup_section = |id: gimli::SectionId| -> Result<_> {
// Note: we really only need the `.debug_str` section,
// but for now we load them all.
load_file_section(id, sup_file, endian, flags, &arena_data, &arena_relocations)
load_file_section(id, sup_file, endian, false, &arena_data, &arena_relocations)
};
dwarf.load_sup(&mut load_sup_section)?;
}
Expand Down Expand Up @@ -647,7 +682,40 @@ where
)?;
}
if flags.info {
dump_info(&dwarf, flags)?;
let dwo_parent_headers = if let Some(loader) = load_dwo_parent_section.as_mut() {
let dwarf = gimli::Dwarf::load(loader)?;
match dwarf
.units()
.map(|unit_header| dwarf.unit(unit_header))
.filter_map(|unit| {
let dwo_id = match unit.header.type_() {
gimli::UnitType::Skeleton(dwo_id) => dwo_id,
gimli::UnitType::Compilation => {
let mut entries = unit.entries();
let (_, this) = entries.next_dfs()?.unwrap();
match this.attr_value(gimli::constants::DW_AT_GNU_dwo_id)? {
Some(gimli::AttributeValue::DwoId(dwo_id)) => dwo_id,
Some(_) => unreachable!(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes this unreachable? (and same for the other use below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dwoid! only ever returns AttributeValue::DwoId

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but then it falls through to self.value.clone() for other forms.

None => return Ok(None),
}
}
_ => return Ok(None),
};

Ok(Some((dwo_id, unit)))
})
.collect()
{
Ok(units) => units,
Err(err) => {
eprintln!("Failed to process --dwo-parent units: {}", err);
return Ok(());
}
}
} else {
HashMap::new()
};
dump_info(&dwarf, dwo_parent_headers, flags)?;
dump_types(&mut BufWriter::new(out.lock()), &dwarf, flags)?;
writeln!(&mut out.lock())?;
}
Expand Down Expand Up @@ -950,7 +1018,11 @@ fn dump_cfi_instructions<R: Reader, W: Write>(
}
}

fn dump_info<R: Reader>(dwarf: &gimli::Dwarf<R>, flags: &Flags) -> Result<()>
fn dump_info<R: Reader>(
dwarf: &gimli::Dwarf<R>,
dwo_parent_headers: HashMap<gimli::DwoId, gimli::Unit<R>>,
flags: &Flags,
) -> Result<()>
where
R::Endian: Send + Sync,
{
Expand Down Expand Up @@ -997,14 +1069,49 @@ where
}
}

let unit = match dwarf.unit(header) {
let mut unit = match dwarf.unit(header) {
Ok(unit) => unit,
Err(err) => {
writeln_error(buf, dwarf, err.into(), "Failed to parse unit root entry")?;
return Ok(());
}
};

if flags.dwo {
if let Some(dwo_id) = match unit.header.type_() {
UnitType::SplitCompilation(dwo_id) => Some(dwo_id),
UnitType::Compilation => {
let mut entries = unit.entries();
let this = match entries.next_dfs() {
Ok(v) => v.unwrap().1,
Err(err) => {
writeln_error(buf, dwarf, err.into(), "Failed to load CU root unit")?;
return Ok(());
}
};
match this.attr_value(gimli::constants::DW_AT_GNU_dwo_id) {
Ok(None) => None,
Ok(Some(gimli::AttributeValue::DwoId(v))) => Some(v),
Ok(Some(_)) => unreachable!(),
Err(err) => {
writeln_error(
buf,
dwarf,
err.into(),
"Failed to parse DW_AT_GNU_dwo_id",
)?;
return Ok(());
}
}
}
_ => None,
} {
if let Some(parent_unit) = dwo_parent_headers.get(&dwo_id) {
unit.copy_relocated_attributes(parent_unit);
}
}
}

let entries_result = dump_entries(buf, unit, dwarf, flags);
if let Err(err) = entries_result {
writeln_error(buf, dwarf, err, "Failed to dump entries")?;
Expand Down Expand Up @@ -1650,7 +1757,7 @@ fn dump_loc_list<R: Reader, W: Write>(
unit: &gimli::Unit<R>,
dwarf: &gimli::Dwarf<R>,
) -> Result<()> {
let raw_locations = dwarf.locations.raw_locations(offset, unit.encoding())?;
let raw_locations = dwarf.raw_locations(unit, offset)?;
let raw_locations: Vec<_> = raw_locations.collect()?;
let mut locations = dwarf.locations(unit, offset)?;
writeln!(
Expand Down Expand Up @@ -1778,7 +1885,7 @@ fn dump_range_list<R: Reader, W: Write>(
unit: &gimli::Unit<R>,
dwarf: &gimli::Dwarf<R>,
) -> Result<()> {
let raw_ranges = dwarf.ranges.raw_ranges(offset, unit.encoding())?;
let raw_ranges = dwarf.raw_ranges(unit, offset)?;
let raw_ranges: Vec<_> = raw_ranges.collect()?;
let mut ranges = dwarf.ranges(unit, offset)?;
writeln!(
Expand Down
37 changes: 35 additions & 2 deletions src/read/dwarf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use crate::read::{
Abbreviations, AttributeValue, DebugAbbrev, DebugAddr, DebugAranges, DebugInfo,
DebugInfoUnitHeadersIter, DebugLine, DebugLineStr, DebugStr, DebugStrOffsets, DebugTypes,
DebugTypesUnitHeadersIter, DebuggingInformationEntry, EntriesCursor, EntriesRaw, EntriesTree,
Error, IncompleteLineProgram, LocListIter, LocationLists, Range, RangeLists, Reader,
ReaderOffset, ReaderOffsetId, Result, RngListIter, Section, UnitHeader, UnitOffset,
Error, IncompleteLineProgram, LocListIter, LocationLists, Range, RangeLists, RawLocListIter,
RawRngListIter, Reader, ReaderOffset, ReaderOffsetId, Result, RngListIter, Section, UnitHeader,
UnitOffset,
};

/// All of the commonly used DWARF sections, and other common information.
Expand Down Expand Up @@ -296,6 +297,12 @@ impl<R: Reader> Dwarf<R> {
unit: &Unit<R>,
offset: RangeListsOffset<R::Offset>,
) -> Result<RngListIter<R>> {
let offset = if self.file_type == DwarfFileType::Dwo && unit.header.version() < 5 {
RangeListsOffset(offset.0.wrapping_add(unit.rnglists_base.0))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that RangeListsOffset has different meanings at different places in the code. While your changes look correct, it is hard to be sure, and ditto for users of this library. e.g. it looks like the AttributeValue::RangeListsRef support in write/unit.rs needs fixing too. I prefer if we introduce something like RawRangeListsOffset that is used in AttributeValue, and then convert that into a RangeListsOffset. Or do the conversion when parsing attributes but we may not have the required info there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the downside of a new type is that then everybody has to be aware of and handle this stuff that's only needed for a non-standard and obsolete extension.

I've never looked at the writing side of gimli but if it's at all possible I would just not support writing these non-standard GNU extensions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid concern, but if it's important enough for you to handle it, then maybe others want to as well? It is uglier to use, but see what you think of philipc@473e138

On the writing side, the only support required is for reading it when converting. It won't write it out in the same form.

} else {
offset
};

self.ranges.ranges(
offset,
unit.encoding(),
Expand All @@ -305,6 +312,20 @@ impl<R: Reader> Dwarf<R> {
)
}

/// Iterate over the `RawRngListEntry`ies starting at the given offset.
pub fn raw_ranges(
&self,
unit: &Unit<R>,
offset: RangeListsOffset<R::Offset>,
) -> Result<RawRngListIter<R>> {
let offset = if self.file_type == DwarfFileType::Dwo && unit.header.version() < 5 {
RangeListsOffset(offset.0.wrapping_add(unit.rnglists_base.0))
} else {
offset
};
self.ranges.raw_ranges(offset, unit.encoding())
}

/// Try to return an attribute value as a range list offset.
///
/// If the attribute value is one of:
Expand Down Expand Up @@ -436,6 +457,18 @@ impl<R: Reader> Dwarf<R> {
}
}

/// Iterate over the raw `LocationListEntry`s starting at the given offset.
pub fn raw_locations(
&self,
unit: &Unit<R>,
offset: LocationListsOffset<R::Offset>,
) -> Result<RawLocListIter<R>> {
match self.file_type {
DwarfFileType::Main => self.locations.raw_locations(offset, unit.encoding()),
DwarfFileType::Dwo => self.locations.raw_locations_dwo(offset, unit.encoding()),
}
}

/// Try to return an attribute value as a location list offset.
///
/// If the attribute value is one of:
Expand Down