Skip to main content

rsleigh_cli/
wasm.rs

1//! WebAssembly decompiler — parse .wasm binaries and emit C-like pseudocode.
2//!
3//! WASM is a stack-based VM, not a register machine, so we can't use the SLEIGH
4//! pipeline. Instead, this module directly parses WASM bytecode and reconstructs
5//! expressions by simulating the value stack.
6
7use wasmparser::{BlockType, Operator, Parser, Payload, ValType};
8
9/// A discovered WASM function with its name, type, and body offset.
10pub struct WasmFunc {
11    pub index: u32,
12    pub name: String,
13    pub params: Vec<ValType>,
14    pub results: Vec<ValType>,
15    pub locals: Vec<ValType>,
16    pub body_offset: usize,
17    pub body_size: usize,
18}
19
20/// Parse a WASM binary and return the list of functions.
21pub fn parse_wasm(data: &[u8]) -> Vec<WasmFunc> {
22    let mut functions = Vec::new();
23    let mut func_types: Vec<(Vec<ValType>, Vec<ValType>)> = Vec::new();
24    let mut func_type_indices: Vec<u32> = Vec::new();
25    let mut import_count: u32 = 0;
26    let mut export_names: std::collections::HashMap<u32, String> = std::collections::HashMap::new();
27    let mut code_idx: u32 = 0;
28
29    let parser = Parser::new(0);
30    for payload in parser.parse_all(data) {
31        let Ok(payload) = payload else { continue };
32        match payload {
33            Payload::TypeSection(reader) => {
34                for ty in reader.into_iter_err_on_gc_types() {
35                    if let Ok(ft) = ty {
36                        let params: Vec<ValType> = ft.params().to_vec();
37                        let results: Vec<ValType> = ft.results().to_vec();
38                        func_types.push((params, results));
39                    }
40                }
41            }
42            Payload::ImportSection(reader) => {
43                for imp in reader {
44                    if let Ok(imp) = imp {
45                        if matches!(imp.ty, wasmparser::TypeRef::Func(_)) {
46                            import_count += 1;
47                        }
48                    }
49                }
50            }
51            Payload::FunctionSection(reader) => {
52                for idx in reader {
53                    if let Ok(idx) = idx {
54                        func_type_indices.push(idx);
55                    }
56                }
57            }
58            Payload::ExportSection(reader) => {
59                for exp in reader {
60                    if let Ok(exp) = exp {
61                        if exp.kind == wasmparser::ExternalKind::Func {
62                            export_names.insert(exp.index, exp.name.to_string());
63                        }
64                    }
65                }
66            }
67            Payload::CodeSectionEntry(body) => {
68                let func_idx = import_count + code_idx;
69                let type_idx = func_type_indices
70                    .get(code_idx as usize)
71                    .copied()
72                    .unwrap_or(0) as usize;
73                let (params, results) = func_types.get(type_idx).cloned().unwrap_or_default();
74
75                let mut locals = Vec::new();
76                if let Ok(local_reader) = body.get_locals_reader() {
77                    for local in local_reader {
78                        if let Ok((count, ty)) = local {
79                            for _ in 0..count {
80                                locals.push(ty);
81                            }
82                        }
83                    }
84                }
85
86                let name = export_names
87                    .get(&func_idx)
88                    .cloned()
89                    .unwrap_or_else(|| format!("func_{}", func_idx));
90
91                let range = body.range();
92                functions.push(WasmFunc {
93                    index: func_idx,
94                    name,
95                    params,
96                    results,
97                    locals,
98                    body_offset: range.start,
99                    body_size: range.end - range.start,
100                });
101                code_idx += 1;
102            }
103            _ => {}
104        }
105    }
106    functions
107}
108
109fn valtype_to_c(ty: ValType) -> &'static str {
110    match ty {
111        ValType::I32 => "int",
112        ValType::I64 => "long",
113        ValType::F32 => "float",
114        ValType::F64 => "double",
115        _ => "void",
116    }
117}
118
119/// Decompile a single WASM function to C-like pseudocode.
120pub fn decompile_wasm_func(data: &[u8], func: &WasmFunc, all_funcs: &[WasmFunc]) -> String {
121    let mut out = String::new();
122
123    // Function signature
124    let ret_type = func
125        .results
126        .first()
127        .map(|t| valtype_to_c(*t))
128        .unwrap_or("void");
129    let params_str: Vec<String> = func
130        .params
131        .iter()
132        .enumerate()
133        .map(|(i, t)| format!("{} param_{}", valtype_to_c(*t), i))
134        .collect();
135    out.push_str(&format!("// {}\n", func.name));
136    out.push_str(&format!(
137        "{} {}({}) {{\n",
138        ret_type,
139        func.name,
140        params_str.join(", ")
141    ));
142
143    // Local declarations
144    let param_count = func.params.len();
145    for (i, ty) in func.locals.iter().enumerate() {
146        out.push_str(&format!("    {} local_{};\n", valtype_to_c(*ty), i));
147    }
148    if !func.locals.is_empty() {
149        out.push_str("\n");
150    }
151
152    // Decompile body by simulating the value stack
153    let body_start = func.body_offset;
154    let body_end = func.body_offset + func.body_size;
155    if body_end > data.len() {
156        out.push_str("    // body out of bounds\n}\n");
157        return out;
158    }
159
160    let parser = Parser::new(body_start as u64);
161    let mut stack: Vec<String> = Vec::new();
162    let mut indent = 1usize;
163
164    for payload in parser.parse_all(&data[..body_end]) {
165        let Ok(payload) = payload else { continue };
166        if let Payload::CodeSectionEntry(body) = payload {
167            let Ok(ops_reader) = body.get_operators_reader() else {
168                continue;
169            };
170            for op in ops_reader {
171                let Ok(op) = op else { continue };
172                let pad = "    ".repeat(indent);
173                match op {
174                    Operator::LocalGet { local_index } => {
175                        let name = if (local_index as usize) < param_count {
176                            format!("param_{}", local_index)
177                        } else {
178                            format!("local_{}", local_index as usize - param_count)
179                        };
180                        stack.push(name);
181                    }
182                    Operator::LocalSet { local_index } => {
183                        let val = stack.pop().unwrap_or("?".into());
184                        let name = if (local_index as usize) < param_count {
185                            format!("param_{}", local_index)
186                        } else {
187                            format!("local_{}", local_index as usize - param_count)
188                        };
189                        out.push_str(&format!("{}{} = {};\n", pad, name, val));
190                    }
191                    Operator::LocalTee { local_index } => {
192                        let val = stack.last().cloned().unwrap_or("?".into());
193                        let name = if (local_index as usize) < param_count {
194                            format!("param_{}", local_index)
195                        } else {
196                            format!("local_{}", local_index as usize - param_count)
197                        };
198                        out.push_str(&format!("{}{} = {};\n", pad, name, val));
199                    }
200                    Operator::GlobalGet { global_index } => {
201                        stack.push(format!("global_{}", global_index))
202                    }
203                    Operator::GlobalSet { global_index } => {
204                        let val = stack.pop().unwrap_or("?".into());
205                        out.push_str(&format!("{}global_{} = {};\n", pad, global_index, val));
206                    }
207                    Operator::I32Const { value } => stack.push(format!("{}", value)),
208                    Operator::I64Const { value } => stack.push(format!("{}L", value)),
209                    Operator::F32Const { value } => {
210                        stack.push(format!("{}f", f32::from_bits(value.bits())))
211                    }
212                    Operator::F64Const { value } => {
213                        stack.push(format!("{}", f64::from_bits(value.bits())))
214                    }
215                    // Arithmetic
216                    Operator::I32Add | Operator::I64Add => binop(&mut stack, "+"),
217                    Operator::I32Sub | Operator::I64Sub => binop(&mut stack, "-"),
218                    Operator::I32Mul | Operator::I64Mul => binop(&mut stack, "*"),
219                    Operator::I32DivS | Operator::I64DivS => binop(&mut stack, "/"),
220                    Operator::I32DivU | Operator::I64DivU => binop(&mut stack, "/ (unsigned)"),
221                    Operator::I32RemS | Operator::I64RemS => binop(&mut stack, "%"),
222                    Operator::I32RemU | Operator::I64RemU => binop(&mut stack, "% (unsigned)"),
223                    // Bitwise
224                    Operator::I32And | Operator::I64And => binop(&mut stack, "&"),
225                    Operator::I32Or | Operator::I64Or => binop(&mut stack, "|"),
226                    Operator::I32Xor | Operator::I64Xor => binop(&mut stack, "^"),
227                    Operator::I32Shl | Operator::I64Shl => binop(&mut stack, "<<"),
228                    Operator::I32ShrS | Operator::I64ShrS => binop(&mut stack, ">>"),
229                    Operator::I32ShrU | Operator::I64ShrU => binop(&mut stack, ">>> "),
230                    // Comparison
231                    Operator::I32Eqz | Operator::I64Eqz => {
232                        let a = stack.pop().unwrap_or("?".into());
233                        stack.push(format!("{} == 0", a));
234                    }
235                    Operator::I32Eq | Operator::I64Eq => binop(&mut stack, "=="),
236                    Operator::I32Ne | Operator::I64Ne => binop(&mut stack, "!="),
237                    Operator::I32LtS | Operator::I64LtS => binop(&mut stack, "<"),
238                    Operator::I32GtS | Operator::I64GtS => binop(&mut stack, ">"),
239                    Operator::I32LeS | Operator::I64LeS => binop(&mut stack, "<="),
240                    Operator::I32GeS | Operator::I64GeS => binop(&mut stack, ">="),
241                    Operator::I32LtU | Operator::I64LtU => binop(&mut stack, "< (unsigned)"),
242                    Operator::I32GtU | Operator::I64GtU => binop(&mut stack, "> (unsigned)"),
243                    Operator::I32LeU | Operator::I64LeU => binop(&mut stack, "<= (unsigned)"),
244                    Operator::I32GeU | Operator::I64GeU => binop(&mut stack, ">= (unsigned)"),
245                    // Float ops
246                    Operator::F32Add | Operator::F64Add => binop(&mut stack, "+"),
247                    Operator::F32Sub | Operator::F64Sub => binop(&mut stack, "-"),
248                    Operator::F32Mul | Operator::F64Mul => binop(&mut stack, "*"),
249                    Operator::F32Div | Operator::F64Div => binop(&mut stack, "/"),
250                    // Memory
251                    Operator::I32Load { memarg } => {
252                        let addr = stack.pop().unwrap_or("?".into());
253                        if memarg.offset > 0 {
254                            stack.push(format!("*(int*)({} + {})", addr, memarg.offset));
255                        } else {
256                            stack.push(format!("*(int*)({})", addr));
257                        }
258                    }
259                    Operator::I64Load { memarg } => {
260                        let addr = stack.pop().unwrap_or("?".into());
261                        if memarg.offset > 0 {
262                            stack.push(format!("*(long*)({} + {})", addr, memarg.offset));
263                        } else {
264                            stack.push(format!("*(long*)({})", addr));
265                        }
266                    }
267                    Operator::I32Store { memarg } => {
268                        let val = stack.pop().unwrap_or("?".into());
269                        let addr = stack.pop().unwrap_or("?".into());
270                        if memarg.offset > 0 {
271                            out.push_str(&format!(
272                                "{}*(int*)({} + {}) = {};\n",
273                                pad, addr, memarg.offset, val
274                            ));
275                        } else {
276                            out.push_str(&format!("{}*(int*)({}) = {};\n", pad, addr, val));
277                        }
278                    }
279                    Operator::I64Store { memarg } => {
280                        let val = stack.pop().unwrap_or("?".into());
281                        let addr = stack.pop().unwrap_or("?".into());
282                        if memarg.offset > 0 {
283                            out.push_str(&format!(
284                                "{}*(long*)({} + {}) = {};\n",
285                                pad, addr, memarg.offset, val
286                            ));
287                        } else {
288                            out.push_str(&format!("{}*(long*)({}) = {};\n", pad, addr, val));
289                        }
290                    }
291                    Operator::I32Load8S { memarg } | Operator::I32Load8U { memarg } => {
292                        let addr = stack.pop().unwrap_or("?".into());
293                        let expr = if memarg.offset > 0 {
294                            format!("*(byte*)({} + {})", addr, memarg.offset)
295                        } else {
296                            format!("*(byte*)({})", addr)
297                        };
298                        stack.push(expr);
299                    }
300                    Operator::I32Load16S { memarg } | Operator::I32Load16U { memarg } => {
301                        let addr = stack.pop().unwrap_or("?".into());
302                        let expr = if memarg.offset > 0 {
303                            format!("*(short*)({} + {})", addr, memarg.offset)
304                        } else {
305                            format!("*(short*)({})", addr)
306                        };
307                        stack.push(expr);
308                    }
309                    Operator::I32Store8 { memarg } => {
310                        let val = stack.pop().unwrap_or("?".into());
311                        let addr = stack.pop().unwrap_or("?".into());
312                        out.push_str(&format!(
313                            "{}*(byte*)({}{}) = {};\n",
314                            pad,
315                            addr,
316                            if memarg.offset > 0 {
317                                format!(" + {}", memarg.offset)
318                            } else {
319                                String::new()
320                            },
321                            val
322                        ));
323                    }
324                    Operator::I32Store16 { memarg } => {
325                        let val = stack.pop().unwrap_or("?".into());
326                        let addr = stack.pop().unwrap_or("?".into());
327                        out.push_str(&format!(
328                            "{}*(short*)({}{}) = {};\n",
329                            pad,
330                            addr,
331                            if memarg.offset > 0 {
332                                format!(" + {}", memarg.offset)
333                            } else {
334                                String::new()
335                            },
336                            val
337                        ));
338                    }
339                    // Control flow
340                    Operator::Block { blockty } => {
341                        out.push_str(&format!("{}{{ // block\n", pad));
342                        indent += 1;
343                    }
344                    Operator::Loop { blockty } => {
345                        out.push_str(&format!("{}while (1) {{ // loop\n", pad));
346                        indent += 1;
347                    }
348                    Operator::If { blockty } => {
349                        let cond = stack.pop().unwrap_or("?".into());
350                        out.push_str(&format!("{}if ({}) {{\n", pad, cond));
351                        indent += 1;
352                    }
353                    Operator::Else => {
354                        if indent > 1 {
355                            indent -= 1;
356                        }
357                        let pad = "    ".repeat(indent);
358                        out.push_str(&format!("{}}} else {{\n", pad));
359                        indent += 1;
360                    }
361                    Operator::End => {
362                        if indent > 1 {
363                            indent -= 1;
364                        }
365                        let pad = "    ".repeat(indent);
366                        // Don't emit closing brace for the function body end
367                        if indent >= 1 {
368                            out.push_str(&format!("{}}}\n", pad));
369                        }
370                    }
371                    Operator::Br { relative_depth } => {
372                        out.push_str(&format!("{}break; // br {}\n", pad, relative_depth));
373                    }
374                    Operator::BrIf { relative_depth } => {
375                        let cond = stack.pop().unwrap_or("?".into());
376                        out.push_str(&format!(
377                            "{}if ({}) break; // br_if {}\n",
378                            pad, cond, relative_depth
379                        ));
380                    }
381                    Operator::Return => {
382                        let val = stack.pop();
383                        if let Some(v) = val {
384                            out.push_str(&format!("{}return {};\n", pad, v));
385                        } else {
386                            out.push_str(&format!("{}return;\n", pad));
387                        }
388                    }
389                    Operator::Call { function_index } => {
390                        let callee = all_funcs
391                            .iter()
392                            .find(|f| f.index == function_index)
393                            .map(|f| f.name.as_str())
394                            .unwrap_or("?");
395                        // Get callee's param count from type info
396                        let callee_params = all_funcs
397                            .iter()
398                            .find(|f| f.index == function_index)
399                            .map(|f| f.params.len())
400                            .unwrap_or(0);
401                        let mut args = Vec::new();
402                        for _ in 0..callee_params {
403                            args.push(stack.pop().unwrap_or("?".into()));
404                        }
405                        args.reverse();
406                        let callee_has_result = all_funcs
407                            .iter()
408                            .find(|f| f.index == function_index)
409                            .map(|f| !f.results.is_empty())
410                            .unwrap_or(false);
411                        let call_expr = format!("{}({})", callee, args.join(", "));
412                        if callee_has_result {
413                            stack.push(call_expr);
414                        } else {
415                            out.push_str(&format!("{}{};\n", pad, call_expr));
416                        }
417                    }
418                    Operator::Drop => {
419                        stack.pop();
420                    }
421                    Operator::Select => {
422                        let cond = stack.pop().unwrap_or("?".into());
423                        let b = stack.pop().unwrap_or("?".into());
424                        let a = stack.pop().unwrap_or("?".into());
425                        stack.push(format!("{} ? {} : {}", cond, a, b));
426                    }
427                    Operator::MemoryGrow { .. } => {
428                        let pages = stack.pop().unwrap_or("?".into());
429                        stack.push(format!("memory_grow({})", pages));
430                    }
431                    Operator::MemorySize { .. } => {
432                        stack.push("memory_size()".into());
433                    }
434                    // Conversions
435                    Operator::I32WrapI64 => {
436                        let v = stack.pop().unwrap_or("?".into());
437                        stack.push(format!("(int){}", v));
438                    }
439                    Operator::I64ExtendI32S => {
440                        let v = stack.pop().unwrap_or("?".into());
441                        stack.push(format!("(long){}", v));
442                    }
443                    Operator::I64ExtendI32U => {
444                        let v = stack.pop().unwrap_or("?".into());
445                        stack.push(format!("(unsigned long){}", v));
446                    }
447                    Operator::Unreachable => {
448                        out.push_str(&format!("{}__builtin_unreachable();\n", pad));
449                    }
450                    Operator::Nop => {}
451                    _ => {
452                        // Unknown op — emit as comment
453                        // stack.push(format!("/* {:?} */", op));
454                    }
455                }
456            }
457        }
458    }
459
460    // If there's a value left on the stack and the function returns, emit return
461    if !func.results.is_empty() {
462        if let Some(val) = stack.pop() {
463            let pad = "    ".repeat(1);
464            out.push_str(&format!("{}return {};\n", pad, val));
465        }
466    }
467
468    out.push_str("}\n");
469    out
470}
471
472fn binop(stack: &mut Vec<String>, op: &str) {
473    let b = stack.pop().unwrap_or("?".into());
474    let a = stack.pop().unwrap_or("?".into());
475    stack.push(format!("{} {} {}", a, op, b));
476}