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}