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!(
85                    "The Section Contributions Substream has a version number that is not supported. Version: 0x{:08x}",
86                    version
87                );
88            }
89        }
90
91        let records_bytes = p.into_rest();
92        let Ok(contribs) = <[SectionContribEntry]>::ref_from_bytes(records_bytes) else {
93            bail!(
94                "The Section Contributions stream has an invalid size. It is not a multiple of the section contribution record size.  Size: 0x{:x}",
95                bytes.len()
96            );
97        };
98        Ok(SectionContributionsSubstream { contribs })
99    }
100
101    /// Searches for a section contribution that contains the given offset.
102    /// The `section` must match exactly. This uses binary search.
103    pub fn find(&self, section: u16, offset: i32) -> Option<&SectionContribEntry> {
104        let i = self.find_index(section, offset)?;
105        Some(&self.contribs[i])
106    }
107
108    /// Searches for the index of a section contribution that contains the given offset.
109    /// The `section` must match exactly. This uses binary search.
110    pub fn find_index(&self, section: u16, offset: i32) -> Option<usize> {
111        match self
112            .contribs
113            .binary_search_by_key(&(section, offset), |con| {
114                (con.section.get(), con.offset.get())
115            }) {
116            Ok(i) => Some(i),
117            Err(i) => {
118                // We didn't find it, but i is close to it.
119                if i > 0 {
120                    let previous = &self.contribs[i - 1];
121                    if previous.contains_offset(offset) {
122                        return Some(i - 1);
123                    }
124                }
125
126                if i + 1 < self.contribs.len() {
127                    let next = &self.contribs[i + 1];
128                    if next.contains_offset(offset) {
129                        return Some(i + 1);
130                    }
131                }
132
133                None
134            }
135        }
136    }
137
138    /// Searches for a section contribution that contains the given offset.
139    /// The `section` must match exactly. This uses sequential scan (brute force).
140    pub fn find_brute(&self, section: u16, offset: i32) -> Option<&SectionContribEntry> {
141        let i = self.find_index_brute(section, offset)?;
142        Some(&self.contribs[i])
143    }
144
145    /// Searches for the index of a section contribution that contains the given offset.
146    /// The `section` must match exactly. This uses sequential scan (brute force).
147    pub fn find_index_brute(&self, section: u16, offset: i32) -> Option<usize> {
148        self.contribs
149            .iter()
150            .position(|c| c.section.get() == section && c.contains_offset(offset))
151    }
152}
153
154/// Decodes the Section Contribution Substream.
155pub struct SectionContributionsSubstreamMut<'a> {
156    /// The array of section contributions.
157    pub contribs: &'a mut [SectionContribEntry],
158}
159
160impl<'a> SectionContributionsSubstreamMut<'a> {
161    /// Parses the header of the Section Contributions Substream.
162    pub fn parse(bytes: &'a mut [u8]) -> anyhow::Result<Self> {
163        let bytes_len = bytes.len();
164
165        let mut p = ParserMut::new(bytes);
166        if p.is_empty() {
167            return Ok(Self { contribs: &mut [] });
168        }
169
170        let version = p.u32()?;
171
172        match version {
173            SECTION_CONTRIBUTIONS_SUBSTREAM_VER60 => {}
174            _ => {
175                bail!(
176                    "The Section Contributions Substream has a version number that is not supported. Version: 0x{:08x}",
177                    version
178                );
179            }
180        }
181
182        let records_bytes = p.into_rest();
183
184        let Ok(contribs) = <[SectionContribEntry]>::mut_from_bytes(records_bytes) else {
185            bail!(
186                "The Section Contributions stream has an invalid size. It is not a multiple of the section contribution record size.  Size: 0x{:x}",
187                bytes_len
188            );
189        };
190        Ok(Self { contribs })
191    }
192
193    /// Given a lookup table that maps module indexes from old to new, this edits a
194    /// Section Contributions table and converts module indexes.
195    pub fn remap_module_indexes(&mut self, modules_old_to_new: &[u32]) -> anyhow::Result<()> {
196        for (i, contrib) in self.contribs.iter_mut().enumerate() {
197            let old = contrib.module_index.get();
198            if let Some(&new) = modules_old_to_new.get(old as usize) {
199                contrib.module_index.set(new as u16);
200            } else {
201                bail!(
202                    "Section contribution record (at contribution index #{i} has module index {old}, \
203                       which is out of range (num modules is {})",
204                    modules_old_to_new.len()
205                );
206            }
207
208            // While we're at it, make sure that the padding fields are cleared.
209            contrib.padding1 = [0; 2];
210            contrib.padding2 = [0; 2];
211        }
212        Ok(())
213    }
214}