///|
const C13_SUBSECTION_HEADER_SIZE : Int32 = 8
///|
const C13_LINE_FRAGMENT_HEADER_SIZE : Int32 = 12
///|
const C13_LINE_BLOCK_HEADER_SIZE : Int32 = 12
///|
const C13_LINE_ENTRY_SIZE : Int32 = 8
///|
const C13_COLUMN_ENTRY_SIZE : Int32 = 4
///|
const C13_CHECKSUM_HEADER_SIZE : Int32 = 6
///|
const C13_SUBSECTION_MASK : UInt32 = 0x7FFFFFFFU
///|
const C13_KIND_LINES : UInt32 = 0x000000F2U
///|
const C13_KIND_STRING_TABLE : UInt32 = 0x000000F3U
///|
const C13_KIND_FILE_CHECKSUMS : UInt32 = 0x000000F4U
///|
const C13_LINES_HAVE_COLUMNS : UInt16 = 0x0001
///|
/// Resource limits for one module's C13 debug subsections.
pub struct C13Limits {
max_bytes : Int32
max_subsections : Int32
max_file_checksums : Int32
max_line_fragments : Int32
max_line_entries : Int32
}
///|
pub fn C13Limits::default() -> C13Limits {
C13Limits::{
max_bytes: 128 * 1024 * 1024,
max_subsections: 4096,
max_file_checksums: 1_000_000,
max_line_fragments: 1_000_000,
max_line_entries: 2_000_000,
}
}
///|
/// One raw C13 debug subsection.
pub struct C13Subsection {
kind : UInt32
payload : Bytes
}
///|
pub fn C13Subsection::kind(self : C13Subsection) -> UInt32 {
self.kind
}
///|
pub fn C13Subsection::payload(self : C13Subsection) -> Bytes {
self.payload
}
///|
/// A flat CodeView string table addressed by byte offsets.
pub struct DebugStringTable {
bytes : Bytes
}
///|
pub fn DebugStringTable::bytes(self : DebugStringTable) -> Bytes {
self.bytes
}
///|
pub fn DebugStringTable::string_at(
self : DebugStringTable,
offset : UInt32,
) -> String? {
let start = offset.reinterpret_as_int()
if start < 0 || start >= self.bytes.length() {
return None
}
let mut cursor = start
while cursor < self.bytes.length() {
if self.bytes.at(cursor) == 0 {
return Some(@utf8.decode_lossy(self.bytes.exact_view(start~, end=cursor)))
}
cursor = cursor + 1
}
None
}
///|
/// One file-checksum entry in a C13 FileChecksums subsection.
pub struct FileChecksum {
subsection_offset : UInt32
file_name_offset : UInt32
kind : Int32
checksum : Bytes
file_path : String
}
///|
pub fn FileChecksum::subsection_offset(self : FileChecksum) -> UInt32 {
self.subsection_offset
}
///|
pub fn FileChecksum::file_name_offset(self : FileChecksum) -> UInt32 {
self.file_name_offset
}
///|
pub fn FileChecksum::kind(self : FileChecksum) -> Int32 {
self.kind
}
///|
pub fn FileChecksum::checksum(self : FileChecksum) -> Bytes {
self.checksum
}
///|
pub fn FileChecksum::file_path(self : FileChecksum) -> String {
self.file_path
}
///|
pub struct LineColumnRange {
start : UInt16
end : UInt16
}
///|
pub fn LineColumnRange::start(self : LineColumnRange) -> UInt16 {
self.start
}
///|
pub fn LineColumnRange::end(self : LineColumnRange) -> UInt16 {
self.end
}
///|
pub struct LineNumber {
offset : UInt32
line_start : UInt32
line_end : UInt32
is_statement : Bool
columns : LineColumnRange?
}
///|
pub fn LineNumber::offset(self : LineNumber) -> UInt32 {
self.offset
}
///|
pub fn LineNumber::line_start(self : LineNumber) -> UInt32 {
self.line_start
}
///|
pub fn LineNumber::line_end(self : LineNumber) -> UInt32 {
self.line_end
}
///|
pub fn LineNumber::is_statement(self : LineNumber) -> Bool {
self.is_statement
}
///|
pub fn LineNumber::columns(self : LineNumber) -> LineColumnRange? {
self.columns
}
///|
pub struct LineBlock {
file_checksum_offset : UInt32
file_path : String
record_size : UInt32
lines : Array[LineNumber]
}
///|
pub fn LineBlock::file_checksum_offset(self : LineBlock) -> UInt32 {
self.file_checksum_offset
}
///|
pub fn LineBlock::file_path(self : LineBlock) -> String {
self.file_path
}
///|
pub fn LineBlock::record_size(self : LineBlock) -> UInt32 {
self.record_size
}
///|
pub fn LineBlock::line_count(self : LineBlock) -> Int32 {
self.lines.length()
}
///|
pub fn LineBlock::line_at(self : LineBlock, index : Int32) -> LineNumber? {
if index < 0 || index >= self.lines.length() {
None
} else {
Some(self.lines[index])
}
}
///|
pub struct LineFragment {
relocation_offset : UInt32
relocation_segment : UInt16
flags : UInt16
code_size : UInt32
blocks : Array[LineBlock]
}
///|
pub fn LineFragment::relocation_offset(self : LineFragment) -> UInt32 {
self.relocation_offset
}
///|
pub fn LineFragment::relocation_segment(self : LineFragment) -> UInt16 {
self.relocation_segment
}
///|
pub fn LineFragment::flags(self : LineFragment) -> UInt16 {
self.flags
}
///|
pub fn LineFragment::has_columns(self : LineFragment) -> Bool {
(self.flags & C13_LINES_HAVE_COLUMNS) != 0
}
///|
pub fn LineFragment::code_size(self : LineFragment) -> UInt32 {
self.code_size
}
///|
pub fn LineFragment::block_count(self : LineFragment) -> Int32 {
self.blocks.length()
}
///|
pub fn LineFragment::block_at(self : LineFragment, index : Int32) -> LineBlock? {
if index < 0 || index >= self.blocks.length() {
None
} else {
Some(self.blocks[index])
}
}
///|
/// A resolved source line at a segment-relative code offset.
pub struct SourceLocation {
segment : UInt16
code_offset : UInt32
file_path : String
line_start : UInt32
line_end : UInt32
is_statement : Bool
columns : LineColumnRange?
}
///|
pub fn SourceLocation::segment(self : SourceLocation) -> UInt16 {
self.segment
}
///|
pub fn SourceLocation::code_offset(self : SourceLocation) -> UInt32 {
self.code_offset
}
///|
pub fn SourceLocation::file_path(self : SourceLocation) -> String {
self.file_path
}
///|
pub fn SourceLocation::line_start(self : SourceLocation) -> UInt32 {
self.line_start
}
///|
pub fn SourceLocation::line_end(self : SourceLocation) -> UInt32 {
self.line_end
}
///|
pub fn SourceLocation::is_statement(self : SourceLocation) -> Bool {
self.is_statement
}
///|
pub fn SourceLocation::columns(self : SourceLocation) -> LineColumnRange? {
self.columns
}
///|
/// Parsed C13 subsections, file checksums, and line contributions.
pub struct C13DebugInfo {
subsections : Array[C13Subsection]
string_table : DebugStringTable?
checksums : Array[FileChecksum]
line_fragments : Array[LineFragment]
}
///|
pub fn C13DebugInfo::subsection_count(self : C13DebugInfo) -> Int32 {
self.subsections.length()
}
///|
pub fn C13DebugInfo::subsection_at(
self : C13DebugInfo,
index : Int32,
) -> C13Subsection? {
if index < 0 || index >= self.subsections.length() {
None
} else {
Some(self.subsections[index])
}
}
///|
pub fn C13DebugInfo::string_table(self : C13DebugInfo) -> DebugStringTable? {
self.string_table
}
///|
pub fn C13DebugInfo::file_checksum_count(self : C13DebugInfo) -> Int32 {
self.checksums.length()
}
///|
pub fn C13DebugInfo::file_checksum_at(
self : C13DebugInfo,
index : Int32,
) -> FileChecksum? {
if index < 0 || index >= self.checksums.length() {
None
} else {
Some(self.checksums[index])
}
}
///|
pub fn C13DebugInfo::line_fragment_count(self : C13DebugInfo) -> Int32 {
self.line_fragments.length()
}
///|
pub fn C13DebugInfo::line_fragment_at(
self : C13DebugInfo,
index : Int32,
) -> LineFragment? {
if index < 0 || index >= self.line_fragments.length() {
None
} else {
Some(self.line_fragments[index])
}
}
///|
pub fn C13DebugInfo::find_line(
self : C13DebugInfo,
segment : UInt16,
offset : UInt32,
) -> SourceLocation? {
for fragment in self.line_fragments {
let fragment_end = fragment.relocation_offset.to_uint64() +
fragment.code_size.to_uint64()
if fragment.relocation_segment == segment &&
offset.to_uint64() >= fragment.relocation_offset.to_uint64() &&
offset.to_uint64() < fragment_end {
let mut best : (LineBlock, LineNumber)? = None
for block in fragment.blocks {
for line in block.lines {
if line.offset.to_uint64() <= offset.to_uint64() {
let replace = match best {
Some((_, current)) =>
line.offset.to_uint64() > current.offset.to_uint64()
None => true
}
if replace {
best = Some((block, line))
}
}
}
}
match best {
Some((block, line)) =>
return Some(SourceLocation::{
segment,
code_offset: offset,
file_path: block.file_path,
line_start: line.line_start,
line_end: line.line_end,
is_statement: line.is_statement,
columns: line.columns,
})
None => ()
}
}
}
None
}
///|
/// Parse the C13 subsection buffer stored in a module debug stream.
pub fn ModuleDebugStream::c13_info(
self : ModuleDebugStream,
limits : C13Limits,
) -> C13DebugInfo raise SymbolError {
parse_c13_debug_info(self.c13_lines, limits)
}
///|
/// Parse C13 data with a PDB `/names` table fallback.
pub fn ModuleDebugStream::c13_info_with_fallback(
self : ModuleDebugStream,
fallback : DebugStringTable?,
limits : C13Limits,
) -> C13DebugInfo raise SymbolError {
parse_c13_debug_info_with_fallback(self.c13_lines, fallback, limits)
}
///|
/// Read a module debug stream and parse its C13 line information.
pub fn MsfFile::module_c13_info(
self : MsfFile,
dbi : DbiFile,
module_index : Int32,
symbol_limits : SymbolLimits,
limits : C13Limits,
) -> C13DebugInfo raise SymbolError {
let debug = self.module_debug_stream(dbi, module_index, symbol_limits)
let fallback = try {
let info = self.pdb_info(PdbInfoLimits::default())
let names = self.pdb_string_table(
info,
"/names",
PdbStringTableLimits::default(),
)
Some(names.strings())
} catch {
_ => None
}
debug.c13_info_with_fallback(fallback, limits)
}
///|
pub fn parse_c13_debug_info(
data : Bytes,
limits : C13Limits,
) -> C13DebugInfo raise SymbolError {
parse_c13_debug_info_with_fallback(data, None, limits)
}
///|
pub fn parse_c13_debug_info_with_fallback(
data : Bytes,
fallback : DebugStringTable?,
limits : C13Limits,
) -> C13DebugInfo raise SymbolError {
if data.length() > limits.max_bytes {
raise C13LimitExceeded("C13 debug byte limit exceeded")
}
let subsections = parse_c13_subsections(data, limits)
let mut string_table : DebugStringTable? = None
for subsection in subsections {
if subsection.kind == C13_KIND_STRING_TABLE {
string_table = Some(DebugStringTable::{ bytes: subsection.payload, })
break
}
}
let string_table = match string_table {
Some(value) => Some(value)
None => fallback
}
let checksums = if has_c13_subsection(subsections, C13_KIND_FILE_CHECKSUMS) {
let table = match string_table {
Some(value) => value
None => raise MissingC13StringTable
}
parse_file_checksums(subsections, table, limits)
} else {
[]
}
let line_fragments = if has_c13_subsection(subsections, C13_KIND_LINES) {
parse_line_fragments(subsections, checksums, limits)
} else {
[]
}
C13DebugInfo::{ subsections, string_table, checksums, line_fragments, }
}
///|
fn parse_c13_subsections(
data : Bytes,
limits : C13Limits,
) -> Array[C13Subsection] raise SymbolError {
let subsections = []
let mut position = 0
while position < data.length() {
if subsections.length() >= limits.max_subsections {
raise C13LimitExceeded("C13 subsection count limit exceeded")
}
let start = position
let header_end = checked_symbol_add(
start,
C13_SUBSECTION_HEADER_SIZE,
"C13 subsection header",
)
if header_end > data.length() {
raise TruncatedC13(header_end)
}
let (kind_raw, cursor) = read_symbol_u32(data, start, header_end)
let (length_raw, cursor) = read_symbol_u32(data, cursor, header_end)
let length = symbol_uint_to_int(length_raw, "C13 subsection length")
let payload_end = checked_symbol_add(
cursor, length, "C13 subsection payload",
)
if payload_end > data.length() {
raise TruncatedC13(payload_end)
}
let aligned_end = align_symbol4(payload_end)
if aligned_end > data.length() {
raise TruncatedC13(aligned_end)
}
subsections.push(C13Subsection::{
kind: kind_raw & C13_SUBSECTION_MASK,
payload: data.exact_view(start=cursor, end=payload_end).to_owned(),
})
position = aligned_end
}
subsections
}
///|
fn has_c13_subsection(
subsections : Array[C13Subsection],
kind : UInt32,
) -> Bool {
for subsection in subsections {
if subsection.kind == kind {
return true
}
}
false
}
///|
fn parse_file_checksums(
subsections : Array[C13Subsection],
strings : DebugStringTable,
limits : C13Limits,
) -> Array[FileChecksum] raise SymbolError {
let checksums = []
for subsection in subsections {
if subsection.kind != C13_KIND_FILE_CHECKSUMS {
continue
}
let payload = subsection.payload
let mut position = 0
while position < payload.length() {
if checksums.length() >= limits.max_file_checksums {
raise C13LimitExceeded("file checksum count limit exceeded")
}
let entry_offset = position
let header_end = checked_symbol_add(
position,
C13_CHECKSUM_HEADER_SIZE,
"file checksum header",
)
if header_end > payload.length() {
raise TruncatedC13(header_end)
}
let (file_name_offset, cursor) = read_symbol_u32(
payload, position, header_end,
)
let (checksum_size, cursor) = read_symbol_u8(payload, cursor, header_end)
let (checksum_kind, cursor) = read_symbol_u8(payload, cursor, header_end)
let checksum_end = checked_symbol_add(
cursor, checksum_size, "file checksum bytes",
)
if checksum_end > payload.length() {
raise TruncatedC13(checksum_end)
}
let file_path = match strings.string_at(file_name_offset) {
Some(value) => value
None =>
raise InvalidFileChecksum(
"file name offset is outside the string table",
)
}
checksums.push(FileChecksum::{
subsection_offset: entry_offset.reinterpret_as_uint(),
file_name_offset,
kind: checksum_kind,
checksum: payload.exact_view(start=cursor, end=checksum_end).to_owned(),
file_path,
})
position = align_symbol4(checksum_end)
if position > payload.length() {
raise TruncatedC13(position)
}
}
}
checksums
}
///|
fn parse_line_fragments(
subsections : Array[C13Subsection],
checksums : Array[FileChecksum],
limits : C13Limits,
) -> Array[LineFragment] raise SymbolError {
let fragments = []
let mut total_lines = 0
for subsection in subsections {
if subsection.kind != C13_KIND_LINES {
continue
}
if fragments.length() >= limits.max_line_fragments {
raise C13LimitExceeded("line fragment count limit exceeded")
}
let payload = subsection.payload
if payload.length() < C13_LINE_FRAGMENT_HEADER_SIZE {
raise TruncatedC13(payload.length())
}
let (relocation_offset, cursor) = read_symbol_u32(
payload,
0,
payload.length(),
)
let (relocation_segment, cursor) = read_symbol_u16(
payload,
cursor,
payload.length(),
)
let (flags, cursor) = read_symbol_u16(payload, cursor, payload.length())
let (code_size, cursor) = read_symbol_u32(payload, cursor, payload.length())
let mut position = cursor
let has_columns = (flags & C13_LINES_HAVE_COLUMNS) != 0
let blocks = []
while position < payload.length() {
let block_start = position
let block_header_end = checked_symbol_add(
position,
C13_LINE_BLOCK_HEADER_SIZE,
"line block header",
)
if block_header_end > payload.length() {
raise TruncatedC13(block_header_end)
}
let (file_checksum_offset, cursor) = read_symbol_u32(
payload, position, block_header_end,
)
let (line_count_raw, cursor) = read_symbol_u32(
payload, cursor, block_header_end,
)
let (block_size_raw, cursor) = read_symbol_u32(
payload, cursor, block_header_end,
)
let line_count = symbol_uint_to_int(line_count_raw, "line count")
let block_size = symbol_uint_to_int(block_size_raw, "line block size")
if line_count < 0 || block_size < C13_LINE_BLOCK_HEADER_SIZE {
raise InvalidLineInfo("invalid line block header")
}
total_lines = checked_symbol_add(total_lines, line_count, "line count")
if total_lines > limits.max_line_entries {
raise C13LimitExceeded("line entry count limit exceeded")
}
let block_end = checked_symbol_add(block_start, block_size, "line block")
if block_end > payload.length() {
raise TruncatedC13(block_end)
}
let line_bytes = checked_symbol_mul(
line_count,
C13_LINE_ENTRY_SIZE,
"line entries",
)
let column_bytes = if has_columns {
checked_symbol_mul(line_count, C13_COLUMN_ENTRY_SIZE, "column entries")
} else {
0
}
let records_end = checked_symbol_add(
checked_symbol_add(cursor, line_bytes, "line entries"),
column_bytes,
"line columns",
)
if records_end > block_end {
raise InvalidLineInfo("line block content exceeds its declared size")
}
let file_path = file_path_for_checksum(checksums, file_checksum_offset)
let lines = []
let line_cursor_start = cursor
for index in 0..> 24) & 0x0000007FU,
is_statement: (line_flags & 0x80000000U) != 0U,
columns,
})
}
blocks.push(LineBlock::{
file_checksum_offset,
file_path,
record_size: block_size_raw,
lines,
})
position = block_end
}
fragments.push(LineFragment::{
relocation_offset,
relocation_segment,
flags,
code_size,
blocks,
})
}
fragments
}
///|
fn file_path_for_checksum(
checksums : Array[FileChecksum],
offset : UInt32,
) -> String raise SymbolError {
for checksum in checksums {
if checksum.subsection_offset == offset {
return checksum.file_path
}
}
raise InvalidLineInfo("line block references a missing file checksum")
}
///|
fn align_symbol4(value : Int32) -> Int32 raise SymbolError {
if value < 0 || value > 0x7FFFFFFD {
raise InvalidC13Subsection("C13 alignment overflows Int range")
}
(value + 3) / 4 * 4
}