1mod checksum;
7mod subsection;
8
9pub use checksum::*;
10pub use subsection::*;
11
12use crate::codeview::syms::OffsetSegment;
13use crate::names::NameIndex;
14use anyhow::{Context, bail};
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, LE, U16, U32, Unaligned};
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!(
132 "This C13 Line Data substream contains LINES subsections, but does not contain a FILE_CHECKSUMS subsection."
133 );
134 }
135 _ => {}
136 }
137 }
138 };
139
140 Ok(())
141 }
142}
143
144pub struct LineDataMut<'a> {
147 bytes: &'a mut [u8],
148}
149
150impl<'a> LineDataMut<'a> {
151 pub fn new(bytes: &'a mut [u8]) -> Self {
153 Self { bytes }
154 }
155
156 pub fn subsections_mut(&mut self) -> SubsectionIterMut<'_> {
158 SubsectionIterMut::new(self.bytes)
159 }
160
161 pub fn remap_name_indexes<F>(&mut self, name_remapping: F) -> anyhow::Result<()>
172 where
173 F: Fn(NameIndex) -> anyhow::Result<NameIndex>,
174 {
175 for subsection in self.subsections_mut() {
176 match subsection.kind {
177 SubsectionKind::FILE_CHECKSUMS => {
178 let mut checksums = FileChecksumsSubsectionMut::new(subsection.data);
179 for checksum in checksums.iter_mut() {
180 let old_name = NameIndex(checksum.header.name.get());
182 let new_name = name_remapping(old_name)
183 .with_context(|| format!("old_name: {old_name}"))?;
184 checksum.header.name = U32::new(new_name.0);
185 }
186 }
187
188 _ => {}
189 }
190 }
191
192 Ok(())
193 }
194}
195
196pub struct LinesSubsection<'a> {
202 pub contribution: &'a Contribution,
204 pub blocks_data: &'a [u8],
207}
208
209impl<'a> LinesSubsection<'a> {
210 pub fn parse(bytes: &'a [u8]) -> Result<Self, ParserError> {
212 let mut p = Parser::new(bytes);
213 Ok(Self {
214 contribution: p.get()?,
215 blocks_data: p.into_rest(),
216 })
217 }
218
219 pub fn blocks(&self) -> IterBlocks<'a> {
221 IterBlocks {
222 bytes: self.blocks_data,
223 have_columns: self.contribution.have_columns(),
224 }
225 }
226}
227
228pub struct LinesSubsectionMut<'a> {
234 pub contribution: &'a mut Contribution,
236 pub blocks_data: &'a mut [u8],
239}
240
241impl<'a> LinesSubsectionMut<'a> {
242 pub fn parse(bytes: &'a mut [u8]) -> Result<Self, ParserError> {
244 let mut p = ParserMut::new(bytes);
245 Ok(Self {
246 contribution: p.get_mut()?,
247 blocks_data: p.into_rest(),
248 })
249 }
250
251 pub fn blocks(&self) -> IterBlocks<'_> {
253 IterBlocks {
254 bytes: self.blocks_data,
255 have_columns: self.contribution.have_columns(),
256 }
257 }
258
259 pub fn blocks_mut(&mut self) -> IterBlocksMut<'_> {
261 IterBlocksMut {
262 bytes: self.blocks_data,
263 have_columns: self.contribution.have_columns(),
264 }
265 }
266}
267
268pub struct IterBlocks<'a> {
270 bytes: &'a [u8],
271 have_columns: bool,
272}
273
274impl<'a> HasRestLen for IterBlocks<'a> {
275 fn rest_len(&self) -> usize {
276 self.bytes.len()
277 }
278}
279
280impl<'a> Iterator for IterBlocks<'a> {
281 type Item = Block<'a>;
282
283 fn next(&mut self) -> Option<Self::Item> {
284 if self.bytes.is_empty() {
285 return None;
286 }
287
288 let mut p = Parser::new(self.bytes);
289 let Ok(header) = p.get::<BlockHeader>() else {
290 warn!("failed to read BlockHeader");
291 return None;
292 };
293
294 let block_size: usize = header.block_size.get() as usize;
295 let Some(data_len) = block_size.checked_sub(size_of::<BlockHeader>()) else {
296 warn!("invalid block; block_size is less than size of block header");
297 return None;
298 };
299
300 trace!(
301 file_index = header.file_index.get(),
302 num_lines = header.num_lines.get(),
303 block_size = header.block_size.get(),
304 data_len,
305 "block header"
306 );
307
308 let Ok(data) = p.bytes(data_len) else {
309 warn!(
310 needed_bytes = data_len,
311 have_bytes = p.len(),
312 "invalid block: need more bytes for block contents"
313 );
314 return None;
315 };
316
317 self.bytes = p.into_rest();
318 Some(Block {
319 header,
320 data,
321 have_columns: self.have_columns,
322 })
323 }
324}
325
326pub struct IterBlocksMut<'a> {
328 bytes: &'a mut [u8],
329 have_columns: bool,
330}
331
332impl<'a> HasRestLen for IterBlocksMut<'a> {
333 fn rest_len(&self) -> usize {
334 self.bytes.len()
335 }
336}
337
338impl<'a> Iterator for IterBlocksMut<'a> {
339 type Item = BlockMut<'a>;
340
341 fn next(&mut self) -> Option<Self::Item> {
342 if self.bytes.is_empty() {
343 return None;
344 }
345
346 let mut p = ParserMut::new(take(&mut self.bytes));
347 let Ok(header) = p.get_mut::<BlockHeader>() else {
348 warn!("failed to read BlockHeader");
349 return None;
350 };
351
352 let block_size: usize = header.block_size.get() as usize;
353 let Some(data_len) = block_size.checked_sub(size_of::<BlockHeader>()) else {
354 warn!("invalid block; block_size is less than size of block header");
355 return None;
356 };
357
358 trace!(
359 "block header: file_index = {}, num_lines = {}, block_size = {}, data_len = {}",
360 header.file_index.get(),
361 header.num_lines.get(),
362 header.block_size.get(),
363 data_len
364 );
365
366 let Ok(data) = p.bytes_mut(data_len) else {
367 warn!(
368 "invalid block: need {} bytes for block contents, only have {}",
369 data_len,
370 p.len()
371 );
372 return None;
373 };
374
375 self.bytes = p.into_rest();
376 Some(BlockMut {
377 header,
378 data,
379 have_columns: self.have_columns,
380 })
381 }
382}
383
384pub struct Block<'a> {
388 pub header: &'a BlockHeader,
390 pub have_columns: bool,
392 pub data: &'a [u8],
395}
396
397impl<'a> Block<'a> {
398 pub fn lines(&self) -> &'a [LineRecord] {
400 let num_lines = self.header.num_lines.get() as usize;
401 if let Ok((lines, _)) = <[LineRecord]>::ref_from_prefix_with_elems(self.data, num_lines) {
402 lines
403 } else {
404 warn!("failed to get lines_data for a block; wrong size");
405 &[]
406 }
407 }
408
409 pub fn columns(&self) -> Option<&'a [ColumnRecord]> {
411 if !self.have_columns {
412 return None;
413 }
414
415 let num_lines = self.header.num_lines.get() as usize;
416 let lines_size = num_lines * size_of::<LineRecord>();
417 let Some(column_data) = self.data.get(lines_size..) else {
418 warn!("failed to get column data for a block; wrong size");
419 return None;
420 };
421
422 let Ok((columns, _)) = <[ColumnRecord]>::ref_from_prefix_with_elems(column_data, num_lines)
423 else {
424 warn!("failed to get column data for a block; byte size is wrong");
425 return None;
426 };
427
428 Some(columns)
429 }
430}
431
432pub struct BlockMut<'a> {
436 pub header: &'a mut BlockHeader,
438 pub have_columns: bool,
440 pub data: &'a mut [u8],
443}
444
445#[derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned, Clone)]
449#[repr(C)]
450pub struct LineRecord {
451 pub offset: U32<LE>,
454
455 pub flags: U32<LE>,
464}
465
466impl LineRecord {
467 pub fn line_num_start(&self) -> u32 {
469 self.flags.get() & 0x00_ff_ff_ff
470 }
471
472 pub fn delta_line_end(&self) -> u8 {
475 ((self.flags.get() >> 24) & 0x7f) as u8
476 }
477
478 pub fn statement(&self) -> bool {
480 (self.flags.get() >> 31) != 0
481 }
482}
483
484impl std::fmt::Debug for LineRecord {
485 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
486 write!(fmt, "+{} L{}", self.offset.get(), self.line_num_start())?;
487
488 let delta_line_end = self.delta_line_end();
489 if delta_line_end != 0 {
490 write!(fmt, "..+{delta_line_end}")?;
491 }
492
493 if self.statement() {
494 write!(fmt, " S")?;
495 }
496
497 Ok(())
498 }
499}
500
501#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
503#[repr(C)]
504pub struct ColumnRecord {
505 pub start_offset: U16<LE>,
507 pub end_offset: U16<LE>,
509}
510
511#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
512#[repr(C)]
513#[allow(missing_docs)]
514pub struct Contribution {
515 pub offset: U32<LE>,
516 pub segment: U16<LE>,
517 pub flags: U16<LE>,
518 pub size: U32<LE>,
519 }
522
523impl Contribution {
524 pub fn have_columns(&self) -> bool {
526 (self.flags.get() & CV_LINES_HAVE_COLUMNS) != 0
527 }
528
529 pub fn offset_segment(&self) -> OffsetSegment {
531 OffsetSegment {
532 offset: self.offset,
533 segment: self.segment,
534 }
535 }
536}
537
538pub const CV_LINES_HAVE_COLUMNS: u16 = 0x0001;
540
541#[allow(missing_docs)]
542pub struct LinesEntry<'a> {
543 pub header: &'a Contribution,
544 pub blocks: &'a [u8],
545}
546
547#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned)]
551#[repr(C)]
552pub struct BlockHeader {
553 pub file_index: U32<LE>,
555 pub num_lines: U32<LE>,
559 pub block_size: U32<LE>,
562 }
565
566pub fn fixup_c13_line_data(
569 file_permutation: &[u32], sorted_names: &crate::names::NameIndexMapping,
571 c13_line_data: &mut crate::lines::LineDataMut<'_>,
572) -> anyhow::Result<()> {
573 let mut checksum_files_mapping: Vec<(u32, u32)> = Vec::with_capacity(file_permutation.len());
575
576 for subsection in c13_line_data.subsections_mut() {
577 match subsection.kind {
578 SubsectionKind::FILE_CHECKSUMS => {
579 let mut checksums = FileChecksumsSubsectionMut::new(subsection.data);
580 let mut checksum_ranges = Vec::with_capacity(file_permutation.len());
581 for (checksum_range, checksum) in checksums.iter_mut().with_ranges() {
582 let old_name = NameIndex(checksum.header.name.get());
584 let new_name = sorted_names
585 .map_old_to_new(old_name)
586 .with_context(|| format!("old_name: {old_name}"))?;
587 checksum.header.name = U32::new(new_name.0);
588 checksum_ranges.push(checksum_range);
589 }
590
591 let mut new_checksums: Vec<u8> = Vec::with_capacity(subsection.data.len());
595 for &old_file_index in file_permutation.iter() {
596 let old_range = checksum_ranges[old_file_index as usize].clone();
597 checksum_files_mapping
598 .push((old_range.start as u32, new_checksums.len() as u32));
599 let old_checksum_data = &subsection.data[old_range];
600 new_checksums.extend_from_slice(old_checksum_data);
601 }
602 checksum_files_mapping.sort_unstable();
603
604 assert_eq!(new_checksums.len(), subsection.data.len());
605 subsection.data.copy_from_slice(&new_checksums);
606 }
607
608 _ => {}
609 }
610 }
611
612 for subsection in c13_line_data.subsections_mut() {
617 match subsection.kind {
618 SubsectionKind::LINES => {
619 let mut lines = LinesSubsectionMut::parse(subsection.data)?;
621 for block in lines.blocks_mut() {
622 let old_file_index = block.header.file_index.get();
623 match checksum_files_mapping
624 .binary_search_by_key(&old_file_index, |&(old, _new)| old)
625 {
626 Ok(i) => {
627 let (_old, new) = checksum_files_mapping[i];
628 block.header.file_index = U32::new(new);
629 }
630 Err(_) => {
631 bail!(
632 "DEBUG_S_LINES section contains invalid file index: {old_file_index}"
633 );
634 }
635 }
636 }
637 }
638
639 _ => {}
640 }
641 }
642
643 Ok(())
644}
645
646pub const JMC_LINE_NO_STEP_INTO: u32 = 0xf00f00;
661
662pub const JMC_LINE_FEE_FEE: u32 = 0xfeefee;
664
665pub fn is_jmc_line(line: u32) -> bool {
667 line == JMC_LINE_NO_STEP_INTO || line == JMC_LINE_FEE_FEE
668}