| use alloc::vec::Vec; |
| use core::fmt; |
| use core::num::{NonZeroU64, Wrapping}; |
| use core::result; |
| |
| use crate::common::{ |
| DebugLineOffset, DebugLineStrOffset, DebugStrOffset, DebugStrOffsetsIndex, Encoding, Format, |
| LineEncoding, SectionId, |
| }; |
| use crate::constants; |
| use crate::endianity::Endianity; |
| use crate::read::{AttributeValue, EndianSlice, Error, Reader, ReaderOffset, Result, Section}; |
| |
| /// The `DebugLine` struct contains the source location to instruction mapping |
| /// found in the `.debug_line` section. |
| #[derive(Debug, Default, Clone, Copy)] |
| pub struct DebugLine<R> { |
| debug_line_section: R, |
| } |
| |
| impl<'input, Endian> DebugLine<EndianSlice<'input, Endian>> |
| where |
| Endian: Endianity, |
| { |
| /// Construct a new `DebugLine` instance from the data in the `.debug_line` |
| /// section. |
| /// |
| /// It is the caller's responsibility to read the `.debug_line` section and |
| /// present it as a `&[u8]` slice. That means using some ELF loader on |
| /// Linux, a Mach-O loader on macOS, etc. |
| /// |
| /// ``` |
| /// use gimli::{DebugLine, LittleEndian}; |
| /// |
| /// # let buf = [0x00, 0x01, 0x02, 0x03]; |
| /// # let read_debug_line_section_somehow = || &buf; |
| /// let debug_line = DebugLine::new(read_debug_line_section_somehow(), LittleEndian); |
| /// ``` |
| pub fn new(debug_line_section: &'input [u8], endian: Endian) -> Self { |
| Self::from(EndianSlice::new(debug_line_section, endian)) |
| } |
| } |
| |
| impl<R: Reader> DebugLine<R> { |
| /// Parse the line number program whose header is at the given `offset` in the |
| /// `.debug_line` section. |
| /// |
| /// The `address_size` must match the compilation unit that the lines apply to. |
| /// The `comp_dir` should be from the `DW_AT_comp_dir` attribute of the compilation |
| /// unit. The `comp_name` should be from the `DW_AT_name` attribute of the |
| /// compilation unit. |
| /// |
| /// ```rust,no_run |
| /// use gimli::{DebugLine, DebugLineOffset, IncompleteLineProgram, EndianSlice, LittleEndian}; |
| /// |
| /// # let buf = []; |
| /// # let read_debug_line_section_somehow = || &buf; |
| /// let debug_line = DebugLine::new(read_debug_line_section_somehow(), LittleEndian); |
| /// |
| /// // In a real example, we'd grab the offset via a compilation unit |
| /// // entry's `DW_AT_stmt_list` attribute, and the address size from that |
| /// // unit directly. |
| /// let offset = DebugLineOffset(0); |
| /// let address_size = 8; |
| /// |
| /// let program = debug_line.program(offset, address_size, None, None) |
| /// .expect("should have found a header at that offset, and parsed it OK"); |
| /// ``` |
| pub fn program( |
| &self, |
| offset: DebugLineOffset<R::Offset>, |
| address_size: u8, |
| comp_dir: Option<R>, |
| comp_name: Option<R>, |
| ) -> Result<IncompleteLineProgram<R>> { |
| let input = &mut self.debug_line_section.clone(); |
| input.skip(offset.0)?; |
| let header = LineProgramHeader::parse(input, offset, address_size, comp_dir, comp_name)?; |
| let program = IncompleteLineProgram { header }; |
| Ok(program) |
| } |
| } |
| |
| impl<T> DebugLine<T> { |
| /// Create a `DebugLine` section that references the data in `self`. |
| /// |
| /// This is useful when `R` implements `Reader` but `T` does not. |
| /// |
| /// ## Example Usage |
| /// |
| /// ```rust,no_run |
| /// # let load_section = || unimplemented!(); |
| /// // Read the DWARF section into a `Vec` with whatever object loader you're using. |
| /// let owned_section: gimli::DebugLine<Vec<u8>> = load_section(); |
| /// // Create a reference to the DWARF section. |
| /// let section = owned_section.borrow(|section| { |
| /// gimli::EndianSlice::new(§ion, gimli::LittleEndian) |
| /// }); |
| /// ``` |
| pub fn borrow<'a, F, R>(&'a self, mut borrow: F) -> DebugLine<R> |
| where |
| F: FnMut(&'a T) -> R, |
| { |
| borrow(&self.debug_line_section).into() |
| } |
| } |
| |
| impl<R> Section<R> for DebugLine<R> { |
| fn id() -> SectionId { |
| SectionId::DebugLine |
| } |
| |
| fn reader(&self) -> &R { |
| &self.debug_line_section |
| } |
| } |
| |
| impl<R> From<R> for DebugLine<R> { |
| fn from(debug_line_section: R) -> Self { |
| DebugLine { debug_line_section } |
| } |
| } |
| |
| /// Deprecated. `LineNumberProgram` has been renamed to `LineProgram`. |
| #[deprecated(note = "LineNumberProgram has been renamed to LineProgram, use that instead.")] |
| pub type LineNumberProgram<R, Offset> = dyn LineProgram<R, Offset>; |
| |
| /// A `LineProgram` provides access to a `LineProgramHeader` and |
| /// a way to add files to the files table if necessary. Gimli consumers should |
| /// never need to use or see this trait. |
| pub trait LineProgram<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| /// Get a reference to the held `LineProgramHeader`. |
| fn header(&self) -> &LineProgramHeader<R, Offset>; |
| /// Add a file to the file table if necessary. |
| fn add_file(&mut self, file: FileEntry<R, Offset>); |
| } |
| |
| impl<R, Offset> LineProgram<R, Offset> for IncompleteLineProgram<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| fn header(&self) -> &LineProgramHeader<R, Offset> { |
| &self.header |
| } |
| fn add_file(&mut self, file: FileEntry<R, Offset>) { |
| self.header.file_names.push(file); |
| } |
| } |
| |
| impl<'program, R, Offset> LineProgram<R, Offset> for &'program CompleteLineProgram<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| fn header(&self) -> &LineProgramHeader<R, Offset> { |
| &self.header |
| } |
| fn add_file(&mut self, _: FileEntry<R, Offset>) { |
| // Nop. Our file table is already complete. |
| } |
| } |
| |
| /// Deprecated. `StateMachine` has been renamed to `LineRows`. |
| #[deprecated(note = "StateMachine has been renamed to LineRows, use that instead.")] |
| pub type StateMachine<R, Program, Offset> = LineRows<R, Program, Offset>; |
| |
| /// Executes a `LineProgram` to iterate over the rows in the matrix of line number information. |
| /// |
| /// "The hypothetical machine used by a consumer of the line number information |
| /// to expand the byte-coded instruction stream into a matrix of line number |
| /// information." -- Section 6.2.1 |
| #[derive(Debug, Clone)] |
| pub struct LineRows<R, Program, Offset = <R as Reader>::Offset> |
| where |
| Program: LineProgram<R, Offset>, |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| program: Program, |
| row: LineRow, |
| instructions: LineInstructions<R>, |
| } |
| |
| type OneShotLineRows<R, Offset = <R as Reader>::Offset> = |
| LineRows<R, IncompleteLineProgram<R, Offset>, Offset>; |
| |
| type ResumedLineRows<'program, R, Offset = <R as Reader>::Offset> = |
| LineRows<R, &'program CompleteLineProgram<R, Offset>, Offset>; |
| |
| impl<R, Program, Offset> LineRows<R, Program, Offset> |
| where |
| Program: LineProgram<R, Offset>, |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| #[allow(clippy::new_ret_no_self)] |
| fn new(program: IncompleteLineProgram<R, Offset>) -> OneShotLineRows<R, Offset> { |
| let row = LineRow::new(program.header()); |
| let instructions = LineInstructions { |
| input: program.header().program_buf.clone(), |
| }; |
| LineRows { |
| program, |
| row, |
| instructions, |
| } |
| } |
| |
| fn resume<'program>( |
| program: &'program CompleteLineProgram<R, Offset>, |
| sequence: &LineSequence<R>, |
| ) -> ResumedLineRows<'program, R, Offset> { |
| let row = LineRow::new(program.header()); |
| let instructions = sequence.instructions.clone(); |
| LineRows { |
| program, |
| row, |
| instructions, |
| } |
| } |
| |
| /// Get a reference to the header for this state machine's line number |
| /// program. |
| #[inline] |
| pub fn header(&self) -> &LineProgramHeader<R, Offset> { |
| self.program.header() |
| } |
| |
| /// Parse and execute the next instructions in the line number program until |
| /// another row in the line number matrix is computed. |
| /// |
| /// The freshly computed row is returned as `Ok(Some((header, row)))`. |
| /// If the matrix is complete, and there are no more new rows in the line |
| /// number matrix, then `Ok(None)` is returned. If there was an error parsing |
| /// an instruction, then `Err(e)` is returned. |
| /// |
| /// Unfortunately, the references mean that this cannot be a |
| /// `FallibleIterator`. |
| pub fn next_row(&mut self) -> Result<Option<(&LineProgramHeader<R, Offset>, &LineRow)>> { |
| // Perform any reset that was required after copying the previous row. |
| self.row.reset(self.program.header()); |
| |
| loop { |
| // Split the borrow here, rather than calling `self.header()`. |
| match self.instructions.next_instruction(self.program.header()) { |
| Err(err) => return Err(err), |
| Ok(None) => return Ok(None), |
| Ok(Some(instruction)) => { |
| if self.row.execute(instruction, &mut self.program) { |
| return Ok(Some((self.header(), &self.row))); |
| } |
| // Fall through, parse the next instruction, and see if that |
| // yields a row. |
| } |
| } |
| } |
| } |
| } |
| |
| /// Deprecated. `Opcode` has been renamed to `LineInstruction`. |
| #[deprecated(note = "Opcode has been renamed to LineInstruction, use that instead.")] |
| pub type Opcode<R> = LineInstruction<R, <R as Reader>::Offset>; |
| |
| /// A parsed line number program instruction. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum LineInstruction<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| /// > ### 6.2.5.1 Special Opcodes |
| /// > |
| /// > Each ubyte special opcode has the following effect on the state machine: |
| /// > |
| /// > 1. Add a signed integer to the line register. |
| /// > |
| /// > 2. Modify the operation pointer by incrementing the address and |
| /// > op_index registers as described below. |
| /// > |
| /// > 3. Append a row to the matrix using the current values of the state |
| /// > machine registers. |
| /// > |
| /// > 4. Set the basic_block register to “false.” |
| /// > |
| /// > 5. Set the prologue_end register to “false.” |
| /// > |
| /// > 6. Set the epilogue_begin register to “false.” |
| /// > |
| /// > 7. Set the discriminator register to 0. |
| /// > |
| /// > All of the special opcodes do those same seven things; they differ from |
| /// > one another only in what values they add to the line, address and |
| /// > op_index registers. |
| Special(u8), |
| |
| /// "[`LineInstruction::Copy`] appends a row to the matrix using the current |
| /// values of the state machine registers. Then it sets the discriminator |
| /// register to 0, and sets the basic_block, prologue_end and epilogue_begin |
| /// registers to “false.”" |
| Copy, |
| |
| /// "The DW_LNS_advance_pc opcode takes a single unsigned LEB128 operand as |
| /// the operation advance and modifies the address and op_index registers |
| /// [the same as `LineInstruction::Special`]" |
| AdvancePc(u64), |
| |
| /// "The DW_LNS_advance_line opcode takes a single signed LEB128 operand and |
| /// adds that value to the line register of the state machine." |
| AdvanceLine(i64), |
| |
| /// "The DW_LNS_set_file opcode takes a single unsigned LEB128 operand and |
| /// stores it in the file register of the state machine." |
| SetFile(u64), |
| |
| /// "The DW_LNS_set_column opcode takes a single unsigned LEB128 operand and |
| /// stores it in the column register of the state machine." |
| SetColumn(u64), |
| |
| /// "The DW_LNS_negate_stmt opcode takes no operands. It sets the is_stmt |
| /// register of the state machine to the logical negation of its current |
| /// value." |
| NegateStatement, |
| |
| /// "The DW_LNS_set_basic_block opcode takes no operands. It sets the |
| /// basic_block register of the state machine to “true.”" |
| SetBasicBlock, |
| |
| /// > The DW_LNS_const_add_pc opcode takes no operands. It advances the |
| /// > address and op_index registers by the increments corresponding to |
| /// > special opcode 255. |
| /// > |
| /// > When the line number program needs to advance the address by a small |
| /// > amount, it can use a single special opcode, which occupies a single |
| /// > byte. When it needs to advance the address by up to twice the range of |
| /// > the last special opcode, it can use DW_LNS_const_add_pc followed by a |
| /// > special opcode, for a total of two bytes. Only if it needs to advance |
| /// > the address by more than twice that range will it need to use both |
| /// > DW_LNS_advance_pc and a special opcode, requiring three or more bytes. |
| ConstAddPc, |
| |
| /// > The DW_LNS_fixed_advance_pc opcode takes a single uhalf (unencoded) |
| /// > operand and adds it to the address register of the state machine and |
| /// > sets the op_index register to 0. This is the only standard opcode whose |
| /// > operand is not a variable length number. It also does not multiply the |
| /// > operand by the minimum_instruction_length field of the header. |
| FixedAddPc(u16), |
| |
| /// "[`LineInstruction::SetPrologueEnd`] sets the prologue_end register to “true”." |
| SetPrologueEnd, |
| |
| /// "[`LineInstruction::SetEpilogueBegin`] sets the epilogue_begin register to |
| /// “true”." |
| SetEpilogueBegin, |
| |
| /// "The DW_LNS_set_isa opcode takes a single unsigned LEB128 operand and |
| /// stores that value in the isa register of the state machine." |
| SetIsa(u64), |
| |
| /// An unknown standard opcode with zero operands. |
| UnknownStandard0(constants::DwLns), |
| |
| /// An unknown standard opcode with one operand. |
| UnknownStandard1(constants::DwLns, u64), |
| |
| /// An unknown standard opcode with multiple operands. |
| UnknownStandardN(constants::DwLns, R), |
| |
| /// > [`LineInstruction::EndSequence`] sets the end_sequence register of the state |
| /// > machine to “true” and appends a row to the matrix using the current |
| /// > values of the state-machine registers. Then it resets the registers to |
| /// > the initial values specified above (see Section 6.2.2). Every line |
| /// > number program sequence must end with a DW_LNE_end_sequence instruction |
| /// > which creates a row whose address is that of the byte after the last |
| /// > target machine instruction of the sequence. |
| EndSequence, |
| |
| /// > The DW_LNE_set_address opcode takes a single relocatable address as an |
| /// > operand. The size of the operand is the size of an address on the target |
| /// > machine. It sets the address register to the value given by the |
| /// > relocatable address and sets the op_index register to 0. |
| /// > |
| /// > All of the other line number program opcodes that affect the address |
| /// > register add a delta to it. This instruction stores a relocatable value |
| /// > into it instead. |
| SetAddress(u64), |
| |
| /// Defines a new source file in the line number program and appends it to |
| /// the line number program header's list of source files. |
| DefineFile(FileEntry<R, Offset>), |
| |
| /// "The DW_LNE_set_discriminator opcode takes a single parameter, an |
| /// unsigned LEB128 integer. It sets the discriminator register to the new |
| /// value." |
| SetDiscriminator(u64), |
| |
| /// An unknown extended opcode and the slice of its unparsed operands. |
| UnknownExtended(constants::DwLne, R), |
| } |
| |
| impl<R, Offset> LineInstruction<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| fn parse<'header>( |
| header: &'header LineProgramHeader<R>, |
| input: &mut R, |
| ) -> Result<LineInstruction<R>> |
| where |
| R: 'header, |
| { |
| let opcode = input.read_u8()?; |
| if opcode == 0 { |
| let length = input.read_uleb128().and_then(R::Offset::from_u64)?; |
| let mut instr_rest = input.split(length)?; |
| let opcode = instr_rest.read_u8()?; |
| |
| match constants::DwLne(opcode) { |
| constants::DW_LNE_end_sequence => Ok(LineInstruction::EndSequence), |
| |
| constants::DW_LNE_set_address => { |
| let address = instr_rest.read_address(header.address_size())?; |
| Ok(LineInstruction::SetAddress(address)) |
| } |
| |
| constants::DW_LNE_define_file => { |
| if header.version() <= 4 { |
| let path_name = instr_rest.read_null_terminated_slice()?; |
| let entry = FileEntry::parse(&mut instr_rest, path_name)?; |
| Ok(LineInstruction::DefineFile(entry)) |
| } else { |
| Ok(LineInstruction::UnknownExtended( |
| constants::DW_LNE_define_file, |
| instr_rest, |
| )) |
| } |
| } |
| |
| constants::DW_LNE_set_discriminator => { |
| let discriminator = instr_rest.read_uleb128()?; |
| Ok(LineInstruction::SetDiscriminator(discriminator)) |
| } |
| |
| otherwise => Ok(LineInstruction::UnknownExtended(otherwise, instr_rest)), |
| } |
| } else if opcode >= header.opcode_base { |
| Ok(LineInstruction::Special(opcode)) |
| } else { |
| match constants::DwLns(opcode) { |
| constants::DW_LNS_copy => Ok(LineInstruction::Copy), |
| |
| constants::DW_LNS_advance_pc => { |
| let advance = input.read_uleb128()?; |
| Ok(LineInstruction::AdvancePc(advance)) |
| } |
| |
| constants::DW_LNS_advance_line => { |
| let increment = input.read_sleb128()?; |
| Ok(LineInstruction::AdvanceLine(increment)) |
| } |
| |
| constants::DW_LNS_set_file => { |
| let file = input.read_uleb128()?; |
| Ok(LineInstruction::SetFile(file)) |
| } |
| |
| constants::DW_LNS_set_column => { |
| let column = input.read_uleb128()?; |
| Ok(LineInstruction::SetColumn(column)) |
| } |
| |
| constants::DW_LNS_negate_stmt => Ok(LineInstruction::NegateStatement), |
| |
| constants::DW_LNS_set_basic_block => Ok(LineInstruction::SetBasicBlock), |
| |
| constants::DW_LNS_const_add_pc => Ok(LineInstruction::ConstAddPc), |
| |
| constants::DW_LNS_fixed_advance_pc => { |
| let advance = input.read_u16()?; |
| Ok(LineInstruction::FixedAddPc(advance)) |
| } |
| |
| constants::DW_LNS_set_prologue_end => Ok(LineInstruction::SetPrologueEnd), |
| |
| constants::DW_LNS_set_epilogue_begin => Ok(LineInstruction::SetEpilogueBegin), |
| |
| constants::DW_LNS_set_isa => { |
| let isa = input.read_uleb128()?; |
| Ok(LineInstruction::SetIsa(isa)) |
| } |
| |
| otherwise => { |
| let mut opcode_lengths = header.standard_opcode_lengths().clone(); |
| opcode_lengths.skip(R::Offset::from_u8(opcode - 1))?; |
| let num_args = opcode_lengths.read_u8()? as usize; |
| match num_args { |
| 0 => Ok(LineInstruction::UnknownStandard0(otherwise)), |
| 1 => { |
| let arg = input.read_uleb128()?; |
| Ok(LineInstruction::UnknownStandard1(otherwise, arg)) |
| } |
| _ => { |
| let mut args = input.clone(); |
| for _ in 0..num_args { |
| input.read_uleb128()?; |
| } |
| let len = input.offset_from(&args); |
| args.truncate(len)?; |
| Ok(LineInstruction::UnknownStandardN(otherwise, args)) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| impl<R, Offset> fmt::Display for LineInstruction<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { |
| match *self { |
| LineInstruction::Special(opcode) => write!(f, "Special opcode {}", opcode), |
| LineInstruction::Copy => write!(f, "{}", constants::DW_LNS_copy), |
| LineInstruction::AdvancePc(advance) => { |
| write!(f, "{} by {}", constants::DW_LNS_advance_pc, advance) |
| } |
| LineInstruction::AdvanceLine(increment) => { |
| write!(f, "{} by {}", constants::DW_LNS_advance_line, increment) |
| } |
| LineInstruction::SetFile(file) => { |
| write!(f, "{} to {}", constants::DW_LNS_set_file, file) |
| } |
| LineInstruction::SetColumn(column) => { |
| write!(f, "{} to {}", constants::DW_LNS_set_column, column) |
| } |
| LineInstruction::NegateStatement => write!(f, "{}", constants::DW_LNS_negate_stmt), |
| LineInstruction::SetBasicBlock => write!(f, "{}", constants::DW_LNS_set_basic_block), |
| LineInstruction::ConstAddPc => write!(f, "{}", constants::DW_LNS_const_add_pc), |
| LineInstruction::FixedAddPc(advance) => { |
| write!(f, "{} by {}", constants::DW_LNS_fixed_advance_pc, advance) |
| } |
| LineInstruction::SetPrologueEnd => write!(f, "{}", constants::DW_LNS_set_prologue_end), |
| LineInstruction::SetEpilogueBegin => { |
| write!(f, "{}", constants::DW_LNS_set_epilogue_begin) |
| } |
| LineInstruction::SetIsa(isa) => write!(f, "{} to {}", constants::DW_LNS_set_isa, isa), |
| LineInstruction::UnknownStandard0(opcode) => write!(f, "Unknown {}", opcode), |
| LineInstruction::UnknownStandard1(opcode, arg) => { |
| write!(f, "Unknown {} with operand {}", opcode, arg) |
| } |
| LineInstruction::UnknownStandardN(opcode, ref args) => { |
| write!(f, "Unknown {} with operands {:?}", opcode, args) |
| } |
| LineInstruction::EndSequence => write!(f, "{}", constants::DW_LNE_end_sequence), |
| LineInstruction::SetAddress(address) => { |
| write!(f, "{} to {}", constants::DW_LNE_set_address, address) |
| } |
| LineInstruction::DefineFile(_) => write!(f, "{}", constants::DW_LNE_define_file), |
| LineInstruction::SetDiscriminator(discr) => { |
| write!(f, "{} to {}", constants::DW_LNE_set_discriminator, discr) |
| } |
| LineInstruction::UnknownExtended(opcode, _) => write!(f, "Unknown {}", opcode), |
| } |
| } |
| } |
| |
| /// Deprecated. `OpcodesIter` has been renamed to `LineInstructions`. |
| #[deprecated(note = "OpcodesIter has been renamed to LineInstructions, use that instead.")] |
| pub type OpcodesIter<R> = LineInstructions<R>; |
| |
| /// An iterator yielding parsed instructions. |
| /// |
| /// See |
| /// [`LineProgramHeader::instructions`](./struct.LineProgramHeader.html#method.instructions) |
| /// for more details. |
| #[derive(Clone, Debug)] |
| pub struct LineInstructions<R: Reader> { |
| input: R, |
| } |
| |
| impl<R: Reader> LineInstructions<R> { |
| fn remove_trailing(&self, other: &LineInstructions<R>) -> Result<LineInstructions<R>> { |
| let offset = other.input.offset_from(&self.input); |
| let mut input = self.input.clone(); |
| input.truncate(offset)?; |
| Ok(LineInstructions { input }) |
| } |
| } |
| |
| impl<R: Reader> LineInstructions<R> { |
| /// Advance the iterator and return the next instruction. |
| /// |
| /// Returns the newly parsed instruction as `Ok(Some(instruction))`. Returns |
| /// `Ok(None)` when iteration is complete and all instructions have already been |
| /// parsed and yielded. If an error occurs while parsing the next attribute, |
| /// then this error is returned as `Err(e)`, and all subsequent calls return |
| /// `Ok(None)`. |
| /// |
| /// Unfortunately, the `header` parameter means that this cannot be a |
| /// `FallibleIterator`. |
| #[allow(clippy::inline_always)] |
| #[inline(always)] |
| pub fn next_instruction( |
| &mut self, |
| header: &LineProgramHeader<R>, |
| ) -> Result<Option<LineInstruction<R>>> { |
| if self.input.is_empty() { |
| return Ok(None); |
| } |
| |
| match LineInstruction::parse(header, &mut self.input) { |
| Ok(instruction) => Ok(Some(instruction)), |
| Err(e) => { |
| self.input.empty(); |
| Err(e) |
| } |
| } |
| } |
| } |
| |
| /// Deprecated. `LineNumberRow` has been renamed to `LineRow`. |
| #[deprecated(note = "LineNumberRow has been renamed to LineRow, use that instead.")] |
| pub type LineNumberRow = LineRow; |
| |
| /// A row in the line number program's resulting matrix. |
| /// |
| /// Each row is a copy of the registers of the state machine, as defined in section 6.2.2. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub struct LineRow { |
| address: Wrapping<u64>, |
| op_index: Wrapping<u64>, |
| file: u64, |
| line: Wrapping<u64>, |
| column: u64, |
| is_stmt: bool, |
| basic_block: bool, |
| end_sequence: bool, |
| prologue_end: bool, |
| epilogue_begin: bool, |
| isa: u64, |
| discriminator: u64, |
| } |
| |
| impl LineRow { |
| /// Create a line number row in the initial state for the given program. |
| pub fn new<R: Reader>(header: &LineProgramHeader<R>) -> Self { |
| LineRow { |
| // "At the beginning of each sequence within a line number program, the |
| // state of the registers is:" -- Section 6.2.2 |
| address: Wrapping(0), |
| op_index: Wrapping(0), |
| file: 1, |
| line: Wrapping(1), |
| column: 0, |
| // "determined by default_is_stmt in the line number program header" |
| is_stmt: header.line_encoding.default_is_stmt, |
| basic_block: false, |
| end_sequence: false, |
| prologue_end: false, |
| epilogue_begin: false, |
| // "The isa value 0 specifies that the instruction set is the |
| // architecturally determined default instruction set. This may be fixed |
| // by the ABI, or it may be specified by other means, for example, by |
| // the object file description." |
| isa: 0, |
| discriminator: 0, |
| } |
| } |
| |
| /// "The program-counter value corresponding to a machine instruction |
| /// generated by the compiler." |
| #[inline] |
| pub fn address(&self) -> u64 { |
| self.address.0 |
| } |
| |
| /// > An unsigned integer representing the index of an operation within a VLIW |
| /// > instruction. The index of the first operation is 0. For non-VLIW |
| /// > architectures, this register will always be 0. |
| /// > |
| /// > The address and op_index registers, taken together, form an operation |
| /// > pointer that can reference any individual operation with the |
| /// > instruction stream. |
| #[inline] |
| pub fn op_index(&self) -> u64 { |
| self.op_index.0 |
| } |
| |
| /// "An unsigned integer indicating the identity of the source file |
| /// corresponding to a machine instruction." |
| #[inline] |
| pub fn file_index(&self) -> u64 { |
| self.file |
| } |
| |
| /// The source file corresponding to the current machine instruction. |
| #[inline] |
| pub fn file<'header, R: Reader>( |
| &self, |
| header: &'header LineProgramHeader<R>, |
| ) -> Option<&'header FileEntry<R>> { |
| header.file(self.file) |
| } |
| |
| /// "An unsigned integer indicating a source line number. Lines are numbered |
| /// beginning at 1. The compiler may emit the value 0 in cases where an |
| /// instruction cannot be attributed to any source line." |
| /// Line number values of 0 are represented as `None`. |
| #[inline] |
| pub fn line(&self) -> Option<NonZeroU64> { |
| NonZeroU64::new(self.line.0) |
| } |
| |
| /// "An unsigned integer indicating a column number within a source |
| /// line. Columns are numbered beginning at 1. The value 0 is reserved to |
| /// indicate that a statement begins at the “left edge” of the line." |
| #[inline] |
| pub fn column(&self) -> ColumnType { |
| NonZeroU64::new(self.column) |
| .map(ColumnType::Column) |
| .unwrap_or(ColumnType::LeftEdge) |
| } |
| |
| /// "A boolean indicating that the current instruction is a recommended |
| /// breakpoint location. A recommended breakpoint location is intended to |
| /// “represent” a line, a statement and/or a semantically distinct subpart |
| /// of a statement." |
| #[inline] |
| pub fn is_stmt(&self) -> bool { |
| self.is_stmt |
| } |
| |
| /// "A boolean indicating that the current instruction is the beginning of a |
| /// basic block." |
| #[inline] |
| pub fn basic_block(&self) -> bool { |
| self.basic_block |
| } |
| |
| /// "A boolean indicating that the current address is that of the first byte |
| /// after the end of a sequence of target machine instructions. end_sequence |
| /// terminates a sequence of lines; therefore other information in the same |
| /// row is not meaningful." |
| #[inline] |
| pub fn end_sequence(&self) -> bool { |
| self.end_sequence |
| } |
| |
| /// "A boolean indicating that the current address is one (of possibly many) |
| /// where execution should be suspended for an entry breakpoint of a |
| /// function." |
| #[inline] |
| pub fn prologue_end(&self) -> bool { |
| self.prologue_end |
| } |
| |
| /// "A boolean indicating that the current address is one (of possibly many) |
| /// where execution should be suspended for an exit breakpoint of a |
| /// function." |
| #[inline] |
| pub fn epilogue_begin(&self) -> bool { |
| self.epilogue_begin |
| } |
| |
| /// Tag for the current instruction set architecture. |
| /// |
| /// > An unsigned integer whose value encodes the applicable instruction set |
| /// > architecture for the current instruction. |
| /// > |
| /// > The encoding of instruction sets should be shared by all users of a |
| /// > given architecture. It is recommended that this encoding be defined by |
| /// > the ABI authoring committee for each architecture. |
| #[inline] |
| pub fn isa(&self) -> u64 { |
| self.isa |
| } |
| |
| /// "An unsigned integer identifying the block to which the current |
| /// instruction belongs. Discriminator values are assigned arbitrarily by |
| /// the DWARF producer and serve to distinguish among multiple blocks that |
| /// may all be associated with the same source file, line, and column. Where |
| /// only one block exists for a given source position, the discriminator |
| /// value should be zero." |
| #[inline] |
| pub fn discriminator(&self) -> u64 { |
| self.discriminator |
| } |
| |
| /// Execute the given instruction, and return true if a new row in the |
| /// line number matrix needs to be generated. |
| /// |
| /// Unknown opcodes are treated as no-ops. |
| #[inline] |
| pub fn execute<R, Program>( |
| &mut self, |
| instruction: LineInstruction<R>, |
| program: &mut Program, |
| ) -> bool |
| where |
| Program: LineProgram<R>, |
| R: Reader, |
| { |
| match instruction { |
| LineInstruction::Special(opcode) => { |
| self.exec_special_opcode(opcode, program.header()); |
| true |
| } |
| |
| LineInstruction::Copy => true, |
| |
| LineInstruction::AdvancePc(operation_advance) => { |
| self.apply_operation_advance(operation_advance, program.header()); |
| false |
| } |
| |
| LineInstruction::AdvanceLine(line_increment) => { |
| self.apply_line_advance(line_increment); |
| false |
| } |
| |
| LineInstruction::SetFile(file) => { |
| self.file = file; |
| false |
| } |
| |
| LineInstruction::SetColumn(column) => { |
| self.column = column; |
| false |
| } |
| |
| LineInstruction::NegateStatement => { |
| self.is_stmt = !self.is_stmt; |
| false |
| } |
| |
| LineInstruction::SetBasicBlock => { |
| self.basic_block = true; |
| false |
| } |
| |
| LineInstruction::ConstAddPc => { |
| let adjusted = self.adjust_opcode(255, program.header()); |
| let operation_advance = adjusted / program.header().line_encoding.line_range; |
| self.apply_operation_advance(u64::from(operation_advance), program.header()); |
| false |
| } |
| |
| LineInstruction::FixedAddPc(operand) => { |
| self.address += Wrapping(u64::from(operand)); |
| self.op_index.0 = 0; |
| false |
| } |
| |
| LineInstruction::SetPrologueEnd => { |
| self.prologue_end = true; |
| false |
| } |
| |
| LineInstruction::SetEpilogueBegin => { |
| self.epilogue_begin = true; |
| false |
| } |
| |
| LineInstruction::SetIsa(isa) => { |
| self.isa = isa; |
| false |
| } |
| |
| LineInstruction::EndSequence => { |
| self.end_sequence = true; |
| true |
| } |
| |
| LineInstruction::SetAddress(address) => { |
| self.address.0 = address; |
| self.op_index.0 = 0; |
| false |
| } |
| |
| LineInstruction::DefineFile(entry) => { |
| program.add_file(entry); |
| false |
| } |
| |
| LineInstruction::SetDiscriminator(discriminator) => { |
| self.discriminator = discriminator; |
| false |
| } |
| |
| // Compatibility with future opcodes. |
| LineInstruction::UnknownStandard0(_) |
| | LineInstruction::UnknownStandard1(_, _) |
| | LineInstruction::UnknownStandardN(_, _) |
| | LineInstruction::UnknownExtended(_, _) => false, |
| } |
| } |
| |
| /// Perform any reset that was required after copying the previous row. |
| #[inline] |
| pub fn reset<R: Reader>(&mut self, header: &LineProgramHeader<R>) { |
| if self.end_sequence { |
| // Previous instruction was EndSequence, so reset everything |
| // as specified in Section 6.2.5.3. |
| *self = Self::new(header); |
| } else { |
| // Previous instruction was one of: |
| // - Special - specified in Section 6.2.5.1, steps 4-7 |
| // - Copy - specified in Section 6.2.5.2 |
| // The reset behaviour is the same in both cases. |
| self.discriminator = 0; |
| self.basic_block = false; |
| self.prologue_end = false; |
| self.epilogue_begin = false; |
| } |
| } |
| |
| /// Step 1 of section 6.2.5.1 |
| fn apply_line_advance(&mut self, line_increment: i64) { |
| if line_increment < 0 { |
| let decrement = -line_increment as u64; |
| if decrement <= self.line.0 { |
| self.line.0 -= decrement; |
| } else { |
| self.line.0 = 0; |
| } |
| } else { |
| self.line += Wrapping(line_increment as u64); |
| } |
| } |
| |
| /// Step 2 of section 6.2.5.1 |
| fn apply_operation_advance<R: Reader>( |
| &mut self, |
| operation_advance: u64, |
| header: &LineProgramHeader<R>, |
| ) { |
| let operation_advance = Wrapping(operation_advance); |
| |
| let minimum_instruction_length = u64::from(header.line_encoding.minimum_instruction_length); |
| let minimum_instruction_length = Wrapping(minimum_instruction_length); |
| |
| let maximum_operations_per_instruction = |
| u64::from(header.line_encoding.maximum_operations_per_instruction); |
| let maximum_operations_per_instruction = Wrapping(maximum_operations_per_instruction); |
| |
| if maximum_operations_per_instruction.0 == 1 { |
| self.address += minimum_instruction_length * operation_advance; |
| self.op_index.0 = 0; |
| } else { |
| let op_index_with_advance = self.op_index + operation_advance; |
| self.address += minimum_instruction_length |
| * (op_index_with_advance / maximum_operations_per_instruction); |
| self.op_index = op_index_with_advance % maximum_operations_per_instruction; |
| } |
| } |
| |
| #[inline] |
| fn adjust_opcode<R: Reader>(&self, opcode: u8, header: &LineProgramHeader<R>) -> u8 { |
| opcode - header.opcode_base |
| } |
| |
| /// Section 6.2.5.1 |
| fn exec_special_opcode<R: Reader>(&mut self, opcode: u8, header: &LineProgramHeader<R>) { |
| let adjusted_opcode = self.adjust_opcode(opcode, header); |
| |
| let line_range = header.line_encoding.line_range; |
| let line_advance = adjusted_opcode % line_range; |
| let operation_advance = adjusted_opcode / line_range; |
| |
| // Step 1 |
| let line_base = i64::from(header.line_encoding.line_base); |
| self.apply_line_advance(line_base + i64::from(line_advance)); |
| |
| // Step 2 |
| self.apply_operation_advance(u64::from(operation_advance), header); |
| } |
| } |
| |
| /// The type of column that a row is referring to. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub enum ColumnType { |
| /// The `LeftEdge` means that the statement begins at the start of the new |
| /// line. |
| LeftEdge, |
| /// A column number, whose range begins at 1. |
| Column(NonZeroU64), |
| } |
| |
| /// Deprecated. `LineNumberSequence` has been renamed to `LineSequence`. |
| #[deprecated(note = "LineNumberSequence has been renamed to LineSequence, use that instead.")] |
| pub type LineNumberSequence<R> = LineSequence<R>; |
| |
| /// A sequence within a line number program. A sequence, as defined in section |
| /// 6.2.5 of the standard, is a linear subset of a line number program within |
| /// which addresses are monotonically increasing. |
| #[derive(Clone, Debug)] |
| pub struct LineSequence<R: Reader> { |
| /// The first address that is covered by this sequence within the line number |
| /// program. |
| pub start: u64, |
| /// The first address that is *not* covered by this sequence within the line |
| /// number program. |
| pub end: u64, |
| instructions: LineInstructions<R>, |
| } |
| |
| /// Deprecated. `LineNumberProgramHeader` has been renamed to `LineProgramHeader`. |
| #[deprecated( |
| note = "LineNumberProgramHeader has been renamed to LineProgramHeader, use that instead." |
| )] |
| pub type LineNumberProgramHeader<R, Offset> = LineProgramHeader<R, Offset>; |
| |
| /// A header for a line number program in the `.debug_line` section, as defined |
| /// in section 6.2.4 of the standard. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct LineProgramHeader<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| encoding: Encoding, |
| offset: DebugLineOffset<Offset>, |
| unit_length: Offset, |
| |
| header_length: Offset, |
| |
| line_encoding: LineEncoding, |
| |
| /// "The number assigned to the first special opcode." |
| opcode_base: u8, |
| |
| /// "This array specifies the number of LEB128 operands for each of the |
| /// standard opcodes. The first element of the array corresponds to the |
| /// opcode whose value is 1, and the last element corresponds to the opcode |
| /// whose value is `opcode_base - 1`." |
| standard_opcode_lengths: R, |
| |
| /// "A sequence of directory entry format descriptions." |
| directory_entry_format: Vec<FileEntryFormat>, |
| |
| /// > Entries in this sequence describe each path that was searched for |
| /// > included source files in this compilation. (The paths include those |
| /// > directories specified explicitly by the user for the compiler to search |
| /// > and those the compiler searches without explicit direction.) Each path |
| /// > entry is either a full path name or is relative to the current directory |
| /// > of the compilation. |
| /// > |
| /// > The last entry is followed by a single null byte. |
| include_directories: Vec<AttributeValue<R, Offset>>, |
| |
| /// "A sequence of file entry format descriptions." |
| file_name_entry_format: Vec<FileEntryFormat>, |
| |
| /// "Entries in this sequence describe source files that contribute to the |
| /// line number information for this compilation unit or is used in other |
| /// contexts." |
| file_names: Vec<FileEntry<R, Offset>>, |
| |
| /// The encoded line program instructions. |
| program_buf: R, |
| |
| /// The current directory of the compilation. |
| comp_dir: Option<R>, |
| |
| /// The primary source file. |
| comp_file: Option<FileEntry<R, Offset>>, |
| } |
| |
| impl<R, Offset> LineProgramHeader<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| /// Return the offset of the line number program header in the `.debug_line` section. |
| pub fn offset(&self) -> DebugLineOffset<R::Offset> { |
| self.offset |
| } |
| |
| /// Return the length of the line number program and header, not including |
| /// the length of the encoded length itself. |
| pub fn unit_length(&self) -> R::Offset { |
| self.unit_length |
| } |
| |
| /// Return the encoding parameters for this header's line program. |
| pub fn encoding(&self) -> Encoding { |
| self.encoding |
| } |
| |
| /// Get the version of this header's line program. |
| pub fn version(&self) -> u16 { |
| self.encoding.version |
| } |
| |
| /// Get the length of the encoded line number program header, not including |
| /// the length of the encoded length itself. |
| pub fn header_length(&self) -> R::Offset { |
| self.header_length |
| } |
| |
| /// Get the size in bytes of a target machine address. |
| pub fn address_size(&self) -> u8 { |
| self.encoding.address_size |
| } |
| |
| /// Whether this line program is encoded in 64- or 32-bit DWARF. |
| pub fn format(&self) -> Format { |
| self.encoding.format |
| } |
| |
| /// Get the line encoding parameters for this header's line program. |
| pub fn line_encoding(&self) -> LineEncoding { |
| self.line_encoding |
| } |
| |
| /// Get the minimum instruction length any instruction in this header's line |
| /// program may have. |
| pub fn minimum_instruction_length(&self) -> u8 { |
| self.line_encoding.minimum_instruction_length |
| } |
| |
| /// Get the maximum number of operations each instruction in this header's |
| /// line program may have. |
| pub fn maximum_operations_per_instruction(&self) -> u8 { |
| self.line_encoding.maximum_operations_per_instruction |
| } |
| |
| /// Get the default value of the `is_stmt` register for this header's line |
| /// program. |
| pub fn default_is_stmt(&self) -> bool { |
| self.line_encoding.default_is_stmt |
| } |
| |
| /// Get the line base for this header's line program. |
| pub fn line_base(&self) -> i8 { |
| self.line_encoding.line_base |
| } |
| |
| /// Get the line range for this header's line program. |
| pub fn line_range(&self) -> u8 { |
| self.line_encoding.line_range |
| } |
| |
| /// Get opcode base for this header's line program. |
| pub fn opcode_base(&self) -> u8 { |
| self.opcode_base |
| } |
| |
| /// An array of `u8` that specifies the number of LEB128 operands for |
| /// each of the standard opcodes. |
| pub fn standard_opcode_lengths(&self) -> &R { |
| &self.standard_opcode_lengths |
| } |
| |
| /// Get the format of a directory entry. |
| pub fn directory_entry_format(&self) -> &[FileEntryFormat] { |
| &self.directory_entry_format[..] |
| } |
| |
| /// Get the set of include directories for this header's line program. |
| /// |
| /// For DWARF version <= 4, the compilation's current directory is not included |
| /// in the return value, but is implicitly considered to be in the set per spec. |
| pub fn include_directories(&self) -> &[AttributeValue<R, Offset>] { |
| &self.include_directories[..] |
| } |
| |
| /// The include directory with the given directory index. |
| /// |
| /// A directory index of 0 corresponds to the compilation unit directory. |
| pub fn directory(&self, directory: u64) -> Option<AttributeValue<R, Offset>> { |
| if self.encoding.version <= 4 { |
| if directory == 0 { |
| self.comp_dir.clone().map(AttributeValue::String) |
| } else { |
| let directory = directory as usize - 1; |
| self.include_directories.get(directory).cloned() |
| } |
| } else { |
| self.include_directories.get(directory as usize).cloned() |
| } |
| } |
| |
| /// Get the format of a file name entry. |
| pub fn file_name_entry_format(&self) -> &[FileEntryFormat] { |
| &self.file_name_entry_format[..] |
| } |
| |
| /// Return true if the file entries may have valid timestamps. |
| /// |
| /// Only returns false if we definitely know that all timestamp fields |
| /// are invalid. |
| pub fn file_has_timestamp(&self) -> bool { |
| self.encoding.version <= 4 |
| || self |
| .file_name_entry_format |
| .iter() |
| .any(|x| x.content_type == constants::DW_LNCT_timestamp) |
| } |
| |
| /// Return true if the file entries may have valid sizes. |
| /// |
| /// Only returns false if we definitely know that all size fields |
| /// are invalid. |
| pub fn file_has_size(&self) -> bool { |
| self.encoding.version <= 4 |
| || self |
| .file_name_entry_format |
| .iter() |
| .any(|x| x.content_type == constants::DW_LNCT_size) |
| } |
| |
| /// Return true if the file name entry format contains an MD5 field. |
| pub fn file_has_md5(&self) -> bool { |
| self.file_name_entry_format |
| .iter() |
| .any(|x| x.content_type == constants::DW_LNCT_MD5) |
| } |
| |
| /// Get the list of source files that appear in this header's line program. |
| pub fn file_names(&self) -> &[FileEntry<R, Offset>] { |
| &self.file_names[..] |
| } |
| |
| /// The source file with the given file index. |
| /// |
| /// A file index of 0 corresponds to the compilation unit file. |
| /// Note that a file index of 0 is invalid for DWARF version <= 4, |
| /// but we support it anyway. |
| pub fn file(&self, file: u64) -> Option<&FileEntry<R, Offset>> { |
| if self.encoding.version <= 4 { |
| if file == 0 { |
| self.comp_file.as_ref() |
| } else { |
| let file = file as usize - 1; |
| self.file_names.get(file) |
| } |
| } else { |
| self.file_names.get(file as usize) |
| } |
| } |
| |
| /// Get the raw, un-parsed `EndianSlice` containing this header's line number |
| /// program. |
| /// |
| /// ``` |
| /// # fn foo() { |
| /// use gimli::{LineProgramHeader, EndianSlice, NativeEndian}; |
| /// |
| /// fn get_line_number_program_header<'a>() -> LineProgramHeader<EndianSlice<'a, NativeEndian>> { |
| /// // Get a line number program header from some offset in a |
| /// // `.debug_line` section... |
| /// # unimplemented!() |
| /// } |
| /// |
| /// let header = get_line_number_program_header(); |
| /// let raw_program = header.raw_program_buf(); |
| /// println!("The length of the raw program in bytes is {}", raw_program.len()); |
| /// # } |
| /// ``` |
| pub fn raw_program_buf(&self) -> R { |
| self.program_buf.clone() |
| } |
| |
| /// Iterate over the instructions in this header's line number program, parsing |
| /// them as we go. |
| pub fn instructions(&self) -> LineInstructions<R> { |
| LineInstructions { |
| input: self.program_buf.clone(), |
| } |
| } |
| |
| fn parse( |
| input: &mut R, |
| offset: DebugLineOffset<Offset>, |
| mut address_size: u8, |
| mut comp_dir: Option<R>, |
| comp_name: Option<R>, |
| ) -> Result<LineProgramHeader<R, Offset>> { |
| let (unit_length, format) = input.read_initial_length()?; |
| let rest = &mut input.split(unit_length)?; |
| |
| let version = rest.read_u16()?; |
| if version < 2 || version > 5 { |
| return Err(Error::UnknownVersion(u64::from(version))); |
| } |
| |
| if version >= 5 { |
| address_size = rest.read_u8()?; |
| let segment_selector_size = rest.read_u8()?; |
| if segment_selector_size != 0 { |
| return Err(Error::UnsupportedSegmentSize); |
| } |
| } |
| |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| |
| let header_length = rest.read_length(format)?; |
| |
| let mut program_buf = rest.clone(); |
| program_buf.skip(header_length)?; |
| rest.truncate(header_length)?; |
| |
| let minimum_instruction_length = rest.read_u8()?; |
| if minimum_instruction_length == 0 { |
| return Err(Error::MinimumInstructionLengthZero); |
| } |
| |
| // This field did not exist before DWARF 4, but is specified to be 1 for |
| // non-VLIW architectures, which makes it a no-op. |
| let maximum_operations_per_instruction = if version >= 4 { rest.read_u8()? } else { 1 }; |
| if maximum_operations_per_instruction == 0 { |
| return Err(Error::MaximumOperationsPerInstructionZero); |
| } |
| |
| let default_is_stmt = rest.read_u8()? != 0; |
| let line_base = rest.read_i8()?; |
| let line_range = rest.read_u8()?; |
| if line_range == 0 { |
| return Err(Error::LineRangeZero); |
| } |
| let line_encoding = LineEncoding { |
| minimum_instruction_length, |
| maximum_operations_per_instruction, |
| default_is_stmt, |
| line_base, |
| line_range, |
| }; |
| |
| let opcode_base = rest.read_u8()?; |
| if opcode_base == 0 { |
| return Err(Error::OpcodeBaseZero); |
| } |
| |
| let standard_opcode_count = R::Offset::from_u8(opcode_base - 1); |
| let standard_opcode_lengths = rest.split(standard_opcode_count)?; |
| |
| let directory_entry_format; |
| let mut include_directories = Vec::new(); |
| if version <= 4 { |
| directory_entry_format = Vec::new(); |
| loop { |
| let directory = rest.read_null_terminated_slice()?; |
| if directory.is_empty() { |
| break; |
| } |
| include_directories.push(AttributeValue::String(directory)); |
| } |
| } else { |
| comp_dir = None; |
| directory_entry_format = FileEntryFormat::parse(rest)?; |
| let count = rest.read_uleb128()?; |
| for _ in 0..count { |
| include_directories.push(parse_directory_v5( |
| rest, |
| encoding, |
| &directory_entry_format, |
| )?); |
| } |
| } |
| |
| let comp_file; |
| let file_name_entry_format; |
| let mut file_names = Vec::new(); |
| if version <= 4 { |
| comp_file = comp_name.map(|name| FileEntry { |
| path_name: AttributeValue::String(name), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }); |
| |
| file_name_entry_format = Vec::new(); |
| loop { |
| let path_name = rest.read_null_terminated_slice()?; |
| if path_name.is_empty() { |
| break; |
| } |
| file_names.push(FileEntry::parse(rest, path_name)?); |
| } |
| } else { |
| comp_file = None; |
| file_name_entry_format = FileEntryFormat::parse(rest)?; |
| let count = rest.read_uleb128()?; |
| for _ in 0..count { |
| file_names.push(parse_file_v5(rest, encoding, &file_name_entry_format)?); |
| } |
| } |
| |
| let header = LineProgramHeader { |
| encoding, |
| offset, |
| unit_length, |
| header_length, |
| line_encoding, |
| opcode_base, |
| standard_opcode_lengths, |
| directory_entry_format, |
| include_directories, |
| file_name_entry_format, |
| file_names, |
| program_buf, |
| comp_dir, |
| comp_file, |
| }; |
| Ok(header) |
| } |
| } |
| |
| /// Deprecated. `IncompleteLineNumberProgram` has been renamed to `IncompleteLineProgram`. |
| #[deprecated( |
| note = "IncompleteLineNumberProgram has been renamed to IncompleteLineProgram, use that instead." |
| )] |
| pub type IncompleteLineNumberProgram<R, Offset> = IncompleteLineProgram<R, Offset>; |
| |
| /// A line number program that has not been run to completion. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct IncompleteLineProgram<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| header: LineProgramHeader<R, Offset>, |
| } |
| |
| impl<R, Offset> IncompleteLineProgram<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| /// Retrieve the `LineProgramHeader` for this program. |
| pub fn header(&self) -> &LineProgramHeader<R, Offset> { |
| &self.header |
| } |
| |
| /// Construct a new `LineRows` for executing this program to iterate |
| /// over rows in the line information matrix. |
| pub fn rows(self) -> OneShotLineRows<R, Offset> { |
| OneShotLineRows::new(self) |
| } |
| |
| /// Execute the line number program, completing the `IncompleteLineProgram` |
| /// into a `CompleteLineProgram` and producing an array of sequences within |
| /// the line number program that can later be used with |
| /// `CompleteLineProgram::resume_from`. |
| /// |
| /// ``` |
| /// # fn foo() { |
| /// use gimli::{IncompleteLineProgram, EndianSlice, NativeEndian}; |
| /// |
| /// fn get_line_number_program<'a>() -> IncompleteLineProgram<EndianSlice<'a, NativeEndian>> { |
| /// // Get a line number program from some offset in a |
| /// // `.debug_line` section... |
| /// # unimplemented!() |
| /// } |
| /// |
| /// let program = get_line_number_program(); |
| /// let (program, sequences) = program.sequences().unwrap(); |
| /// println!("There are {} sequences in this line number program", sequences.len()); |
| /// # } |
| /// ``` |
| #[allow(clippy::type_complexity)] |
| pub fn sequences(self) -> Result<(CompleteLineProgram<R, Offset>, Vec<LineSequence<R>>)> { |
| let mut sequences = Vec::new(); |
| let mut rows = self.rows(); |
| let mut instructions = rows.instructions.clone(); |
| let mut sequence_start_addr = None; |
| loop { |
| let sequence_end_addr; |
| if rows.next_row()?.is_none() { |
| break; |
| } |
| |
| let row = &rows.row; |
| if row.end_sequence() { |
| sequence_end_addr = row.address(); |
| } else if sequence_start_addr.is_none() { |
| sequence_start_addr = Some(row.address()); |
| continue; |
| } else { |
| continue; |
| } |
| |
| // We just finished a sequence. |
| sequences.push(LineSequence { |
| // In theory one could have multiple DW_LNE_end_sequence instructions |
| // in a row. |
| start: sequence_start_addr.unwrap_or(0), |
| end: sequence_end_addr, |
| instructions: instructions.remove_trailing(&rows.instructions)?, |
| }); |
| sequence_start_addr = None; |
| instructions = rows.instructions.clone(); |
| } |
| |
| let program = CompleteLineProgram { |
| header: rows.program.header, |
| }; |
| Ok((program, sequences)) |
| } |
| } |
| |
| /// Deprecated. `CompleteLineNumberProgram` has been renamed to `CompleteLineProgram`. |
| #[deprecated( |
| note = "CompleteLineNumberProgram has been renamed to CompleteLineProgram, use that instead." |
| )] |
| pub type CompleteLineNumberProgram<R, Offset> = CompleteLineProgram<R, Offset>; |
| |
| /// A line number program that has previously been run to completion. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct CompleteLineProgram<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| header: LineProgramHeader<R, Offset>, |
| } |
| |
| impl<R, Offset> CompleteLineProgram<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| /// Retrieve the `LineProgramHeader` for this program. |
| pub fn header(&self) -> &LineProgramHeader<R, Offset> { |
| &self.header |
| } |
| |
| /// Construct a new `LineRows` for executing the subset of the line |
| /// number program identified by 'sequence' and generating the line information |
| /// matrix. |
| /// |
| /// ``` |
| /// # fn foo() { |
| /// use gimli::{IncompleteLineProgram, EndianSlice, NativeEndian}; |
| /// |
| /// fn get_line_number_program<'a>() -> IncompleteLineProgram<EndianSlice<'a, NativeEndian>> { |
| /// // Get a line number program from some offset in a |
| /// // `.debug_line` section... |
| /// # unimplemented!() |
| /// } |
| /// |
| /// let program = get_line_number_program(); |
| /// let (program, sequences) = program.sequences().unwrap(); |
| /// for sequence in &sequences { |
| /// let mut sm = program.resume_from(sequence); |
| /// } |
| /// # } |
| /// ``` |
| pub fn resume_from<'program>( |
| &'program self, |
| sequence: &LineSequence<R>, |
| ) -> ResumedLineRows<'program, R, Offset> { |
| ResumedLineRows::resume(self, sequence) |
| } |
| } |
| |
| /// An entry in the `LineProgramHeader`'s `file_names` set. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub struct FileEntry<R, Offset = <R as Reader>::Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| path_name: AttributeValue<R, Offset>, |
| directory_index: u64, |
| timestamp: u64, |
| size: u64, |
| md5: [u8; 16], |
| } |
| |
| impl<R, Offset> FileEntry<R, Offset> |
| where |
| R: Reader<Offset = Offset>, |
| Offset: ReaderOffset, |
| { |
| // version 2-4 |
| fn parse(input: &mut R, path_name: R) -> Result<FileEntry<R, Offset>> { |
| let directory_index = input.read_uleb128()?; |
| let timestamp = input.read_uleb128()?; |
| let size = input.read_uleb128()?; |
| |
| let entry = FileEntry { |
| path_name: AttributeValue::String(path_name), |
| directory_index, |
| timestamp, |
| size, |
| md5: [0; 16], |
| }; |
| |
| Ok(entry) |
| } |
| |
| /// > A slice containing the full or relative path name of |
| /// > a source file. If the entry contains a file name or a relative path |
| /// > name, the file is located relative to either the compilation directory |
| /// > (as specified by the DW_AT_comp_dir attribute given in the compilation |
| /// > unit) or one of the directories in the include_directories section. |
| pub fn path_name(&self) -> AttributeValue<R, Offset> { |
| self.path_name.clone() |
| } |
| |
| /// > An unsigned LEB128 number representing the directory index of the |
| /// > directory in which the file was found. |
| /// > |
| /// > ... |
| /// > |
| /// > The directory index represents an entry in the include_directories |
| /// > section of the line number program header. The index is 0 if the file |
| /// > was found in the current directory of the compilation, 1 if it was found |
| /// > in the first directory in the include_directories section, and so |
| /// > on. The directory index is ignored for file names that represent full |
| /// > path names. |
| pub fn directory_index(&self) -> u64 { |
| self.directory_index |
| } |
| |
| /// Get this file's directory. |
| /// |
| /// A directory index of 0 corresponds to the compilation unit directory. |
| pub fn directory(&self, header: &LineProgramHeader<R>) -> Option<AttributeValue<R, Offset>> { |
| header.directory(self.directory_index) |
| } |
| |
| /// The implementation-defined time of last modification of the file, |
| /// or 0 if not available. |
| pub fn timestamp(&self) -> u64 { |
| self.timestamp |
| } |
| |
| /// "An unsigned LEB128 number representing the time of last modification of |
| /// the file, or 0 if not available." |
| // Terminology changed in DWARF version 5. |
| #[doc(hidden)] |
| pub fn last_modification(&self) -> u64 { |
| self.timestamp |
| } |
| |
| /// The size of the file in bytes, or 0 if not available. |
| pub fn size(&self) -> u64 { |
| self.size |
| } |
| |
| /// "An unsigned LEB128 number representing the length in bytes of the file, |
| /// or 0 if not available." |
| // Terminology changed in DWARF version 5. |
| #[doc(hidden)] |
| pub fn length(&self) -> u64 { |
| self.size |
| } |
| |
| /// A 16-byte MD5 digest of the file contents. |
| /// |
| /// Only valid if `LineProgramHeader::file_has_md5` returns `true`. |
| pub fn md5(&self) -> &[u8; 16] { |
| &self.md5 |
| } |
| } |
| |
| /// The format of a component of an include directory or file name entry. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub struct FileEntryFormat { |
| /// The type of information that is represented by the component. |
| pub content_type: constants::DwLnct, |
| |
| /// The encoding form of the component value. |
| pub form: constants::DwForm, |
| } |
| |
| impl FileEntryFormat { |
| fn parse<R: Reader>(input: &mut R) -> Result<Vec<FileEntryFormat>> { |
| let format_count = input.read_u8()? as usize; |
| let mut format = Vec::with_capacity(format_count); |
| let mut path_count = 0; |
| for _ in 0..format_count { |
| let content_type = input.read_uleb128()?; |
| let content_type = if content_type > u64::from(u16::max_value()) { |
| constants::DwLnct(u16::max_value()) |
| } else { |
| constants::DwLnct(content_type as u16) |
| }; |
| if content_type == constants::DW_LNCT_path { |
| path_count += 1; |
| } |
| |
| let form = constants::DwForm(input.read_uleb128_u16()?); |
| |
| format.push(FileEntryFormat { content_type, form }); |
| } |
| if path_count != 1 { |
| return Err(Error::MissingFileEntryFormatPath); |
| } |
| Ok(format) |
| } |
| } |
| |
| fn parse_directory_v5<R: Reader>( |
| input: &mut R, |
| encoding: Encoding, |
| formats: &[FileEntryFormat], |
| ) -> Result<AttributeValue<R>> { |
| let mut path_name = None; |
| |
| for format in formats { |
| let value = parse_attribute(input, encoding, format.form)?; |
| if format.content_type == constants::DW_LNCT_path { |
| path_name = Some(value); |
| } |
| } |
| |
| Ok(path_name.unwrap()) |
| } |
| |
| fn parse_file_v5<R: Reader>( |
| input: &mut R, |
| encoding: Encoding, |
| formats: &[FileEntryFormat], |
| ) -> Result<FileEntry<R>> { |
| let mut path_name = None; |
| let mut directory_index = 0; |
| let mut timestamp = 0; |
| let mut size = 0; |
| let mut md5 = [0; 16]; |
| |
| for format in formats { |
| let value = parse_attribute(input, encoding, format.form)?; |
| match format.content_type { |
| constants::DW_LNCT_path => path_name = Some(value), |
| constants::DW_LNCT_directory_index => { |
| if let Some(value) = value.udata_value() { |
| directory_index = value; |
| } |
| } |
| constants::DW_LNCT_timestamp => { |
| if let Some(value) = value.udata_value() { |
| timestamp = value; |
| } |
| } |
| constants::DW_LNCT_size => { |
| if let Some(value) = value.udata_value() { |
| size = value; |
| } |
| } |
| constants::DW_LNCT_MD5 => { |
| if let AttributeValue::Block(mut value) = value { |
| if value.len().into_u64() == 16 { |
| md5 = value.read_u8_array()?; |
| } |
| } |
| } |
| // Ignore unknown content types. |
| _ => {} |
| } |
| } |
| |
| Ok(FileEntry { |
| path_name: path_name.unwrap(), |
| directory_index, |
| timestamp, |
| size, |
| md5, |
| }) |
| } |
| |
| // TODO: this should be shared with unit::parse_attribute(), but that is hard to do. |
| fn parse_attribute<R: Reader>( |
| input: &mut R, |
| encoding: Encoding, |
| form: constants::DwForm, |
| ) -> Result<AttributeValue<R>> { |
| Ok(match form { |
| constants::DW_FORM_block1 => { |
| let len = input.read_u8().map(R::Offset::from_u8)?; |
| let block = input.split(len)?; |
| AttributeValue::Block(block) |
| } |
| constants::DW_FORM_block2 => { |
| let len = input.read_u16().map(R::Offset::from_u16)?; |
| let block = input.split(len)?; |
| AttributeValue::Block(block) |
| } |
| constants::DW_FORM_block4 => { |
| let len = input.read_u32().map(R::Offset::from_u32)?; |
| let block = input.split(len)?; |
| AttributeValue::Block(block) |
| } |
| constants::DW_FORM_block => { |
| let len = input.read_uleb128().and_then(R::Offset::from_u64)?; |
| let block = input.split(len)?; |
| AttributeValue::Block(block) |
| } |
| constants::DW_FORM_data1 => { |
| let data = input.read_u8()?; |
| AttributeValue::Data1(data) |
| } |
| constants::DW_FORM_data2 => { |
| let data = input.read_u16()?; |
| AttributeValue::Data2(data) |
| } |
| constants::DW_FORM_data4 => { |
| let data = input.read_u32()?; |
| AttributeValue::Data4(data) |
| } |
| constants::DW_FORM_data8 => { |
| let data = input.read_u64()?; |
| AttributeValue::Data8(data) |
| } |
| constants::DW_FORM_data16 => { |
| let block = input.split(R::Offset::from_u8(16))?; |
| AttributeValue::Block(block) |
| } |
| constants::DW_FORM_udata => { |
| let data = input.read_uleb128()?; |
| AttributeValue::Udata(data) |
| } |
| constants::DW_FORM_sdata => { |
| let data = input.read_sleb128()?; |
| AttributeValue::Sdata(data) |
| } |
| constants::DW_FORM_flag => { |
| let present = input.read_u8()?; |
| AttributeValue::Flag(present != 0) |
| } |
| constants::DW_FORM_sec_offset => { |
| let offset = input.read_offset(encoding.format)?; |
| AttributeValue::SecOffset(offset) |
| } |
| constants::DW_FORM_string => { |
| let string = input.read_null_terminated_slice()?; |
| AttributeValue::String(string) |
| } |
| constants::DW_FORM_strp => { |
| let offset = input.read_offset(encoding.format)?; |
| AttributeValue::DebugStrRef(DebugStrOffset(offset)) |
| } |
| constants::DW_FORM_strp_sup | constants::DW_FORM_GNU_strp_alt => { |
| let offset = input.read_offset(encoding.format)?; |
| AttributeValue::DebugStrRefSup(DebugStrOffset(offset)) |
| } |
| constants::DW_FORM_line_strp => { |
| let offset = input.read_offset(encoding.format)?; |
| AttributeValue::DebugLineStrRef(DebugLineStrOffset(offset)) |
| } |
| constants::DW_FORM_strx | constants::DW_FORM_GNU_str_index => { |
| let index = input.read_uleb128().and_then(R::Offset::from_u64)?; |
| AttributeValue::DebugStrOffsetsIndex(DebugStrOffsetsIndex(index)) |
| } |
| constants::DW_FORM_strx1 => { |
| let index = input.read_u8().map(R::Offset::from_u8)?; |
| AttributeValue::DebugStrOffsetsIndex(DebugStrOffsetsIndex(index)) |
| } |
| constants::DW_FORM_strx2 => { |
| let index = input.read_u16().map(R::Offset::from_u16)?; |
| AttributeValue::DebugStrOffsetsIndex(DebugStrOffsetsIndex(index)) |
| } |
| constants::DW_FORM_strx3 => { |
| let index = input.read_uint(3).and_then(R::Offset::from_u64)?; |
| AttributeValue::DebugStrOffsetsIndex(DebugStrOffsetsIndex(index)) |
| } |
| constants::DW_FORM_strx4 => { |
| let index = input.read_u32().map(R::Offset::from_u32)?; |
| AttributeValue::DebugStrOffsetsIndex(DebugStrOffsetsIndex(index)) |
| } |
| _ => { |
| return Err(Error::UnknownForm); |
| } |
| }) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::constants; |
| use crate::endianity::LittleEndian; |
| use crate::read::{EndianSlice, Error}; |
| use crate::test_util::GimliSectionMethods; |
| use core::u64; |
| use core::u8; |
| use test_assembler::{Endian, Label, LabelMaker, Section}; |
| |
| #[test] |
| fn test_parse_debug_line_32_ok() { |
| #[rustfmt::skip] |
| let buf = [ |
| // 32-bit length = 62. |
| 0x3e, 0x00, 0x00, 0x00, |
| // Version. |
| 0x04, 0x00, |
| // Header length = 40. |
| 0x28, 0x00, 0x00, 0x00, |
| // Minimum instruction length. |
| 0x01, |
| // Maximum operations per byte. |
| 0x01, |
| // Default is_stmt. |
| 0x01, |
| // Line base. |
| 0x00, |
| // Line range. |
| 0x01, |
| // Opcode base. |
| 0x03, |
| // Standard opcode lengths for opcodes 1 .. opcode base - 1. |
| 0x01, 0x02, |
| // Include directories = '/', 'i', 'n', 'c', '\0', '/', 'i', 'n', 'c', '2', '\0', '\0' |
| 0x2f, 0x69, 0x6e, 0x63, 0x00, 0x2f, 0x69, 0x6e, 0x63, 0x32, 0x00, 0x00, |
| // File names |
| // foo.rs |
| 0x66, 0x6f, 0x6f, 0x2e, 0x72, 0x73, 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| // bar.h |
| 0x62, 0x61, 0x72, 0x2e, 0x68, 0x00, |
| 0x01, |
| 0x00, |
| 0x00, |
| // End file names. |
| 0x00, |
| |
| // Dummy line program data. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| |
| // Dummy next line program. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| ]; |
| |
| let rest = &mut EndianSlice::new(&buf, LittleEndian); |
| let comp_dir = EndianSlice::new(b"/comp_dir", LittleEndian); |
| let comp_name = EndianSlice::new(b"/comp_name", LittleEndian); |
| |
| let header = |
| LineProgramHeader::parse(rest, DebugLineOffset(0), 4, Some(comp_dir), Some(comp_name)) |
| .expect("should parse header ok"); |
| |
| assert_eq!( |
| *rest, |
| EndianSlice::new(&buf[buf.len() - 16..], LittleEndian) |
| ); |
| |
| assert_eq!(header.offset, DebugLineOffset(0)); |
| assert_eq!(header.version(), 4); |
| assert_eq!(header.minimum_instruction_length(), 1); |
| assert_eq!(header.maximum_operations_per_instruction(), 1); |
| assert_eq!(header.default_is_stmt(), true); |
| assert_eq!(header.line_base(), 0); |
| assert_eq!(header.line_range(), 1); |
| assert_eq!(header.opcode_base(), 3); |
| assert_eq!(header.directory(0), Some(AttributeValue::String(comp_dir))); |
| assert_eq!( |
| header.file(0).unwrap().path_name, |
| AttributeValue::String(comp_name) |
| ); |
| |
| let expected_lengths = [1, 2]; |
| assert_eq!(header.standard_opcode_lengths().slice(), &expected_lengths); |
| |
| let expected_include_directories = [ |
| AttributeValue::String(EndianSlice::new(b"/inc", LittleEndian)), |
| AttributeValue::String(EndianSlice::new(b"/inc2", LittleEndian)), |
| ]; |
| assert_eq!(header.include_directories(), &expected_include_directories); |
| |
| let expected_file_names = [ |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"foo.rs", LittleEndian)), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }, |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"bar.h", LittleEndian)), |
| directory_index: 1, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }, |
| ]; |
| assert_eq!(&*header.file_names(), &expected_file_names); |
| } |
| |
| #[test] |
| fn test_parse_debug_line_header_length_too_short() { |
| #[rustfmt::skip] |
| let buf = [ |
| // 32-bit length = 62. |
| 0x3e, 0x00, 0x00, 0x00, |
| // Version. |
| 0x04, 0x00, |
| // Header length = 20. TOO SHORT!!! |
| 0x15, 0x00, 0x00, 0x00, |
| // Minimum instruction length. |
| 0x01, |
| // Maximum operations per byte. |
| 0x01, |
| // Default is_stmt. |
| 0x01, |
| // Line base. |
| 0x00, |
| // Line range. |
| 0x01, |
| // Opcode base. |
| 0x03, |
| // Standard opcode lengths for opcodes 1 .. opcode base - 1. |
| 0x01, 0x02, |
| // Include directories = '/', 'i', 'n', 'c', '\0', '/', 'i', 'n', 'c', '2', '\0', '\0' |
| 0x2f, 0x69, 0x6e, 0x63, 0x00, 0x2f, 0x69, 0x6e, 0x63, 0x32, 0x00, 0x00, |
| // File names |
| // foo.rs |
| 0x66, 0x6f, 0x6f, 0x2e, 0x72, 0x73, 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| // bar.h |
| 0x62, 0x61, 0x72, 0x2e, 0x68, 0x00, |
| 0x01, |
| 0x00, |
| 0x00, |
| // End file names. |
| 0x00, |
| |
| // Dummy line program data. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| |
| // Dummy next line program. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| ]; |
| |
| let input = &mut EndianSlice::new(&buf, LittleEndian); |
| |
| match LineProgramHeader::parse(input, DebugLineOffset(0), 4, None, None) { |
| Err(Error::UnexpectedEof(_)) => return, |
| otherwise => panic!("Unexpected result: {:?}", otherwise), |
| } |
| } |
| |
| #[test] |
| fn test_parse_debug_line_unit_length_too_short() { |
| #[rustfmt::skip] |
| let buf = [ |
| // 32-bit length = 40. TOO SHORT!!! |
| 0x28, 0x00, 0x00, 0x00, |
| // Version. |
| 0x04, 0x00, |
| // Header length = 40. |
| 0x28, 0x00, 0x00, 0x00, |
| // Minimum instruction length. |
| 0x01, |
| // Maximum operations per byte. |
| 0x01, |
| // Default is_stmt. |
| 0x01, |
| // Line base. |
| 0x00, |
| // Line range. |
| 0x01, |
| // Opcode base. |
| 0x03, |
| // Standard opcode lengths for opcodes 1 .. opcode base - 1. |
| 0x01, 0x02, |
| // Include directories = '/', 'i', 'n', 'c', '\0', '/', 'i', 'n', 'c', '2', '\0', '\0' |
| 0x2f, 0x69, 0x6e, 0x63, 0x00, 0x2f, 0x69, 0x6e, 0x63, 0x32, 0x00, 0x00, |
| // File names |
| // foo.rs |
| 0x66, 0x6f, 0x6f, 0x2e, 0x72, 0x73, 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| // bar.h |
| 0x62, 0x61, 0x72, 0x2e, 0x68, 0x00, |
| 0x01, |
| 0x00, |
| 0x00, |
| // End file names. |
| 0x00, |
| |
| // Dummy line program data. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| |
| // Dummy next line program. |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| ]; |
| |
| let input = &mut EndianSlice::new(&buf, LittleEndian); |
| |
| match LineProgramHeader::parse(input, DebugLineOffset(0), 4, None, None) { |
| Err(Error::UnexpectedEof(_)) => return, |
| otherwise => panic!("Unexpected result: {:?}", otherwise), |
| } |
| } |
| |
| const OPCODE_BASE: u8 = 13; |
| const STANDARD_OPCODE_LENGTHS: &[u8] = &[0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1]; |
| |
| fn make_test_header( |
| buf: EndianSlice<LittleEndian>, |
| ) -> LineProgramHeader<EndianSlice<LittleEndian>> { |
| let encoding = Encoding { |
| format: Format::Dwarf32, |
| version: 4, |
| address_size: 8, |
| }; |
| let line_encoding = LineEncoding { |
| line_base: -3, |
| line_range: 12, |
| ..Default::default() |
| }; |
| LineProgramHeader { |
| encoding, |
| offset: DebugLineOffset(0), |
| unit_length: 1, |
| header_length: 1, |
| line_encoding, |
| opcode_base: OPCODE_BASE, |
| standard_opcode_lengths: EndianSlice::new(STANDARD_OPCODE_LENGTHS, LittleEndian), |
| file_names: vec![ |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"foo.c", LittleEndian)), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }, |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"bar.rs", LittleEndian)), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }, |
| ], |
| include_directories: vec![], |
| directory_entry_format: vec![], |
| file_name_entry_format: vec![], |
| program_buf: buf, |
| comp_dir: None, |
| comp_file: None, |
| } |
| } |
| |
| fn make_test_program( |
| buf: EndianSlice<LittleEndian>, |
| ) -> IncompleteLineProgram<EndianSlice<LittleEndian>> { |
| IncompleteLineProgram { |
| header: make_test_header(buf), |
| } |
| } |
| |
| #[test] |
| fn test_parse_special_opcodes() { |
| for i in OPCODE_BASE..u8::MAX { |
| let input = [i, 0, 0, 0]; |
| let input = EndianSlice::new(&input, LittleEndian); |
| let header = make_test_header(input); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!(*rest, *input.range_from(1..)); |
| assert_eq!(opcode, LineInstruction::Special(i)); |
| } |
| } |
| |
| #[test] |
| fn test_parse_standard_opcodes() { |
| fn test<Operands>( |
| raw: constants::DwLns, |
| operands: Operands, |
| expected: LineInstruction<EndianSlice<LittleEndian>>, |
| ) where |
| Operands: AsRef<[u8]>, |
| { |
| let mut input = Vec::new(); |
| input.push(raw.0); |
| input.extend_from_slice(operands.as_ref()); |
| |
| let expected_rest = [0, 1, 2, 3, 4]; |
| input.extend_from_slice(&expected_rest); |
| |
| let input = EndianSlice::new(&*input, LittleEndian); |
| let header = make_test_header(input); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!(opcode, expected); |
| assert_eq!(*rest, expected_rest); |
| } |
| |
| test(constants::DW_LNS_copy, [], LineInstruction::Copy); |
| test( |
| constants::DW_LNS_advance_pc, |
| [42], |
| LineInstruction::AdvancePc(42), |
| ); |
| test( |
| constants::DW_LNS_advance_line, |
| [9], |
| LineInstruction::AdvanceLine(9), |
| ); |
| test(constants::DW_LNS_set_file, [7], LineInstruction::SetFile(7)); |
| test( |
| constants::DW_LNS_set_column, |
| [1], |
| LineInstruction::SetColumn(1), |
| ); |
| test( |
| constants::DW_LNS_negate_stmt, |
| [], |
| LineInstruction::NegateStatement, |
| ); |
| test( |
| constants::DW_LNS_set_basic_block, |
| [], |
| LineInstruction::SetBasicBlock, |
| ); |
| test( |
| constants::DW_LNS_const_add_pc, |
| [], |
| LineInstruction::ConstAddPc, |
| ); |
| test( |
| constants::DW_LNS_fixed_advance_pc, |
| [42, 0], |
| LineInstruction::FixedAddPc(42), |
| ); |
| test( |
| constants::DW_LNS_set_prologue_end, |
| [], |
| LineInstruction::SetPrologueEnd, |
| ); |
| test( |
| constants::DW_LNS_set_isa, |
| [57 + 0x80, 100], |
| LineInstruction::SetIsa(12857), |
| ); |
| } |
| |
| #[test] |
| fn test_parse_unknown_standard_opcode_no_args() { |
| let input = [OPCODE_BASE, 1, 2, 3]; |
| let input = EndianSlice::new(&input, LittleEndian); |
| let mut standard_opcode_lengths = Vec::new(); |
| let mut header = make_test_header(input); |
| standard_opcode_lengths.extend(header.standard_opcode_lengths.slice()); |
| standard_opcode_lengths.push(0); |
| header.opcode_base += 1; |
| header.standard_opcode_lengths = EndianSlice::new(&standard_opcode_lengths, LittleEndian); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!( |
| opcode, |
| LineInstruction::UnknownStandard0(constants::DwLns(OPCODE_BASE)) |
| ); |
| assert_eq!(*rest, *input.range_from(1..)); |
| } |
| |
| #[test] |
| fn test_parse_unknown_standard_opcode_one_arg() { |
| let input = [OPCODE_BASE, 1, 2, 3]; |
| let input = EndianSlice::new(&input, LittleEndian); |
| let mut standard_opcode_lengths = Vec::new(); |
| let mut header = make_test_header(input); |
| standard_opcode_lengths.extend(header.standard_opcode_lengths.slice()); |
| standard_opcode_lengths.push(1); |
| header.opcode_base += 1; |
| header.standard_opcode_lengths = EndianSlice::new(&standard_opcode_lengths, LittleEndian); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!( |
| opcode, |
| LineInstruction::UnknownStandard1(constants::DwLns(OPCODE_BASE), 1) |
| ); |
| assert_eq!(*rest, *input.range_from(2..)); |
| } |
| |
| #[test] |
| fn test_parse_unknown_standard_opcode_many_args() { |
| let input = [OPCODE_BASE, 1, 2, 3]; |
| let input = EndianSlice::new(&input, LittleEndian); |
| let args = EndianSlice::new(&input[1..], LittleEndian); |
| let mut standard_opcode_lengths = Vec::new(); |
| let mut header = make_test_header(input); |
| standard_opcode_lengths.extend(header.standard_opcode_lengths.slice()); |
| standard_opcode_lengths.push(3); |
| header.opcode_base += 1; |
| header.standard_opcode_lengths = EndianSlice::new(&standard_opcode_lengths, LittleEndian); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!( |
| opcode, |
| LineInstruction::UnknownStandardN(constants::DwLns(OPCODE_BASE), args) |
| ); |
| assert_eq!(*rest, []); |
| } |
| |
| #[test] |
| fn test_parse_extended_opcodes() { |
| fn test<Operands>( |
| raw: constants::DwLne, |
| operands: Operands, |
| expected: LineInstruction<EndianSlice<LittleEndian>>, |
| ) where |
| Operands: AsRef<[u8]>, |
| { |
| let mut input = Vec::new(); |
| input.push(0); |
| |
| let operands = operands.as_ref(); |
| input.push(1 + operands.len() as u8); |
| |
| input.push(raw.0); |
| input.extend_from_slice(operands); |
| |
| let expected_rest = [0, 1, 2, 3, 4]; |
| input.extend_from_slice(&expected_rest); |
| |
| let input = EndianSlice::new(&input, LittleEndian); |
| let header = make_test_header(input); |
| |
| let mut rest = input; |
| let opcode = |
| LineInstruction::parse(&header, &mut rest).expect("Should parse the opcode OK"); |
| |
| assert_eq!(opcode, expected); |
| assert_eq!(*rest, expected_rest); |
| } |
| |
| test( |
| constants::DW_LNE_end_sequence, |
| [], |
| LineInstruction::EndSequence, |
| ); |
| test( |
| constants::DW_LNE_set_address, |
| [1, 2, 3, 4, 5, 6, 7, 8], |
| LineInstruction::SetAddress(578_437_695_752_307_201), |
| ); |
| test( |
| constants::DW_LNE_set_discriminator, |
| [42], |
| LineInstruction::SetDiscriminator(42), |
| ); |
| |
| let mut file = Vec::new(); |
| // "foo.c" |
| let path_name = [b'f', b'o', b'o', b'.', b'c', 0]; |
| file.extend_from_slice(&path_name); |
| // Directory index. |
| file.push(0); |
| // Last modification of file. |
| file.push(1); |
| // Size of file. |
| file.push(2); |
| |
| test( |
| constants::DW_LNE_define_file, |
| file, |
| LineInstruction::DefineFile(FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"foo.c", LittleEndian)), |
| directory_index: 0, |
| timestamp: 1, |
| size: 2, |
| md5: [0; 16], |
| }), |
| ); |
| |
| // Unknown extended opcode. |
| let operands = [1, 2, 3, 4, 5, 6]; |
| let opcode = constants::DwLne(99); |
| test( |
| opcode, |
| operands, |
| LineInstruction::UnknownExtended(opcode, EndianSlice::new(&operands, LittleEndian)), |
| ); |
| } |
| |
| #[test] |
| fn test_file_entry_directory() { |
| let path_name = [b'f', b'o', b'o', b'.', b'r', b's', 0]; |
| |
| let mut file = FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(&path_name, LittleEndian)), |
| directory_index: 1, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }; |
| |
| let mut header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let dir = AttributeValue::String(EndianSlice::new(b"dir", LittleEndian)); |
| header.include_directories.push(dir); |
| |
| assert_eq!(file.directory(&header), Some(dir)); |
| |
| // Now test the compilation's current directory. |
| file.directory_index = 0; |
| assert_eq!(file.directory(&header), None); |
| } |
| |
| fn assert_exec_opcode<'input>( |
| header: LineProgramHeader<EndianSlice<'input, LittleEndian>>, |
| mut registers: LineRow, |
| opcode: LineInstruction<EndianSlice<'input, LittleEndian>>, |
| expected_registers: LineRow, |
| expect_new_row: bool, |
| ) { |
| let mut program = IncompleteLineProgram { header }; |
| let is_new_row = registers.execute(opcode, &mut program); |
| |
| assert_eq!(is_new_row, expect_new_row); |
| assert_eq!(registers, expected_registers); |
| } |
| |
| #[test] |
| fn test_exec_special_noop() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::Special(16); |
| let expected_registers = initial_registers; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_negative_line_advance() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.line.0 = 10; |
| |
| let opcode = LineInstruction::Special(13); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.line.0 -= 3; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_positive_line_advance() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let initial_registers = LineRow::new(&header); |
| |
| let opcode = LineInstruction::Special(19); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.line.0 += 3; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_positive_address_advance() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let initial_registers = LineRow::new(&header); |
| |
| let opcode = LineInstruction::Special(52); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 3; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_positive_address_and_line_advance() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let initial_registers = LineRow::new(&header); |
| |
| let opcode = LineInstruction::Special(55); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 3; |
| expected_registers.line.0 += 3; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_positive_address_and_negative_line_advance() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.line.0 = 10; |
| |
| let opcode = LineInstruction::Special(49); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 3; |
| expected_registers.line.0 -= 3; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_special_line_underflow() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.line.0 = 2; |
| |
| // -3 line advance. |
| let opcode = LineInstruction::Special(13); |
| |
| let mut expected_registers = initial_registers; |
| // Clamp at 0. No idea if this is the best way to handle this situation |
| // or not... |
| expected_registers.line.0 = 0; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_copy() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.address.0 = 1337; |
| initial_registers.line.0 = 42; |
| |
| let opcode = LineInstruction::Copy; |
| |
| let expected_registers = initial_registers; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_advance_pc() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::AdvancePc(42); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 42; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_advance_pc_overflow() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let opcode = LineInstruction::AdvancePc(42); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.address.0 = u64::MAX; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 = 41; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_advance_line() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::AdvanceLine(42); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.line.0 += 42; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_advance_line_overflow() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let opcode = LineInstruction::AdvanceLine(42); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.line.0 = u64::MAX; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.line.0 = 41; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_set_file_in_bounds() { |
| for file_idx in 1..3 { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetFile(file_idx); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.file = file_idx; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| } |
| |
| #[test] |
| fn test_exec_set_file_out_of_bounds() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetFile(100); |
| |
| // The spec doesn't say anything about rejecting input programs |
| // that set the file register out of bounds of the actual number |
| // of files that have been defined. Instead, we cross our |
| // fingers and hope that one gets defined before |
| // `LineRow::file` gets called and handle the error at |
| // that time if need be. |
| let mut expected_registers = initial_registers; |
| expected_registers.file = 100; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_file_entry_file_index_out_of_bounds() { |
| // These indices are 1-based, so 0 is invalid. 100 is way more than the |
| // number of files defined in the header. |
| let out_of_bounds_indices = [0, 100]; |
| |
| for file_idx in &out_of_bounds_indices[..] { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let mut row = LineRow::new(&header); |
| |
| row.file = *file_idx; |
| |
| assert_eq!(row.file(&header), None); |
| } |
| } |
| |
| #[test] |
| fn test_file_entry_file_index_in_bounds() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let mut row = LineRow::new(&header); |
| |
| row.file = 2; |
| |
| assert_eq!(row.file(&header), Some(&header.file_names()[1])); |
| } |
| |
| #[test] |
| fn test_exec_set_column() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetColumn(42); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.column = 42; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_negate_statement() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::NegateStatement; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.is_stmt = !initial_registers.is_stmt; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_set_basic_block() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.basic_block = false; |
| |
| let opcode = LineInstruction::SetBasicBlock; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.basic_block = true; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_const_add_pc() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::ConstAddPc; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 20; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_fixed_add_pc() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.op_index.0 = 1; |
| |
| let opcode = LineInstruction::FixedAddPc(10); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 += 10; |
| expected_registers.op_index.0 = 0; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_set_prologue_end() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| |
| let mut initial_registers = LineRow::new(&header); |
| initial_registers.prologue_end = false; |
| |
| let opcode = LineInstruction::SetPrologueEnd; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.prologue_end = true; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_set_isa() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetIsa(1993); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.isa = 1993; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_unknown_standard_0() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::UnknownStandard0(constants::DwLns(111)); |
| let expected_registers = initial_registers; |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_unknown_standard_1() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::UnknownStandard1(constants::DwLns(111), 2); |
| let expected_registers = initial_registers; |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_unknown_standard_n() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::UnknownStandardN( |
| constants::DwLns(111), |
| EndianSlice::new(&[2, 2, 2], LittleEndian), |
| ); |
| let expected_registers = initial_registers; |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_end_sequence() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::EndSequence; |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.end_sequence = true; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, true); |
| } |
| |
| #[test] |
| fn test_exec_set_address() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetAddress(3030); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.address.0 = 3030; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_define_file() { |
| let mut program = make_test_program(EndianSlice::new(&[], LittleEndian)); |
| let mut row = LineRow::new(program.header()); |
| |
| let file = FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"test.cpp", LittleEndian)), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [0; 16], |
| }; |
| |
| let opcode = LineInstruction::DefineFile(file); |
| let is_new_row = row.execute(opcode, &mut program); |
| |
| assert_eq!(is_new_row, false); |
| assert_eq!(Some(&file), program.header().file_names.last()); |
| } |
| |
| #[test] |
| fn test_exec_set_discriminator() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::SetDiscriminator(9); |
| |
| let mut expected_registers = initial_registers; |
| expected_registers.discriminator = 9; |
| |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| #[test] |
| fn test_exec_unknown_extended() { |
| let header = make_test_header(EndianSlice::new(&[], LittleEndian)); |
| let initial_registers = LineRow::new(&header); |
| let opcode = LineInstruction::UnknownExtended( |
| constants::DwLne(74), |
| EndianSlice::new(&[], LittleEndian), |
| ); |
| let expected_registers = initial_registers; |
| assert_exec_opcode(header, initial_registers, opcode, expected_registers, false); |
| } |
| |
| /// Ensure that `LineRows<R,P>` is covariant wrt R. |
| /// This only needs to compile. |
| #[allow(dead_code, unreachable_code, unused_variables)] |
| fn test_line_rows_variance<'a, 'b>(_: &'a [u8], _: &'b [u8]) |
| where |
| 'a: 'b, |
| { |
| let a: &OneShotLineRows<EndianSlice<'a, LittleEndian>> = unimplemented!(); |
| let _: &OneShotLineRows<EndianSlice<'b, LittleEndian>> = a; |
| } |
| |
| #[test] |
| fn test_parse_debug_line_v5_ok() { |
| let expected_lengths = &[1, 2]; |
| let expected_program = &[0, 1, 2, 3, 4]; |
| let expected_rest = &[5, 6, 7, 8, 9]; |
| let expected_include_directories = [ |
| AttributeValue::String(EndianSlice::new(b"dir1", LittleEndian)), |
| AttributeValue::String(EndianSlice::new(b"dir2", LittleEndian)), |
| ]; |
| let expected_file_names = [ |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"file1", LittleEndian)), |
| directory_index: 0, |
| timestamp: 0, |
| size: 0, |
| md5: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], |
| }, |
| FileEntry { |
| path_name: AttributeValue::String(EndianSlice::new(b"file2", LittleEndian)), |
| directory_index: 1, |
| timestamp: 0, |
| size: 0, |
| md5: [ |
| 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, |
| ], |
| }, |
| ]; |
| |
| for format in vec![Format::Dwarf32, Format::Dwarf64] { |
| let length = Label::new(); |
| let header_length = Label::new(); |
| let start = Label::new(); |
| let header_start = Label::new(); |
| let end = Label::new(); |
| let header_end = Label::new(); |
| let section = Section::with_endian(Endian::Little) |
| .initial_length(format, &length, &start) |
| .D16(5) |
| // Address size. |
| .D8(4) |
| // Segment selector size. |
| .D8(0) |
| .word_label(format.word_size(), &header_length) |
| .mark(&header_start) |
| // Minimum instruction length. |
| .D8(1) |
| // Maximum operations per byte. |
| .D8(1) |
| // Default is_stmt. |
| .D8(1) |
| // Line base. |
| .D8(0) |
| // Line range. |
| .D8(1) |
| // Opcode base. |
| .D8(expected_lengths.len() as u8 + 1) |
| // Standard opcode lengths for opcodes 1 .. opcode base - 1. |
| .append_bytes(expected_lengths) |
| // Directory entry format count. |
| .D8(1) |
| .uleb(constants::DW_LNCT_path.0 as u64) |
| .uleb(constants::DW_FORM_string.0 as u64) |
| // Directory count. |
| .D8(2) |
| .append_bytes(b"dir1\0") |
| .append_bytes(b"dir2\0") |
| // File entry format count. |
| .D8(3) |
| .uleb(constants::DW_LNCT_path.0 as u64) |
| .uleb(constants::DW_FORM_string.0 as u64) |
| .uleb(constants::DW_LNCT_directory_index.0 as u64) |
| .uleb(constants::DW_FORM_data1.0 as u64) |
| .uleb(constants::DW_LNCT_MD5.0 as u64) |
| .uleb(constants::DW_FORM_data16.0 as u64) |
| // File count. |
| .D8(2) |
| .append_bytes(b"file1\0") |
| .D8(0) |
| .append_bytes(&expected_file_names[0].md5) |
| .append_bytes(b"file2\0") |
| .D8(1) |
| .append_bytes(&expected_file_names[1].md5) |
| .mark(&header_end) |
| // Dummy line program data. |
| .append_bytes(expected_program) |
| .mark(&end) |
| // Dummy trailing data. |
| .append_bytes(expected_rest); |
| length.set_const((&end - &start) as u64); |
| header_length.set_const((&header_end - &header_start) as u64); |
| let section = section.get_contents().unwrap(); |
| |
| let input = &mut EndianSlice::new(§ion, LittleEndian); |
| |
| let header = LineProgramHeader::parse(input, DebugLineOffset(0), 0, None, None) |
| .expect("should parse header ok"); |
| |
| assert_eq!(header.raw_program_buf().slice(), expected_program); |
| assert_eq!(input.slice(), expected_rest); |
| |
| assert_eq!(header.offset, DebugLineOffset(0)); |
| assert_eq!(header.version(), 5); |
| assert_eq!(header.address_size(), 4); |
| assert_eq!(header.minimum_instruction_length(), 1); |
| assert_eq!(header.maximum_operations_per_instruction(), 1); |
| assert_eq!(header.default_is_stmt(), true); |
| assert_eq!(header.line_base(), 0); |
| assert_eq!(header.line_range(), 1); |
| assert_eq!(header.opcode_base(), expected_lengths.len() as u8 + 1); |
| assert_eq!(header.standard_opcode_lengths().slice(), expected_lengths); |
| assert_eq!( |
| header.directory_entry_format(), |
| &[FileEntryFormat { |
| content_type: constants::DW_LNCT_path, |
| form: constants::DW_FORM_string, |
| }] |
| ); |
| assert_eq!(header.include_directories(), expected_include_directories); |
| assert_eq!(header.directory(0), Some(expected_include_directories[0])); |
| assert_eq!( |
| header.file_name_entry_format(), |
| &[ |
| FileEntryFormat { |
| content_type: constants::DW_LNCT_path, |
| form: constants::DW_FORM_string, |
| }, |
| FileEntryFormat { |
| content_type: constants::DW_LNCT_directory_index, |
| form: constants::DW_FORM_data1, |
| }, |
| FileEntryFormat { |
| content_type: constants::DW_LNCT_MD5, |
| form: constants::DW_FORM_data16, |
| } |
| ] |
| ); |
| assert_eq!(header.file_names(), expected_file_names); |
| assert_eq!(header.file(0), Some(&expected_file_names[0])); |
| } |
| } |
| } |