1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use rustc_demangle::demangle;
use std::collections::HashMap;
use std::sync::Arc;
use wasmgdb_ddbug_parser as ddbug_parser;

type BoxError = Box<dyn std::error::Error>;

#[derive(Debug)]
pub struct Frame {
    pub name: String,
    pub location: FrameLocation,
}

#[derive(Debug)]
pub struct FrameLocation {
    pub file: String,
    pub line: u32,
}

impl FrameLocation {
    fn unknown() -> Self {
        Self {
            file: "unknown.rs".to_owned(),
            line: 0,
        }
    }
}

pub struct CoredumpToStack {
    coredump: core_wasm_ast::Module,

    /// Function names from the name custom section
    func_names: Option<HashMap<u32, String>>,

    /// Wasm module containing debugging information, not necessarily valid Wasm.
    debug_module: Option<Vec<u8>>,
}

impl CoredumpToStack {
    pub fn new(coredump_bytes: &[u8]) -> Result<Self, BoxError> {
        let coredump = wasm_parser::parse(coredump_bytes)
            .map_err(|err| format!("failed to parse Wasm module: {}", err))?;

        Ok(Self {
            coredump,
            func_names: None,
            debug_module: None,
        })
    }

    pub fn with_name_section(self, bytes: &[u8]) -> Result<Self, BoxError> {
        let name_section = wasm_parser::parse_custom_section_name(bytes)?;
        let func_names = name_section
            .func_names
            .ok_or::<BoxError>("missing function names in name section".into())?;
        let func_names = func_names.lock().unwrap();

        Ok(Self {
            coredump: self.coredump,
            func_names: Some(func_names.clone()),
            debug_module: None,
        })
    }

    pub fn with_debug_module(self, bytes: &[u8]) -> Result<Self, BoxError> {
        let module = wasm_parser::parse(&bytes)
            .map_err(|err| format!("failed to parse Wasm module: {}", err))?;
        let module = core_wasm_ast::traverse::WasmModule::new(Arc::new(module));

        let func_names = module.func_names.lock().unwrap();

        Ok(Self {
            coredump: self.coredump,
            func_names: Some(func_names.clone()),
            debug_module: Some(bytes.to_owned()),
        })
    }

    pub fn stack(self) -> Result<Vec<Frame>, BoxError> {
        let coredump_wasm = core_wasm_ast::traverse::WasmModule::new(Arc::new(self.coredump));

        let func_names = self
            .func_names
            .ok_or::<BoxError>("missing name section".into())?;
        let coredump = coredump_wasm.get_coredump()?;

        let mut frames = vec![];

        let arena = ddbug_parser::Arena::new();
        #[allow(unused_assignments)]
        // file is used in the functions_by_linkage_name condition, we just
        // moved file here to increase its lifetime.
        let mut file = None;

        let functions_by_linkage_name = if let Some(debug_module) = &self.debug_module {
            let object = object::read::File::parse(debug_module.as_slice()).unwrap();
            file = Some(
                ddbug_parser::File::parse_object(
                    &object,
                    &object,
                    "module.wasm".to_owned(),
                    &arena,
                )
                .unwrap(),
            );
            let mut ddbug = ddbug_parser::FileHash::new(&file.as_ref().unwrap());

            let mut new = HashMap::new();

            // For Rust, demangle names in case the name section contains the names
            // unmangled.
            for (k, v) in ddbug.functions_by_linkage_name.iter() {
                new.insert(demangle(&k).to_string(), v.clone());
            }

            ddbug.functions_by_linkage_name.extend(new);
            Some(ddbug.functions_by_linkage_name)
        } else {
            // Without the Wasm module with debugging information we have little
            // information about the functions, only their linkage name.
            None
        };

        for frame in &coredump.stacks[0].frames {
            let linkage_name = func_names.get(&frame.funcidx).unwrap().to_owned();

            if let Some(functions_by_linkage_name) = &functions_by_linkage_name {
                if let Some(function) = functions_by_linkage_name.get(&linkage_name) {
                    let mut name = "".to_owned();

                    if let Some(ns) = function.namespace() {
                        name += &format!("{}::", ns.name().unwrap());
                    }
                    name += function.name().unwrap_or(&linkage_name);

                    let file = format!(
                        "{}/{}",
                        function.source().directory().unwrap_or(""),
                        function.source().file().unwrap_or("unknown.rs")
                    );

                    let location = FrameLocation {
                        file,
                        line: function.source().line(),
                    };

                    frames.push(Frame { name, location })
                } else {
                    let location = FrameLocation::unknown();
                    frames.push(Frame {
                        name: linkage_name,
                        location,
                    })
                }
            } else {
                let location = FrameLocation::unknown();
                frames.push(Frame {
                    name: linkage_name,
                    location,
                })
            }
        }

        Ok(frames)
    }
}