pdb/
strings.rs

1use std::borrow::Cow;
2
3use scroll::{ctx::TryFromCtx, Endian, Pread};
4
5use crate::common::*;
6use crate::msf::Stream;
7
8/// Magic bytes identifying the string name table.
9///
10/// This value is declared as `NMT::verHdr` in `nmt.h`.
11const PDB_NMT_HDR: u32 = 0xEFFE_EFFE;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14enum StringTableHashVersion {
15    /// Default hash method used for reverse string lookups.
16    ///
17    /// The hash function has originally been defined in `LHashPbCb`.
18    LongHash = 1,
19
20    /// Revised hash method used for reverse string lookups.
21    ///
22    /// The hash function has originally been defined in `LHashPbCbV2`.
23    LongHashV2 = 2,
24}
25
26impl StringTableHashVersion {
27    fn parse_u32(value: u32) -> Result<Self> {
28        match value {
29            1 => Ok(Self::LongHash),
30            2 => Ok(Self::LongHashV2),
31            _ => Err(Error::UnimplementedFeature(
32                "unknown string table hash version",
33            )),
34        }
35    }
36}
37
38/// Raw header of the string table stream.
39#[repr(C)]
40#[derive(Clone, Copy, Debug)]
41struct StringTableHeader {
42    /// Magic bytes of the string table.
43    magic: u32,
44    /// Version of the hash table after the names.
45    hash_version: u32,
46    /// The size of all names in bytes.
47    names_size: u32,
48}
49
50impl<'t> TryFromCtx<'t, Endian> for StringTableHeader {
51    type Error = scroll::Error;
52
53    fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> {
54        let mut offset = 0;
55        let data = Self {
56            magic: this.gread_with(&mut offset, le)?,
57            hash_version: this.gread_with(&mut offset, le)?,
58            names_size: this.gread_with(&mut offset, le)?,
59        };
60        Ok((data, offset))
61    }
62}
63
64impl StringTableHeader {
65    /// Start index of the names buffer in the string table stream.
66    fn names_start(self) -> usize {
67        std::mem::size_of::<Self>()
68    }
69
70    /// End index of the names buffer in the string table stream.
71    fn names_end(self) -> usize {
72        self.names_start() + self.names_size as usize
73    }
74}
75
76/// The global string table of a PDB.
77///
78/// The string table is a two-way mapping from offset to string and back. It can be used to resolve
79/// [`StringRef`] offsets to their string values. Sometimes, it is also referred to as "Name table".
80/// The mapping from string to offset has not been implemented yet.
81///
82/// Use [`PDB::string_table`](crate::PDB::string_table) to obtain an instance.
83#[derive(Debug)]
84pub struct StringTable<'s> {
85    header: StringTableHeader,
86    #[allow(dead_code)] // reason = "reverse-lookups through hash table not implemented"
87    hash_version: StringTableHashVersion,
88    stream: Stream<'s>,
89}
90
91impl<'s> StringTable<'s> {
92    pub(crate) fn parse(stream: Stream<'s>) -> Result<Self> {
93        let mut buf = stream.parse_buffer();
94        let header = buf.parse::<StringTableHeader>()?;
95
96        if header.magic != PDB_NMT_HDR {
97            return Err(Error::UnimplementedFeature(
98                "invalid string table signature",
99            ));
100        }
101
102        // The string table should at least contain all names as C-strings. Their combined size is
103        // declared in the `names_size` header field.
104        if buf.len() < header.names_end() {
105            return Err(Error::UnexpectedEof);
106        }
107
108        let hash_version = StringTableHashVersion::parse_u32(header.hash_version)?;
109
110        // After the name buffer, the stream contains a closed hash table for reverse mapping. From
111        // the original header file (`nmi.h`):
112        //
113        //     Strings are mapped into name indices using a closed hash table of NIs.
114        //     To find a string, we hash it and probe into the table, and compare the
115        //     string against each successive ni's name until we hit or find an empty
116        //     hash table entry.
117
118        Ok(StringTable {
119            header,
120            hash_version,
121            stream,
122        })
123    }
124}
125
126impl<'s> StringTable<'s> {
127    /// Resolves a string value from this string table.
128    ///
129    /// Errors if the offset is out of bounds, otherwise returns the raw binary string value.
130    pub fn get(&self, offset: StringRef) -> Result<RawString<'_>> {
131        if offset.0 >= self.header.names_size {
132            return Err(Error::UnexpectedEof);
133        }
134
135        let string_offset = self.header.names_start() + offset.0 as usize;
136        let data = &self.stream.as_slice()[string_offset..self.header.names_end()];
137        ParseBuffer::from(data).parse_cstring()
138    }
139}
140
141impl StringRef {
142    /// Resolves the raw string value of this reference.
143    ///
144    /// This method errors if the offset is out of bounds of the string table. Use
145    /// [`PDB::string_table`](crate::PDB::string_table) to obtain an instance of the string table.
146    pub fn to_raw_string<'s>(self, strings: &'s StringTable<'_>) -> Result<RawString<'s>> {
147        strings.get(self)
148    }
149
150    /// Resolves and decodes the UTF-8 string value of this reference.
151    ///
152    /// This method errors if the offset is out of bounds of the string table. Use
153    /// [`PDB::string_table`](crate::PDB::string_table) to obtain an instance of the string table.
154    pub fn to_string_lossy<'s>(self, strings: &'s StringTable<'_>) -> Result<Cow<'s, str>> {
155        strings.get(self).map(|r| r.to_string())
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    use std::mem;
164
165    #[test]
166    fn test_string_table_header() {
167        assert_eq!(mem::size_of::<StringTableHeader>(), 12);
168        assert_eq!(mem::align_of::<StringTableHeader>(), 4);
169    }
170}