| use alloc::vec::Vec; |
| use indexmap::{IndexMap, IndexSet}; |
| use std::ops::{Deref, DerefMut}; |
| |
| use crate::common::{DebugLineOffset, Encoding, Format, LineEncoding, SectionId}; |
| use crate::constants; |
| use crate::leb128; |
| use crate::write::{ |
| Address, DebugLineStrOffsets, DebugStrOffsets, Error, LineStringId, LineStringTable, Result, |
| Section, StringId, Writer, |
| }; |
| |
| /// The number assigned to the first special opcode. |
| // |
| // We output all instructions for all DWARF versions, since readers |
| // should be able to ignore instructions they don't support. |
| const OPCODE_BASE: u8 = 13; |
| |
| /// A line number program. |
| #[derive(Debug, Clone)] |
| pub struct LineProgram { |
| /// True if this line program was created with `LineProgram::none()`. |
| none: bool, |
| encoding: Encoding, |
| line_encoding: LineEncoding, |
| |
| /// A list of source directory path names. |
| /// |
| /// If a path is relative, then the directory is located relative to the working |
| /// directory of the compilation unit. |
| /// |
| /// The first entry is for the working directory of the compilation unit. |
| directories: IndexSet<LineString>, |
| |
| /// A list of source file entries. |
| /// |
| /// Each entry has a path name and a directory. |
| /// |
| /// If a path is a relative, then the file is located relative to the |
| /// directory. Otherwise the directory is meaningless. |
| /// |
| /// Does not include comp_file, even for version >= 5. |
| files: IndexMap<(LineString, DirectoryId), FileInfo>, |
| |
| /// The primary source file of the compilation unit. |
| /// This is required for version >= 5, but we never reference it elsewhere |
| /// because DWARF defines DW_AT_decl_file=0 to mean not specified. |
| comp_file: (LineString, FileInfo), |
| |
| /// True if the file entries may have valid timestamps. |
| /// |
| /// Entries may still have a timestamp of 0 even if this is set. |
| /// For version <= 4, this is ignored. |
| /// For version 5, this controls whether to emit `DW_LNCT_timestamp`. |
| pub file_has_timestamp: bool, |
| |
| /// True if the file entries may have valid sizes. |
| /// |
| /// Entries may still have a size of 0 even if this is set. |
| /// For version <= 4, this is ignored. |
| /// For version 5, this controls whether to emit `DW_LNCT_size`. |
| pub file_has_size: bool, |
| |
| /// True if the file entries have valid MD5 checksums. |
| /// |
| /// For version <= 4, this is ignored. |
| /// For version 5, this controls whether to emit `DW_LNCT_MD5`. |
| pub file_has_md5: bool, |
| |
| prev_row: LineRow, |
| row: LineRow, |
| // TODO: this probably should be either rows or sequences instead |
| instructions: Vec<LineInstruction>, |
| in_sequence: bool, |
| } |
| |
| impl LineProgram { |
| /// Create a new `LineProgram`. |
| /// |
| /// `comp_dir` defines the working directory of the compilation unit, |
| /// and must be the same as the `DW_AT_comp_dir` attribute |
| /// of the compilation unit DIE. |
| /// |
| /// `comp_file` and `comp_file_info` define the primary source file |
| /// of the compilation unit and must be the same as the `DW_AT_name` |
| /// attribute of the compilation unit DIE. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `line_encoding.line_base` > 0. |
| /// |
| /// Panics if `line_encoding.line_base` + `line_encoding.line_range` <= 0. |
| /// |
| /// Panics if `comp_dir` is empty or contains a null byte. |
| /// |
| /// Panics if `comp_file` is empty or contains a null byte. |
| pub fn new( |
| encoding: Encoding, |
| line_encoding: LineEncoding, |
| comp_dir: LineString, |
| comp_file: LineString, |
| comp_file_info: Option<FileInfo>, |
| ) -> LineProgram { |
| // We require a special opcode for a line advance of 0. |
| // See the debug_asserts in generate_row(). |
| assert!(line_encoding.line_base <= 0); |
| assert!(line_encoding.line_base + line_encoding.line_range as i8 > 0); |
| let mut program = LineProgram { |
| none: false, |
| encoding, |
| line_encoding, |
| directories: IndexSet::new(), |
| files: IndexMap::new(), |
| comp_file: (comp_file, comp_file_info.unwrap_or_default()), |
| prev_row: LineRow::initial_state(line_encoding), |
| row: LineRow::initial_state(line_encoding), |
| instructions: Vec::new(), |
| in_sequence: false, |
| file_has_timestamp: false, |
| file_has_size: false, |
| file_has_md5: false, |
| }; |
| // For all DWARF versions, directory index 0 is comp_dir. |
| // For version <= 4, the entry is implicit. We still add |
| // it here so that we use it, but we don't emit it. |
| program.add_directory(comp_dir); |
| program |
| } |
| |
| /// Create a new `LineProgram` with no fields set. |
| /// |
| /// This can be used when the `LineProgram` will not be used. |
| /// |
| /// You should not attempt to add files or line instructions to |
| /// this line program, or write it to the `.debug_line` section. |
| pub fn none() -> Self { |
| let line_encoding = LineEncoding::default(); |
| LineProgram { |
| none: true, |
| encoding: Encoding { |
| format: Format::Dwarf32, |
| version: 2, |
| address_size: 0, |
| }, |
| line_encoding, |
| directories: IndexSet::new(), |
| files: IndexMap::new(), |
| comp_file: (LineString::String(Vec::new()), FileInfo::default()), |
| prev_row: LineRow::initial_state(line_encoding), |
| row: LineRow::initial_state(line_encoding), |
| instructions: Vec::new(), |
| in_sequence: false, |
| file_has_timestamp: false, |
| file_has_size: false, |
| file_has_md5: false, |
| } |
| } |
| |
| /// Return true if this line program was created with `LineProgram::none()`. |
| #[inline] |
| pub fn is_none(&self) -> bool { |
| self.none |
| } |
| |
| /// Return the encoding parameters for this line program. |
| #[inline] |
| pub fn encoding(&self) -> Encoding { |
| self.encoding |
| } |
| |
| /// Return the DWARF version for this line program. |
| #[inline] |
| pub fn version(&self) -> u16 { |
| self.encoding.version |
| } |
| |
| /// Return the address size in bytes for this line program. |
| #[inline] |
| pub fn address_size(&self) -> u8 { |
| self.encoding.address_size |
| } |
| |
| /// Return the DWARF format for this line program. |
| #[inline] |
| pub fn format(&self) -> Format { |
| self.encoding.format |
| } |
| |
| /// Return the id for the working directory of the compilation unit. |
| #[inline] |
| pub fn default_directory(&self) -> DirectoryId { |
| DirectoryId(0) |
| } |
| |
| /// Add a directory entry and return its id. |
| /// |
| /// If the directory already exists, then return the id of the existing entry. |
| /// |
| /// If the path is relative, then the directory is located relative to the working |
| /// directory of the compilation unit. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `directory` is empty or contains a null byte. |
| pub fn add_directory(&mut self, directory: LineString) -> DirectoryId { |
| if let LineString::String(ref val) = directory { |
| // For DWARF version <= 4, directories must not be empty. |
| // The first directory isn't emitted so skip the check for it. |
| if self.encoding.version <= 4 && !self.directories.is_empty() { |
| assert!(!val.is_empty()); |
| } |
| assert!(!val.contains(&0)); |
| } |
| let (index, _) = self.directories.insert_full(directory); |
| DirectoryId(index) |
| } |
| |
| /// Get a reference to a directory entry. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `id` is invalid. |
| pub fn get_directory(&self, id: DirectoryId) -> &LineString { |
| self.directories.get_index(id.0).unwrap() |
| } |
| |
| /// Add a file entry and return its id. |
| /// |
| /// If the file already exists, then return the id of the existing entry. |
| /// |
| /// If the file path is relative, then the file is located relative |
| /// to the directory. Otherwise the directory is meaningless, but it |
| /// is still used as a key for file entries. |
| /// |
| /// If `info` is `None`, then new entries are assigned |
| /// default information, and existing entries are unmodified. |
| /// |
| /// If `info` is not `None`, then it is always assigned to the |
| /// entry, even if the entry already exists. |
| /// |
| /// # Panics |
| /// |
| /// Panics if 'file' is empty or contains a null byte. |
| pub fn add_file( |
| &mut self, |
| file: LineString, |
| directory: DirectoryId, |
| info: Option<FileInfo>, |
| ) -> FileId { |
| if let LineString::String(ref val) = file { |
| assert!(!val.is_empty()); |
| assert!(!val.contains(&0)); |
| } |
| |
| let key = (file, directory); |
| let index = if let Some(info) = info { |
| let (index, _) = self.files.insert_full(key, info); |
| index |
| } else { |
| let entry = self.files.entry(key); |
| let index = entry.index(); |
| entry.or_default(); |
| index |
| }; |
| FileId::new(index) |
| } |
| |
| /// Get a reference to a file entry. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `id` is invalid. |
| pub fn get_file(&self, id: FileId) -> (&LineString, DirectoryId) { |
| match id.index() { |
| None => (&self.comp_file.0, DirectoryId(0)), |
| Some(index) => self |
| .files |
| .get_index(index) |
| .map(|entry| (&(entry.0).0, (entry.0).1)) |
| .unwrap(), |
| } |
| } |
| |
| /// Get a reference to the info for a file entry. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `id` is invalid. |
| pub fn get_file_info(&self, id: FileId) -> &FileInfo { |
| match id.index() { |
| None => &self.comp_file.1, |
| Some(index) => self.files.get_index(index).map(|entry| entry.1).unwrap(), |
| } |
| } |
| |
| /// Get a mutable reference to the info for a file entry. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `id` is invalid. |
| pub fn get_file_info_mut(&mut self, id: FileId) -> &mut FileInfo { |
| match id.index() { |
| None => &mut self.comp_file.1, |
| Some(index) => self |
| .files |
| .get_index_mut(index) |
| .map(|entry| entry.1) |
| .unwrap(), |
| } |
| } |
| |
| /// Begin a new sequence and set its base address. |
| /// |
| /// # Panics |
| /// |
| /// Panics if a sequence has already begun. |
| pub fn begin_sequence(&mut self, address: Option<Address>) { |
| assert!(!self.in_sequence); |
| self.in_sequence = true; |
| if let Some(address) = address { |
| self.instructions.push(LineInstruction::SetAddress(address)); |
| } |
| } |
| |
| /// End the sequence, and reset the row to its default values. |
| /// |
| /// Only the `address_offset` and op_index` fields of the current row are used. |
| /// |
| /// # Panics |
| /// |
| /// Panics if a sequence has not begun. |
| pub fn end_sequence(&mut self, address_offset: u64) { |
| assert!(self.in_sequence); |
| self.in_sequence = false; |
| self.row.address_offset = address_offset; |
| let op_advance = self.op_advance(); |
| if op_advance != 0 { |
| self.instructions |
| .push(LineInstruction::AdvancePc(op_advance)); |
| } |
| self.instructions.push(LineInstruction::EndSequence); |
| self.prev_row = LineRow::initial_state(self.line_encoding); |
| self.row = LineRow::initial_state(self.line_encoding); |
| } |
| |
| /// Return true if a sequence has begun. |
| #[inline] |
| pub fn in_sequence(&self) -> bool { |
| self.in_sequence |
| } |
| |
| /// Returns a reference to the data for the current row. |
| #[inline] |
| pub fn row(&mut self) -> &mut LineRow { |
| &mut self.row |
| } |
| |
| /// Generates the line number information instructions for the current row. |
| /// |
| /// After the instructions are generated, it sets `discriminator` to 0, and sets |
| /// `basic_block`, `prologue_end`, and `epilogue_begin` to false. |
| /// |
| /// # Panics |
| /// |
| /// Panics if a sequence has not begun. |
| /// Panics if the address_offset decreases. |
| pub fn generate_row(&mut self) { |
| assert!(self.in_sequence); |
| |
| // Output fields that are reset on every row. |
| if self.row.discriminator != 0 { |
| self.instructions |
| .push(LineInstruction::SetDiscriminator(self.row.discriminator)); |
| self.row.discriminator = 0; |
| } |
| if self.row.basic_block { |
| self.instructions.push(LineInstruction::SetBasicBlock); |
| self.row.basic_block = false; |
| } |
| if self.row.prologue_end { |
| self.instructions.push(LineInstruction::SetPrologueEnd); |
| self.row.prologue_end = false; |
| } |
| if self.row.epilogue_begin { |
| self.instructions.push(LineInstruction::SetEpilogueBegin); |
| self.row.epilogue_begin = false; |
| } |
| |
| // Output fields that are not reset on every row. |
| if self.row.is_statement != self.prev_row.is_statement { |
| self.instructions.push(LineInstruction::NegateStatement); |
| } |
| if self.row.file != self.prev_row.file { |
| self.instructions |
| .push(LineInstruction::SetFile(self.row.file)); |
| } |
| if self.row.column != self.prev_row.column { |
| self.instructions |
| .push(LineInstruction::SetColumn(self.row.column)); |
| } |
| if self.row.isa != self.prev_row.isa { |
| self.instructions |
| .push(LineInstruction::SetIsa(self.row.isa)); |
| } |
| |
| // Advance the line, address, and operation index. |
| let line_base = i64::from(self.line_encoding.line_base) as u64; |
| let line_range = u64::from(self.line_encoding.line_range); |
| let line_advance = self.row.line as i64 - self.prev_row.line as i64; |
| let op_advance = self.op_advance(); |
| |
| // Default to special advances of 0. |
| let special_base = u64::from(OPCODE_BASE); |
| // TODO: handle lack of special opcodes for 0 line advance |
| debug_assert!(self.line_encoding.line_base <= 0); |
| debug_assert!(self.line_encoding.line_base + self.line_encoding.line_range as i8 >= 0); |
| let special_default = special_base.wrapping_sub(line_base); |
| let mut special = special_default; |
| let mut use_special = false; |
| |
| if line_advance != 0 { |
| let special_line = (line_advance as u64).wrapping_sub(line_base); |
| if special_line < line_range { |
| special = special_base + special_line; |
| use_special = true; |
| } else { |
| self.instructions |
| .push(LineInstruction::AdvanceLine(line_advance)); |
| } |
| } |
| |
| if op_advance != 0 { |
| // Using ConstAddPc can save a byte. |
| let (special_op_advance, const_add_pc) = if special + op_advance * line_range <= 255 { |
| (op_advance, false) |
| } else { |
| let op_range = (255 - special_base) / line_range; |
| (op_advance - op_range, true) |
| }; |
| |
| let special_op = special_op_advance * line_range; |
| if special + special_op <= 255 { |
| special += special_op; |
| use_special = true; |
| if const_add_pc { |
| self.instructions.push(LineInstruction::ConstAddPc); |
| } |
| } else { |
| self.instructions |
| .push(LineInstruction::AdvancePc(op_advance)); |
| } |
| } |
| |
| if use_special && special != special_default { |
| debug_assert!(special >= special_base); |
| debug_assert!(special <= 255); |
| self.instructions |
| .push(LineInstruction::Special(special as u8)); |
| } else { |
| self.instructions.push(LineInstruction::Copy); |
| } |
| |
| self.prev_row = self.row; |
| } |
| |
| fn op_advance(&self) -> u64 { |
| debug_assert!(self.row.address_offset >= self.prev_row.address_offset); |
| let mut address_advance = self.row.address_offset - self.prev_row.address_offset; |
| if self.line_encoding.minimum_instruction_length != 1 { |
| debug_assert_eq!( |
| self.row.address_offset % u64::from(self.line_encoding.minimum_instruction_length), |
| 0 |
| ); |
| address_advance /= u64::from(self.line_encoding.minimum_instruction_length); |
| } |
| address_advance * u64::from(self.line_encoding.maximum_operations_per_instruction) |
| + self.row.op_index |
| - self.prev_row.op_index |
| } |
| |
| /// Returns true if the line number program has no instructions. |
| /// |
| /// Does not check the file or directory entries. |
| #[inline] |
| pub fn is_empty(&self) -> bool { |
| self.instructions.is_empty() |
| } |
| |
| /// Write the line number program to the given section. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `self.is_none()`. |
| pub fn write<W: Writer>( |
| &self, |
| w: &mut DebugLine<W>, |
| encoding: Encoding, |
| debug_line_str_offsets: &DebugLineStrOffsets, |
| debug_str_offsets: &DebugStrOffsets, |
| ) -> Result<DebugLineOffset> { |
| assert!(!self.is_none()); |
| |
| if encoding.version < self.version() |
| || encoding.format != self.format() |
| || encoding.address_size != self.address_size() |
| { |
| return Err(Error::IncompatibleLineProgramEncoding); |
| } |
| |
| let offset = w.offset(); |
| |
| let length_offset = w.write_initial_length(self.format())?; |
| let length_base = w.len(); |
| |
| if self.version() < 2 || self.version() > 5 { |
| return Err(Error::UnsupportedVersion(self.version())); |
| } |
| w.write_u16(self.version())?; |
| |
| if self.version() >= 5 { |
| w.write_u8(self.address_size())?; |
| // Segment selector size. |
| w.write_u8(0)?; |
| } |
| |
| let header_length_offset = w.len(); |
| w.write_udata(0, self.format().word_size())?; |
| let header_length_base = w.len(); |
| |
| w.write_u8(self.line_encoding.minimum_instruction_length)?; |
| if self.version() >= 4 { |
| w.write_u8(self.line_encoding.maximum_operations_per_instruction)?; |
| } else if self.line_encoding.maximum_operations_per_instruction != 1 { |
| return Err(Error::NeedVersion(4)); |
| }; |
| w.write_u8(if self.line_encoding.default_is_stmt { |
| 1 |
| } else { |
| 0 |
| })?; |
| w.write_u8(self.line_encoding.line_base as u8)?; |
| w.write_u8(self.line_encoding.line_range)?; |
| w.write_u8(OPCODE_BASE)?; |
| w.write(&[0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1])?; |
| |
| if self.version() <= 4 { |
| // The first directory is stored as DW_AT_comp_dir. |
| for dir in self.directories.iter().skip(1) { |
| dir.write( |
| w, |
| constants::DW_FORM_string, |
| self.encoding, |
| debug_line_str_offsets, |
| debug_str_offsets, |
| )?; |
| } |
| w.write_u8(0)?; |
| |
| for ((file, dir), info) in self.files.iter() { |
| file.write( |
| w, |
| constants::DW_FORM_string, |
| self.encoding, |
| debug_line_str_offsets, |
| debug_str_offsets, |
| )?; |
| w.write_uleb128(dir.0 as u64)?; |
| w.write_uleb128(info.timestamp)?; |
| w.write_uleb128(info.size)?; |
| } |
| w.write_u8(0)?; |
| } else { |
| // Directory entry formats (only ever 1). |
| w.write_u8(1)?; |
| w.write_uleb128(u64::from(constants::DW_LNCT_path.0))?; |
| let dir_form = self.directories.get_index(0).unwrap().form(); |
| w.write_uleb128(dir_form.0.into())?; |
| |
| // Directory entries. |
| w.write_uleb128(self.directories.len() as u64)?; |
| for dir in self.directories.iter() { |
| dir.write( |
| w, |
| dir_form, |
| self.encoding, |
| debug_line_str_offsets, |
| debug_str_offsets, |
| )?; |
| } |
| |
| // File name entry formats. |
| let count = 2 |
| + if self.file_has_timestamp { 1 } else { 0 } |
| + if self.file_has_size { 1 } else { 0 } |
| + if self.file_has_md5 { 1 } else { 0 }; |
| w.write_u8(count)?; |
| w.write_uleb128(u64::from(constants::DW_LNCT_path.0))?; |
| let file_form = self.comp_file.0.form(); |
| w.write_uleb128(file_form.0.into())?; |
| w.write_uleb128(u64::from(constants::DW_LNCT_directory_index.0))?; |
| w.write_uleb128(constants::DW_FORM_udata.0.into())?; |
| if self.file_has_timestamp { |
| w.write_uleb128(u64::from(constants::DW_LNCT_timestamp.0))?; |
| w.write_uleb128(constants::DW_FORM_udata.0.into())?; |
| } |
| if self.file_has_size { |
| w.write_uleb128(u64::from(constants::DW_LNCT_size.0))?; |
| w.write_uleb128(constants::DW_FORM_udata.0.into())?; |
| } |
| if self.file_has_md5 { |
| w.write_uleb128(u64::from(constants::DW_LNCT_MD5.0))?; |
| w.write_uleb128(constants::DW_FORM_data16.0.into())?; |
| } |
| |
| // File name entries. |
| w.write_uleb128(self.files.len() as u64 + 1)?; |
| let mut write_file = |file: &LineString, dir: DirectoryId, info: &FileInfo| { |
| file.write( |
| w, |
| file_form, |
| self.encoding, |
| debug_line_str_offsets, |
| debug_str_offsets, |
| )?; |
| w.write_uleb128(dir.0 as u64)?; |
| if self.file_has_timestamp { |
| w.write_uleb128(info.timestamp)?; |
| } |
| if self.file_has_size { |
| w.write_uleb128(info.size)?; |
| } |
| if self.file_has_md5 { |
| w.write(&info.md5)?; |
| } |
| Ok(()) |
| }; |
| write_file(&self.comp_file.0, DirectoryId(0), &self.comp_file.1)?; |
| for ((file, dir), info) in self.files.iter() { |
| write_file(file, *dir, info)?; |
| } |
| } |
| |
| let header_length = (w.len() - header_length_base) as u64; |
| w.write_udata_at( |
| header_length_offset, |
| header_length, |
| self.format().word_size(), |
| )?; |
| |
| for instruction in &self.instructions { |
| instruction.write(w, self.address_size())?; |
| } |
| |
| let length = (w.len() - length_base) as u64; |
| w.write_initial_length_at(length_offset, length, self.format())?; |
| |
| Ok(offset) |
| } |
| } |
| |
| /// A row in the line number table that corresponds to a machine instruction. |
| #[derive(Debug, Clone, Copy)] |
| pub struct LineRow { |
| /// The offset of the instruction from the start address of the sequence. |
| pub address_offset: u64, |
| /// The index of an operation within a VLIW instruction. |
| /// |
| /// The index of the first operation is 0. |
| /// Set to 0 for non-VLIW instructions. |
| pub op_index: u64, |
| |
| /// The source file corresponding to the instruction. |
| pub file: FileId, |
| /// The line number within the source file. |
| /// |
| /// Lines are numbered beginning at 1. Set to 0 if there is no source line. |
| pub line: u64, |
| /// The column number within the source line. |
| /// |
| /// Columns are numbered beginning at 1. Set to 0 for the "left edge" of the line. |
| pub column: u64, |
| /// An additional discriminator used to distinguish between source locations. |
| /// This value is assigned arbitrarily by the DWARF producer. |
| pub discriminator: u64, |
| |
| /// Set to true if the instruction is a recommended breakpoint for a statement. |
| pub is_statement: bool, |
| /// Set to true if the instruction is the beginning of a basic block. |
| pub basic_block: bool, |
| /// Set to true if the instruction is a recommended breakpoint at the entry of a |
| /// function. |
| pub prologue_end: bool, |
| /// Set to true if the instruction is a recommended breakpoint prior to the exit of |
| /// a function. |
| pub epilogue_begin: bool, |
| |
| /// The instruction set architecture of the instruction. |
| /// |
| /// Set to 0 for the default ISA. Other values are defined by the architecture ABI. |
| pub isa: u64, |
| } |
| |
| impl LineRow { |
| /// Return the initial state as specified in the DWARF standard. |
| fn initial_state(line_encoding: LineEncoding) -> Self { |
| LineRow { |
| address_offset: 0, |
| op_index: 0, |
| |
| file: FileId::initial_state(), |
| line: 1, |
| column: 0, |
| discriminator: 0, |
| |
| is_statement: line_encoding.default_is_stmt, |
| basic_block: false, |
| prologue_end: false, |
| epilogue_begin: false, |
| |
| isa: 0, |
| } |
| } |
| } |
| |
| /// An instruction in a line number program. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| enum LineInstruction { |
| // Special opcodes |
| Special(u8), |
| |
| // Standard opcodes |
| Copy, |
| AdvancePc(u64), |
| AdvanceLine(i64), |
| SetFile(FileId), |
| SetColumn(u64), |
| NegateStatement, |
| SetBasicBlock, |
| ConstAddPc, |
| // DW_LNS_fixed_advance_pc is not supported. |
| SetPrologueEnd, |
| SetEpilogueBegin, |
| SetIsa(u64), |
| |
| // Extended opcodes |
| EndSequence, |
| // TODO: this doubles the size of this enum. |
| SetAddress(Address), |
| // DW_LNE_define_file is not supported. |
| SetDiscriminator(u64), |
| } |
| |
| impl LineInstruction { |
| /// Write the line number instruction to the given section. |
| fn write<W: Writer>(self, w: &mut DebugLine<W>, address_size: u8) -> Result<()> { |
| use self::LineInstruction::*; |
| match self { |
| Special(val) => w.write_u8(val)?, |
| Copy => w.write_u8(constants::DW_LNS_copy.0)?, |
| AdvancePc(val) => { |
| w.write_u8(constants::DW_LNS_advance_pc.0)?; |
| w.write_uleb128(val)?; |
| } |
| AdvanceLine(val) => { |
| w.write_u8(constants::DW_LNS_advance_line.0)?; |
| w.write_sleb128(val)?; |
| } |
| SetFile(val) => { |
| w.write_u8(constants::DW_LNS_set_file.0)?; |
| w.write_uleb128(val.raw())?; |
| } |
| SetColumn(val) => { |
| w.write_u8(constants::DW_LNS_set_column.0)?; |
| w.write_uleb128(val)?; |
| } |
| NegateStatement => w.write_u8(constants::DW_LNS_negate_stmt.0)?, |
| SetBasicBlock => w.write_u8(constants::DW_LNS_set_basic_block.0)?, |
| ConstAddPc => w.write_u8(constants::DW_LNS_const_add_pc.0)?, |
| SetPrologueEnd => w.write_u8(constants::DW_LNS_set_prologue_end.0)?, |
| SetEpilogueBegin => w.write_u8(constants::DW_LNS_set_epilogue_begin.0)?, |
| SetIsa(val) => { |
| w.write_u8(constants::DW_LNS_set_isa.0)?; |
| w.write_uleb128(val)?; |
| } |
| EndSequence => { |
| w.write_u8(0)?; |
| w.write_uleb128(1)?; |
| w.write_u8(constants::DW_LNE_end_sequence.0)?; |
| } |
| SetAddress(address) => { |
| w.write_u8(0)?; |
| w.write_uleb128(1 + u64::from(address_size))?; |
| w.write_u8(constants::DW_LNE_set_address.0)?; |
| w.write_address(address, address_size)?; |
| } |
| SetDiscriminator(val) => { |
| let mut bytes = [0u8; 10]; |
| // bytes is long enough so this will never fail. |
| let len = leb128::write::unsigned(&mut { &mut bytes[..] }, val).unwrap(); |
| w.write_u8(0)?; |
| w.write_uleb128(1 + len as u64)?; |
| w.write_u8(constants::DW_LNE_set_discriminator.0)?; |
| w.write(&bytes[..len])?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// A string value for use in defining paths in line number programs. |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum LineString { |
| /// A slice of bytes representing a string. Must not include null bytes. |
| /// Not guaranteed to be UTF-8 or anything like that. |
| String(Vec<u8>), |
| |
| /// A reference to a string in the `.debug_str` section. |
| StringRef(StringId), |
| |
| /// A reference to a string in the `.debug_line_str` section. |
| LineStringRef(LineStringId), |
| } |
| |
| impl LineString { |
| /// Create a `LineString` using the normal form for the given encoding. |
| pub fn new<T>(val: T, encoding: Encoding, line_strings: &mut LineStringTable) -> Self |
| where |
| T: Into<Vec<u8>>, |
| { |
| let val = val.into(); |
| if encoding.version <= 4 { |
| LineString::String(val) |
| } else { |
| LineString::LineStringRef(line_strings.add(val)) |
| } |
| } |
| |
| fn form(&self) -> constants::DwForm { |
| match *self { |
| LineString::String(..) => constants::DW_FORM_string, |
| LineString::StringRef(..) => constants::DW_FORM_strp, |
| LineString::LineStringRef(..) => constants::DW_FORM_line_strp, |
| } |
| } |
| |
| fn write<W: Writer>( |
| &self, |
| w: &mut DebugLine<W>, |
| form: constants::DwForm, |
| encoding: Encoding, |
| debug_line_str_offsets: &DebugLineStrOffsets, |
| debug_str_offsets: &DebugStrOffsets, |
| ) -> Result<()> { |
| if form != self.form() { |
| return Err(Error::LineStringFormMismatch); |
| } |
| |
| match *self { |
| LineString::String(ref val) => { |
| if encoding.version <= 4 { |
| debug_assert!(!val.is_empty()); |
| } |
| w.write(val)?; |
| w.write_u8(0)?; |
| } |
| LineString::StringRef(val) => { |
| if encoding.version < 5 { |
| return Err(Error::NeedVersion(5)); |
| } |
| w.write_offset( |
| debug_str_offsets.get(val).0, |
| SectionId::DebugStr, |
| encoding.format.word_size(), |
| )?; |
| } |
| LineString::LineStringRef(val) => { |
| if encoding.version < 5 { |
| return Err(Error::NeedVersion(5)); |
| } |
| w.write_offset( |
| debug_line_str_offsets.get(val).0, |
| SectionId::DebugLineStr, |
| encoding.format.word_size(), |
| )?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// An identifier for a directory in a `LineProgram`. |
| /// |
| /// Defaults to the working directory of the compilation unit. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct DirectoryId(usize); |
| |
| // Force FileId access via the methods. |
| mod id { |
| /// An identifier for a file in a `LineProgram`. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct FileId(usize); |
| |
| impl FileId { |
| /// Create a FileId given an index into `LineProgram::files`. |
| pub(crate) fn new(index: usize) -> Self { |
| FileId(index + 1) |
| } |
| |
| /// The index of the file in `LineProgram::files`. |
| pub(super) fn index(self) -> Option<usize> { |
| if self.0 == 0 { |
| None |
| } else { |
| Some(self.0 - 1) |
| } |
| } |
| |
| /// The initial state of the file register. |
| pub(super) fn initial_state() -> Self { |
| FileId(1) |
| } |
| |
| /// The raw value used when writing. |
| pub(crate) fn raw(self) -> u64 { |
| self.0 as u64 |
| } |
| |
| /// The id for file index 0 in DWARF version 5. |
| /// Only used when converting. |
| // Used for tests only. |
| #[allow(unused)] |
| pub(super) fn zero() -> Self { |
| FileId(0) |
| } |
| } |
| } |
| pub use self::id::*; |
| |
| /// Extra information for file in a `LineProgram`. |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
| pub struct FileInfo { |
| /// The implementation defined timestamp of the last modification of the file, |
| /// or 0 if not available. |
| pub timestamp: u64, |
| |
| /// The size of the file in bytes, or 0 if not available. |
| pub size: u64, |
| |
| /// A 16-byte MD5 digest of the file contents. |
| /// |
| /// Only used if version >= 5 and `LineProgram::file_has_md5` is `true`. |
| pub md5: [u8; 16], |
| } |
| |
| define_section!( |
| DebugLine, |
| DebugLineOffset, |
| "A writable `.debug_line` section." |
| ); |
| |
| #[cfg(feature = "read")] |
| mod convert { |
| use super::*; |
| use crate::read::{self, Reader}; |
| use crate::write::{self, ConvertError, ConvertResult}; |
| |
| impl LineProgram { |
| /// Create a line number program by reading the data from the given program. |
| /// |
| /// Return the program and a mapping from file index to `FileId`. |
| pub fn from<R: Reader<Offset = usize>>( |
| mut from_program: read::IncompleteLineProgram<R>, |
| dwarf: &read::Dwarf<R>, |
| line_strings: &mut write::LineStringTable, |
| strings: &mut write::StringTable, |
| convert_address: &dyn Fn(u64) -> Option<Address>, |
| ) -> ConvertResult<(LineProgram, Vec<FileId>)> { |
| // Create mappings in case the source has duplicate files or directories. |
| let mut dirs = Vec::new(); |
| let mut files = Vec::new(); |
| |
| let mut program = { |
| let from_header = from_program.header(); |
| let encoding = from_header.encoding(); |
| |
| let comp_dir = match from_header.directory(0) { |
| Some(comp_dir) => LineString::from(comp_dir, dwarf, line_strings, strings)?, |
| None => LineString::new(&[][..], encoding, line_strings), |
| }; |
| |
| let (comp_name, comp_file_info) = match from_header.file(0) { |
| Some(comp_file) => { |
| if comp_file.directory_index() != 0 { |
| return Err(ConvertError::InvalidDirectoryIndex); |
| } |
| ( |
| LineString::from(comp_file.path_name(), dwarf, line_strings, strings)?, |
| Some(FileInfo { |
| timestamp: comp_file.timestamp(), |
| size: comp_file.size(), |
| md5: *comp_file.md5(), |
| }), |
| ) |
| } |
| None => (LineString::new(&[][..], encoding, line_strings), None), |
| }; |
| |
| if from_header.line_base() > 0 { |
| return Err(ConvertError::InvalidLineBase); |
| } |
| let mut program = LineProgram::new( |
| encoding, |
| from_header.line_encoding(), |
| comp_dir, |
| comp_name, |
| comp_file_info, |
| ); |
| |
| let file_skip; |
| if from_header.version() <= 4 { |
| // The first directory is implicit. |
| dirs.push(DirectoryId(0)); |
| // A file index of 0 is invalid for version <= 4, but putting |
| // something there makes the indexing easier. |
| file_skip = 0; |
| files.push(FileId::zero()); |
| } else { |
| // We don't add the first file to `files`, but still allow |
| // it to be referenced from converted instructions. |
| file_skip = 1; |
| files.push(FileId::zero()); |
| } |
| |
| for from_dir in from_header.include_directories() { |
| let from_dir = |
| LineString::from(from_dir.clone(), dwarf, line_strings, strings)?; |
| dirs.push(program.add_directory(from_dir)); |
| } |
| |
| program.file_has_timestamp = from_header.file_has_timestamp(); |
| program.file_has_size = from_header.file_has_size(); |
| program.file_has_md5 = from_header.file_has_md5(); |
| for from_file in from_header.file_names().iter().skip(file_skip) { |
| let from_name = |
| LineString::from(from_file.path_name(), dwarf, line_strings, strings)?; |
| let from_dir = from_file.directory_index(); |
| if from_dir >= dirs.len() as u64 { |
| return Err(ConvertError::InvalidDirectoryIndex); |
| } |
| let from_dir = dirs[from_dir as usize]; |
| let from_info = Some(FileInfo { |
| timestamp: from_file.timestamp(), |
| size: from_file.size(), |
| md5: *from_file.md5(), |
| }); |
| files.push(program.add_file(from_name, from_dir, from_info)); |
| } |
| |
| program |
| }; |
| |
| // We can't use the `from_program.rows()` because that wouldn't let |
| // us preserve address relocations. |
| let mut from_row = read::LineRow::new(from_program.header()); |
| let mut instructions = from_program.header().instructions(); |
| let mut address = None; |
| while let Some(instruction) = instructions.next_instruction(from_program.header())? { |
| match instruction { |
| read::LineInstruction::SetAddress(val) => { |
| if program.in_sequence() { |
| return Err(ConvertError::UnsupportedLineInstruction); |
| } |
| match convert_address(val) { |
| Some(val) => address = Some(val), |
| None => return Err(ConvertError::InvalidAddress), |
| } |
| from_row.execute(read::LineInstruction::SetAddress(0), &mut from_program); |
| } |
| read::LineInstruction::DefineFile(_) => { |
| return Err(ConvertError::UnsupportedLineInstruction); |
| } |
| _ => { |
| if from_row.execute(instruction, &mut from_program) { |
| if !program.in_sequence() { |
| program.begin_sequence(address); |
| address = None; |
| } |
| if from_row.end_sequence() { |
| program.end_sequence(from_row.address()); |
| } else { |
| program.row().address_offset = from_row.address(); |
| program.row().op_index = from_row.op_index(); |
| program.row().file = { |
| let file = from_row.file_index(); |
| if file >= files.len() as u64 { |
| return Err(ConvertError::InvalidFileIndex); |
| } |
| if file == 0 && program.version() <= 4 { |
| return Err(ConvertError::InvalidFileIndex); |
| } |
| files[file as usize] |
| }; |
| program.row().line = match from_row.line() { |
| Some(line) => line.get(), |
| None => 0, |
| }; |
| program.row().column = match from_row.column() { |
| read::ColumnType::LeftEdge => 0, |
| read::ColumnType::Column(val) => val.get(), |
| }; |
| program.row().discriminator = from_row.discriminator(); |
| program.row().is_statement = from_row.is_stmt(); |
| program.row().basic_block = from_row.basic_block(); |
| program.row().prologue_end = from_row.prologue_end(); |
| program.row().epilogue_begin = from_row.epilogue_begin(); |
| program.row().isa = from_row.isa(); |
| program.generate_row(); |
| } |
| from_row.reset(from_program.header()); |
| } |
| } |
| }; |
| } |
| Ok((program, files)) |
| } |
| } |
| |
| impl LineString { |
| fn from<R: Reader<Offset = usize>>( |
| from_attr: read::AttributeValue<R>, |
| dwarf: &read::Dwarf<R>, |
| line_strings: &mut write::LineStringTable, |
| strings: &mut write::StringTable, |
| ) -> ConvertResult<LineString> { |
| Ok(match from_attr { |
| read::AttributeValue::String(r) => LineString::String(r.to_slice()?.to_vec()), |
| read::AttributeValue::DebugStrRef(offset) => { |
| let r = dwarf.debug_str.get_str(offset)?; |
| let id = strings.add(r.to_slice()?); |
| LineString::StringRef(id) |
| } |
| read::AttributeValue::DebugLineStrRef(offset) => { |
| let r = dwarf.debug_line_str.get_str(offset)?; |
| let id = line_strings.add(r.to_slice()?); |
| LineString::LineStringRef(id) |
| } |
| _ => return Err(ConvertError::UnsupportedLineStringForm), |
| }) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| #[cfg(feature = "read")] |
| mod tests { |
| use super::*; |
| use crate::read; |
| use crate::write::{DebugLineStr, DebugStr, EndianVec, StringTable}; |
| use crate::LittleEndian; |
| |
| #[test] |
| fn test_line_program_table() { |
| let dir1 = LineString::String(b"dir1".to_vec()); |
| let file1 = LineString::String(b"file1".to_vec()); |
| let dir2 = LineString::String(b"dir2".to_vec()); |
| let file2 = LineString::String(b"file2".to_vec()); |
| |
| let mut programs = Vec::new(); |
| for &version in &[2, 3, 4, 5] { |
| for &address_size in &[4, 8] { |
| for &format in &[Format::Dwarf32, Format::Dwarf64] { |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| let mut program = LineProgram::new( |
| encoding, |
| LineEncoding::default(), |
| dir1.clone(), |
| file1.clone(), |
| None, |
| ); |
| |
| { |
| assert_eq!(&dir1, program.get_directory(program.default_directory())); |
| program.file_has_timestamp = true; |
| program.file_has_size = true; |
| if encoding.version >= 5 { |
| program.file_has_md5 = true; |
| } |
| |
| let dir_id = program.add_directory(dir2.clone()); |
| assert_eq!(&dir2, program.get_directory(dir_id)); |
| assert_eq!(dir_id, program.add_directory(dir2.clone())); |
| |
| let file_info = FileInfo { |
| timestamp: 1, |
| size: 2, |
| md5: if encoding.version >= 5 { |
| [3; 16] |
| } else { |
| [0; 16] |
| }, |
| }; |
| let file_id = program.add_file(file2.clone(), dir_id, Some(file_info)); |
| assert_eq!((&file2, dir_id), program.get_file(file_id)); |
| assert_eq!(file_info, *program.get_file_info(file_id)); |
| |
| program.get_file_info_mut(file_id).size = 3; |
| assert_ne!(file_info, *program.get_file_info(file_id)); |
| assert_eq!(file_id, program.add_file(file2.clone(), dir_id, None)); |
| assert_ne!(file_info, *program.get_file_info(file_id)); |
| assert_eq!( |
| file_id, |
| program.add_file(file2.clone(), dir_id, Some(file_info)) |
| ); |
| assert_eq!(file_info, *program.get_file_info(file_id)); |
| |
| programs.push((program, file_id, encoding)); |
| } |
| } |
| } |
| } |
| |
| let debug_line_str_offsets = DebugLineStrOffsets::none(); |
| let debug_str_offsets = DebugStrOffsets::none(); |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let mut debug_line_offsets = Vec::new(); |
| for (program, _, encoding) in &programs { |
| debug_line_offsets.push( |
| program |
| .write( |
| &mut debug_line, |
| *encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(), |
| ); |
| } |
| |
| let read_debug_line = read::DebugLine::new(debug_line.slice(), LittleEndian); |
| |
| let convert_address = &|address| Some(Address::Constant(address)); |
| for ((program, file_id, encoding), offset) in programs.iter().zip(debug_line_offsets.iter()) |
| { |
| let read_program = read_debug_line |
| .program( |
| *offset, |
| encoding.address_size, |
| Some(read::EndianSlice::new(b"dir1", LittleEndian)), |
| Some(read::EndianSlice::new(b"file1", LittleEndian)), |
| ) |
| .unwrap(); |
| |
| let dwarf = read::Dwarf::default(); |
| let mut convert_line_strings = LineStringTable::default(); |
| let mut convert_strings = StringTable::default(); |
| let (convert_program, convert_files) = LineProgram::from( |
| read_program, |
| &dwarf, |
| &mut convert_line_strings, |
| &mut convert_strings, |
| convert_address, |
| ) |
| .unwrap(); |
| assert_eq!(convert_program.version(), program.version()); |
| assert_eq!(convert_program.address_size(), program.address_size()); |
| assert_eq!(convert_program.format(), program.format()); |
| |
| let convert_file_id = convert_files[file_id.raw() as usize]; |
| let (file, dir) = program.get_file(*file_id); |
| let (convert_file, convert_dir) = convert_program.get_file(convert_file_id); |
| assert_eq!(file, convert_file); |
| assert_eq!( |
| program.get_directory(dir), |
| convert_program.get_directory(convert_dir) |
| ); |
| assert_eq!( |
| program.get_file_info(*file_id), |
| convert_program.get_file_info(convert_file_id) |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_line_row() { |
| let dir1 = &b"dir1"[..]; |
| let file1 = &b"file1"[..]; |
| let file2 = &b"file2"[..]; |
| let convert_address = &|address| Some(Address::Constant(address)); |
| |
| let debug_line_str_offsets = DebugLineStrOffsets::none(); |
| let debug_str_offsets = DebugStrOffsets::none(); |
| |
| for &version in &[2, 3, 4, 5] { |
| for &address_size in &[4, 8] { |
| for &format in &[Format::Dwarf32, Format::Dwarf64] { |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| let line_base = -5; |
| let line_range = 14; |
| let neg_line_base = (-line_base) as u8; |
| let mut program = LineProgram::new( |
| encoding, |
| LineEncoding { |
| line_base, |
| line_range, |
| ..Default::default() |
| }, |
| LineString::String(dir1.to_vec()), |
| LineString::String(file1.to_vec()), |
| None, |
| ); |
| let dir_id = program.default_directory(); |
| program.add_file(LineString::String(file1.to_vec()), dir_id, None); |
| let file_id = |
| program.add_file(LineString::String(file2.to_vec()), dir_id, None); |
| |
| // Test sequences. |
| { |
| let mut program = program.clone(); |
| let address = Address::Constant(0x12); |
| program.begin_sequence(Some(address)); |
| assert_eq!( |
| program.instructions, |
| vec![LineInstruction::SetAddress(address)] |
| ); |
| } |
| |
| { |
| let mut program = program.clone(); |
| program.begin_sequence(None); |
| assert_eq!(program.instructions, Vec::new()); |
| } |
| |
| { |
| let mut program = program.clone(); |
| program.begin_sequence(None); |
| program.end_sequence(0x1234); |
| assert_eq!( |
| program.instructions, |
| vec![ |
| LineInstruction::AdvancePc(0x1234), |
| LineInstruction::EndSequence |
| ] |
| ); |
| } |
| |
| // Create a base program. |
| program.begin_sequence(None); |
| program.row.line = 0x1000; |
| program.generate_row(); |
| let base_row = program.row; |
| let base_instructions = program.instructions.clone(); |
| |
| // Create test cases. |
| let mut tests = Vec::new(); |
| |
| let row = base_row; |
| tests.push((row, vec![LineInstruction::Copy])); |
| |
| let mut row = base_row; |
| row.line -= u64::from(neg_line_base); |
| tests.push((row, vec![LineInstruction::Special(OPCODE_BASE)])); |
| |
| let mut row = base_row; |
| row.line += u64::from(line_range) - 1; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![LineInstruction::Special(OPCODE_BASE + line_range - 1)], |
| )); |
| |
| let mut row = base_row; |
| row.line += u64::from(line_range); |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![ |
| LineInstruction::AdvanceLine(i64::from(line_range - neg_line_base)), |
| LineInstruction::Copy, |
| ], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = 1; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![LineInstruction::Special(OPCODE_BASE + line_range)], |
| )); |
| |
| let op_range = (255 - OPCODE_BASE) / line_range; |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range); |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![LineInstruction::Special( |
| OPCODE_BASE + op_range * line_range, |
| )], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range); |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range); |
| row.line -= u64::from(neg_line_base); |
| tests.push((row, vec![LineInstruction::Special(255)])); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range); |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range) + 1; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![LineInstruction::ConstAddPc, LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range); |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range) + 2; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![ |
| LineInstruction::ConstAddPc, |
| LineInstruction::Special(OPCODE_BASE + 6), |
| ], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range) * 2; |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range); |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![LineInstruction::ConstAddPc, LineInstruction::Special(255)], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range) * 2; |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range) + 1; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![ |
| LineInstruction::AdvancePc(row.address_offset), |
| LineInstruction::Copy, |
| ], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = u64::from(op_range) * 2; |
| row.line += u64::from(255 - OPCODE_BASE - op_range * line_range) + 2; |
| row.line -= u64::from(neg_line_base); |
| tests.push(( |
| row, |
| vec![ |
| LineInstruction::AdvancePc(row.address_offset), |
| LineInstruction::Special(OPCODE_BASE + 6), |
| ], |
| )); |
| |
| let mut row = base_row; |
| row.address_offset = 0x1234; |
| tests.push(( |
| row, |
| vec![LineInstruction::AdvancePc(0x1234), LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.line += 0x1234; |
| tests.push(( |
| row, |
| vec![LineInstruction::AdvanceLine(0x1234), LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.file = file_id; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetFile(file_id), LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.column = 0x1234; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetColumn(0x1234), LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.discriminator = 0x1234; |
| tests.push(( |
| row, |
| vec![ |
| LineInstruction::SetDiscriminator(0x1234), |
| LineInstruction::Copy, |
| ], |
| )); |
| |
| let mut row = base_row; |
| row.is_statement = !row.is_statement; |
| tests.push(( |
| row, |
| vec![LineInstruction::NegateStatement, LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.basic_block = true; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetBasicBlock, LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.prologue_end = true; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetPrologueEnd, LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.epilogue_begin = true; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetEpilogueBegin, LineInstruction::Copy], |
| )); |
| |
| let mut row = base_row; |
| row.isa = 0x1234; |
| tests.push(( |
| row, |
| vec![LineInstruction::SetIsa(0x1234), LineInstruction::Copy], |
| )); |
| |
| for test in tests { |
| // Test generate_row(). |
| let mut program = program.clone(); |
| program.row = test.0; |
| program.generate_row(); |
| assert_eq!( |
| &program.instructions[base_instructions.len()..], |
| &test.1[..] |
| ); |
| |
| // Test LineProgram::from(). |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let debug_line_offset = program |
| .write( |
| &mut debug_line, |
| encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(); |
| |
| let read_debug_line = |
| read::DebugLine::new(debug_line.slice(), LittleEndian); |
| let read_program = read_debug_line |
| .program( |
| debug_line_offset, |
| address_size, |
| Some(read::EndianSlice::new(dir1, LittleEndian)), |
| Some(read::EndianSlice::new(file1, LittleEndian)), |
| ) |
| .unwrap(); |
| |
| let dwarf = read::Dwarf::default(); |
| let mut convert_line_strings = LineStringTable::default(); |
| let mut convert_strings = StringTable::default(); |
| let (convert_program, _convert_files) = LineProgram::from( |
| read_program, |
| &dwarf, |
| &mut convert_line_strings, |
| &mut convert_strings, |
| convert_address, |
| ) |
| .unwrap(); |
| assert_eq!( |
| &convert_program.instructions[base_instructions.len()..], |
| &test.1[..] |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn test_line_instruction() { |
| let dir1 = &b"dir1"[..]; |
| let file1 = &b"file1"[..]; |
| |
| let debug_line_str_offsets = DebugLineStrOffsets::none(); |
| let debug_str_offsets = DebugStrOffsets::none(); |
| |
| for &version in &[2, 3, 4, 5] { |
| for &address_size in &[4, 8] { |
| for &format in &[Format::Dwarf32, Format::Dwarf64] { |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| let mut program = LineProgram::new( |
| encoding, |
| LineEncoding::default(), |
| LineString::String(dir1.to_vec()), |
| LineString::String(file1.to_vec()), |
| None, |
| ); |
| let dir_id = program.default_directory(); |
| let file_id = |
| program.add_file(LineString::String(file1.to_vec()), dir_id, None); |
| |
| for &(ref inst, ref expect_inst) in &[ |
| ( |
| LineInstruction::Special(OPCODE_BASE), |
| read::LineInstruction::Special(OPCODE_BASE), |
| ), |
| ( |
| LineInstruction::Special(255), |
| read::LineInstruction::Special(255), |
| ), |
| (LineInstruction::Copy, read::LineInstruction::Copy), |
| ( |
| LineInstruction::AdvancePc(0x12), |
| read::LineInstruction::AdvancePc(0x12), |
| ), |
| ( |
| LineInstruction::AdvanceLine(0x12), |
| read::LineInstruction::AdvanceLine(0x12), |
| ), |
| ( |
| LineInstruction::SetFile(file_id), |
| read::LineInstruction::SetFile(file_id.raw()), |
| ), |
| ( |
| LineInstruction::SetColumn(0x12), |
| read::LineInstruction::SetColumn(0x12), |
| ), |
| ( |
| LineInstruction::NegateStatement, |
| read::LineInstruction::NegateStatement, |
| ), |
| ( |
| LineInstruction::SetBasicBlock, |
| read::LineInstruction::SetBasicBlock, |
| ), |
| ( |
| LineInstruction::ConstAddPc, |
| read::LineInstruction::ConstAddPc, |
| ), |
| ( |
| LineInstruction::SetPrologueEnd, |
| read::LineInstruction::SetPrologueEnd, |
| ), |
| ( |
| LineInstruction::SetEpilogueBegin, |
| read::LineInstruction::SetEpilogueBegin, |
| ), |
| ( |
| LineInstruction::SetIsa(0x12), |
| read::LineInstruction::SetIsa(0x12), |
| ), |
| ( |
| LineInstruction::EndSequence, |
| read::LineInstruction::EndSequence, |
| ), |
| ( |
| LineInstruction::SetAddress(Address::Constant(0x12)), |
| read::LineInstruction::SetAddress(0x12), |
| ), |
| ( |
| LineInstruction::SetDiscriminator(0x12), |
| read::LineInstruction::SetDiscriminator(0x12), |
| ), |
| ][..] |
| { |
| let mut program = program.clone(); |
| program.instructions.push(*inst); |
| |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let debug_line_offset = program |
| .write( |
| &mut debug_line, |
| encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(); |
| |
| let read_debug_line = |
| read::DebugLine::new(debug_line.slice(), LittleEndian); |
| let read_program = read_debug_line |
| .program( |
| debug_line_offset, |
| address_size, |
| Some(read::EndianSlice::new(dir1, LittleEndian)), |
| Some(read::EndianSlice::new(file1, LittleEndian)), |
| ) |
| .unwrap(); |
| let read_header = read_program.header(); |
| let mut read_insts = read_header.instructions(); |
| assert_eq!( |
| *expect_inst, |
| read_insts.next_instruction(read_header).unwrap().unwrap() |
| ); |
| assert_eq!(None, read_insts.next_instruction(read_header).unwrap()); |
| } |
| } |
| } |
| } |
| } |
| |
| // Test that the address/line advance is correct. We don't test for optimality. |
| #[test] |
| fn test_advance() { |
| let encoding = Encoding { |
| format: Format::Dwarf32, |
| version: 4, |
| address_size: 8, |
| }; |
| |
| let dir1 = &b"dir1"[..]; |
| let file1 = &b"file1"[..]; |
| |
| let addresses = 0..50; |
| let lines = -10..25i64; |
| |
| let debug_line_str_offsets = DebugLineStrOffsets::none(); |
| let debug_str_offsets = DebugStrOffsets::none(); |
| |
| for minimum_instruction_length in vec![1, 4] { |
| for maximum_operations_per_instruction in vec![1, 3] { |
| for line_base in vec![-5, 0] { |
| for line_range in vec![10, 20] { |
| let line_encoding = LineEncoding { |
| minimum_instruction_length, |
| maximum_operations_per_instruction, |
| line_base, |
| line_range, |
| default_is_stmt: true, |
| }; |
| let mut program = LineProgram::new( |
| encoding, |
| line_encoding, |
| LineString::String(dir1.to_vec()), |
| LineString::String(file1.to_vec()), |
| None, |
| ); |
| for address_advance in addresses.clone() { |
| program.begin_sequence(Some(Address::Constant(0x1000))); |
| program.row().line = 0x10000; |
| program.generate_row(); |
| for line_advance in lines.clone() { |
| { |
| let row = program.row(); |
| row.address_offset += |
| address_advance * u64::from(minimum_instruction_length); |
| row.line = row.line.wrapping_add(line_advance as u64); |
| } |
| program.generate_row(); |
| } |
| let address_offset = program.row().address_offset |
| + u64::from(minimum_instruction_length); |
| program.end_sequence(address_offset); |
| } |
| |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let debug_line_offset = program |
| .write( |
| &mut debug_line, |
| encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(); |
| |
| let read_debug_line = |
| read::DebugLine::new(debug_line.slice(), LittleEndian); |
| let read_program = read_debug_line |
| .program( |
| debug_line_offset, |
| 8, |
| Some(read::EndianSlice::new(dir1, LittleEndian)), |
| Some(read::EndianSlice::new(file1, LittleEndian)), |
| ) |
| .unwrap(); |
| |
| let mut rows = read_program.rows(); |
| for address_advance in addresses.clone() { |
| let mut address; |
| let mut line; |
| { |
| let row = rows.next_row().unwrap().unwrap().1; |
| address = row.address(); |
| line = row.line().unwrap().get(); |
| } |
| assert_eq!(address, 0x1000); |
| assert_eq!(line, 0x10000); |
| for line_advance in lines.clone() { |
| let row = rows.next_row().unwrap().unwrap().1; |
| assert_eq!( |
| row.address() - address, |
| address_advance * u64::from(minimum_instruction_length) |
| ); |
| assert_eq!( |
| (row.line().unwrap().get() as i64) - (line as i64), |
| line_advance |
| ); |
| address = row.address(); |
| line = row.line().unwrap().get(); |
| } |
| let row = rows.next_row().unwrap().unwrap().1; |
| assert!(row.end_sequence()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn test_line_string() { |
| let version = 5; |
| |
| let file = b"file1"; |
| |
| let mut strings = StringTable::default(); |
| let string_id = strings.add("file2"); |
| let mut debug_str = DebugStr::from(EndianVec::new(LittleEndian)); |
| let debug_str_offsets = strings.write(&mut debug_str).unwrap(); |
| |
| let mut line_strings = LineStringTable::default(); |
| let line_string_id = line_strings.add("file3"); |
| let mut debug_line_str = DebugLineStr::from(EndianVec::new(LittleEndian)); |
| let debug_line_str_offsets = line_strings.write(&mut debug_line_str).unwrap(); |
| |
| for &address_size in &[4, 8] { |
| for &format in &[Format::Dwarf32, Format::Dwarf64] { |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| |
| for (file, expect_file) in vec![ |
| ( |
| LineString::String(file.to_vec()), |
| read::AttributeValue::String(read::EndianSlice::new(file, LittleEndian)), |
| ), |
| ( |
| LineString::StringRef(string_id), |
| read::AttributeValue::DebugStrRef(debug_str_offsets.get(string_id)), |
| ), |
| ( |
| LineString::LineStringRef(line_string_id), |
| read::AttributeValue::DebugLineStrRef( |
| debug_line_str_offsets.get(line_string_id), |
| ), |
| ), |
| ] { |
| let program = LineProgram::new( |
| encoding, |
| LineEncoding::default(), |
| LineString::String(b"dir".to_vec()), |
| file, |
| None, |
| ); |
| |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let debug_line_offset = program |
| .write( |
| &mut debug_line, |
| encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(); |
| |
| let read_debug_line = read::DebugLine::new(debug_line.slice(), LittleEndian); |
| let read_program = read_debug_line |
| .program(debug_line_offset, address_size, None, None) |
| .unwrap(); |
| let read_header = read_program.header(); |
| assert_eq!(read_header.file(0).unwrap().path_name(), expect_file); |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn test_missing_comp_dir() { |
| let debug_line_str_offsets = DebugLineStrOffsets::none(); |
| let debug_str_offsets = DebugStrOffsets::none(); |
| |
| for &version in &[2, 3, 4, 5] { |
| for &address_size in &[4, 8] { |
| for &format in &[Format::Dwarf32, Format::Dwarf64] { |
| let encoding = Encoding { |
| format, |
| version, |
| address_size, |
| }; |
| let program = LineProgram::new( |
| encoding, |
| LineEncoding::default(), |
| LineString::String(Vec::new()), |
| LineString::String(Vec::new()), |
| None, |
| ); |
| |
| let mut debug_line = DebugLine::from(EndianVec::new(LittleEndian)); |
| let debug_line_offset = program |
| .write( |
| &mut debug_line, |
| encoding, |
| &debug_line_str_offsets, |
| &debug_str_offsets, |
| ) |
| .unwrap(); |
| |
| let read_debug_line = read::DebugLine::new(debug_line.slice(), LittleEndian); |
| let read_program = read_debug_line |
| .program( |
| debug_line_offset, |
| address_size, |
| // Testing missing comp_dir/comp_name. |
| None, |
| None, |
| ) |
| .unwrap(); |
| |
| let dwarf = read::Dwarf::default(); |
| let mut convert_line_strings = LineStringTable::default(); |
| let mut convert_strings = StringTable::default(); |
| let convert_address = &|address| Some(Address::Constant(address)); |
| LineProgram::from( |
| read_program, |
| &dwarf, |
| &mut convert_line_strings, |
| &mut convert_strings, |
| convert_address, |
| ) |
| .unwrap(); |
| } |
| } |
| } |
| } |
| } |