1mod checksum;
7mod subsection;
8
9pub use checksum::*;
10pub use subsection::*;
11
12use crate::codeview::syms::OffsetSegment;
13use crate::names::NameIndex;
14use anyhow::{bail, Context};
15use ms_codeview::parser::{Parser, ParserError, ParserMut};
16use ms_codeview::{HasRestLen, IteratorWithRangesExt};
17use std::mem::{size_of, take};
18use tracing::{trace, warn};
19use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned, LE, U16, U32};
20
21#[derive(Copy, Clone, Eq, PartialEq)]
25#[repr(transparent)]
26pub struct SubsectionKind(pub u32);
27
28macro_rules! subsections {
29 ($( $(#[$a:meta])* $name:ident = $value:expr;)*) => {
30 impl SubsectionKind {
31 $(
32 $(#[$a])*
33 #[allow(missing_docs)]
34 pub const $name: SubsectionKind = SubsectionKind($value);
35 )*
36 }
37
38 impl std::fmt::Debug for SubsectionKind {
39 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
40 let s: &str = match *self {
41 $( SubsectionKind::$name => stringify!($name), )*
42 _ => return write!(fmt, "??(0x{:x})", self.0),
43 };
44 fmt.write_str(s)
45 }
46 }
47 }
48}
49
50subsections! {
51 SYMBOLS = 0xf1;
52 LINES = 0xf2;
54 STRING_TABLE = 0xf3;
55 FILE_CHECKSUMS = 0xf4;
58
59 FRAMEDATA = 0xF5;
60 INLINEELINES = 0xF6;
61 CROSSSCOPEIMPORTS = 0xF7;
62 CROSSSCOPEEXPORTS = 0xF8;
63
64 IL_LINES = 0xF9;
65 FUNC_MDTOKEN_MAP = 0xFA;
66 TYPE_MDTOKEN_MAP = 0xFB;
67 MERGED_ASSEMBLYINPUT = 0xFC;
68
69 COFF_SYMBOL_RVA = 0xFD;
70}
71
72pub struct LineData<'a> {
75 bytes: &'a [u8],
76}
77
78impl<'a> LineData<'a> {
79 pub fn new(bytes: &'a [u8]) -> Self {
82 Self { bytes }
83 }
84
85 pub fn subsections(&self) -> SubsectionIter<'a> {
87 SubsectionIter::new(self.bytes)
88 }
89
90 pub fn find_checksums_bytes(&self) -> Option<&'a [u8]> {
92 for subsection in self.subsections() {
93 if subsection.kind == SubsectionKind::FILE_CHECKSUMS {
94 return Some(subsection.data);
95 }
96 }
97 None
98 }
99
100 pub fn find_checksums(&self) -> Option<FileChecksumsSubsection<'a>> {
102 let subsection_bytes = self.find_checksums_bytes()?;
103 Some(FileChecksumsSubsection::new(subsection_bytes))
104 }
105
106 pub fn iter_name_index<F>(&self, mut f: F) -> anyhow::Result<()>
110 where
111 F: FnMut(NameIndex),
112 {
113 if let Some(checksums) = self.find_checksums() {
114 for subsection in self.subsections() {
115 match subsection.kind {
116 SubsectionKind::LINES => {
117 let lines_subsection = LinesSubsection::parse(subsection.data)?;
118 for block in lines_subsection.blocks() {
119 let file = checksums.get_file(block.header.file_index.get())?;
120 let ni = file.header.name.get();
121 f(NameIndex(ni));
122 }
123 }
124 _ => {}
125 }
126 }
127 } else {
128 for subsection in self.subsections() {
129 match subsection.kind {
130 SubsectionKind::LINES => {
131 bail!("This C13 Line Data substream contains LINES subsections, but does not contain a FILE_CHECKSUMS subsection.");
132 }
133 _ => {}
134 }
135 }
136 };
137
138 Ok(())
139 }
140}
141
142pub struct LineDataMut<'a> {
145 bytes: &'a mut [u8],
146}
147
148impl<'a> LineDataMut<'a> {
149 pub fn new(bytes: &'a mut [u8]) -> Self {
151 Self { bytes }
152 }
153
154 pub fn subsections_mut(&mut self) -> SubsectionIterMut<'_> {
156 SubsectionIterMut::new(self.bytes)
157 }
158
159 pub fn remap_name_indexes<F>(&mut self, name_remapping: F) -> anyhow::Result<()>
170 where
171 F: Fn(NameIndex) -> anyhow::Result<NameIndex>,
172 {
173 for subsection in self.subsections_mut() {
174 match subsection.kind {
175 SubsectionKind::FILE_CHECKSUMS => {
176 let mut checksums = FileChecksumsSubsectionMut::new(subsection.data);
177 for checksum in checksums.iter_mut() {
178 let old_name = NameIndex(checksum.header.name.get());
180 let new_name = name_remapping(old_name)
181 .with_context(|| format!("old_name: {old_name}"))?;
182 checksum.header.name = U32::new(new_name.0);
183 }
184 }
185
186 _ => {}
187 }
188 }
189
190 Ok(())
191 }
192}
193
194pub struct LinesSubsection<'a> {
200 pub contribution: &'a Contribution,
202 pub blocks_data: &'a [u8],
205}
206
207impl<'a> LinesSubsection<'a> {
208 pub fn parse(bytes: &'a [u8]) -> Result<Self, ParserError> {
210 let mut p = Parser::new(bytes);
211 Ok(Self {
212 contribution: p.get()?,
213 blocks_data: p.into_rest(),
214 })
215 }
216
217 pub fn blocks(&self) -> IterBlocks<'a> {
219 IterBlocks {
220 bytes: self.blocks_data,
221 have_columns: self.contribution.have_columns(),
222 }
223 }
224}
225
226pub struct LinesSubsectionMut<'a> {
232 pub contribution: &'a mut Contribution,
234 pub blocks_data: &'a mut [u8],
237}
238
239impl<'a> LinesSubsectionMut<'a> {
240 pub fn parse(bytes: &'a mut [u8]) -> Result<Self, ParserError> {
242 let mut p = ParserMut::new(bytes);
243 Ok(Self {
244 contribution: p.get_mut()?,
245 blocks_data: p.into_rest(),
246 })
247 }
248
249 pub fn blocks(&self) -> IterBlocks<'_> {
251 IterBlocks {
252 bytes: self.blocks_data,
253 have_columns: self.contribution.have_columns(),
254 }
255 }
256
257 pub fn blocks_mut(&mut self) -> IterBlocksMut<'_> {
259 IterBlocksMut {
260 bytes: self.blocks_data,
261 have_columns: self.contribution.have_columns(),
262 }
263 }
264}
265
266pub struct IterBlocks<'a> {
268 bytes: &'a [u8],
269 have_columns: bool,
270}
271
272impl<'a> HasRestLen for IterBlocks<'a> {
273 fn rest_len(&self) -> usize {
274 self.bytes.len()
275 }
276}
277
278impl<'a> Iterator for IterBlocks<'a> {
279 type Item = Block<'a>;
280
281 fn next(&mut self) -> Option<Self::Item> {
282 if self.bytes.is_empty() {
283 return None;
284 }
285
286 let mut p = Parser::new(self.bytes);
287 let Ok(header) = p.get::<BlockHeader>() else {
288 warn!("failed to read BlockHeader");
289 return None;
290 };
291
292 let block_size: usize = header.block_size.get() as usize;
293 let Some(data_len) = block_size.checked_sub(size_of::<BlockHeader>()) else {
294 warn!("invalid block; block_size is less than size of block header");
295 return None;
296 };
297
298 trace!(
299 file_index = header.file_index.get(),
300 num_lines = header.num_lines.get(),
301 block_size = header.block_size.get(),
302 data_len,
303 "block header"
304 );
305
306 let Ok(data) = p.bytes(data_len) else {
307 warn!(
308 needed_bytes = data_len,
309 have_bytes = p.len(),
310 "invalid block: need more bytes for block contents"
311 );
312 return None;
313 };
314
315 self.bytes = p.into_rest();
316 Some(Block {
317 header,
318 data,
319 have_columns: self.have_columns,
320 })
321 }
322}
323
324pub struct IterBlocksMut<'a> {
326 bytes: &'a mut [u8],
327 have_columns: bool,
328}
329
330impl<'a> HasRestLen for IterBlocksMut<'a> {
331 fn rest_len(&self) -> usize {
332 self.bytes.len()
333 }
334}
335
336impl<'a> Iterator for IterBlocksMut<'a> {
337 type Item = BlockMut<'a>;
338
339 fn next(&mut self) -> Option<Self::Item> {
340 if self.bytes.is_empty() {
341 return None;
342 }
343
344 let mut p = ParserMut::new(take(&mut self.bytes));
345 let Ok(header) = p.get_mut::<BlockHeader>() else {
346 warn!("failed to read BlockHeader");
347 return None;
348 };
349
350 let block_size: usize = header.block_size.get() as usize;
351 let Some(data_len) = block_size.checked_sub(size_of::<BlockHeader>()) else {
352 warn!("invalid block; block_size is less than size of block header");
353 return None;
354 };
355
356 trace!(
357 "block header: file_index = {}, num_lines = {}, block_size = {}, data_len = {}",
358 header.file_index.get(),
359 header.num_lines.get(),
360 header.block_size.get(),
361 data_len
362 );
363
364 let Ok(data) = p.bytes_mut(data_len) else {
365 warn!(
366 "invalid block: need {} bytes for block contents, only have {}",
367 data_len,
368 p.len()
369 );
370 return None;
371 };
372
373 self.bytes = p.into_rest();
374 Some(BlockMut {
375 header,
376 data,
377 have_columns: self.have_columns,
378 })
379 }
380}
381
382pub struct Block<'a> {
386 pub header: &'a BlockHeader,
388 pub have_columns: bool,
390 pub data: &'a [u8],
393}
394
395impl<'a> Block<'a> {
396 pub fn lines(&self) -> &'a [LineRecord] {
398 let num_lines = self.header.num_lines.get() as usize;
399 if let Ok((lines, _)) = <[LineRecord]>::ref_from_prefix_with_elems(self.data, num_lines) {
400 lines
401 } else {
402 warn!("failed to get lines_data for a block; wrong size");
403 &[]
404 }
405 }
406
407 pub fn columns(&self) -> Option<&'a [ColumnRecord]> {
409 if !self.have_columns {
410 return None;
411 }
412
413 let num_lines = self.header.num_lines.get() as usize;
414 let lines_size = num_lines * size_of::<LineRecord>();
415 let Some(column_data) = self.data.get(lines_size..) else {
416 warn!("failed to get column data for a block; wrong size");
417 return None;
418 };
419
420 let Ok((columns, _)) = <[ColumnRecord]>::ref_from_prefix_with_elems(column_data, num_lines)
421 else {
422 warn!("failed to get column data for a block; byte size is wrong");
423 return None;
424 };
425
426 Some(columns)
427 }
428}
429
430pub struct BlockMut<'a> {
434 pub header: &'a mut BlockHeader,
436 pub have_columns: bool,
438 pub data: &'a mut [u8],
441}
442
443#[derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned, Clone)]
447#[repr(C)]
448pub struct LineRecord {
449 pub offset: U32<LE>,
452
453 pub flags: U32<LE>,
462}
463
464impl LineRecord {
465 pub fn line_num_start(&self) -> u32 {
467 self.flags.get() & 0x00_ff_ff_ff
468 }
469
470 pub fn delta_line_end(&self) -> u8 {
473 ((self.flags.get() >> 24) & 0x7f) as u8
474 }
475
476 pub fn statement(&self) -> bool {
478 (self.flags.get() >> 31) != 0
479 }
480}
481
482impl std::fmt::Debug for LineRecord {
483 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
484 write!(fmt, "+{} L{}", self.offset.get(), self.line_num_start())?;
485
486 let delta_line_end = self.delta_line_end();
487 if delta_line_end != 0 {
488 write!(fmt, "..+{}", delta_line_end)?;
489 }
490
491 if self.statement() {
492 write!(fmt, " S")?;
493 }
494
495 Ok(())
496 }
497}
498
499#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
501#[repr(C)]
502pub struct ColumnRecord {
503 pub start_offset: U16<LE>,
505 pub end_offset: U16<LE>,
507}
508
509#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
510#[repr(C)]
511#[allow(missing_docs)]
512pub struct Contribution {
513 pub offset: U32<LE>,
514 pub segment: U16<LE>,
515 pub flags: U16<LE>,
516 pub size: U32<LE>,
517 }
520
521impl Contribution {
522 pub fn have_columns(&self) -> bool {
524 (self.flags.get() & CV_LINES_HAVE_COLUMNS) != 0
525 }
526
527 pub fn offset_segment(&self) -> OffsetSegment {
529 OffsetSegment {
530 offset: self.offset,
531 segment: self.segment,
532 }
533 }
534}
535
536pub const CV_LINES_HAVE_COLUMNS: u16 = 0x0001;
538
539#[allow(missing_docs)]
540pub struct LinesEntry<'a> {
541 pub header: &'a Contribution,
542 pub blocks: &'a [u8],
543}
544
545#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
549#[repr(C)]
550pub struct BlockHeader {
551 pub file_index: U32<LE>,
553 pub num_lines: U32<LE>,
557 pub block_size: U32<LE>,
560 }
563
564pub fn fixup_c13_line_data(
567 file_permutation: &[u32], sorted_names: &crate::names::NameIndexMapping,
569 c13_line_data: &mut crate::lines::LineDataMut<'_>,
570) -> anyhow::Result<()> {
571 let mut checksum_files_mapping: Vec<(u32, u32)> = Vec::with_capacity(file_permutation.len());
573
574 for subsection in c13_line_data.subsections_mut() {
575 match subsection.kind {
576 SubsectionKind::FILE_CHECKSUMS => {
577 let mut checksums = FileChecksumsSubsectionMut::new(subsection.data);
578 let mut checksum_ranges = Vec::with_capacity(file_permutation.len());
579 for (checksum_range, checksum) in checksums.iter_mut().with_ranges() {
580 let old_name = NameIndex(checksum.header.name.get());
582 let new_name = sorted_names
583 .map_old_to_new(old_name)
584 .with_context(|| format!("old_name: {old_name}"))?;
585 checksum.header.name = U32::new(new_name.0);
586 checksum_ranges.push(checksum_range);
587 }
588
589 let mut new_checksums: Vec<u8> = Vec::with_capacity(subsection.data.len());
593 for &old_file_index in file_permutation.iter() {
594 let old_range = checksum_ranges[old_file_index as usize].clone();
595 checksum_files_mapping
596 .push((old_range.start as u32, new_checksums.len() as u32));
597 let old_checksum_data = &subsection.data[old_range];
598 new_checksums.extend_from_slice(old_checksum_data);
599 }
600 checksum_files_mapping.sort_unstable();
601
602 assert_eq!(new_checksums.len(), subsection.data.len());
603 subsection.data.copy_from_slice(&new_checksums);
604 }
605
606 _ => {}
607 }
608 }
609
610 for subsection in c13_line_data.subsections_mut() {
615 match subsection.kind {
616 SubsectionKind::LINES => {
617 let mut lines = LinesSubsectionMut::parse(subsection.data)?;
619 for block in lines.blocks_mut() {
620 let old_file_index = block.header.file_index.get();
621 match checksum_files_mapping
622 .binary_search_by_key(&old_file_index, |&(old, _new)| old)
623 {
624 Ok(i) => {
625 let (_old, new) = checksum_files_mapping[i];
626 block.header.file_index = U32::new(new);
627 }
628 Err(_) => {
629 bail!("DEBUG_S_LINES section contains invalid file index: {old_file_index}");
630 }
631 }
632 }
633 }
634
635 _ => {}
636 }
637 }
638
639 Ok(())
640}
641
642pub const JMC_LINE_NO_STEP_INTO: u32 = 0xf00f00;
657
658pub const JMC_LINE_FEE_FEE: u32 = 0xfeefee;
660
661pub fn is_jmc_line(line: u32) -> bool {
663 line == JMC_LINE_NO_STEP_INTO || line == JMC_LINE_FEE_FEE
664}