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,
};
const OPCODE_BASE: u8 = 13;
#[derive(Debug, Clone)]
pub struct LineProgram {
none: bool,
encoding: Encoding,
line_encoding: LineEncoding,
directories: IndexSet<LineString>,
files: IndexMap<(LineString, DirectoryId), FileInfo>,
comp_file: (LineString, FileInfo),
pub file_has_timestamp: bool,
pub file_has_size: bool,
pub file_has_md5: bool,
prev_row: LineRow,
row: LineRow,
instructions: Vec<LineInstruction>,
in_sequence: bool,
}
impl LineProgram {
#[allow(clippy::too_many_arguments)]
#[allow(clippy::new_ret_no_self)]
pub fn new(
encoding: Encoding,
line_encoding: LineEncoding,
comp_dir: LineString,
comp_file: LineString,
comp_file_info: Option<FileInfo>,
) -> LineProgram {
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,
};
program.add_directory(comp_dir);
program
}
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,
}
}
#[inline]
pub fn is_none(&self) -> bool {
self.none
}
#[inline]
pub fn encoding(&self) -> Encoding {
self.encoding
}
#[inline]
pub fn version(&self) -> u16 {
self.encoding.version
}
#[inline]
pub fn address_size(&self) -> u8 {
self.encoding.address_size
}
#[inline]
pub fn format(&self) -> Format {
self.encoding.format
}
#[inline]
pub fn default_directory(&self) -> DirectoryId {
DirectoryId(0)
}
pub fn add_directory(&mut self, directory: LineString) -> DirectoryId {
if let LineString::String(ref val) = directory {
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)
}
pub fn get_directory(&self, id: DirectoryId) -> &LineString {
self.directories.get_index(id.0).unwrap()
}
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_insert(FileInfo::default());
index
};
FileId::new(index)
}
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(),
}
}
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(),
}
}
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(),
}
}
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));
}
}
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);
}
#[inline]
pub fn in_sequence(&self) -> bool {
self.in_sequence
}
#[inline]
pub fn row(&mut self) -> &mut LineRow {
&mut self.row
}
pub fn generate_row(&mut self) {
assert!(self.in_sequence);
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;
}
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));
}
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();
let special_base = u64::from(OPCODE_BASE);
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 {
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
}
#[inline]
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
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())?;
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 {
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 {
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())?;
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,
)?;
}
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())?;
}
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)
}
}
#[derive(Debug, Clone, Copy)]
pub struct LineRow {
pub address_offset: u64,
pub op_index: u64,
pub file: FileId,
pub line: u64,
pub column: u64,
pub discriminator: u64,
pub is_statement: bool,
pub basic_block: bool,
pub prologue_end: bool,
pub epilogue_begin: bool,
pub isa: u64,
}
impl LineRow {
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,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LineInstruction {
Special(u8),
Copy,
AdvancePc(u64),
AdvanceLine(i64),
SetFile(FileId),
SetColumn(u64),
NegateStatement,
SetBasicBlock,
ConstAddPc,
SetPrologueEnd,
SetEpilogueBegin,
SetIsa(u64),
EndSequence,
SetAddress(Address),
SetDiscriminator(u64),
}
impl LineInstruction {
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];
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(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LineString {
String(Vec<u8>),
StringRef(StringId),
LineStringRef(LineStringId),
}
impl LineString {
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(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DirectoryId(usize);
mod id {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileId(usize);
impl FileId {
pub(crate) fn new(index: usize) -> Self {
FileId(index + 1)
}
pub(super) fn index(self) -> Option<usize> {
if self.0 == 0 {
None
} else {
Some(self.0 - 1)
}
}
pub(super) fn initial_state() -> Self {
FileId(1)
}
pub(crate) fn raw(self) -> u64 {
self.0 as u64
}
#[allow(unused)]
pub(super) fn zero() -> Self {
FileId(0)
}
}
}
pub use self::id::*;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct FileInfo {
pub timestamp: u64,
pub size: u64,
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 {
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>)> {
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 {
dirs.push(DirectoryId(0));
file_skip = 0;
files.push(FileId::zero());
} else {
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
};
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 = from_row.line().unwrap_or(0);
program.row().column = match from_row.column() {
read::ColumnType::LeftEdge => 0,
read::ColumnType::Column(val) => val,
};
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);
{
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
]
);
}
program.begin_sequence(None);
program.row.line = 0x1000;
program.generate_row();
let base_row = program.row;
let base_instructions = program.instructions.clone();
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 {
let mut program = program.clone();
program.row = test.0;
program.generate_row();
assert_eq!(
&program.instructions[base_instructions.len()..],
&test.1[..]
);
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]
#[allow(clippy::useless_vec)]
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();
}
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() as i64) - (line as i64),
line_advance
);
address = row.address();
line = row.line().unwrap();
}
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,
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();
}
}
}
}
}