use crate::error::{FasError, Result};
use crate::fas::bytes;
use crate::fas::header::Header;
use std::collections::BTreeSet;
pub const DUMP_ROW_SIZE: usize = 28;
pub const DUMP_VIRTUAL: u8 = 1;
pub const DUMP_NOT_IN_OUTPUT: u8 = 2;
#[derive(Debug, Clone)]
pub struct DumpRow {
pub id: usize,
pub file_offset: u32,
pub line_off: u32,
pub dollar: u64,
pub dollar_hi: u8,
pub extended_sib: u32,
pub reloc: u32,
pub addr_type: u8,
pub code_type: u8,
pub flags: u8,
}
impl DumpRow {
pub fn parse(rec: &[u8]) -> Result<Self> {
Self::parse_with_id(0, rec)
}
pub fn parse_with_id(id: usize, rec: &[u8]) -> Result<Self> {
if rec.len() < DUMP_ROW_SIZE {
return Err(FasError::Truncated("dump row"));
}
Ok(Self {
id,
file_offset: bytes::u32_at(rec, 0)?,
line_off: bytes::u32_at(rec, 4)?,
dollar: bytes::u64_at(rec, 8)?,
dollar_hi: bytes::u8_at(rec, 27)?,
extended_sib: bytes::u32_at(rec, 16)?,
reloc: bytes::u32_at(rec, 20)?,
addr_type: bytes::u8_at(rec, 24)?,
code_type: bytes::u8_at(rec, 25)?,
flags: bytes::u8_at(rec, 26)?,
})
}
pub fn address(&self) -> u64 {
self.dollar
}
pub fn is_virtual(&self) -> bool {
self.flags & DUMP_VIRTUAL != 0
}
pub fn not_in_output(&self) -> bool {
self.flags & DUMP_NOT_IN_OUTPUT != 0
}
pub fn is_mappable(&self) -> bool {
!self.is_virtual() && !self.not_in_output() && self.address() != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmittedSpan {
None,
Bytes(u32),
Ambiguous,
}
#[derive(Debug, Clone, Default)]
pub struct AssemblyDump {
pub rows: Vec<DumpRow>,
pub end_file_offset: u32,
pub spans: Vec<EmittedSpan>,
}
impl AssemblyDump {
pub fn parse(raw: &[u8]) -> Result<Self> {
if raw.len() < 4 {
return Err(FasError::Truncated("dump"));
}
let body_len = raw.len() - 4;
if body_len % DUMP_ROW_SIZE != 0 {
return Err(FasError::Geometry("dump rows not a multiple of 28"));
}
let rows = raw[..body_len]
.chunks_exact(DUMP_ROW_SIZE)
.enumerate()
.map(|(id, record)| DumpRow::parse_with_id(id, record))
.collect::<Result<Vec<_>>>()?;
let end_file_offset = bytes::u32_at(raw, body_len)?;
let spans = derive_spans(&rows, end_file_offset);
Ok(Self {
rows,
end_file_offset,
spans,
})
}
pub fn span(&self, row: &DumpRow) -> EmittedSpan {
self.spans
.get(row.id)
.copied()
.unwrap_or(EmittedSpan::Ambiguous)
}
pub fn emitted(&self, row: &DumpRow) -> bool {
matches!(self.span(row), EmittedSpan::Bytes(length) if length > 0)
}
pub fn assembled_addresses(&self) -> BTreeSet<u64> {
self.rows
.iter()
.filter(|row| row.is_mappable() && self.emitted(row))
.map(DumpRow::address)
.collect()
}
pub fn inferred_baddr(&self) -> Option<u64> {
use std::collections::BTreeMap;
let mut counts: BTreeMap<u64, usize> = BTreeMap::new();
for row in &self.rows {
if !row.is_mappable() {
continue;
}
let base = row.address().wrapping_sub(u64::from(row.file_offset));
*counts.entry(base).or_insert(0) += 1;
}
counts.into_iter().max_by_key(|(_, n)| *n).map(|(b, _)| b)
}
}
fn derive_spans(rows: &[DumpRow], end_file_offset: u32) -> Vec<EmittedSpan> {
let mut spans = vec![EmittedSpan::None; rows.len()];
let mut owners = Vec::<(usize, u32)>::new();
for (row_index, row) in rows.iter().enumerate().filter(|(_, row)| row.is_mappable()) {
if let Some((owner_index, offset)) = owners.last_mut() {
if *offset == row.file_offset {
*owner_index = row_index;
continue;
}
}
owners.push((row_index, row.file_offset));
}
for (position, (row_index, offset)) in owners.iter().enumerate() {
let next_offset = owners[position + 1..]
.iter()
.find_map(|(_, next)| (*next > *offset).then_some(*next))
.unwrap_or(end_file_offset);
spans[*row_index] = if next_offset > *offset {
EmittedSpan::Bytes(next_offset - *offset)
} else if next_offset == *offset {
EmittedSpan::None
} else {
EmittedSpan::Ambiguous
};
}
spans
}
pub fn parse_dump(header: &Header, data: &[u8]) -> Result<AssemblyDump> {
match header.dump(data)? {
Some(raw) => AssemblyDump::parse(raw),
None => Ok(AssemblyDump::default()),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn row(file_offset: u32, address: u64, flags: u8) -> DumpRow {
DumpRow {
id: 0,
file_offset,
line_off: 0,
dollar: address,
dollar_hi: 0,
extended_sib: 0,
reloc: 0,
addr_type: 0,
code_type: 64,
flags,
}
}
#[test]
fn repeated_offsets_leave_only_the_last_row_owning_bytes() {
let mut rows = vec![row(10, 0x100a, 0), row(10, 0x100a, 0), row(14, 0x100e, 0)];
for (id, row) in rows.iter_mut().enumerate() {
row.id = id;
}
assert_eq!(
derive_spans(&rows, 18),
[
EmittedSpan::None,
EmittedSpan::Bytes(4),
EmittedSpan::Bytes(4)
]
);
let dump = AssemblyDump {
rows,
end_file_offset: 18,
spans: vec![
EmittedSpan::None,
EmittedSpan::Bytes(4),
EmittedSpan::Bytes(4),
],
};
assert_eq!(dump.assembled_addresses(), [0x100a, 0x100e].into());
}
#[test]
fn virtual_and_reserved_rows_never_emit_output_bytes() {
let mut rows = vec![
row(10, 0x100a, DUMP_VIRTUAL),
row(10, 0x100a, DUMP_NOT_IN_OUTPUT),
row(10, 0x100a, 0),
];
for (id, row) in rows.iter_mut().enumerate() {
row.id = id;
}
assert_eq!(
derive_spans(&rows, 14),
[EmittedSpan::None, EmittedSpan::None, EmittedSpan::Bytes(4)]
);
}
#[test]
fn decreasing_offsets_mark_the_late_row_ambiguous() {
let rows = vec![row(20, 0x1014, 0), row(10, 0x100a, 0)];
assert_eq!(
derive_spans(&rows, 16),
[EmittedSpan::Ambiguous, EmittedSpan::Bytes(6)]
);
}
#[test]
fn final_row_at_output_end_is_zero_byte() {
let rows = vec![row(10, 0x100a, 0), row(14, 0x100e, 0)];
assert_eq!(
derive_spans(&rows, 14),
[EmittedSpan::Bytes(4), EmittedSpan::None]
);
}
}