ms_pdb/dbi/
section_contrib.rs

1//! DBI Section Contribution Substream
2//!
3//! The Section Contributions Substream describes the COFF sections that contributed to a linked
4//! binary. Section contributions come from object files that are submitted to the linker.
5//!
6//! The Section Contributions table is usually quite large, especially for large binaries.
7//!
8//! # References
9//! * [`SC2` in `dbicommon.h`](https://github.com/microsoft/microsoft-pdb/blob/805655a28bd8198004be2ac27e6e0290121a5e89/PDB/include/dbicommon.h#L107)
10
11use super::*;
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
13
14/// Describes one section contribution.
15#[allow(missing_docs)]
16#[derive(Unaligned, IntoBytes, FromBytes, Immutable, KnownLayout, Clone, Debug)]
17#[repr(C)]
18pub struct SectionContribEntry {
19    /// The section index
20    pub section: U16<LE>,
21    /// Alignment padding
22    pub padding1: [u8; 2],
23    pub offset: I32<LE>,
24    pub size: I32<LE>,
25    pub characteristics: U32<LE>,
26    /// The zero-based module index of the module containing this section contribution.
27    pub module_index: U16<LE>,
28    /// Alignment padding
29    pub padding2: [u8; 2],
30    pub data_crc: U32<LE>,
31    pub reloc_crc: U32<LE>,
32}
33
34/// Describes one section contribution.
35#[allow(missing_docs)]
36#[derive(Unaligned, IntoBytes, FromBytes, Immutable, KnownLayout, Clone, Debug)]
37#[repr(C)]
38pub struct SectionContribEntry2 {
39    pub base: SectionContribEntry,
40    pub coff_section: U32<LE>,
41}
42
43impl SectionContribEntry {
44    /// Tests whether `offset` falls within this section contribution.
45    pub fn contains_offset(&self, offset: i32) -> bool {
46        let self_offset = self.offset.get();
47        if offset < self_offset {
48            return false;
49        }
50
51        let overshoot = offset - self_offset;
52        if overshoot >= self.size.get() {
53            return false;
54        }
55
56        true
57    }
58}
59
60/// Decodes the Section Contribution Substream.
61pub struct SectionContributionsSubstream<'a> {
62    /// The array of section contributions.
63    pub contribs: &'a [SectionContribEntry],
64}
65
66/// Version 6.0 of the Section Contributions Substream. This is the only supported version.
67pub const SECTION_CONTRIBUTIONS_SUBSTREAM_VER60: u32 = 0xeffe0000 + 19970605;
68
69impl<'a> SectionContributionsSubstream<'a> {
70    /// Parses the header of the Section Contributions Substream.
71    ///
72    /// It is legal for a Section Contributions Substream to be entirely empty.
73    pub fn parse(bytes: &'a [u8]) -> anyhow::Result<Self> {
74        let mut p = Parser::new(bytes);
75        if p.is_empty() {
76            return Ok(Self { contribs: &[] });
77        }
78
79        let version = p.u32()?;
80
81        match version {
82            SECTION_CONTRIBUTIONS_SUBSTREAM_VER60 => {}
83            _ => {
84                bail!("The Section Contributions Substream has a version number that is not supported. Version: 0x{:08x}", version);
85            }
86        }
87
88        let records_bytes = p.into_rest();
89        let Ok(contribs) = <[SectionContribEntry]>::ref_from_bytes(records_bytes) else {
90            bail!("The Section Contributions stream has an invalid size. It is not a multiple of the section contribution record size.  Size: 0x{:x}",
91                bytes.len());
92        };
93        Ok(SectionContributionsSubstream { contribs })
94    }
95
96    /// Searches for a section contribution that contains the given offset.
97    /// The `section` must match exactly. This uses binary search.
98    pub fn find(&self, section: u16, offset: i32) -> Option<&SectionContribEntry> {
99        let i = self.find_index(section, offset)?;
100        Some(&self.contribs[i])
101    }
102
103    /// Searches for the index of a section contribution that contains the given offset.
104    /// The `section` must match exactly. This uses binary search.
105    pub fn find_index(&self, section: u16, offset: i32) -> Option<usize> {
106        match self
107            .contribs
108            .binary_search_by_key(&(section, offset), |con| {
109                (con.section.get(), con.offset.get())
110            }) {
111            Ok(i) => Some(i),
112            Err(i) => {
113                // We didn't find it, but i is close to it.
114                if i > 0 {
115                    let previous = &self.contribs[i - 1];
116                    if previous.contains_offset(offset) {
117                        return Some(i - 1);
118                    }
119                }
120
121                if i + 1 < self.contribs.len() {
122                    let next = &self.contribs[i + 1];
123                    if next.contains_offset(offset) {
124                        return Some(i + 1);
125                    }
126                }
127
128                None
129            }
130        }
131    }
132
133    /// Searches for a section contribution that contains the given offset.
134    /// The `section` must match exactly. This uses sequential scan (brute force).
135    pub fn find_brute(&self, section: u16, offset: i32) -> Option<&SectionContribEntry> {
136        let i = self.find_index_brute(section, offset)?;
137        Some(&self.contribs[i])
138    }
139
140    /// Searches for the index of a section contribution that contains the given offset.
141    /// The `section` must match exactly. This uses sequential scan (brute force).
142    pub fn find_index_brute(&self, section: u16, offset: i32) -> Option<usize> {
143        self.contribs
144            .iter()
145            .position(|c| c.section.get() == section && c.contains_offset(offset))
146    }
147}
148
149/// Decodes the Section Contribution Substream.
150pub struct SectionContributionsSubstreamMut<'a> {
151    /// The array of section contributions.
152    pub contribs: &'a mut [SectionContribEntry],
153}
154
155impl<'a> SectionContributionsSubstreamMut<'a> {
156    /// Parses the header of the Section Contributions Substream.
157    pub fn parse(bytes: &'a mut [u8]) -> anyhow::Result<Self> {
158        let bytes_len = bytes.len();
159
160        let mut p = ParserMut::new(bytes);
161        if p.is_empty() {
162            return Ok(Self { contribs: &mut [] });
163        }
164
165        let version = p.u32()?;
166
167        match version {
168            SECTION_CONTRIBUTIONS_SUBSTREAM_VER60 => {}
169            _ => {
170                bail!("The Section Contributions Substream has a version number that is not supported. Version: 0x{:08x}", version);
171            }
172        }
173
174        let records_bytes = p.into_rest();
175
176        let Ok(contribs) = <[SectionContribEntry]>::mut_from_bytes(records_bytes) else {
177            bail!("The Section Contributions stream has an invalid size. It is not a multiple of the section contribution record size.  Size: 0x{:x}",
178                bytes_len);
179        };
180        Ok(Self { contribs })
181    }
182
183    /// Given a lookup table that maps module indexes from old to new, this edits a
184    /// Section Contributions table and converts module indexes.
185    pub fn remap_module_indexes(&mut self, modules_old_to_new: &[u32]) -> anyhow::Result<()> {
186        for (i, contrib) in self.contribs.iter_mut().enumerate() {
187            let old = contrib.module_index.get();
188            if let Some(&new) = modules_old_to_new.get(old as usize) {
189                contrib.module_index.set(new as u16);
190            } else {
191                bail!("Section contribution record (at contribution index #{i} has module index {old}, \
192                       which is out of range (num modules is {})",
193                    modules_old_to_new.len());
194            }
195
196            // While we're at it, make sure that the padding fields are cleared.
197            contrib.padding1 = [0; 2];
198            contrib.padding2 = [0; 2];
199        }
200        Ok(())
201    }
202}