///|
const DBI_HEADER_SIZE : Int32 = 64
///|
const DBI_MODULE_HEADER_SIZE : Int32 = 64
///|
const DBI_SECTION_CONTRIB_60_SIZE : Int32 = 28
///|
const DBI_SECTION_CONTRIB_2_SIZE : Int32 = 32
///|
const DBI_SECTION_MAP_HEADER_SIZE : Int32 = 4
///|
const DBI_SECTION_MAP_ENTRY_SIZE : Int32 = 20
///|
const DBI_FILE_INFO_HEADER_SIZE : Int32 = 4
///|
const DBI_VERSION_SIGNATURE : UInt32 = 0xFFFFFFFFU
///|
const DBI_VERSION_70 : UInt32 = 19990903U
///|
const DBI_SECTION_CONTRIBUTION_60 : UInt32 = 0xEFFE0000U + 19970605U
///|
const DBI_SECTION_CONTRIBUTION_2 : UInt32 = 0xEFFE0000U + 20140516U
///|
/// Resource limits for parsing the DBI stream.
pub struct DbiLimits {
max_stream_bytes : Int32
max_modules : Int32
max_section_contributions : Int32
max_section_map_entries : Int32
max_source_files : Int32
max_string_bytes : Int32
max_debug_streams : Int32
}
///|
pub fn DbiLimits::default() -> DbiLimits {
DbiLimits::{
max_stream_bytes: 128 * 1024 * 1024,
max_modules: 65_536,
max_section_contributions: 1_000_000,
max_section_map_entries: 65_536,
max_source_files: 1_000_000,
max_string_bytes: 64 * 1024 * 1024,
max_debug_streams: 64,
}
}
///|
/// The version marker at the start of the section-contribution substream.
pub enum SectionContributionVersion {
None
V60
V2
}
///|
/// Optional indexed streams listed in the DBI debug header.
pub enum DebugStreamKind {
Fpo
Exception
Fixup
OmapToSrc
OmapFromSrc
SectionHdr
TokenRidMap
Xdata
Pdata
NewFpo
SectionHdrOrig
}
///|
pub fn DebugStreamKind::all() -> Array[DebugStreamKind] {
[
Fpo,
Exception,
Fixup,
OmapToSrc,
OmapFromSrc,
SectionHdr,
TokenRidMap,
Xdata,
Pdata,
NewFpo,
SectionHdrOrig,
]
}
///|
/// The fixed 64-byte header at the start of DBI stream 3.
pub struct DbiHeader {
version_signature : UInt32
version : UInt32
age : UInt32
global_symbol_stream_index : UInt16
build_number : UInt16
public_symbol_stream_index : UInt16
pdb_dll_version : UInt16
symbol_record_stream_index : UInt16
pdb_dll_rebuild : UInt16
modi_substream_bytes : Int32
section_contributions_bytes : Int32
section_map_bytes : Int32
file_info_bytes : Int32
type_server_map_bytes : Int32
mfc_type_server_index : UInt32
optional_debug_header_bytes : Int32
ec_substream_bytes : Int32
flags : UInt16
machine_type : UInt16
reserved : UInt32
}
///|
pub fn DbiHeader::version_signature(self : DbiHeader) -> UInt32 {
self.version_signature
}
///|
pub fn DbiHeader::version(self : DbiHeader) -> UInt32 {
self.version
}
///|
pub fn DbiHeader::age(self : DbiHeader) -> UInt32 {
self.age
}
///|
pub fn DbiHeader::global_symbol_stream_index(self : DbiHeader) -> UInt16 {
self.global_symbol_stream_index
}
///|
pub fn DbiHeader::build_number(self : DbiHeader) -> UInt16 {
self.build_number
}
///|
pub fn DbiHeader::build_major_version(self : DbiHeader) -> UInt16 {
(self.build_number >> 8) & 0x7F
}
///|
pub fn DbiHeader::build_minor_version(self : DbiHeader) -> UInt16 {
self.build_number & 0x00FF
}
///|
pub fn DbiHeader::uses_new_version_format(self : DbiHeader) -> Bool {
(self.build_number & 0x8000) != 0
}
///|
pub fn DbiHeader::public_symbol_stream_index(self : DbiHeader) -> UInt16 {
self.public_symbol_stream_index
}
///|
pub fn DbiHeader::pdb_dll_version(self : DbiHeader) -> UInt16 {
self.pdb_dll_version
}
///|
pub fn DbiHeader::symbol_record_stream_index(self : DbiHeader) -> UInt16 {
self.symbol_record_stream_index
}
///|
pub fn DbiHeader::pdb_dll_rebuild(self : DbiHeader) -> UInt16 {
self.pdb_dll_rebuild
}
///|
pub fn DbiHeader::modi_substream_bytes(self : DbiHeader) -> Int32 {
self.modi_substream_bytes
}
///|
pub fn DbiHeader::section_contributions_bytes(self : DbiHeader) -> Int32 {
self.section_contributions_bytes
}
///|
pub fn DbiHeader::section_map_bytes(self : DbiHeader) -> Int32 {
self.section_map_bytes
}
///|
pub fn DbiHeader::file_info_bytes(self : DbiHeader) -> Int32 {
self.file_info_bytes
}
///|
pub fn DbiHeader::type_server_map_bytes(self : DbiHeader) -> Int32 {
self.type_server_map_bytes
}
///|
pub fn DbiHeader::mfc_type_server_index(self : DbiHeader) -> UInt32 {
self.mfc_type_server_index
}
///|
pub fn DbiHeader::optional_debug_header_bytes(self : DbiHeader) -> Int32 {
self.optional_debug_header_bytes
}
///|
pub fn DbiHeader::ec_substream_bytes(self : DbiHeader) -> Int32 {
self.ec_substream_bytes
}
///|
pub fn DbiHeader::flags(self : DbiHeader) -> UInt16 {
self.flags
}
///|
pub fn DbiHeader::is_incrementally_linked(self : DbiHeader) -> Bool {
(self.flags & 0x0001) != 0
}
///|
pub fn DbiHeader::is_stripped(self : DbiHeader) -> Bool {
(self.flags & 0x0002) != 0
}
///|
pub fn DbiHeader::has_c_types(self : DbiHeader) -> Bool {
(self.flags & 0x0004) != 0
}
///|
pub fn DbiHeader::machine_type(self : DbiHeader) -> UInt16 {
self.machine_type
}
///|
pub fn DbiHeader::reserved(self : DbiHeader) -> UInt32 {
self.reserved
}
///|
/// One section contribution record.
pub struct SectionContribution {
version : SectionContributionVersion
section_index : UInt16
offset : Int32
size : Int32
characteristics : UInt32
module_index : UInt16
data_crc : UInt32
relocation_crc : UInt32
coff_section_index : UInt32?
}
///|
pub fn SectionContribution::version(
self : SectionContribution,
) -> SectionContributionVersion {
self.version
}
///|
pub fn SectionContribution::section_index(self : SectionContribution) -> UInt16 {
self.section_index
}
///|
pub fn SectionContribution::offset(self : SectionContribution) -> Int32 {
self.offset
}
///|
pub fn SectionContribution::size(self : SectionContribution) -> Int32 {
self.size
}
///|
pub fn SectionContribution::characteristics(
self : SectionContribution,
) -> UInt32 {
self.characteristics
}
///|
pub fn SectionContribution::module_index(self : SectionContribution) -> UInt16 {
self.module_index
}
///|
pub fn SectionContribution::data_crc(self : SectionContribution) -> UInt32 {
self.data_crc
}
///|
pub fn SectionContribution::relocation_crc(
self : SectionContribution,
) -> UInt32 {
self.relocation_crc
}
///|
pub fn SectionContribution::coff_section_index(
self : SectionContribution,
) -> UInt32? {
self.coff_section_index
}
///|
/// One logical-to-physical section mapping.
pub struct SectionMapEntry {
flags : UInt16
overlay : UInt16
group : UInt16
frame : UInt16
section_name_offset : UInt16
class_name_offset : UInt16
offset : UInt32
byte_length : UInt32
}
///|
pub fn SectionMapEntry::flags(self : SectionMapEntry) -> UInt16 {
self.flags
}
///|
pub fn SectionMapEntry::overlay(self : SectionMapEntry) -> UInt16 {
self.overlay
}
///|
pub fn SectionMapEntry::group(self : SectionMapEntry) -> UInt16 {
self.group
}
///|
pub fn SectionMapEntry::frame(self : SectionMapEntry) -> UInt16 {
self.frame
}
///|
pub fn SectionMapEntry::section_name_offset(self : SectionMapEntry) -> UInt16 {
self.section_name_offset
}
///|
pub fn SectionMapEntry::class_name_offset(self : SectionMapEntry) -> UInt16 {
self.class_name_offset
}
///|
pub fn SectionMapEntry::offset(self : SectionMapEntry) -> UInt32 {
self.offset
}
///|
pub fn SectionMapEntry::byte_length(self : SectionMapEntry) -> UInt32 {
self.byte_length
}
///|
/// A parsed section map and its two declared counts.
pub struct SectionMap {
physical_count : UInt16
logical_count : UInt16
entries : Array[SectionMapEntry]
}
///|
pub fn SectionMap::physical_count(self : SectionMap) -> UInt16 {
self.physical_count
}
///|
pub fn SectionMap::logical_count(self : SectionMap) -> UInt16 {
self.logical_count
}
///|
pub fn SectionMap::entry_count(self : SectionMap) -> Int32 {
self.entries.length()
}
///|
pub fn SectionMap::entry(self : SectionMap, index : Int32) -> SectionMapEntry? {
if index < 0 || index >= self.entries.length() {
None
} else {
Some(self.entries[index])
}
}
///|
/// One variable-length module descriptor from the MODI substream.
pub struct DbiModule {
record_index : Int32
module_name : String
object_name : String
section_contribution : SectionContribution
flags : UInt16
module_stream_index : UInt16
symbol_debug_bytes : UInt32
c11_line_bytes : UInt32
c13_line_bytes : UInt32
declared_source_file_count : UInt16
source_file_name_index : UInt32
pdb_file_path_index : UInt32
source_files : Array[String]
}
///|
pub fn DbiModule::record_index(self : DbiModule) -> Int32 {
self.record_index
}
///|
pub fn DbiModule::module_name(self : DbiModule) -> String {
self.module_name
}
///|
pub fn DbiModule::object_name(self : DbiModule) -> String {
self.object_name
}
///|
pub fn DbiModule::section_contribution(self : DbiModule) -> SectionContribution {
self.section_contribution
}
///|
pub fn DbiModule::flags(self : DbiModule) -> UInt16 {
self.flags
}
///|
pub fn DbiModule::has_ec_info(self : DbiModule) -> Bool {
(self.flags & 0x0002) != 0
}
///|
pub fn DbiModule::type_server_index(self : DbiModule) -> UInt16 {
(self.flags >> 8) & 0x00FF
}
///|
pub fn DbiModule::module_stream_index(self : DbiModule) -> UInt16 {
self.module_stream_index
}
///|
pub fn DbiModule::symbol_debug_bytes(self : DbiModule) -> UInt32 {
self.symbol_debug_bytes
}
///|
pub fn DbiModule::c11_line_bytes(self : DbiModule) -> UInt32 {
self.c11_line_bytes
}
///|
pub fn DbiModule::c13_line_bytes(self : DbiModule) -> UInt32 {
self.c13_line_bytes
}
///|
pub fn DbiModule::declared_source_file_count(self : DbiModule) -> UInt16 {
self.declared_source_file_count
}
///|
pub fn DbiModule::source_file_name_index(self : DbiModule) -> UInt32 {
self.source_file_name_index
}
///|
pub fn DbiModule::pdb_file_path_index(self : DbiModule) -> UInt32 {
self.pdb_file_path_index
}
///|
pub fn DbiModule::source_files(self : DbiModule) -> Array[String] {
self.source_files.copy()
}
///|
fn DbiModule::with_source_files(
self : DbiModule,
source_files : Array[String],
) -> DbiModule {
DbiModule::{
record_index: self.record_index,
module_name: self.module_name,
object_name: self.object_name,
section_contribution: self.section_contribution,
flags: self.flags,
module_stream_index: self.module_stream_index,
symbol_debug_bytes: self.symbol_debug_bytes,
c11_line_bytes: self.c11_line_bytes,
c13_line_bytes: self.c13_line_bytes,
declared_source_file_count: self.declared_source_file_count,
source_file_name_index: self.source_file_name_index,
pdb_file_path_index: self.pdb_file_path_index,
source_files,
}
}
///|
/// A parsed DBI stream with its addressable substreams.
pub struct DbiFile {
data : Bytes
header : DbiHeader
modules : Array[DbiModule]
section_contributions : Array[SectionContribution]
section_contribution_version : SectionContributionVersion
section_map : SectionMap
reported_source_file_count : UInt16
type_server_map : Bytes
ec_substream : Bytes
optional_debug_streams : Array[UInt16]
}
///|
pub fn DbiFile::data(self : DbiFile) -> Bytes {
self.data
}
///|
pub fn DbiFile::header(self : DbiFile) -> DbiHeader {
self.header
}
///|
pub fn DbiFile::module_count(self : DbiFile) -> Int32 {
self.modules.length()
}
///|
pub fn DbiFile::module_at(self : DbiFile, index : Int32) -> DbiModule? {
if index < 0 || index >= self.modules.length() {
None
} else {
Some(self.modules[index])
}
}
///|
pub fn DbiFile::modules(self : DbiFile) -> Array[DbiModule] {
self.modules.copy()
}
///|
pub fn DbiFile::section_contribution_count(self : DbiFile) -> Int32 {
self.section_contributions.length()
}
///|
pub fn DbiFile::section_contribution(
self : DbiFile,
index : Int32,
) -> SectionContribution? {
if index < 0 || index >= self.section_contributions.length() {
None
} else {
Some(self.section_contributions[index])
}
}
///|
pub fn DbiFile::section_contribution_version(
self : DbiFile,
) -> SectionContributionVersion {
self.section_contribution_version
}
///|
pub fn DbiFile::section_map(self : DbiFile) -> SectionMap {
self.section_map
}
///|
pub fn DbiFile::reported_source_file_count(self : DbiFile) -> UInt16 {
self.reported_source_file_count
}
///|
pub fn DbiFile::source_file_count(self : DbiFile) -> Int32 {
let mut count = 0
for item in self.modules {
count = count + item.source_files.length()
}
count
}
///|
pub fn DbiFile::type_server_map(self : DbiFile) -> Bytes {
self.type_server_map
}
///|
pub fn DbiFile::ec_substream(self : DbiFile) -> Bytes {
self.ec_substream
}
///|
pub fn DbiFile::optional_debug_streams(self : DbiFile) -> Array[UInt16] {
self.optional_debug_streams.copy()
}
///|
pub fn DbiFile::debug_stream_index(
self : DbiFile,
kind : DebugStreamKind,
) -> UInt16? {
let index = match kind {
Fpo => 0
Exception => 1
Fixup => 2
OmapToSrc => 3
OmapFromSrc => 4
SectionHdr => 5
TokenRidMap => 6
Xdata => 7
Pdata => 8
NewFpo => 9
SectionHdrOrig => 10
}
if index < 0 || index >= self.optional_debug_streams.length() {
None
} else {
Some(self.optional_debug_streams[index])
}
}
///|
/// Parse stream 3 of an MSF file as the DBI stream.
pub fn MsfFile::dbi(
self : MsfFile,
limits : DbiLimits,
) -> DbiFile raise DbiError {
let bytes = self.stream(3) catch { _ => raise DbiStreamUnavailable }
parse_dbi(bytes, limits)
}
///|
/// Parse a complete DBI stream and all bounded substreams.
pub fn parse_dbi(data : Bytes, limits : DbiLimits) -> DbiFile raise DbiError {
if data.length() > limits.max_stream_bytes {
raise DbiLimitExceeded("DBI stream byte limit exceeded")
}
let header = parse_dbi_header(data)
let expected_size = dbi_substream_end(header)
if expected_size != data.length() {
raise InvalidDbiHeader(
"DBI stream length does not equal the header and substream sizes",
)
}
validate_dbi_alignment(header)
let modi_start = DBI_HEADER_SIZE
let modi_end = checked_dbi_add(
modi_start,
header.modi_substream_bytes,
"MODI substream",
)
let contributions_end = checked_dbi_add(
modi_end,
header.section_contributions_bytes,
"section contribution substream",
)
let section_map_end = checked_dbi_add(
contributions_end,
header.section_map_bytes,
"section map substream",
)
let file_info_end = checked_dbi_add(
section_map_end,
header.file_info_bytes,
"file info substream",
)
let type_server_end = checked_dbi_add(
file_info_end,
header.type_server_map_bytes,
"type server map substream",
)
let ec_end = checked_dbi_add(
type_server_end,
header.ec_substream_bytes,
"EC substream",
)
let debug_end = checked_dbi_add(
ec_end,
header.optional_debug_header_bytes,
"optional debug header",
)
if debug_end != data.length() {
raise InvalidDbiHeader("DBI substreams do not cover the complete stream")
}
let modules = parse_dbi_modules(
data.exact_view(start=modi_start, end=modi_end).to_owned(),
limits,
)
let (contribution_version, contributions) = parse_section_contributions(
data.exact_view(start=modi_end, end=contributions_end).to_owned(),
limits,
)
let section_map = parse_section_map(
data.exact_view(start=contributions_end, end=section_map_end).to_owned(),
limits,
)
let (modules, reported_source_file_count) = attach_file_info(
data.exact_view(start=section_map_end, end=file_info_end).to_owned(),
modules,
limits,
)
let optional_debug_streams = parse_optional_debug_header(
data.exact_view(start=ec_end, end=debug_end).to_owned(),
limits,
)
DbiFile::{
data,
header,
modules,
section_contributions: contributions,
section_contribution_version: contribution_version,
section_map,
reported_source_file_count,
type_server_map: data
.exact_view(start=file_info_end, end=type_server_end)
.to_owned(),
ec_substream: data.exact_view(start=type_server_end, end=ec_end).to_owned(),
optional_debug_streams,
}
}
///|
fn parse_dbi_header(data : Bytes) -> DbiHeader raise DbiError {
require_dbi_bytes(data, 0, DBI_HEADER_SIZE, "DBI header")
let (version_signature, cursor) = read_dbi_u32(data, 0, DBI_HEADER_SIZE)
if version_signature != DBI_VERSION_SIGNATURE {
raise InvalidDbiHeader("DBI version signature must be 0xFFFFFFFF")
}
let (version, cursor) = read_dbi_u32(data, cursor, DBI_HEADER_SIZE)
if version < DBI_VERSION_70 {
raise UnsupportedDbiVersion(version)
}
let (age, cursor) = read_dbi_u32(data, cursor, DBI_HEADER_SIZE)
let (global_symbol_stream_index, cursor) = read_dbi_u16(
data,
cursor,
DBI_HEADER_SIZE,
)
let (build_number, cursor) = read_dbi_u16(data, cursor, DBI_HEADER_SIZE)
let (public_symbol_stream_index, cursor) = read_dbi_u16(
data,
cursor,
DBI_HEADER_SIZE,
)
let (pdb_dll_version, cursor) = read_dbi_u16(data, cursor, DBI_HEADER_SIZE)
let (symbol_record_stream_index, cursor) = read_dbi_u16(
data,
cursor,
DBI_HEADER_SIZE,
)
let (pdb_dll_rebuild, cursor) = read_dbi_u16(data, cursor, DBI_HEADER_SIZE)
let (modi_substream_bytes, cursor) = read_dbi_i32(
data,
cursor,
DBI_HEADER_SIZE,
)
let (section_contributions_bytes, cursor) = read_dbi_i32(
data,
cursor,
DBI_HEADER_SIZE,
)
let (section_map_bytes, cursor) = read_dbi_i32(data, cursor, DBI_HEADER_SIZE)
let (file_info_bytes, cursor) = read_dbi_i32(data, cursor, DBI_HEADER_SIZE)
let (type_server_map_bytes, cursor) = read_dbi_i32(
data,
cursor,
DBI_HEADER_SIZE,
)
let (mfc_type_server_index, cursor) = read_dbi_u32(
data,
cursor,
DBI_HEADER_SIZE,
)
let (optional_debug_header_bytes, cursor) = read_dbi_i32(
data,
cursor,
DBI_HEADER_SIZE,
)
let (ec_substream_bytes, cursor) = read_dbi_i32(data, cursor, DBI_HEADER_SIZE)
let (flags, cursor) = read_dbi_u16(data, cursor, DBI_HEADER_SIZE)
let (machine_type, cursor) = read_dbi_u16(data, cursor, DBI_HEADER_SIZE)
let (reserved, cursor) = read_dbi_u32(data, cursor, DBI_HEADER_SIZE)
if cursor != DBI_HEADER_SIZE {
raise InvalidDbiHeader("DBI header did not consume exactly 64 bytes")
}
let sizes = [
modi_substream_bytes, section_contributions_bytes, section_map_bytes, file_info_bytes,
type_server_map_bytes, optional_debug_header_bytes, ec_substream_bytes,
]
for size in sizes {
if size < 0 {
raise InvalidDbiHeader("DBI substream size must not be negative")
}
}
DbiHeader::{
version_signature,
version,
age,
global_symbol_stream_index,
build_number,
public_symbol_stream_index,
pdb_dll_version,
symbol_record_stream_index,
pdb_dll_rebuild,
modi_substream_bytes,
section_contributions_bytes,
section_map_bytes,
file_info_bytes,
type_server_map_bytes,
mfc_type_server_index,
optional_debug_header_bytes,
ec_substream_bytes,
flags,
machine_type,
reserved,
}
}
///|
fn dbi_substream_end(header : DbiHeader) -> Int32 raise DbiError {
let mut end = DBI_HEADER_SIZE
end = checked_dbi_add(end, header.modi_substream_bytes, "MODI substream")
end = checked_dbi_add(
end,
header.section_contributions_bytes,
"section contribution substream",
)
end = checked_dbi_add(end, header.section_map_bytes, "section map substream")
end = checked_dbi_add(end, header.file_info_bytes, "file info substream")
end = checked_dbi_add(
end,
header.type_server_map_bytes,
"type server map substream",
)
end = checked_dbi_add(end, header.ec_substream_bytes, "EC substream")
checked_dbi_add(
end,
header.optional_debug_header_bytes,
"optional debug header",
)
}
///|
fn validate_dbi_alignment(header : DbiHeader) -> Unit raise DbiError {
if header.modi_substream_bytes % 4 != 0 {
raise InvalidDbiHeader("MODI substream is not 4-byte aligned")
}
if header.section_contributions_bytes % 4 != 0 {
raise InvalidDbiHeader("section contribution substream is not aligned")
}
if header.section_map_bytes % 4 != 0 {
raise InvalidDbiHeader("section map substream is not aligned")
}
if header.file_info_bytes % 4 != 0 {
raise InvalidDbiHeader("file info substream is not aligned")
}
if header.type_server_map_bytes % 4 != 0 {
raise InvalidDbiHeader("type server map substream is not aligned")
}
if header.optional_debug_header_bytes % 2 != 0 {
raise InvalidDbiHeader("optional debug header is not uint16-aligned")
}
}
///|
fn parse_section_contributions(
data : Bytes,
limits : DbiLimits,
) -> (SectionContributionVersion, Array[SectionContribution]) raise DbiError {
if data.length() == 0 {
return (None, [])
}
require_dbi_bytes(data, 0, 4, "section contribution version")
let (version_raw, cursor0) = read_dbi_u32(data, 0, data.length())
let mut cursor = cursor0
let version = if version_raw == DBI_SECTION_CONTRIBUTION_60 {
V60
} else if version_raw == DBI_SECTION_CONTRIBUTION_2 {
V2
} else {
raise UnsupportedSectionContributionVersion(version_raw)
}
let record_size = match version {
V60 => DBI_SECTION_CONTRIB_60_SIZE
V2 => DBI_SECTION_CONTRIB_2_SIZE
None => 0
}
let remaining = data.length() - cursor
if remaining % record_size != 0 {
raise InvalidSectionContribution(
"section contribution bytes do not match the record size",
)
}
let count = remaining / record_size
if count > limits.max_section_contributions {
raise DbiLimitExceeded("section contribution count limit exceeded")
}
let contributions = []
for _ in 0.. (SectionContribution, Int32) raise DbiError {
let record_size = match version {
V60 => DBI_SECTION_CONTRIB_60_SIZE
V2 => DBI_SECTION_CONTRIB_2_SIZE
None => raise InvalidSectionContribution("missing contribution version")
}
require_dbi_bytes(data, start, record_size, "section contribution record")
let record_end = checked_dbi_add(start, record_size, "section contribution")
if record_end > end {
raise TruncatedDbi(record_end)
}
let (section_index, cursor) = read_dbi_u16(data, start, record_end)
let (_, cursor) = read_dbi_u16(data, cursor, record_end)
let (offset, cursor) = read_dbi_i32(data, cursor, record_end)
let (size, cursor) = read_dbi_i32(data, cursor, record_end)
let (characteristics, cursor) = read_dbi_u32(data, cursor, record_end)
let (module_index, cursor) = read_dbi_u16(data, cursor, record_end)
let (_, cursor) = read_dbi_u16(data, cursor, record_end)
let (data_crc, cursor) = read_dbi_u32(data, cursor, record_end)
let (relocation_crc, cursor) = read_dbi_u32(data, cursor, record_end)
let (coff_section_index, cursor) = match version {
V2 => {
let (value, next) = read_dbi_u32(data, cursor, record_end)
(Some(value), next)
}
_ => (None, cursor)
}
if cursor != record_end {
raise InvalidSectionContribution(
"section contribution has unexpected trailing bytes",
)
}
(
SectionContribution::{
version,
section_index,
offset,
size,
characteristics,
module_index,
data_crc,
relocation_crc,
coff_section_index,
},
record_end,
)
}
///|
fn parse_section_map(
data : Bytes,
limits : DbiLimits,
) -> SectionMap raise DbiError {
if data.length() == 0 {
return SectionMap::{ physical_count: 0, logical_count: 0, entries: [], }
}
require_dbi_bytes(data, 0, DBI_SECTION_MAP_HEADER_SIZE, "section map header")
let (physical_count, cursor) = read_dbi_u16(
data,
0,
DBI_SECTION_MAP_HEADER_SIZE,
)
let (logical_count, _) = read_dbi_u16(
data,
cursor,
DBI_SECTION_MAP_HEADER_SIZE,
)
let count = physical_count.to_int()
if count > limits.max_section_map_entries {
raise DbiLimitExceeded("section map entry limit exceeded")
}
let expected_size = checked_dbi_add(
DBI_SECTION_MAP_HEADER_SIZE,
checked_dbi_mul(count, DBI_SECTION_MAP_ENTRY_SIZE, "section map"),
"section map",
)
if expected_size != data.length() {
raise InvalidSectionMap("section map size does not match its entry count")
}
let entries = []
let mut cursor = DBI_SECTION_MAP_HEADER_SIZE
for _ in 0.. (SectionMapEntry, Int32) raise DbiError {
require_dbi_bytes(
data,
start,
DBI_SECTION_MAP_ENTRY_SIZE,
"section map entry",
)
let entry_end = checked_dbi_add(
start,
DBI_SECTION_MAP_ENTRY_SIZE,
"section map entry",
)
if entry_end > end {
raise TruncatedDbi(entry_end)
}
let (flags, cursor) = read_dbi_u16(data, start, entry_end)
let (overlay, cursor) = read_dbi_u16(data, cursor, entry_end)
let (group, cursor) = read_dbi_u16(data, cursor, entry_end)
let (frame, cursor) = read_dbi_u16(data, cursor, entry_end)
let (section_name_offset, cursor) = read_dbi_u16(data, cursor, entry_end)
let (class_name_offset, cursor) = read_dbi_u16(data, cursor, entry_end)
let (offset, cursor) = read_dbi_u32(data, cursor, entry_end)
let (byte_length, cursor) = read_dbi_u32(data, cursor, entry_end)
if cursor != entry_end {
raise InvalidSectionMap("section map entry has trailing bytes")
}
(
SectionMapEntry::{
flags,
overlay,
group,
frame,
section_name_offset,
class_name_offset,
offset,
byte_length,
},
entry_end,
)
}
///|
fn parse_dbi_modules(
data : Bytes,
limits : DbiLimits,
) -> Array[DbiModule] raise DbiError {
if data.length() == 0 {
return []
}
if data.length() > limits.max_string_bytes {
raise DbiLimitExceeded("MODI string data limit exceeded")
}
let modules = []
let mut position = 0
while position < data.length() {
if modules.length() >= limits.max_modules {
raise DbiLimitExceeded("module count limit exceeded")
}
let record_start = position
require_dbi_bytes(data, position, DBI_MODULE_HEADER_SIZE, "module record")
let (_, cursor) = read_dbi_u32(data, position, data.length())
let (section_contribution, cursor) = read_section_contribution(
data,
cursor,
data.length(),
V60,
)
let (flags, cursor) = read_dbi_u16(data, cursor, data.length())
let (module_stream_index, cursor) = read_dbi_u16(
data,
cursor,
data.length(),
)
let (symbol_debug_bytes, cursor) = read_dbi_u32(data, cursor, data.length())
let (c11_line_bytes, cursor) = read_dbi_u32(data, cursor, data.length())
let (c13_line_bytes, cursor) = read_dbi_u32(data, cursor, data.length())
let (declared_source_file_count, cursor) = read_dbi_u16(
data,
cursor,
data.length(),
)
let (_, cursor) = read_dbi_u16(data, cursor, data.length())
let (_, cursor) = read_dbi_u32(data, cursor, data.length())
let (source_file_name_index, cursor) = read_dbi_u32(
data,
cursor,
data.length(),
)
let (pdb_file_path_index, cursor) = read_dbi_u32(
data,
cursor,
data.length(),
)
let (module_name, cursor) = read_dbi_string(data, cursor, data.length())
let (object_name, cursor) = read_dbi_string(data, cursor, data.length())
let record_size = align_dbi4(cursor - record_start)
let record_end = checked_dbi_add(
record_start, record_size, "module record end",
)
if record_end > data.length() {
raise TruncatedDbi(record_end)
}
modules.push(DbiModule::{
record_index: modules.length(),
module_name,
object_name,
section_contribution,
flags,
module_stream_index,
symbol_debug_bytes,
c11_line_bytes,
c13_line_bytes,
declared_source_file_count,
source_file_name_index,
pdb_file_path_index,
source_files: [],
})
position = record_end
}
if position != data.length() {
raise InvalidDbiModule("MODI substream has trailing bytes")
}
modules
}
///|
fn attach_file_info(
data : Bytes,
modules : Array[DbiModule],
limits : DbiLimits,
) -> (Array[DbiModule], UInt16) raise DbiError {
if data.length() == 0 {
for item in modules {
if item.declared_source_file_count != 0 {
raise InvalidFileInfo("module source files exist without file info")
}
}
return (modules, 0)
}
if data.length() > limits.max_string_bytes {
raise DbiLimitExceeded("file info string data limit exceeded")
}
require_dbi_bytes(data, 0, DBI_FILE_INFO_HEADER_SIZE, "file info header")
let (num_modules, cursor) = read_dbi_u16(data, 0, DBI_FILE_INFO_HEADER_SIZE)
let (reported_source_file_count, _) = read_dbi_u16(
data,
cursor,
DBI_FILE_INFO_HEADER_SIZE,
)
if num_modules.to_int() != modules.length() {
raise InvalidFileInfo("file info module count does not match MODI records")
}
let module_count = num_modules.to_int()
let module_indices_end = checked_dbi_add(
DBI_FILE_INFO_HEADER_SIZE,
checked_dbi_mul(module_count, 2, "module index array"),
"module index array",
)
let counts_end = checked_dbi_add(
module_indices_end,
checked_dbi_mul(module_count, 2, "module file counts"),
"module file counts",
)
if counts_end > data.length() {
raise TruncatedDbi(counts_end)
}
let mut cursor = DBI_FILE_INFO_HEADER_SIZE
for _ in 0..= module_count {
raise InvalidFileInfo("module index is outside the module table")
}
cursor = next
}
let file_counts = []
let mut total_source_files = 0
for _ in 0.. limits.max_source_files {
raise DbiLimitExceeded("source file count limit exceeded")
}
file_counts.push(count)
cursor = next
}
let offsets_end = checked_dbi_add(
cursor,
checked_dbi_mul(total_source_files, 4, "source file offsets"),
"source file offsets",
)
if offsets_end > data.length() {
raise TruncatedDbi(offsets_end)
}
let offsets = []
for _ in 0.. Array[UInt16] raise DbiError {
if data.length() % 2 != 0 {
raise InvalidDbiHeader("optional debug header has an odd byte count")
}
let count = data.length() / 2
if count > limits.max_debug_streams {
raise DbiLimitExceeded("optional debug stream count limit exceeded")
}
let streams = []
let mut cursor = 0
for _ in 0.. (String, Int32) raise DbiError {
if start < 0 || start >= end {
raise TruncatedDbi(start)
}
let mut cursor = start
while cursor < end {
if data.at(cursor) == 0 {
let name = @utf8.decode_lossy(data.exact_view(start~, end=cursor))
return (name, cursor + 1)
}
cursor = cursor + 1
}
raise InvalidDbiModule("DBI name is not NUL-terminated")
}
///|
fn read_dbi_u16(
data : Bytes,
pos : Int32,
end : Int32,
) -> (UInt16, Int32) raise DbiError {
require_dbi_bytes(data, pos, 2, "uint16 field")
if pos + 2 > end {
raise TruncatedDbi(pos + 2)
}
let value = ((data.at(pos + 1).to_int() << 8) | data.at(pos).to_int()).to_uint16()
(value, pos + 2)
}
///|
fn read_dbi_u32(
data : Bytes,
pos : Int32,
end : Int32,
) -> (UInt32, Int32) raise DbiError {
require_dbi_bytes(data, pos, 4, "uint32 field")
if pos + 4 > end {
raise TruncatedDbi(pos + 4)
}
let value = UInt32(
(data.at(pos + 3).to_int().reinterpret_as_uint() << 24) |
(data.at(pos + 2).to_int().reinterpret_as_uint() << 16) |
(data.at(pos + 1).to_int().reinterpret_as_uint() << 8) |
data.at(pos).to_int().reinterpret_as_uint(),
)
(value, pos + 4)
}
///|
fn read_dbi_i32(
data : Bytes,
pos : Int32,
end : Int32,
) -> (Int32, Int32) raise DbiError {
let (value, next) = read_dbi_u32(data, pos, end)
(value.reinterpret_as_int(), next)
}
///|
fn require_dbi_bytes(
data : Bytes,
start : Int32,
length : Int32,
_field : String,
) -> Unit raise DbiError {
if start < 0 || length < 0 {
raise TruncatedDbi(start)
}
let end = checked_dbi_add(start, length, "DBI field")
if end > data.length() {
raise TruncatedDbi(end)
}
}
///|
fn dbi_uint_to_int(value : UInt32, field : String) -> Int32 raise DbiError {
if value > 0x7FFFFFFFU {
raise InvalidDbiHeader("\{field} exceeds Int range")
}
value.reinterpret_as_int()
}
///|
fn align_dbi4(value : Int32) -> Int32 raise DbiError {
if value < 0 || value > 0x7FFFFFFD {
raise InvalidDbiModule("module record size overflows Int range")
}
(value + 3) / 4 * 4
}
///|
fn checked_dbi_add(
left : Int32,
right : Int32,
field : String,
) -> Int32 raise DbiError {
if left < 0 || right < 0 || left > 0x7FFFFFFF - right {
raise InvalidDbiHeader("\{field} overflows Int range")
}
left + right
}
///|
fn checked_dbi_mul(
left : Int32,
right : Int32,
field : String,
) -> Int32 raise DbiError {
if left < 0 || right < 0 || (left != 0 && right > 0x7FFFFFFF / left) {
raise InvalidDbiHeader("\{field} overflows Int range")
}
left * right
}