use scroll::Pread;
use crate::common::*;
use crate::modi::{constants, FileChecksum, FileIndex, FileInfo, LineInfo, LineInfoKind};
use crate::FallibleIterator;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
#[allow(unused)]
enum DebugSubsectionKind {
Symbols = 0xf1,
Lines = 0xf2,
StringTable = 0xf3,
FileChecksums = 0xf4,
FrameData = 0xf5,
InlineeLines = 0xf6,
CrossScopeImports = 0xf7,
CrossScopeExports = 0xf8,
ILLines = 0xf9,
FuncMDTokenMap = 0xfa,
TypeMDTokenMap = 0xfb,
MergedAssemblyInput = 0xfc,
CoffSymbolRva = 0xfd,
}
impl DebugSubsectionKind {
fn parse(value: u32) -> Result<Option<Self>> {
if value >= 0xf1 && value <= 0xfd {
Ok(Some(unsafe { std::mem::transmute(value) }))
} else if value == constants::DEBUG_S_IGNORE {
Ok(None)
} else {
Err(Error::UnimplementedDebugSubsection(value))
}
}
}
#[derive(Clone, Copy, Debug, Pread)]
struct DebugSubsectionHeader {
kind: u32,
len: u32,
}
impl DebugSubsectionHeader {
fn kind(self) -> Result<Option<DebugSubsectionKind>> {
DebugSubsectionKind::parse(self.kind)
}
fn len(self) -> usize {
self.len as usize
}
}
#[derive(Clone, Copy, Debug)]
struct DebugSubsection<'a> {
pub kind: DebugSubsectionKind,
pub data: &'a [u8],
}
#[derive(Clone, Debug, Default)]
struct DebugSubsectionIterator<'a> {
buf: ParseBuffer<'a>,
}
impl<'a> DebugSubsectionIterator<'a> {
fn new(data: &'a [u8]) -> Self {
DebugSubsectionIterator {
buf: ParseBuffer::from(data),
}
}
}
impl<'a> FallibleIterator for DebugSubsectionIterator<'a> {
type Item = DebugSubsection<'a>;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
while !self.buf.is_empty() {
let header = self.buf.parse::<DebugSubsectionHeader>()?;
let data = self.buf.take(header.len())?;
let kind = match header.kind()? {
Some(kind) => kind,
None => continue,
};
return Ok(Some(DebugSubsection { kind, data }));
}
Ok(None)
}
}
#[derive(Clone, Copy, Debug, Default, Pread)]
struct DebugLinesHeader {
offset: PdbInternalSectionOffset,
flags: u16,
code_size: u32,
}
impl DebugLinesHeader {
fn has_columns(self) -> bool {
self.flags & constants::CV_LINES_HAVE_COLUMNS != 0
}
}
struct DebugLinesSubsection<'a> {
header: DebugLinesHeader,
data: &'a [u8],
}
impl<'a> DebugLinesSubsection<'a> {
fn parse(data: &'a [u8]) -> Result<Self> {
let mut buf = ParseBuffer::from(data);
let header = buf.parse()?;
let data = &data[buf.pos()..];
Ok(DebugLinesSubsection { header, data })
}
fn blocks(&self) -> DebugLinesBlockIterator<'a> {
DebugLinesBlockIterator {
header: self.header,
buf: ParseBuffer::from(self.data),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LineMarkerKind {
DoNotStepOnto,
DoNotStepInto,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Pread)]
struct LineNumberHeader {
offset: u32,
flags: u32,
}
#[derive(Clone, Debug)]
struct LineNumberEntry {
pub offset: u32,
pub start_line: u32,
pub end_line: u32,
pub kind: LineInfoKind,
}
#[derive(Clone, Debug)]
struct LineMarkerEntry {
pub offset: u32,
pub kind: LineMarkerKind,
}
#[derive(Clone, Debug)]
enum LineEntry {
Number(LineNumberEntry),
Marker(LineMarkerEntry),
}
impl LineNumberHeader {
pub fn parse(self) -> LineEntry {
let start_line = self.flags & 0x00ff_ffff;
let marker = match start_line {
0xfee_fee => Some(LineMarkerKind::DoNotStepOnto),
0xf00_f00 => Some(LineMarkerKind::DoNotStepInto),
_ => None,
};
if let Some(kind) = marker {
return LineEntry::Marker(LineMarkerEntry {
offset: self.offset,
kind,
});
}
let line_delta = self.flags & 0x7f00_0000 >> 24;
let high_start = start_line & !0x7f;
let mut end_line = high_start | line_delta;
if end_line < start_line {
end_line += 1 << 7;
}
let kind = if self.flags & 0x8000_0000 != 0 {
LineInfoKind::Statement
} else {
LineInfoKind::Expression
};
LineEntry::Number(LineNumberEntry {
offset: self.offset,
start_line,
end_line,
kind,
})
}
}
#[derive(Clone, Debug, Default)]
struct DebugLinesIterator<'a> {
block: DebugLinesBlockHeader,
buf: ParseBuffer<'a>,
}
impl FallibleIterator for DebugLinesIterator<'_> {
type Item = LineEntry;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
if self.buf.is_empty() {
return Ok(None);
}
self.buf.parse().map(LineNumberHeader::parse).map(Some)
}
}
#[derive(Clone, Copy, Debug, Default, Pread)]
#[repr(C, packed)]
struct ColumnNumberEntry {
start_column: u16,
end_column: u16,
}
#[derive(Clone, Debug, Default)]
struct DebugColumnsIterator<'a> {
block: DebugLinesBlockHeader,
buf: ParseBuffer<'a>,
}
impl FallibleIterator for DebugColumnsIterator<'_> {
type Item = ColumnNumberEntry;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
if self.buf.is_empty() {
return Ok(None);
}
self.buf.parse().map(Some)
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Pread)]
struct DebugLinesBlockHeader {
file_index: u32,
num_lines: u32,
block_size: u32,
}
impl DebugLinesBlockHeader {
fn data_size(&self) -> usize {
self.block_size as usize - std::mem::size_of::<Self>()
}
fn line_size(&self) -> usize {
self.num_lines as usize * std::mem::size_of::<LineNumberHeader>()
}
fn column_size(&self, subsection: DebugLinesHeader) -> usize {
if subsection.has_columns() {
self.num_lines as usize * std::mem::size_of::<ColumnNumberEntry>()
} else {
0
}
}
}
#[derive(Clone, Debug)]
struct DebugLinesBlock<'a> {
header: DebugLinesBlockHeader,
line_data: &'a [u8],
column_data: &'a [u8],
}
impl<'a> DebugLinesBlock<'a> {
#[allow(unused)]
fn file_index(&self) -> FileIndex {
FileIndex(self.header.file_index)
}
fn lines(&self) -> DebugLinesIterator<'a> {
DebugLinesIterator {
block: self.header,
buf: ParseBuffer::from(self.line_data),
}
}
fn columns(&self) -> DebugColumnsIterator<'a> {
DebugColumnsIterator {
block: self.header,
buf: ParseBuffer::from(self.line_data),
}
}
}
#[derive(Clone, Debug, Default)]
struct DebugLinesBlockIterator<'a> {
header: DebugLinesHeader,
buf: ParseBuffer<'a>,
}
impl<'a> FallibleIterator for DebugLinesBlockIterator<'a> {
type Item = DebugLinesBlock<'a>;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
if self.buf.is_empty() {
return Ok(None);
}
let header = self.buf.parse::<DebugLinesBlockHeader>()?;
let data = self.buf.take(header.data_size())?;
let (line_data, data) = data.split_at(header.line_size());
let (column_data, remainder) = data.split_at(header.column_size(self.header));
debug_assert!(remainder.is_empty());
Ok(Some(DebugLinesBlock {
header,
line_data,
column_data,
}))
}
}
#[repr(u8)]
#[allow(unused)]
#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
enum FileChecksumKind {
None = 0,
Md5 = 1,
Sha1 = 2,
Sha256 = 3,
}
impl FileChecksumKind {
fn parse(value: u8) -> Result<Self> {
if value <= 3 {
Ok(unsafe { std::mem::transmute(value) })
} else {
Err(Error::UnimplementedFileChecksumKind(value))
}
}
}
#[derive(Clone, Copy, Debug, Pread)]
struct FileChecksumHeader {
name_offset: u32,
checksum_size: u8,
checksum_kind: u8,
}
#[derive(Clone, Debug)]
struct FileChecksumEntry<'a> {
name: StringRef,
checksum: FileChecksum<'a>,
}
#[derive(Clone, Debug, Default)]
struct DebugFileChecksumsIterator<'a> {
buf: ParseBuffer<'a>,
}
impl<'a> FallibleIterator for DebugFileChecksumsIterator<'a> {
type Item = FileChecksumEntry<'a>;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
if self.buf.is_empty() {
return Ok(None);
}
let header = self.buf.parse::<FileChecksumHeader>()?;
let checksum_data = self.buf.take(header.checksum_size as usize)?;
let checksum = match FileChecksumKind::parse(header.checksum_kind)? {
FileChecksumKind::None => FileChecksum::None,
FileChecksumKind::Md5 => FileChecksum::Md5(checksum_data),
FileChecksumKind::Sha1 => FileChecksum::Sha1(checksum_data),
FileChecksumKind::Sha256 => FileChecksum::Sha256(checksum_data),
};
self.buf.align(4)?;
Ok(Some(FileChecksumEntry {
name: StringRef(header.name_offset),
checksum,
}))
}
}
#[derive(Clone, Debug, Default)]
struct DebugFileChecksumsSubsection<'a> {
data: &'a [u8],
}
impl<'a> DebugFileChecksumsSubsection<'a> {
fn parse(data: &'a [u8]) -> Result<Self> {
Ok(DebugFileChecksumsSubsection { data })
}
#[allow(unused)]
fn entries(&self) -> Result<DebugFileChecksumsIterator<'a>> {
self.entries_at_offset(FileIndex(0))
}
fn entries_at_offset(&self, offset: FileIndex) -> Result<DebugFileChecksumsIterator<'a>> {
let mut buf = ParseBuffer::from(self.data);
buf.take(offset.0 as usize)?;
Ok(DebugFileChecksumsIterator { buf })
}
}
#[derive(Clone, Debug, Default)]
pub struct C13LineIterator<'a> {
sections: DebugSubsectionIterator<'a>,
blocks: DebugLinesBlockIterator<'a>,
lines: DebugLinesIterator<'a>,
columns: DebugColumnsIterator<'a>,
}
impl<'a> FallibleIterator for C13LineIterator<'a> {
type Item = LineInfo;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
loop {
if let Some(entry) = self.lines.next()? {
let column_entry = self.columns.next()?;
let line_entry = match entry {
LineEntry::Number(line_entry) => line_entry,
LineEntry::Marker(_) => continue,
};
let section_header = self.blocks.header;
let block_header = self.lines.block;
return Ok(Some(LineInfo {
offset: section_header.offset + line_entry.offset,
file_index: FileIndex(block_header.file_index),
line_start: line_entry.start_line,
line_end: line_entry.end_line,
column_start: column_entry.map(|e| e.start_column),
column_end: column_entry.map(|e| e.end_column),
kind: line_entry.kind,
}));
}
if let Some(block) = self.blocks.next()? {
self.lines = block.lines();
self.columns = block.columns();
continue;
}
if let Some(section) = self.sections.next()? {
if section.kind == DebugSubsectionKind::Lines {
let lines_section = DebugLinesSubsection::parse(section.data)?;
self.blocks = lines_section.blocks();
}
continue;
}
return Ok(None);
}
}
}
#[derive(Clone, Debug, Default)]
pub struct C13FileIterator<'a> {
checksums: DebugFileChecksumsIterator<'a>,
}
impl<'a> FallibleIterator for C13FileIterator<'a> {
type Item = FileInfo<'a>;
type Error = Error;
fn next(&mut self) -> Result<Option<Self::Item>> {
match self.checksums.next() {
Ok(Some(entry)) => Ok(Some(FileInfo {
name: entry.name,
checksum: entry.checksum,
})),
Ok(None) => Ok(None),
Err(error) => Err(error),
}
}
}
pub struct C13LineProgram<'a> {
data: &'a [u8],
file_checksums: DebugFileChecksumsSubsection<'a>,
}
impl<'a> C13LineProgram<'a> {
pub(crate) fn parse(data: &'a [u8]) -> Result<Self> {
let checksums_data = DebugSubsectionIterator::new(data)
.find(|sec| sec.kind == DebugSubsectionKind::FileChecksums)?
.map(|sec| sec.data);
let file_checksums = match checksums_data {
Some(d) => DebugFileChecksumsSubsection::parse(d)?,
None => DebugFileChecksumsSubsection::default(),
};
Ok(C13LineProgram {
data,
file_checksums,
})
}
pub(crate) fn lines(&self) -> C13LineIterator<'a> {
C13LineIterator {
sections: DebugSubsectionIterator::new(self.data),
blocks: DebugLinesBlockIterator::default(),
lines: DebugLinesIterator::default(),
columns: DebugColumnsIterator::default(),
}
}
pub(crate) fn lines_at_offset(&self, offset: PdbInternalSectionOffset) -> C13LineIterator<'a> {
let section = DebugSubsectionIterator::new(self.data)
.filter(|section| section.kind == DebugSubsectionKind::Lines)
.and_then(|section| DebugLinesSubsection::parse(section.data))
.find(|lines_section| lines_section.header.offset == offset);
match section {
Ok(Some(section)) => C13LineIterator {
sections: DebugSubsectionIterator::default(),
blocks: section.blocks(),
lines: DebugLinesIterator::default(),
columns: DebugColumnsIterator::default(),
},
_ => Default::default(),
}
}
pub(crate) fn files(&self) -> C13FileIterator<'a> {
C13FileIterator {
checksums: self.file_checksums.entries().unwrap_or_default(),
}
}
pub(crate) fn get_file_info(&self, index: FileIndex) -> Result<FileInfo<'a>> {
let mut entries = self.file_checksums.entries_at_offset(index)?;
let entry = entries
.next()?
.ok_or_else(|| Error::InvalidFileChecksumOffset(index.0))?;
Ok(FileInfo {
name: entry.name,
checksum: entry.checksum,
})
}
}