summaryrefslogtreecommitdiff
path: root/vendor/gimli/src/write/line.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gimli/src/write/line.rs')
-rw-r--r--vendor/gimli/src/write/line.rs1957
1 files changed, 1957 insertions, 0 deletions
diff --git a/vendor/gimli/src/write/line.rs b/vendor/gimli/src/write/line.rs
new file mode 100644
index 0000000..c88b735
--- /dev/null
+++ b/vendor/gimli/src/write/line.rs
@@ -0,0 +1,1957 @@
+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();
+ }
+ }
+ }
+ }
+}