// Allow clippy lints when building without clippy. #![allow(unknown_lints)] use gimli::{AttributeValue, UnitHeader}; use object::{Object, ObjectSection}; use rayon::prelude::*; use std::borrow::{Borrow, Cow}; use std::env; use std::fs; use std::io::{self, BufWriter, Write}; use std::iter::Iterator; use std::path::{Path, PathBuf}; use std::process; use std::sync::Mutex; use typed_arena::Arena; trait Reader: gimli::Reader + Send + Sync { type SyncSendEndian: gimli::Endianity + Send + Sync; } impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where Endian: gimli::Endianity + Send + Sync, { type SyncSendEndian = Endian; } struct ErrorWriter { inner: Mutex<(W, usize)>, path: PathBuf, } impl ErrorWriter { #[allow(clippy::needless_pass_by_value)] fn error(&self, s: String) { let mut lock = self.inner.lock().unwrap(); writeln!(&mut lock.0, "DWARF error in {}: {}", self.path.display(), s).unwrap(); lock.1 += 1; } } fn main() { let mut w = BufWriter::new(io::stdout()); let mut errors = 0; for arg in env::args_os().skip(1) { let path = Path::new(&arg); let file = match fs::File::open(&path) { Ok(file) => file, Err(err) => { eprintln!("Failed to open file '{}': {}", path.display(), err); errors += 1; continue; } }; let file = match unsafe { memmap2::Mmap::map(&file) } { Ok(mmap) => mmap, Err(err) => { eprintln!("Failed to map file '{}': {}", path.display(), &err); errors += 1; continue; } }; let file = match object::File::parse(&*file) { Ok(file) => file, Err(err) => { eprintln!("Failed to parse file '{}': {}", path.display(), err); errors += 1; continue; } }; let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; let mut error_writer = ErrorWriter { inner: Mutex::new((&mut w, 0)), path: path.to_owned(), }; validate_file(&mut error_writer, &file, endian); errors += error_writer.inner.into_inner().unwrap().1; } // Flush any errors. drop(w); if errors > 0 { process::exit(1); } } fn validate_file(w: &mut ErrorWriter, file: &object::File, endian: Endian) where W: Write + Send, Endian: gimli::Endianity + Send + Sync, { let arena = Arena::new(); fn load_section<'a, 'file, 'input, S, Endian>( arena: &'a Arena>, file: &'file object::File<'input>, endian: Endian, ) -> S where S: gimli::Section>, Endian: gimli::Endianity + Send + Sync, 'file: 'input, 'a: 'file, { let data = match file.section_by_name(S::section_name()) { Some(ref section) => section .uncompressed_data() .unwrap_or(Cow::Borrowed(&[][..])), None => Cow::Borrowed(&[][..]), }; let data_ref = (*arena.alloc(data)).borrow(); S::from(gimli::EndianSlice::new(data_ref, endian)) } // Variables representing sections of the file. The type of each is inferred from its use in the // validate_info function below. let debug_abbrev = &load_section(&arena, file, endian); let debug_info = &load_section(&arena, file, endian); validate_info(w, debug_info, debug_abbrev); } struct UnitSummary { // True if we successfully parsed all the DIEs and attributes in the compilation unit internally_valid: bool, offset: gimli::DebugInfoOffset, die_offsets: Vec, global_die_references: Vec<(gimli::UnitOffset, gimli::DebugInfoOffset)>, } fn validate_info( w: &mut ErrorWriter, debug_info: &gimli::DebugInfo, debug_abbrev: &gimli::DebugAbbrev, ) where W: Write + Send, R: Reader, { let mut units = Vec::new(); let mut units_iter = debug_info.units(); let mut last_offset = 0; loop { let u = match units_iter.next() { Err(err) => { w.error(format!( "Can't read unit header at offset {:#x}, stopping reading units: {}", last_offset, err )); break; } Ok(None) => break, Ok(Some(u)) => u, }; last_offset = u.offset().as_debug_info_offset().unwrap().0 + u.length_including_self(); units.push(u); } let process_unit = |unit: UnitHeader| -> UnitSummary { let unit_offset = unit.offset().as_debug_info_offset().unwrap(); let mut ret = UnitSummary { internally_valid: false, offset: unit_offset, die_offsets: Vec::new(), global_die_references: Vec::new(), }; let abbrevs = match unit.abbreviations(debug_abbrev) { Ok(abbrevs) => abbrevs, Err(err) => { w.error(format!( "Invalid abbrevs for unit {:#x}: {}", unit_offset.0, &err )); return ret; } }; let mut entries = unit.entries(&abbrevs); let mut unit_refs = Vec::new(); loop { let (_, entry) = match entries.next_dfs() { Err(err) => { w.error(format!( "Invalid DIE for unit {:#x}: {}", unit_offset.0, &err )); return ret; } Ok(None) => break, Ok(Some(entry)) => entry, }; ret.die_offsets.push(entry.offset()); let mut attrs = entry.attrs(); loop { let attr = match attrs.next() { Err(err) => { w.error(format!( "Invalid attribute for unit {:#x} at DIE {:#x}: {}", unit_offset.0, entry.offset().0, &err )); return ret; } Ok(None) => break, Ok(Some(attr)) => attr, }; match attr.value() { AttributeValue::UnitRef(offset) => { unit_refs.push((entry.offset(), offset)); } AttributeValue::DebugInfoRef(offset) => { ret.global_die_references.push((entry.offset(), offset)); } _ => (), } } } ret.internally_valid = true; ret.die_offsets.shrink_to_fit(); ret.global_die_references.shrink_to_fit(); // Check intra-unit references for (from, to) in unit_refs { if ret.die_offsets.binary_search(&to).is_err() { w.error(format!( "Invalid intra-unit reference in unit {:#x} from DIE {:#x} to {:#x}", unit_offset.0, from.0, to.0 )); } } ret }; let processed_units = units.into_par_iter().map(process_unit).collect::>(); let check_unit = |summary: &UnitSummary| { if !summary.internally_valid { return; } for &(from, to) in summary.global_die_references.iter() { let u = match processed_units.binary_search_by_key(&to, |v| v.offset) { Ok(i) => &processed_units[i], Err(i) => { if i > 0 { &processed_units[i - 1] } else { w.error(format!("Invalid cross-unit reference in unit {:#x} from DIE {:#x} to global DIE {:#x}: no unit found", summary.offset.0, from.0, to.0)); continue; } } }; if !u.internally_valid { continue; } let to_offset = gimli::UnitOffset(to.0 - u.offset.0); if u.die_offsets.binary_search(&to_offset).is_err() { w.error(format!("Invalid cross-unit reference in unit {:#x} from DIE {:#x} to global DIE {:#x}: unit at {:#x} contains no DIE {:#x}", summary.offset.0, from.0, to.0, u.offset.0, to_offset.0)); } } }; processed_units.par_iter().for_each(check_unit); }