coredump_to_stack/
lib.rs

1use rustc_demangle::demangle;
2use std::collections::HashMap;
3use std::sync::Arc;
4use std::sync::Mutex;
5use wasmgdb_ddbug_parser as ddbug_parser;
6
7type BoxError = Box<dyn std::error::Error>;
8
9#[derive(Debug)]
10pub struct Frame {
11    pub name: String,
12    pub location: FrameLocation,
13}
14
15#[derive(Debug)]
16pub struct FrameLocation {
17    pub file: String,
18    pub line: u32,
19}
20
21impl FrameLocation {
22    fn unknown() -> Self {
23        Self {
24            file: "unknown.rs".to_owned(),
25            line: 0,
26        }
27    }
28}
29
30pub struct CoredumpToStack {
31    coredump: core_wasm_ast::Module,
32
33    /// Function names from the name custom section
34    func_names: Option<HashMap<u32, String>>,
35
36    /// Wasm module containing debugging information, not necessarily valid Wasm.
37    debug_module: Option<Vec<u8>>,
38}
39
40impl CoredumpToStack {
41    pub fn new(coredump_bytes: &[u8]) -> Result<Self, BoxError> {
42        let coredump = wasm_parser::parse(coredump_bytes)
43            .map_err(|err| format!("failed to parse Wasm module: {}", err))?;
44
45        Ok(Self {
46            coredump,
47            func_names: None,
48            debug_module: None,
49        })
50    }
51
52    pub fn with_debug_sections(
53        self,
54        sections: HashMap<&'static str, Vec<u8>>,
55    ) -> Result<Self, BoxError> {
56        let mut debug_module = vec![];
57        wasm_printer::wasm::write_header(&mut debug_module)
58            .map_err(|err| format!("failed to write header: {err}"))?;
59
60        let name_section_bytes = sections
61            .get("name")
62            .ok_or::<BoxError>("missing function names in name section".into())?;
63        let name_section = wasm_parser::parse_custom_section_name(name_section_bytes)?;
64        let func_names = name_section
65            .func_names
66            .ok_or::<BoxError>("missing function names in name section".into())?;
67        let func_names = func_names.lock().unwrap();
68
69        for (k, v) in sections {
70            if k == "name" {
71                // We can't inject the name custon section in the debug_module
72                // because the object crate (used by gimli and ddbug) fails to
73                // decode the name section without the corresponding wasm function
74                // sections, which we don't want to bring in here.
75                //
76                // So ignore the name section here and we'll parse it ourselves
77                // later.
78                continue;
79            }
80
81            // Use a Unknown custom section to construct the debug_module because
82            // we can just provide arbrirary bytes, instead of an AST in some
83            // cases.
84            let custom_section = core_wasm_ast::CustomSection::Unknown(k.to_owned(), v);
85            // Size will be overriden when priting the module
86            let section_size = core_wasm_ast::Value::new(0);
87            let section = core_wasm_ast::Section::Custom((
88                section_size,
89                Arc::new(Mutex::new(custom_section)),
90            ));
91            wasm_printer::wasm::write_section(&mut debug_module, &section)
92                .map_err(|err| format!("failed to write custom section {k}: {err}"))?;
93        }
94
95        Ok(Self {
96            coredump: self.coredump,
97            func_names: Some(func_names.clone()),
98            debug_module: Some(debug_module.to_owned()),
99        })
100    }
101
102    pub fn with_debug_module(self, bytes: &[u8]) -> Result<Self, BoxError> {
103        let module = wasm_parser::parse(&bytes)
104            .map_err(|err| format!("failed to parse Wasm module: {}", err))?;
105        let module = core_wasm_ast::traverse::WasmModule::new(Arc::new(module));
106
107        let func_names = module.func_names.lock().unwrap();
108
109        Ok(Self {
110            coredump: self.coredump,
111            func_names: Some(func_names.clone()),
112            debug_module: Some(bytes.to_owned()),
113        })
114    }
115
116    pub fn stack(self) -> Result<Vec<Frame>, BoxError> {
117        let coredump_wasm = core_wasm_ast::traverse::WasmModule::new(Arc::new(self.coredump));
118
119        let func_names = self
120            .func_names
121            .ok_or::<BoxError>("missing name section".into())?;
122        let coredump = coredump_wasm.get_coredump()?;
123
124        let mut out_frames = vec![];
125
126        let arena = ddbug_parser::Arena::new();
127        #[allow(unused_assignments)]
128        // file is used in the functions_by_linkage_name condition, we just
129        // moved file here to increase its lifetime.
130        let mut file = None;
131
132        let functions_by_linkage_name = if let Some(debug_module) = &self.debug_module {
133            let object = object::read::File::parse(debug_module.as_slice()).unwrap();
134            file = Some(
135                ddbug_parser::File::parse_object(
136                    &object,
137                    &object,
138                    "module.wasm".to_owned(),
139                    &arena,
140                )
141                .unwrap(),
142            );
143            let mut ddbug = ddbug_parser::FileHash::new(&file.as_ref().unwrap());
144
145            let mut new = HashMap::new();
146
147            // For Rust, demangle names in case the name section contains the names
148            // unmangled.
149            for (k, v) in ddbug.functions_by_linkage_name.iter() {
150                new.insert(demangle(&k).to_string(), v.clone());
151            }
152
153            ddbug.functions_by_linkage_name.extend(new);
154            Some(ddbug.functions_by_linkage_name)
155        } else {
156            // Without the Wasm module with debugging information we have little
157            // information about the functions, only their linkage name.
158            None
159        };
160
161        let mut frames = coredump.stacks[0].frames.clone();
162        frames.reverse();
163
164        for frame in frames {
165            let linkage_name = func_names
166                .get(&frame.funcidx)
167                .unwrap_or(&format!("<unknown-func{}>", frame.funcidx))
168                .to_owned();
169
170            if let Some(functions_by_linkage_name) = &functions_by_linkage_name {
171                if let Some(function) = functions_by_linkage_name.get(&linkage_name) {
172                    let mut name = "".to_owned();
173
174                    if let Some(ns) = function.namespace() {
175                        name += &format!("{}::", ns.name().unwrap());
176                    }
177                    name += function.name().unwrap_or(&linkage_name);
178
179                    let file = format!(
180                        "{}/{}",
181                        function.source().directory().unwrap_or(""),
182                        function.source().file().unwrap_or("unknown.rs")
183                    );
184
185                    let location = FrameLocation {
186                        file,
187                        line: function.source().line(),
188                    };
189
190                    out_frames.push(Frame { name, location })
191                } else {
192                    let location = FrameLocation::unknown();
193                    out_frames.push(Frame {
194                        name: linkage_name,
195                        location,
196                    })
197                }
198            } else {
199                let location = FrameLocation::unknown();
200                out_frames.push(Frame {
201                    name: linkage_name,
202                    location,
203                })
204            }
205        }
206
207        Ok(out_frames)
208    }
209}