#![feature(test)] extern crate test; use gimli::{ AttributeValue, DebugAbbrev, DebugAddr, DebugAddrBase, DebugAranges, DebugInfo, DebugLine, DebugLineOffset, DebugLoc, DebugLocLists, DebugPubNames, DebugPubTypes, DebugRanges, DebugRngLists, Encoding, EndianSlice, EntriesTreeNode, Expression, LittleEndian, LocationLists, Operation, RangeLists, RangeListsOffset, Reader, ReaderOffset, }; use std::env; use std::fs::File; use std::io::Read; use std::path::PathBuf; use std::rc::Rc; pub fn read_section(section: &str) -> Vec { let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into())); path.push("./fixtures/self/"); path.push(section); assert!(path.is_file()); let mut file = File::open(path).unwrap(); let mut buf = Vec::new(); file.read_to_end(&mut buf).unwrap(); buf } #[bench] fn bench_parsing_debug_abbrev(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let unit = debug_info .units() .next() .expect("Should have at least one compilation unit") .expect("And it should parse OK"); let debug_abbrev = read_section("debug_abbrev"); b.iter(|| { let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); test::black_box( unit.abbreviations(&debug_abbrev) .expect("Should parse abbreviations"), ); }); } #[inline] fn impl_bench_parsing_debug_info( debug_info: DebugInfo, debug_abbrev: DebugAbbrev, ) { let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(&debug_abbrev) .expect("Should parse abbreviations"); let mut cursor = unit.entries(&abbrevs); while let Some((_, entry)) = cursor.next_dfs().expect("Should parse next dfs") { let mut attrs = entry.attrs(); loop { match attrs.next() { Ok(Some(ref attr)) => { test::black_box(attr); } Ok(None) => break, e @ Err(_) => { e.expect("Should parse entry's attribute"); } } } } } } #[bench] fn bench_parsing_debug_info(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); b.iter(|| impl_bench_parsing_debug_info(debug_info, debug_abbrev)); } #[bench] fn bench_parsing_debug_info_with_endian_rc_slice(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = Rc::from(&debug_info[..]); let debug_info = gimli::EndianRcSlice::new(debug_info, LittleEndian); let debug_info = DebugInfo::from(debug_info); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = Rc::from(&debug_abbrev[..]); let debug_abbrev = gimli::EndianRcSlice::new(debug_abbrev, LittleEndian); let debug_abbrev = DebugAbbrev::from(debug_abbrev); b.iter(|| impl_bench_parsing_debug_info(debug_info.clone(), debug_abbrev.clone())); } #[bench] fn bench_parsing_debug_info_tree(b: &mut test::Bencher) { let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_info = read_section("debug_info"); b.iter(|| { let debug_info = DebugInfo::new(&debug_info, LittleEndian); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(&debug_abbrev) .expect("Should parse abbreviations"); let mut tree = unit .entries_tree(&abbrevs, None) .expect("Should have entries tree"); let root = tree.root().expect("Should parse root entry"); parse_debug_info_tree(root); } }); } fn parse_debug_info_tree(node: EntriesTreeNode) { { let mut attrs = node.entry().attrs(); loop { match attrs.next() { Ok(Some(ref attr)) => { test::black_box(attr); } Ok(None) => break, e @ Err(_) => { e.expect("Should parse entry's attribute"); } } } } let mut children = node.children(); while let Some(child) = children.next().expect("Should parse child entry") { parse_debug_info_tree(child); } } #[bench] fn bench_parsing_debug_info_raw(b: &mut test::Bencher) { let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_info = read_section("debug_info"); b.iter(|| { let debug_info = DebugInfo::new(&debug_info, LittleEndian); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(&debug_abbrev) .expect("Should parse abbreviations"); let mut raw = unit .entries_raw(&abbrevs, None) .expect("Should have entries"); while !raw.is_empty() { if let Some(abbrev) = raw .read_abbreviation() .expect("Should parse abbreviation code") { for spec in abbrev.attributes().iter().cloned() { match raw.read_attribute(spec) { Ok(ref attr) => { test::black_box(attr); } e @ Err(_) => { e.expect("Should parse attribute"); } } } } } } }); } #[bench] fn bench_parsing_debug_aranges(b: &mut test::Bencher) { let debug_aranges = read_section("debug_aranges"); let debug_aranges = DebugAranges::new(&debug_aranges, LittleEndian); b.iter(|| { let mut headers = debug_aranges.headers(); while let Some(header) = headers.next().expect("Should parse arange header OK") { let mut entries = header.entries(); while let Some(arange) = entries.next().expect("Should parse arange entry OK") { test::black_box(arange); } } }); } #[bench] fn bench_parsing_debug_pubnames(b: &mut test::Bencher) { let debug_pubnames = read_section("debug_pubnames"); let debug_pubnames = DebugPubNames::new(&debug_pubnames, LittleEndian); b.iter(|| { let mut pubnames = debug_pubnames.items(); while let Some(pubname) = pubnames.next().expect("Should parse pubname OK") { test::black_box(pubname); } }); } #[bench] fn bench_parsing_debug_pubtypes(b: &mut test::Bencher) { let debug_pubtypes = read_section("debug_pubtypes"); let debug_pubtypes = DebugPubTypes::new(&debug_pubtypes, LittleEndian); b.iter(|| { let mut pubtypes = debug_pubtypes.items(); while let Some(pubtype) = pubtypes.next().expect("Should parse pubtype OK") { test::black_box(pubtype); } }); } // We happen to know that there is a line number program and header at // offset 0 and that address size is 8 bytes. No need to parse DIEs to grab // this info off of the compilation units. const OFFSET: DebugLineOffset = DebugLineOffset(0); const ADDRESS_SIZE: u8 = 8; #[bench] fn bench_parsing_line_number_program_opcodes(b: &mut test::Bencher) { let debug_line = read_section("debug_line"); let debug_line = DebugLine::new(&debug_line, LittleEndian); b.iter(|| { let program = debug_line .program(OFFSET, ADDRESS_SIZE, None, None) .expect("Should parse line number program header"); let header = program.header(); let mut instructions = header.instructions(); while let Some(instruction) = instructions .next_instruction(header) .expect("Should parse instruction") { test::black_box(instruction); } }); } #[bench] fn bench_executing_line_number_programs(b: &mut test::Bencher) { let debug_line = read_section("debug_line"); let debug_line = DebugLine::new(&debug_line, LittleEndian); b.iter(|| { let program = debug_line .program(OFFSET, ADDRESS_SIZE, None, None) .expect("Should parse line number program header"); let mut rows = program.rows(); while let Some(row) = rows .next_row() .expect("Should parse and execute all rows in the line number program") { test::black_box(row); } }); } #[bench] fn bench_parsing_debug_loc(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_addr = DebugAddr::from(EndianSlice::new(&[], LittleEndian)); let debug_addr_base = DebugAddrBase(0); let debug_loc = read_section("debug_loc"); let debug_loc = DebugLoc::new(&debug_loc, LittleEndian); let debug_loclists = DebugLocLists::new(&[], LittleEndian); let loclists = LocationLists::new(debug_loc, debug_loclists); let mut offsets = Vec::new(); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(&debug_abbrev) .expect("Should parse abbreviations"); let mut cursor = unit.entries(&abbrevs); cursor.next_dfs().expect("Should parse next dfs"); let mut low_pc = 0; { let unit_entry = cursor.current().expect("Should have a root entry"); let low_pc_attr = unit_entry .attr_value(gimli::DW_AT_low_pc) .expect("Should parse low_pc"); if let Some(gimli::AttributeValue::Addr(address)) = low_pc_attr { low_pc = address; } } while cursor.next_dfs().expect("Should parse next dfs").is_some() { let entry = cursor.current().expect("Should have a current entry"); let mut attrs = entry.attrs(); while let Some(attr) = attrs.next().expect("Should parse entry's attribute") { if let gimli::AttributeValue::LocationListsRef(offset) = attr.value() { offsets.push((offset, unit.encoding(), low_pc)); } } } } b.iter(|| { for &(offset, encoding, base_address) in &*offsets { let mut locs = loclists .locations(offset, encoding, base_address, &debug_addr, debug_addr_base) .expect("Should parse locations OK"); while let Some(loc) = locs.next().expect("Should parse next location") { test::black_box(loc); } } }); } #[bench] fn bench_parsing_debug_ranges(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_addr = DebugAddr::from(EndianSlice::new(&[], LittleEndian)); let debug_addr_base = DebugAddrBase(0); let debug_ranges = read_section("debug_ranges"); let debug_ranges = DebugRanges::new(&debug_ranges, LittleEndian); let debug_rnglists = DebugRngLists::new(&[], LittleEndian); let rnglists = RangeLists::new(debug_ranges, debug_rnglists); let mut offsets = Vec::new(); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(&debug_abbrev) .expect("Should parse abbreviations"); let mut cursor = unit.entries(&abbrevs); cursor.next_dfs().expect("Should parse next dfs"); let mut low_pc = 0; { let unit_entry = cursor.current().expect("Should have a root entry"); let low_pc_attr = unit_entry .attr_value(gimli::DW_AT_low_pc) .expect("Should parse low_pc"); if let Some(gimli::AttributeValue::Addr(address)) = low_pc_attr { low_pc = address; } } while cursor.next_dfs().expect("Should parse next dfs").is_some() { let entry = cursor.current().expect("Should have a current entry"); let mut attrs = entry.attrs(); while let Some(attr) = attrs.next().expect("Should parse entry's attribute") { if let gimli::AttributeValue::RangeListsRef(offset) = attr.value() { offsets.push((RangeListsOffset(offset.0), unit.encoding(), low_pc)); } } } } b.iter(|| { for &(offset, encoding, base_address) in &*offsets { let mut ranges = rnglists .ranges(offset, encoding, base_address, &debug_addr, debug_addr_base) .expect("Should parse ranges OK"); while let Some(range) = ranges.next().expect("Should parse next range") { test::black_box(range); } } }); } fn debug_info_expressions( debug_info: &DebugInfo, debug_abbrev: &DebugAbbrev, ) -> Vec<(Expression, Encoding)> { let mut expressions = Vec::new(); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(debug_abbrev) .expect("Should parse abbreviations"); let mut cursor = unit.entries(&abbrevs); while let Some((_, entry)) = cursor.next_dfs().expect("Should parse next dfs") { let mut attrs = entry.attrs(); while let Some(attr) = attrs.next().expect("Should parse entry's attribute") { if let AttributeValue::Exprloc(expression) = attr.value() { expressions.push((expression, unit.encoding())); } } } } expressions } #[bench] fn bench_parsing_debug_info_expressions(b: &mut test::Bencher) { let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let expressions = debug_info_expressions(&debug_info, &debug_abbrev); b.iter(|| { for &(expression, encoding) in &*expressions { let mut pc = expression.0; while !pc.is_empty() { Operation::parse(&mut pc, encoding).expect("Should parse operation"); } } }); } #[bench] fn bench_evaluating_debug_info_expressions(b: &mut test::Bencher) { let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let expressions = debug_info_expressions(&debug_info, &debug_abbrev); b.iter(|| { for &(expression, encoding) in &*expressions { let mut eval = expression.evaluation(encoding); eval.set_initial_value(0); let result = eval.evaluate().expect("Should evaluate expression"); test::black_box(result); } }); } fn debug_loc_expressions( debug_info: &DebugInfo, debug_abbrev: &DebugAbbrev, debug_addr: &DebugAddr, loclists: &LocationLists, ) -> Vec<(Expression, Encoding)> { let debug_addr_base = DebugAddrBase(R::Offset::from_u8(0)); let mut expressions = Vec::new(); let mut iter = debug_info.units(); while let Some(unit) = iter.next().expect("Should parse compilation unit") { let abbrevs = unit .abbreviations(debug_abbrev) .expect("Should parse abbreviations"); let mut cursor = unit.entries(&abbrevs); cursor.next_dfs().expect("Should parse next dfs"); let mut low_pc = 0; { let unit_entry = cursor.current().expect("Should have a root entry"); let low_pc_attr = unit_entry .attr_value(gimli::DW_AT_low_pc) .expect("Should parse low_pc"); if let Some(gimli::AttributeValue::Addr(address)) = low_pc_attr { low_pc = address; } } while cursor.next_dfs().expect("Should parse next dfs").is_some() { let entry = cursor.current().expect("Should have a current entry"); let mut attrs = entry.attrs(); while let Some(attr) = attrs.next().expect("Should parse entry's attribute") { if let gimli::AttributeValue::LocationListsRef(offset) = attr.value() { let mut locs = loclists .locations(offset, unit.encoding(), low_pc, debug_addr, debug_addr_base) .expect("Should parse locations OK"); while let Some(loc) = locs.next().expect("Should parse next location") { expressions.push((loc.data, unit.encoding())); } } } } } expressions } #[bench] fn bench_parsing_debug_loc_expressions(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_addr = DebugAddr::from(EndianSlice::new(&[], LittleEndian)); let debug_loc = read_section("debug_loc"); let debug_loc = DebugLoc::new(&debug_loc, LittleEndian); let debug_loclists = DebugLocLists::new(&[], LittleEndian); let loclists = LocationLists::new(debug_loc, debug_loclists); let expressions = debug_loc_expressions(&debug_info, &debug_abbrev, &debug_addr, &loclists); b.iter(|| { for &(expression, encoding) in &*expressions { let mut pc = expression.0; while !pc.is_empty() { Operation::parse(&mut pc, encoding).expect("Should parse operation"); } } }); } #[bench] fn bench_evaluating_debug_loc_expressions(b: &mut test::Bencher) { let debug_info = read_section("debug_info"); let debug_info = DebugInfo::new(&debug_info, LittleEndian); let debug_abbrev = read_section("debug_abbrev"); let debug_abbrev = DebugAbbrev::new(&debug_abbrev, LittleEndian); let debug_addr = DebugAddr::from(EndianSlice::new(&[], LittleEndian)); let debug_loc = read_section("debug_loc"); let debug_loc = DebugLoc::new(&debug_loc, LittleEndian); let debug_loclists = DebugLocLists::new(&[], LittleEndian); let loclists = LocationLists::new(debug_loc, debug_loclists); let expressions = debug_loc_expressions(&debug_info, &debug_abbrev, &debug_addr, &loclists); b.iter(|| { for &(expression, encoding) in &*expressions { let mut eval = expression.evaluation(encoding); eval.set_initial_value(0); let result = eval.evaluate().expect("Should evaluate expression"); test::black_box(result); } }); } // See comment above `test_parse_self_eh_frame`. #[cfg(target_pointer_width = "64")] mod cfi { use super::*; use fallible_iterator::FallibleIterator; use gimli::{ BaseAddresses, CieOrFde, EhFrame, FrameDescriptionEntry, LittleEndian, UnwindContext, UnwindSection, }; #[bench] fn iterate_entries_and_do_not_parse_any_fde(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); b.iter(|| { let mut entries = eh_frame.entries(&bases); while let Some(entry) = entries.next().expect("Should parse CFI entry OK") { test::black_box(entry); } }); } #[bench] fn iterate_entries_and_parse_every_fde(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); b.iter(|| { let mut entries = eh_frame.entries(&bases); while let Some(entry) = entries.next().expect("Should parse CFI entry OK") { match entry { CieOrFde::Cie(cie) => { test::black_box(cie); } CieOrFde::Fde(partial) => { let fde = partial .parse(EhFrame::cie_from_offset) .expect("Should be able to get CIE for FED"); test::black_box(fde); } }; } }); } #[bench] fn iterate_entries_and_parse_every_fde_and_instructions(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); b.iter(|| { let mut entries = eh_frame.entries(&bases); while let Some(entry) = entries.next().expect("Should parse CFI entry OK") { match entry { CieOrFde::Cie(cie) => { let mut instrs = cie.instructions(&eh_frame, &bases); while let Some(i) = instrs.next().expect("Can parse next CFI instruction OK") { test::black_box(i); } } CieOrFde::Fde(partial) => { let fde = partial .parse(EhFrame::cie_from_offset) .expect("Should be able to get CIE for FED"); let mut instrs = fde.instructions(&eh_frame, &bases); while let Some(i) = instrs.next().expect("Can parse next CFI instruction OK") { test::black_box(i); } } }; } }); } #[bench] fn iterate_entries_evaluate_every_fde(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); let mut ctx = Box::new(UnwindContext::new()); b.iter(|| { let mut entries = eh_frame.entries(&bases); while let Some(entry) = entries.next().expect("Should parse CFI entry OK") { match entry { CieOrFde::Cie(_) => {} CieOrFde::Fde(partial) => { let fde = partial .parse(EhFrame::cie_from_offset) .expect("Should be able to get CIE for FED"); let mut table = fde .rows(&eh_frame, &bases, &mut ctx) .expect("Should be able to initialize ctx"); while let Some(row) = table.next_row().expect("Should get next unwind table row") { test::black_box(row); } } }; } }); } fn instrs_len( eh_frame: &EhFrame, bases: &BaseAddresses, fde: &FrameDescriptionEntry, ) -> usize { fde.instructions(eh_frame, bases) .fold(0, |count, _| Ok(count + 1)) .expect("fold over instructions OK") } fn get_fde_with_longest_cfi_instructions( eh_frame: &EhFrame, bases: &BaseAddresses, ) -> FrameDescriptionEntry { let mut longest: Option<(usize, FrameDescriptionEntry<_>)> = None; let mut entries = eh_frame.entries(bases); while let Some(entry) = entries.next().expect("Should parse CFI entry OK") { match entry { CieOrFde::Cie(_) => {} CieOrFde::Fde(partial) => { let fde = partial .parse(EhFrame::cie_from_offset) .expect("Should be able to get CIE for FED"); let this_len = instrs_len(eh_frame, bases, &fde); let found_new_longest = match longest { None => true, Some((longest_len, ref _fde)) => this_len > longest_len, }; if found_new_longest { longest = Some((this_len, fde)); } } }; } longest.expect("At least one FDE in .eh_frame").1 } #[bench] fn parse_longest_fde_instructions(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); let fde = get_fde_with_longest_cfi_instructions(&eh_frame, &bases); b.iter(|| { let mut instrs = fde.instructions(&eh_frame, &bases); while let Some(i) = instrs.next().expect("Should parse instruction OK") { test::black_box(i); } }); } #[bench] fn eval_longest_fde_instructions_new_ctx_everytime(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); let fde = get_fde_with_longest_cfi_instructions(&eh_frame, &bases); b.iter(|| { let mut ctx = Box::new(UnwindContext::new()); let mut table = fde .rows(&eh_frame, &bases, &mut ctx) .expect("Should initialize the ctx OK"); while let Some(row) = table.next_row().expect("Should get next unwind table row") { test::black_box(row); } }); } #[bench] fn eval_longest_fde_instructions_same_ctx(b: &mut test::Bencher) { let eh_frame = read_section("eh_frame"); let eh_frame = EhFrame::new(&eh_frame, LittleEndian); let bases = BaseAddresses::default() .set_eh_frame(0) .set_got(0) .set_text(0); let fde = get_fde_with_longest_cfi_instructions(&eh_frame, &bases); let mut ctx = Box::new(UnwindContext::new()); b.iter(|| { let mut table = fde .rows(&eh_frame, &bases, &mut ctx) .expect("Should initialize the ctx OK"); while let Some(row) = table.next_row().expect("Should get next unwind table row") { test::black_box(row); } }); } }