symbolic_debuginfo/
wasm.rs

1//! Support for WASM Objects (WebAssembly).
2use std::borrow::Cow;
3use std::fmt;
4
5use thiserror::Error;
6
7use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Uuid};
8
9use crate::base::*;
10use crate::dwarf::{Dwarf, DwarfDebugSession, DwarfError, DwarfSection, Endian};
11
12mod parser;
13
14/// An error when dealing with [`WasmObject`](struct.WasmObject.html).
15#[derive(Debug, Error)]
16#[non_exhaustive]
17pub enum WasmError {
18    /// Failed to read data from a WASM binary
19    #[error("invalid wasm file")]
20    Read(#[from] wasmparser::BinaryReaderError),
21    /// A function in the WASM binary referenced an unknown type
22    #[error("function references unknown type")]
23    UnknownFunctionType,
24}
25
26/// Wasm object container (.wasm), used for executables and debug
27/// companions on web and wasi.
28///
29/// This can only parse binary wasm file and not wast files.
30pub struct WasmObject<'data> {
31    dwarf_sections: Vec<(&'data str, &'data [u8])>,
32    funcs: Vec<Symbol<'data>>,
33    build_id: Option<&'data [u8]>,
34    data: &'data [u8],
35    code_offset: u64,
36    kind: ObjectKind,
37}
38
39impl<'data> WasmObject<'data> {
40    /// Tests whether the buffer could contain a WASM object.
41    pub fn test(data: &[u8]) -> bool {
42        data.starts_with(b"\x00asm")
43    }
44
45    /// The container file format, which currently is always `FileFormat::Wasm`.
46    pub fn file_format(&self) -> FileFormat {
47        FileFormat::Wasm
48    }
49
50    /// The code identifier of this object.
51    ///
52    /// Wasm does not yet provide code IDs.
53    #[inline]
54    pub fn code_id(&self) -> Option<CodeId> {
55        self.build_id.map(CodeId::from_binary)
56    }
57
58    /// The debug information identifier of a WASM file.
59    ///
60    /// Wasm does not yet provide debug IDs.
61    #[inline]
62    pub fn debug_id(&self) -> DebugId {
63        self.build_id
64            .and_then(|data| {
65                data.get(..16)
66                    .and_then(|first_16| Uuid::from_slice(first_16).ok())
67            })
68            .map(DebugId::from_uuid)
69            .unwrap_or_else(DebugId::nil)
70    }
71
72    /// The CPU architecture of this object.
73    pub fn arch(&self) -> Arch {
74        // TODO: we do not yet support wasm64 and thus always return wasm32 here.
75        Arch::Wasm32
76    }
77
78    /// The kind of this object.
79    #[inline]
80    pub fn kind(&self) -> ObjectKind {
81        self.kind
82    }
83
84    /// The address at which the image prefers to be loaded into memory.
85    ///
86    /// This is always 0 as this does not really apply to WASM.
87    pub fn load_address(&self) -> u64 {
88        0
89    }
90
91    /// Determines whether this object exposes a public symbol table.
92    pub fn has_symbols(&self) -> bool {
93        true
94    }
95
96    /// Returns an iterator over symbols in the public symbol table.
97    pub fn symbols(&self) -> WasmSymbolIterator<'data, '_> {
98        WasmSymbolIterator {
99            funcs: self.funcs.clone().into_iter(),
100            _marker: std::marker::PhantomData,
101        }
102    }
103
104    /// Returns an ordered map of symbols in the symbol table.
105    pub fn symbol_map(&self) -> SymbolMap<'data> {
106        self.symbols().collect()
107    }
108
109    /// Determines whether this object contains debug information.
110    #[inline]
111    pub fn has_debug_info(&self) -> bool {
112        self.dwarf_sections
113            .iter()
114            .any(|(name, _)| *name == ".debug_info")
115    }
116
117    /// Constructs a debugging session.
118    pub fn debug_session(&self) -> Result<DwarfDebugSession<'data>, DwarfError> {
119        let symbols = self.symbol_map();
120        // WASM is offset by the negative offset to the code section instead of the load address
121        DwarfDebugSession::parse(self, symbols, -(self.code_offset() as i64), self.kind())
122    }
123
124    /// Determines whether this object contains stack unwinding information.
125    #[inline]
126    pub fn has_unwind_info(&self) -> bool {
127        self.dwarf_sections
128            .iter()
129            .any(|(name, _)| *name == ".debug_frame")
130    }
131
132    /// Determines whether this object contains embedded source.
133    pub fn has_sources(&self) -> bool {
134        false
135    }
136
137    /// Determines whether this object is malformed and was only partially parsed
138    pub fn is_malformed(&self) -> bool {
139        false
140    }
141
142    /// Returns the raw data of the WASM file.
143    pub fn data(&self) -> &'data [u8] {
144        self.data
145    }
146
147    /// Returns the offset of the code section.
148    pub fn code_offset(&self) -> u64 {
149        self.code_offset
150    }
151}
152
153impl fmt::Debug for WasmObject<'_> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.debug_struct("WasmObject")
156            .field("code_id", &self.code_id())
157            .field("debug_id", &self.debug_id())
158            .field("arch", &self.arch())
159            .field("kind", &self.kind())
160            .field("load_address", &format_args!("{:#x}", self.load_address()))
161            .field("has_symbols", &self.has_symbols())
162            .field("has_debug_info", &self.has_debug_info())
163            .field("has_unwind_info", &self.has_unwind_info())
164            .field("is_malformed", &self.is_malformed())
165            .finish()
166    }
167}
168
169impl<'slf, 'd: 'slf> AsSelf<'slf> for WasmObject<'d> {
170    type Ref = WasmObject<'slf>;
171
172    fn as_self(&'slf self) -> &'slf Self::Ref {
173        self
174    }
175}
176
177impl<'d> Parse<'d> for WasmObject<'d> {
178    type Error = WasmError;
179
180    fn test(data: &[u8]) -> bool {
181        Self::test(data)
182    }
183
184    fn parse(data: &'d [u8]) -> Result<Self, WasmError> {
185        Self::parse(data)
186    }
187}
188
189impl<'data: 'object, 'object> ObjectLike<'data, 'object> for WasmObject<'data> {
190    type Error = DwarfError;
191    type Session = DwarfDebugSession<'data>;
192    type SymbolIterator = WasmSymbolIterator<'data, 'object>;
193
194    fn file_format(&self) -> FileFormat {
195        self.file_format()
196    }
197
198    fn code_id(&self) -> Option<CodeId> {
199        self.code_id()
200    }
201
202    fn debug_id(&self) -> DebugId {
203        self.debug_id()
204    }
205
206    fn arch(&self) -> Arch {
207        self.arch()
208    }
209
210    fn kind(&self) -> ObjectKind {
211        self.kind()
212    }
213
214    fn load_address(&self) -> u64 {
215        self.load_address()
216    }
217
218    fn has_symbols(&self) -> bool {
219        self.has_symbols()
220    }
221
222    fn symbols(&'object self) -> Self::SymbolIterator {
223        self.symbols()
224    }
225
226    fn symbol_map(&self) -> SymbolMap<'data> {
227        self.symbol_map()
228    }
229
230    fn has_debug_info(&self) -> bool {
231        self.has_debug_info()
232    }
233
234    fn debug_session(&self) -> Result<Self::Session, Self::Error> {
235        self.debug_session()
236    }
237
238    fn has_unwind_info(&self) -> bool {
239        self.has_unwind_info()
240    }
241
242    fn has_sources(&self) -> bool {
243        self.has_sources()
244    }
245
246    fn is_malformed(&self) -> bool {
247        self.is_malformed()
248    }
249}
250
251impl<'data> Dwarf<'data> for WasmObject<'data> {
252    fn endianity(&self) -> Endian {
253        Endian::Little
254    }
255
256    fn raw_section(&self, section_name: &str) -> Option<DwarfSection<'data>> {
257        self.dwarf_sections.iter().find_map(|(name, data)| {
258            if name.strip_prefix('.') == Some(section_name) {
259                Some(DwarfSection {
260                    data: Cow::Borrowed(data),
261                    // XXX: what are these going to be?
262                    address: 0,
263                    offset: 0,
264                    align: 4,
265                })
266            } else {
267                None
268            }
269        })
270    }
271}
272
273/// An iterator over symbols in the WASM file.
274///
275/// Returned by [`WasmObject::symbols`](struct.WasmObject.html#method.symbols).
276pub struct WasmSymbolIterator<'data, 'object> {
277    funcs: std::vec::IntoIter<Symbol<'data>>,
278    _marker: std::marker::PhantomData<&'object u8>,
279}
280
281impl<'data> Iterator for WasmSymbolIterator<'data, '_> {
282    type Item = Symbol<'data>;
283
284    fn next(&mut self) -> Option<Self::Item> {
285        self.funcs.next()
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn test_invalid_header() {
295        let data = b"\x00asm    ";
296
297        assert!(WasmObject::parse(data).is_err());
298    }
299}