xref: /wasmtime-44.0.1/src/commands/objdump.rs (revision 9500c417)
13e406d2eSAlex Crichton //! Implementation of the `wasmtime objdump` CLI command.
23e406d2eSAlex Crichton 
33e406d2eSAlex Crichton use capstone::InsnGroupType::{CS_GRP_JUMP, CS_GRP_RET};
43e406d2eSAlex Crichton use clap::Parser;
53e406d2eSAlex Crichton use cranelift_codegen::isa::lookup_by_name;
63e406d2eSAlex Crichton use cranelift_codegen::settings::Flags;
73e406d2eSAlex Crichton use object::read::elf::ElfFile64;
83e406d2eSAlex Crichton use object::{Architecture, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol};
93e406d2eSAlex Crichton use pulley_interpreter::decode::{Decoder, DecodingError, OpVisitor};
103e406d2eSAlex Crichton use pulley_interpreter::disas::Disassembler;
1117fbd3c6SChris Fallin use smallvec::SmallVec;
123e406d2eSAlex Crichton use std::io::{IsTerminal, Read, Write};
133e406d2eSAlex Crichton use std::iter::{self, Peekable};
143e406d2eSAlex Crichton use std::path::{Path, PathBuf};
153e406d2eSAlex Crichton use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
1694740588SNick Fitzgerald use wasmtime::{Engine, Result, bail, error::Context as _};
1702155232SChris Fallin use wasmtime_environ::{
1802155232SChris Fallin     FilePos, FrameInstPos, FrameStackShape, FrameStateSlot, FrameTable, FrameTableDescriptorIndex,
19*9500c417SChris Fallin     ModulePC, StackMap, Trap, obj,
2002155232SChris Fallin };
212d25f862SChris Fallin use wasmtime_unwinder::{ExceptionHandler, ExceptionTable};
223e406d2eSAlex Crichton 
233e406d2eSAlex Crichton /// A helper utility in wasmtime to explore the compiled object file format of
243e406d2eSAlex Crichton /// a `*.cwasm` file.
253e406d2eSAlex Crichton #[derive(Parser)]
263e406d2eSAlex Crichton pub struct ObjdumpCommand {
273e406d2eSAlex Crichton     /// The path to a compiled `*.cwasm` file.
283e406d2eSAlex Crichton     ///
293e406d2eSAlex Crichton     /// If this is `-` or not provided then stdin is used as input.
303e406d2eSAlex Crichton     cwasm: Option<PathBuf>,
313e406d2eSAlex Crichton 
323e406d2eSAlex Crichton     /// Whether or not to display function/instruction addresses.
333e406d2eSAlex Crichton     #[arg(long)]
343e406d2eSAlex Crichton     addresses: bool,
353e406d2eSAlex Crichton 
363e406d2eSAlex Crichton     /// Whether or not to try to only display addresses of instruction jump
373e406d2eSAlex Crichton     /// targets.
383e406d2eSAlex Crichton     #[arg(long)]
393e406d2eSAlex Crichton     address_jumps: bool,
403e406d2eSAlex Crichton 
41cb7d8812SAlex Crichton     /// What functions should be printed
42cb7d8812SAlex Crichton     #[arg(long, default_value = "wasm", value_name = "KIND")]
433e406d2eSAlex Crichton     funcs: Vec<Func>,
443e406d2eSAlex Crichton 
453e406d2eSAlex Crichton     /// String filter to apply to function names to only print some functions.
465b076899SAlex Crichton     #[arg(long, value_name = "STR")]
473e406d2eSAlex Crichton     filter: Option<String>,
483e406d2eSAlex Crichton 
493e406d2eSAlex Crichton     /// Whether or not instruction bytes are disassembled.
503e406d2eSAlex Crichton     #[arg(long)]
513e406d2eSAlex Crichton     bytes: bool,
523e406d2eSAlex Crichton 
533e406d2eSAlex Crichton     /// Whether or not to use color.
543e406d2eSAlex Crichton     #[arg(long, default_value = "auto")]
553e406d2eSAlex Crichton     color: ColorChoice,
563e406d2eSAlex Crichton 
573e406d2eSAlex Crichton     /// Whether or not to interleave instructions with address maps.
585b076899SAlex Crichton     #[arg(long, require_equals = true, value_name = "true|false")]
595b076899SAlex Crichton     addrmap: Option<Option<bool>>,
603e406d2eSAlex Crichton 
613e406d2eSAlex Crichton     /// Column width of how large an address is rendered as.
625b076899SAlex Crichton     #[arg(long, default_value = "10", value_name = "N")]
633e406d2eSAlex Crichton     address_width: usize,
643e406d2eSAlex Crichton 
653e406d2eSAlex Crichton     /// Whether or not to show information about what instructions can trap.
665b076899SAlex Crichton     #[arg(long, require_equals = true, value_name = "true|false")]
675b076899SAlex Crichton     traps: Option<Option<bool>>,
685b076899SAlex Crichton 
695b076899SAlex Crichton     /// Whether or not to show information about stack maps.
705b076899SAlex Crichton     #[arg(long, require_equals = true, value_name = "true|false")]
715b076899SAlex Crichton     stack_maps: Option<Option<bool>>,
722d25f862SChris Fallin 
732d25f862SChris Fallin     /// Whether or not to show information about exception tables.
742d25f862SChris Fallin     #[arg(long, require_equals = true, value_name = "true|false")]
752d25f862SChris Fallin     exception_tables: Option<Option<bool>>,
7602155232SChris Fallin 
7702155232SChris Fallin     /// Whether or not to show information about frame tables.
7802155232SChris Fallin     #[arg(long, require_equals = true, value_name = "true|false")]
7902155232SChris Fallin     frame_tables: Option<Option<bool>>,
805b076899SAlex Crichton }
815b076899SAlex Crichton 
optional_flag_with_default(flag: Option<Option<bool>>, default: bool) -> bool825b076899SAlex Crichton fn optional_flag_with_default(flag: Option<Option<bool>>, default: bool) -> bool {
835b076899SAlex Crichton     match flag {
845b076899SAlex Crichton         None => default,
855b076899SAlex Crichton         Some(None) => true,
865b076899SAlex Crichton         Some(Some(val)) => val,
875b076899SAlex Crichton     }
883e406d2eSAlex Crichton }
893e406d2eSAlex Crichton 
903e406d2eSAlex Crichton impl ObjdumpCommand {
addrmap(&self) -> bool915b076899SAlex Crichton     fn addrmap(&self) -> bool {
925b076899SAlex Crichton         optional_flag_with_default(self.addrmap, false)
935b076899SAlex Crichton     }
945b076899SAlex Crichton 
traps(&self) -> bool955b076899SAlex Crichton     fn traps(&self) -> bool {
965b076899SAlex Crichton         optional_flag_with_default(self.traps, true)
975b076899SAlex Crichton     }
985b076899SAlex Crichton 
stack_maps(&self) -> bool995b076899SAlex Crichton     fn stack_maps(&self) -> bool {
1005b076899SAlex Crichton         optional_flag_with_default(self.stack_maps, true)
1015b076899SAlex Crichton     }
1025b076899SAlex Crichton 
exception_tables(&self) -> bool1032d25f862SChris Fallin     fn exception_tables(&self) -> bool {
1042d25f862SChris Fallin         optional_flag_with_default(self.exception_tables, true)
1052d25f862SChris Fallin     }
1062d25f862SChris Fallin 
frame_tables(&self) -> bool10702155232SChris Fallin     fn frame_tables(&self) -> bool {
10802155232SChris Fallin         optional_flag_with_default(self.frame_tables, true)
10902155232SChris Fallin     }
11002155232SChris Fallin 
1113e406d2eSAlex Crichton     /// Executes the command.
execute(self) -> Result<()>1123e406d2eSAlex Crichton     pub fn execute(self) -> Result<()> {
1133e406d2eSAlex Crichton         // Setup stdout handling color options. Also build some variables used
1143e406d2eSAlex Crichton         // below to configure colors of certain items.
1153e406d2eSAlex Crichton         let mut choice = self.color;
1163e406d2eSAlex Crichton         if choice == ColorChoice::Auto && !std::io::stdout().is_terminal() {
1173e406d2eSAlex Crichton             choice = ColorChoice::Never;
1183e406d2eSAlex Crichton         }
1193e406d2eSAlex Crichton         let mut stdout = StandardStream::stdout(choice);
1203e406d2eSAlex Crichton 
1213e406d2eSAlex Crichton         let mut color_address = ColorSpec::new();
1223e406d2eSAlex Crichton         color_address.set_bold(true).set_fg(Some(Color::Yellow));
1233e406d2eSAlex Crichton         let mut color_bytes = ColorSpec::new();
1243e406d2eSAlex Crichton         color_bytes.set_fg(Some(Color::Magenta));
1253e406d2eSAlex Crichton 
1263e406d2eSAlex Crichton         let bytes = self.read_cwasm()?;
1273e406d2eSAlex Crichton 
1283e406d2eSAlex Crichton         // Double-check this is a `*.cwasm`
1293e406d2eSAlex Crichton         if Engine::detect_precompiled(&bytes).is_none() {
1303e406d2eSAlex Crichton             bail!("not a `*.cwasm` file from wasmtime: {:?}", self.cwasm);
1313e406d2eSAlex Crichton         }
1323e406d2eSAlex Crichton 
1333e406d2eSAlex Crichton         // Parse the input as an ELF file, extract the `.text` section.
1343e406d2eSAlex Crichton         let elf = ElfFile64::<Endianness>::parse(&bytes)?;
1353e406d2eSAlex Crichton         let text = elf
1363e406d2eSAlex Crichton             .section_by_name(".text")
1373e406d2eSAlex Crichton             .context("missing .text section")?;
1383e406d2eSAlex Crichton         let text = text.data()?;
1393e406d2eSAlex Crichton 
14017fbd3c6SChris Fallin         let frame_table_descriptors = elf
14117fbd3c6SChris Fallin             .section_by_name(obj::ELF_WASMTIME_FRAMES)
14217fbd3c6SChris Fallin             .and_then(|section| section.data().ok())
14317fbd3c6SChris Fallin             .and_then(|bytes| FrameTable::parse(bytes, text).ok());
14417fbd3c6SChris Fallin 
14517fbd3c6SChris Fallin         let mut breakpoints = frame_table_descriptors
14617fbd3c6SChris Fallin             .iter()
14717fbd3c6SChris Fallin             .flat_map(|ftd| ftd.breakpoint_patches())
14817fbd3c6SChris Fallin             .map(|(wasm_pc, patch)| (wasm_pc, patch.offset, SmallVec::from(patch.enable)))
14917fbd3c6SChris Fallin             .collect::<Vec<_>>();
15017fbd3c6SChris Fallin         breakpoints.sort_by_key(|(_wasm_pc, native_offset, _patch)| *native_offset);
15117fbd3c6SChris Fallin         let breakpoints: Box<dyn Iterator<Item = _>> = Box::new(breakpoints.into_iter());
15217fbd3c6SChris Fallin         let breakpoints = breakpoints.peekable();
15317fbd3c6SChris Fallin 
1543e406d2eSAlex Crichton         // Build the helper that'll get used to attach decorations/annotations
1553e406d2eSAlex Crichton         // to various instructions.
1563e406d2eSAlex Crichton         let mut decorator = Decorator {
1573e406d2eSAlex Crichton             addrmap: elf
1583e406d2eSAlex Crichton                 .section_by_name(obj::ELF_WASMTIME_ADDRMAP)
1593e406d2eSAlex Crichton                 .and_then(|section| section.data().ok())
1603e406d2eSAlex Crichton                 .and_then(|bytes| wasmtime_environ::iterate_address_map(bytes))
1613e406d2eSAlex Crichton                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
1623e406d2eSAlex Crichton             traps: elf
1633e406d2eSAlex Crichton                 .section_by_name(obj::ELF_WASMTIME_TRAPS)
1643e406d2eSAlex Crichton                 .and_then(|section| section.data().ok())
1653e406d2eSAlex Crichton                 .and_then(|bytes| wasmtime_environ::iterate_traps(bytes))
1663e406d2eSAlex Crichton                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
1675b076899SAlex Crichton             stack_maps: elf
1685b076899SAlex Crichton                 .section_by_name(obj::ELF_WASMTIME_STACK_MAP)
1695b076899SAlex Crichton                 .and_then(|section| section.data().ok())
1705b076899SAlex Crichton                 .and_then(|bytes| StackMap::iter(bytes))
1715b076899SAlex Crichton                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
1722d25f862SChris Fallin             exception_tables: elf
1732d25f862SChris Fallin                 .section_by_name(obj::ELF_WASMTIME_EXCEPTIONS)
1742d25f862SChris Fallin                 .and_then(|section| section.data().ok())
1752d25f862SChris Fallin                 .and_then(|bytes| ExceptionTable::parse(bytes).ok())
1762d25f862SChris Fallin                 .map(|table| table.into_iter())
1772d25f862SChris Fallin                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
17802155232SChris Fallin             frame_tables: elf
17902155232SChris Fallin                 .section_by_name(obj::ELF_WASMTIME_FRAMES)
18002155232SChris Fallin                 .and_then(|section| section.data().ok())
18117fbd3c6SChris Fallin                 .and_then(|bytes| FrameTable::parse(bytes, text).ok())
18202155232SChris Fallin                 .map(|table| table.into_program_points())
18302155232SChris Fallin                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
18402155232SChris Fallin 
18517fbd3c6SChris Fallin             breakpoints,
18617fbd3c6SChris Fallin 
18717fbd3c6SChris Fallin             frame_table_descriptors,
18802155232SChris Fallin 
1893e406d2eSAlex Crichton             objdump: &self,
1903e406d2eSAlex Crichton         };
1913e406d2eSAlex Crichton 
1923e406d2eSAlex Crichton         // Iterate over all symbols which will be functions for a cwasm and
1933e406d2eSAlex Crichton         // we'll disassemble them all.
1943e406d2eSAlex Crichton         let mut first = true;
1953e406d2eSAlex Crichton         for sym in elf.symbols() {
1963e406d2eSAlex Crichton             let name = match sym.name() {
1973e406d2eSAlex Crichton                 Ok(name) => name,
1983e406d2eSAlex Crichton                 Err(_) => continue,
1993e406d2eSAlex Crichton             };
2003e406d2eSAlex Crichton             let bytes = &text[sym.address() as usize..][..sym.size() as usize];
2013e406d2eSAlex Crichton 
20217fbd3c6SChris Fallin             let kind = if name.starts_with("wasmtime_builtin")
20317fbd3c6SChris Fallin                 || name.starts_with("wasmtime_patchable_builtin")
20417fbd3c6SChris Fallin             {
2053e406d2eSAlex Crichton                 Func::Builtin
2063e406d2eSAlex Crichton             } else if name.contains("]::function[") {
2073e406d2eSAlex Crichton                 Func::Wasm
208cb980839SAlex Crichton             } else if name.contains("trampoline")
209cb980839SAlex Crichton                 || name.ends_with("_array_call")
210cb980839SAlex Crichton                 || name.ends_with("_wasm_call")
211cb980839SAlex Crichton             {
2123e406d2eSAlex Crichton                 Func::Trampoline
213da8aa9c1SAlex Crichton             } else if name.contains("libcall") || name.starts_with("component") {
2143e406d2eSAlex Crichton                 Func::Libcall
2153e406d2eSAlex Crichton             } else {
2163e406d2eSAlex Crichton                 panic!("unknown symbol: {name}")
2173e406d2eSAlex Crichton             };
2183e406d2eSAlex Crichton 
2193e406d2eSAlex Crichton             // Apply any filters, if provided, to this function to look at just
2203e406d2eSAlex Crichton             // one function in the disassembly.
2213e406d2eSAlex Crichton             if self.funcs.is_empty() {
2223e406d2eSAlex Crichton                 if kind != Func::Wasm {
2233e406d2eSAlex Crichton                     continue;
2243e406d2eSAlex Crichton                 }
2253e406d2eSAlex Crichton             } else {
2263e406d2eSAlex Crichton                 if !(self.funcs.contains(&Func::All) || self.funcs.contains(&kind)) {
2273e406d2eSAlex Crichton                     continue;
2283e406d2eSAlex Crichton                 }
2293e406d2eSAlex Crichton             }
2303e406d2eSAlex Crichton             if let Some(filter) = &self.filter {
2313e406d2eSAlex Crichton                 if !name.contains(filter) {
2323e406d2eSAlex Crichton                     continue;
2333e406d2eSAlex Crichton                 }
2343e406d2eSAlex Crichton             }
2353e406d2eSAlex Crichton 
2363e406d2eSAlex Crichton             // Place a blank line between functions.
2373e406d2eSAlex Crichton             if first {
2383e406d2eSAlex Crichton                 first = false;
2393e406d2eSAlex Crichton             } else {
2403e406d2eSAlex Crichton                 writeln!(stdout)?;
2413e406d2eSAlex Crichton             }
2423e406d2eSAlex Crichton 
2433e406d2eSAlex Crichton             // Print the function's address, if so desired. Then print the
2443e406d2eSAlex Crichton             // function name.
2453e406d2eSAlex Crichton             if self.addresses {
2463e406d2eSAlex Crichton                 stdout.set_color(color_address.clone().set_bold(true))?;
2473e406d2eSAlex Crichton                 write!(stdout, "{:08x} ", sym.address())?;
2483e406d2eSAlex Crichton                 stdout.reset()?;
2493e406d2eSAlex Crichton             }
2503e406d2eSAlex Crichton             stdout.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)))?;
2513e406d2eSAlex Crichton             write!(stdout, "{name}")?;
2523e406d2eSAlex Crichton             stdout.reset()?;
2533e406d2eSAlex Crichton             writeln!(stdout, ":")?;
2543e406d2eSAlex Crichton 
2553e406d2eSAlex Crichton             // Tracking variables for rough heuristics of printing targets of
2563e406d2eSAlex Crichton             // jump instructions for `--address-jumps` mode.
2573e406d2eSAlex Crichton             let mut prev_jump = false;
2583e406d2eSAlex Crichton             let mut write_offsets = false;
2593e406d2eSAlex Crichton 
2603e406d2eSAlex Crichton             for inst in self.disas(&elf, bytes, sym.address())? {
2613e406d2eSAlex Crichton                 let Inst {
2623e406d2eSAlex Crichton                     address,
2633e406d2eSAlex Crichton                     is_jump,
2643e406d2eSAlex Crichton                     is_return,
2653e406d2eSAlex Crichton                     disassembly: disas,
2663e406d2eSAlex Crichton                     bytes,
2673e406d2eSAlex Crichton                 } = inst;
2683e406d2eSAlex Crichton 
2693e406d2eSAlex Crichton                 // Generate an infinite list of bytes to make printing below
2703e406d2eSAlex Crichton                 // easier, but only limit `inline_bytes` to get printed before
2713e406d2eSAlex Crichton                 // an instruction.
2723e406d2eSAlex Crichton                 let mut bytes = bytes.iter().map(Some).chain(iter::repeat(None));
2733e406d2eSAlex Crichton                 let inline_bytes = 9;
2743e406d2eSAlex Crichton                 let width = self.address_width;
2753e406d2eSAlex Crichton 
2762d25f862SChris Fallin                 // Collect any "decorations" or annotations for this
2772d25f862SChris Fallin                 // instruction. This includes the address map, stack
2782d25f862SChris Fallin                 // maps, exception handlers, etc.
2792d25f862SChris Fallin                 //
2802d25f862SChris Fallin                 // Once they're collected then we print them before or
2812d25f862SChris Fallin                 // after the instruction attempting to use some
2822d25f862SChris Fallin                 // unicode characters to make it easier to read/scan.
2832d25f862SChris Fallin                 //
2842d25f862SChris Fallin                 // Note that some decorations occur "before" an
2852d25f862SChris Fallin                 // instruction: for example, exception handler entries
2862d25f862SChris Fallin                 // logically occur at the return point after a call,
2872d25f862SChris Fallin                 // so "before" the instruction following the call.
2882d25f862SChris Fallin                 let mut pre_decorations = Vec::new();
2892d25f862SChris Fallin                 let mut post_decorations = Vec::new();
2902d25f862SChris Fallin                 decorator.decorate(address, &mut pre_decorations, &mut post_decorations);
2912d25f862SChris Fallin 
2922d25f862SChris Fallin                 let print_whitespace_to_decoration = |stdout: &mut StandardStream| -> Result<()> {
2932d25f862SChris Fallin                     write!(stdout, "{:width$}  ", "")?;
2942d25f862SChris Fallin                     if self.bytes {
2952d25f862SChris Fallin                         for _ in 0..inline_bytes + 1 {
2962d25f862SChris Fallin                             write!(stdout, "   ")?;
2972d25f862SChris Fallin                         }
2982d25f862SChris Fallin                     }
2992d25f862SChris Fallin                     Ok(())
3002d25f862SChris Fallin                 };
3012d25f862SChris Fallin 
3022d25f862SChris Fallin                 let print_decorations =
3032d25f862SChris Fallin                     |stdout: &mut StandardStream, decorations: Vec<String>| -> Result<()> {
3042d25f862SChris Fallin                         for (i, decoration) in decorations.iter().enumerate() {
3052d25f862SChris Fallin                             print_whitespace_to_decoration(stdout)?;
3062d25f862SChris Fallin                             let mut color = ColorSpec::new();
3072d25f862SChris Fallin                             color.set_fg(Some(Color::Cyan));
3082d25f862SChris Fallin                             stdout.set_color(&color)?;
3092d25f862SChris Fallin                             let final_decoration = i == decorations.len() - 1;
3102d25f862SChris Fallin                             if !final_decoration {
3112d25f862SChris Fallin                                 write!(stdout, "├")?;
3122d25f862SChris Fallin                             } else {
3132d25f862SChris Fallin                                 write!(stdout, "╰")?;
3142d25f862SChris Fallin                             }
3152d25f862SChris Fallin                             for (i, line) in decoration.lines().enumerate() {
3162d25f862SChris Fallin                                 if i == 0 {
3172d25f862SChris Fallin                                     write!(stdout, "─╼ ")?;
3182d25f862SChris Fallin                                 } else {
3192d25f862SChris Fallin                                     print_whitespace_to_decoration(stdout)?;
3202d25f862SChris Fallin                                     if final_decoration {
3212d25f862SChris Fallin                                         write!(stdout, "    ")?;
3222d25f862SChris Fallin                                     } else {
3232d25f862SChris Fallin                                         write!(stdout, "│   ")?;
3242d25f862SChris Fallin                                     }
3252d25f862SChris Fallin                                 }
3262d25f862SChris Fallin                                 writeln!(stdout, "{line}")?;
3272d25f862SChris Fallin                             }
3282d25f862SChris Fallin                             stdout.reset()?;
3292d25f862SChris Fallin                         }
3302d25f862SChris Fallin                         Ok(())
3312d25f862SChris Fallin                     };
3322d25f862SChris Fallin 
3332d25f862SChris Fallin                 print_decorations(&mut stdout, pre_decorations)?;
3342d25f862SChris Fallin 
3353e406d2eSAlex Crichton                 // Some instructions may disassemble to multiple lines, such as
3363e406d2eSAlex Crichton                 // `br_table` with Pulley. Handle separate lines per-instruction
3373e406d2eSAlex Crichton                 // here.
3383e406d2eSAlex Crichton                 for (i, line) in disas.lines().enumerate() {
3393e406d2eSAlex Crichton                     let print_address = self.addresses
3403e406d2eSAlex Crichton                         || (self.address_jumps && (write_offsets || (prev_jump && !is_jump)));
3413e406d2eSAlex Crichton                     if i == 0 && print_address {
3423e406d2eSAlex Crichton                         stdout.set_color(&color_address)?;
3433e406d2eSAlex Crichton                         write!(stdout, "{address:>width$x}: ")?;
3443e406d2eSAlex Crichton                         stdout.reset()?;
3453e406d2eSAlex Crichton                     } else {
3463e406d2eSAlex Crichton                         write!(stdout, "{:width$}  ", "")?;
3473e406d2eSAlex Crichton                     }
3483e406d2eSAlex Crichton 
3493e406d2eSAlex Crichton                     // If we're printing inline bytes then print up to
3503e406d2eSAlex Crichton                     // `inline_bytes` of instruction data, and any remaining
3513e406d2eSAlex Crichton                     // data will go on the next line, if any, or after the
3523e406d2eSAlex Crichton                     // instruction below.
3533e406d2eSAlex Crichton                     if self.bytes {
3543e406d2eSAlex Crichton                         stdout.set_color(&color_bytes)?;
3553e406d2eSAlex Crichton                         for byte in bytes.by_ref().take(inline_bytes) {
3563e406d2eSAlex Crichton                             match byte {
3573e406d2eSAlex Crichton                                 Some(byte) => write!(stdout, "{byte:02x} ")?,
3583e406d2eSAlex Crichton                                 None => write!(stdout, "   ")?,
3593e406d2eSAlex Crichton                             }
3603e406d2eSAlex Crichton                         }
3613e406d2eSAlex Crichton                         write!(stdout, "  ")?;
3623e406d2eSAlex Crichton                         stdout.reset()?;
3633e406d2eSAlex Crichton                     }
3643e406d2eSAlex Crichton 
3653e406d2eSAlex Crichton                     writeln!(stdout, "{line}")?;
3663e406d2eSAlex Crichton                 }
3673e406d2eSAlex Crichton 
3683e406d2eSAlex Crichton                 // Flip write_offsets to true once we've seen a `ret`, as
3693e406d2eSAlex Crichton                 // instructions that follow the return are often related to trap
3703e406d2eSAlex Crichton                 // tables.
3713e406d2eSAlex Crichton                 write_offsets |= is_return;
3723e406d2eSAlex Crichton                 prev_jump = is_jump;
3733e406d2eSAlex Crichton 
3743e406d2eSAlex Crichton                 // After the instruction is printed then finish printing the
3753e406d2eSAlex Crichton                 // instruction bytes if any are present. Still limit to
3763e406d2eSAlex Crichton                 // `inline_bytes` per line.
3773e406d2eSAlex Crichton                 if self.bytes {
3783e406d2eSAlex Crichton                     let mut inline = 0;
3793e406d2eSAlex Crichton                     stdout.set_color(&color_bytes)?;
3803e406d2eSAlex Crichton                     for byte in bytes {
3813e406d2eSAlex Crichton                         let Some(byte) = byte else { break };
3823e406d2eSAlex Crichton                         if inline == 0 {
3833e406d2eSAlex Crichton                             write!(stdout, "{:width$}  ", "")?;
3843e406d2eSAlex Crichton                         } else {
3853e406d2eSAlex Crichton                             write!(stdout, " ")?;
3863e406d2eSAlex Crichton                         }
3873e406d2eSAlex Crichton                         write!(stdout, "{byte:02x}")?;
3883e406d2eSAlex Crichton                         inline += 1;
3893e406d2eSAlex Crichton                         if inline == inline_bytes {
3903e406d2eSAlex Crichton                             writeln!(stdout)?;
3913e406d2eSAlex Crichton                             inline = 0;
3923e406d2eSAlex Crichton                         }
3933e406d2eSAlex Crichton                     }
3943e406d2eSAlex Crichton                     stdout.reset()?;
3953e406d2eSAlex Crichton                     if inline > 0 {
3963e406d2eSAlex Crichton                         writeln!(stdout)?;
3973e406d2eSAlex Crichton                     }
3983e406d2eSAlex Crichton                 }
3993e406d2eSAlex Crichton 
4002d25f862SChris Fallin                 print_decorations(&mut stdout, post_decorations)?;
4013e406d2eSAlex Crichton             }
4023e406d2eSAlex Crichton         }
4033e406d2eSAlex Crichton         Ok(())
4043e406d2eSAlex Crichton     }
4053e406d2eSAlex Crichton 
4063e406d2eSAlex Crichton     /// Disassembles `func` contained within `elf` returning a list of
4073e406d2eSAlex Crichton     /// instructions that represent the function.
disas(&self, elf: &ElfFile64<'_, Endianness>, func: &[u8], addr: u64) -> Result<Vec<Inst>>4083e406d2eSAlex Crichton     fn disas(&self, elf: &ElfFile64<'_, Endianness>, func: &[u8], addr: u64) -> Result<Vec<Inst>> {
4093e406d2eSAlex Crichton         let cranelift_target = match elf.architecture() {
4103e406d2eSAlex Crichton             Architecture::X86_64 => "x86_64",
4113e406d2eSAlex Crichton             Architecture::Aarch64 => "aarch64",
4123e406d2eSAlex Crichton             Architecture::S390x => "s390x",
4133e406d2eSAlex Crichton             Architecture::Riscv64 => {
4143e406d2eSAlex Crichton                 let e_flags = match elf.flags() {
4153e406d2eSAlex Crichton                     FileFlags::Elf { e_flags, .. } => e_flags,
4163e406d2eSAlex Crichton                     _ => bail!("not an ELF file"),
4173e406d2eSAlex Crichton                 };
4183e406d2eSAlex Crichton                 if e_flags & (obj::EF_WASMTIME_PULLEY32 | obj::EF_WASMTIME_PULLEY64) != 0 {
4193e406d2eSAlex Crichton                     return self.disas_pulley(func, addr);
4203e406d2eSAlex Crichton                 } else {
4213e406d2eSAlex Crichton                     "riscv64"
4223e406d2eSAlex Crichton                 }
4233e406d2eSAlex Crichton             }
4243e406d2eSAlex Crichton             other => bail!("unknown architecture {other:?}"),
4253e406d2eSAlex Crichton         };
4263e406d2eSAlex Crichton         let builder =
4273e406d2eSAlex Crichton             lookup_by_name(cranelift_target).context("failed to load cranelift ISA builder")?;
4283e406d2eSAlex Crichton         let flags = cranelift_codegen::settings::builder();
4293e406d2eSAlex Crichton         let isa = builder.finish(Flags::new(flags))?;
4303e406d2eSAlex Crichton         let isa = &*isa;
4313e406d2eSAlex Crichton         let capstone = isa
4323e406d2eSAlex Crichton             .to_capstone()
4333e406d2eSAlex Crichton             .context("failed to create a capstone disassembler")?;
4343e406d2eSAlex Crichton 
4353e406d2eSAlex Crichton         let insts = capstone
4363e406d2eSAlex Crichton             .disasm_all(func, addr)?
4373e406d2eSAlex Crichton             .into_iter()
4383e406d2eSAlex Crichton             .map(|inst| {
4393e406d2eSAlex Crichton                 let detail = capstone.insn_detail(&inst).ok();
4403e406d2eSAlex Crichton                 let detail = detail.as_ref();
4413e406d2eSAlex Crichton                 let is_jump = detail
4423e406d2eSAlex Crichton                     .map(|d| {
4433e406d2eSAlex Crichton                         d.groups()
4443e406d2eSAlex Crichton                             .iter()
4453e406d2eSAlex Crichton                             .find(|g| g.0 as u32 == CS_GRP_JUMP)
4463e406d2eSAlex Crichton                             .is_some()
4473e406d2eSAlex Crichton                     })
4483e406d2eSAlex Crichton                     .unwrap_or(false);
4493e406d2eSAlex Crichton 
4503e406d2eSAlex Crichton                 let is_return = detail
4513e406d2eSAlex Crichton                     .map(|d| {
4523e406d2eSAlex Crichton                         d.groups()
4533e406d2eSAlex Crichton                             .iter()
4543e406d2eSAlex Crichton                             .find(|g| g.0 as u32 == CS_GRP_RET)
4553e406d2eSAlex Crichton                             .is_some()
4563e406d2eSAlex Crichton                     })
4573e406d2eSAlex Crichton                     .unwrap_or(false);
4583e406d2eSAlex Crichton 
4593e406d2eSAlex Crichton                 let disassembly = match (inst.mnemonic(), inst.op_str()) {
4603e406d2eSAlex Crichton                     (Some(i), Some(o)) => {
4613e406d2eSAlex Crichton                         if o.is_empty() {
4623e406d2eSAlex Crichton                             format!("{i}")
4633e406d2eSAlex Crichton                         } else {
4643e406d2eSAlex Crichton                             format!("{i:7} {o}")
4653e406d2eSAlex Crichton                         }
4663e406d2eSAlex Crichton                     }
4673e406d2eSAlex Crichton                     (Some(i), None) => format!("{i}"),
4683e406d2eSAlex Crichton                     _ => unreachable!(),
4693e406d2eSAlex Crichton                 };
4703e406d2eSAlex Crichton 
4713e406d2eSAlex Crichton                 let address = inst.address();
4723e406d2eSAlex Crichton                 Inst {
4733e406d2eSAlex Crichton                     address,
4743e406d2eSAlex Crichton                     is_jump,
4753e406d2eSAlex Crichton                     is_return,
4763e406d2eSAlex Crichton                     bytes: inst.bytes().to_vec(),
4773e406d2eSAlex Crichton                     disassembly,
4783e406d2eSAlex Crichton                 }
4793e406d2eSAlex Crichton             })
4803e406d2eSAlex Crichton             .collect::<Vec<_>>();
4813e406d2eSAlex Crichton         Ok(insts)
4823e406d2eSAlex Crichton     }
4833e406d2eSAlex Crichton 
4843e406d2eSAlex Crichton     /// Same as `dias` above, but just for Pulley.
disas_pulley(&self, func: &[u8], addr: u64) -> Result<Vec<Inst>>4853e406d2eSAlex Crichton     fn disas_pulley(&self, func: &[u8], addr: u64) -> Result<Vec<Inst>> {
4863e406d2eSAlex Crichton         let mut result = vec![];
4873e406d2eSAlex Crichton 
4883e406d2eSAlex Crichton         let mut disas = Disassembler::new(func);
4893e406d2eSAlex Crichton         disas.offsets(false);
4903e406d2eSAlex Crichton         disas.hexdump(false);
4913e406d2eSAlex Crichton         disas.start_offset(usize::try_from(addr).unwrap());
4923e406d2eSAlex Crichton         let mut decoder = Decoder::new();
4933e406d2eSAlex Crichton         let mut last_disas_pos = 0;
4943e406d2eSAlex Crichton         loop {
4953e406d2eSAlex Crichton             let start_addr = disas.bytecode().position();
4963e406d2eSAlex Crichton 
4973e406d2eSAlex Crichton             match decoder.decode_one(&mut disas) {
4983e406d2eSAlex Crichton                 // If we got EOF at the initial position, then we're done disassembling.
4993e406d2eSAlex Crichton                 Err(DecodingError::UnexpectedEof { position }) if position == start_addr => break,
5003e406d2eSAlex Crichton 
5013e406d2eSAlex Crichton                 // Otherwise, propagate the error.
5023e406d2eSAlex Crichton                 Err(e) => {
5033e406d2eSAlex Crichton                     return Err(e).context("failed to disassembly pulley bytecode");
5043e406d2eSAlex Crichton                 }
5053e406d2eSAlex Crichton 
5063e406d2eSAlex Crichton                 Ok(()) => {
5073e406d2eSAlex Crichton                     let bytes_range = start_addr..disas.bytecode().position();
5083e406d2eSAlex Crichton                     let disassembly = disas.disas()[last_disas_pos..].trim();
5093e406d2eSAlex Crichton                     last_disas_pos = disas.disas().len();
5103e406d2eSAlex Crichton                     let address = u64::try_from(start_addr).unwrap() + addr;
5113e406d2eSAlex Crichton                     let is_jump = disassembly.contains("jump") || disassembly.contains("br_");
5123e406d2eSAlex Crichton                     let is_return = disassembly == "ret";
5133e406d2eSAlex Crichton                     result.push(Inst {
5143e406d2eSAlex Crichton                         bytes: func[bytes_range].to_vec(),
5153e406d2eSAlex Crichton                         address,
5163e406d2eSAlex Crichton                         is_jump,
5173e406d2eSAlex Crichton                         is_return,
5183e406d2eSAlex Crichton                         disassembly: disassembly.to_string(),
5193e406d2eSAlex Crichton                     });
5203e406d2eSAlex Crichton                 }
5213e406d2eSAlex Crichton             }
5223e406d2eSAlex Crichton         }
5233e406d2eSAlex Crichton 
5243e406d2eSAlex Crichton         Ok(result)
5253e406d2eSAlex Crichton     }
5263e406d2eSAlex Crichton 
5273e406d2eSAlex Crichton     /// Helper to read the input bytes of the `*.cwasm` handling stdin
5283e406d2eSAlex Crichton     /// automatically.
read_cwasm(&self) -> Result<Vec<u8>>5293e406d2eSAlex Crichton     fn read_cwasm(&self) -> Result<Vec<u8>> {
5303e406d2eSAlex Crichton         if let Some(path) = &self.cwasm {
5313e406d2eSAlex Crichton             if path != Path::new("-") {
5323e406d2eSAlex Crichton                 return std::fs::read(path).with_context(|| format!("failed to read {path:?}"));
5333e406d2eSAlex Crichton             }
5343e406d2eSAlex Crichton         }
5353e406d2eSAlex Crichton 
5363e406d2eSAlex Crichton         let mut stdin = Vec::new();
5373e406d2eSAlex Crichton         std::io::stdin()
5383e406d2eSAlex Crichton             .read_to_end(&mut stdin)
5393e406d2eSAlex Crichton             .context("failed to read stdin")?;
5403e406d2eSAlex Crichton         Ok(stdin)
5413e406d2eSAlex Crichton     }
5423e406d2eSAlex Crichton }
5433e406d2eSAlex Crichton 
5443e406d2eSAlex Crichton /// Helper structure to package up metadata about an instruction.
5453e406d2eSAlex Crichton struct Inst {
5463e406d2eSAlex Crichton     address: u64,
5473e406d2eSAlex Crichton     is_jump: bool,
5483e406d2eSAlex Crichton     is_return: bool,
5493e406d2eSAlex Crichton     disassembly: String,
5503e406d2eSAlex Crichton     bytes: Vec<u8>,
5513e406d2eSAlex Crichton }
5523e406d2eSAlex Crichton 
553cb7d8812SAlex Crichton #[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq)]
5543e406d2eSAlex Crichton enum Func {
5553e406d2eSAlex Crichton     All,
5563e406d2eSAlex Crichton     Wasm,
5573e406d2eSAlex Crichton     Trampoline,
5583e406d2eSAlex Crichton     Builtin,
5593e406d2eSAlex Crichton     Libcall,
5603e406d2eSAlex Crichton }
5613e406d2eSAlex Crichton 
5623e406d2eSAlex Crichton struct Decorator<'a> {
5633e406d2eSAlex Crichton     objdump: &'a ObjdumpCommand,
5643e406d2eSAlex Crichton     addrmap: Option<Peekable<Box<dyn Iterator<Item = (u32, FilePos)> + 'a>>>,
5653e406d2eSAlex Crichton     traps: Option<Peekable<Box<dyn Iterator<Item = (u32, Trap)> + 'a>>>,
5665b076899SAlex Crichton     stack_maps: Option<Peekable<Box<dyn Iterator<Item = (u32, StackMap<'a>)> + 'a>>>,
567fa1d6867SChris Fallin     exception_tables:
568fa1d6867SChris Fallin         Option<Peekable<Box<dyn Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a>>>,
56902155232SChris Fallin     frame_tables: Option<
57002155232SChris Fallin         Peekable<
57102155232SChris Fallin             Box<
57202155232SChris Fallin                 dyn Iterator<
57302155232SChris Fallin                         Item = (
57402155232SChris Fallin                             u32,
57502155232SChris Fallin                             FrameInstPos,
576*9500c417SChris Fallin                             Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>,
57702155232SChris Fallin                         ),
57802155232SChris Fallin                     > + 'a,
57902155232SChris Fallin             >,
58002155232SChris Fallin         >,
58102155232SChris Fallin     >,
58202155232SChris Fallin 
58317fbd3c6SChris Fallin     // Breakpoint table, sorted by native offset instead so we can
58417fbd3c6SChris Fallin     // display inline with disassembly (the table in the image is
58517fbd3c6SChris Fallin     // sorted by Wasm PC).
586*9500c417SChris Fallin     breakpoints: Peekable<Box<dyn Iterator<Item = (ModulePC, usize, SmallVec<[u8; 8]>)>>>,
58717fbd3c6SChris Fallin 
58802155232SChris Fallin     frame_table_descriptors: Option<FrameTable<'a>>,
5893e406d2eSAlex Crichton }
5903e406d2eSAlex Crichton 
5913e406d2eSAlex Crichton impl Decorator<'_> {
decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>)5922d25f862SChris Fallin     fn decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>) {
5932d25f862SChris Fallin         self.addrmap(address, post_list);
5942d25f862SChris Fallin         self.traps(address, post_list);
5952d25f862SChris Fallin         self.stack_maps(address, post_list);
5962d25f862SChris Fallin         self.exception_table(address, pre_list);
59702155232SChris Fallin         self.frame_table(address, pre_list, post_list);
59817fbd3c6SChris Fallin         self.breakpoints(address, pre_list);
5993e406d2eSAlex Crichton     }
6003e406d2eSAlex Crichton 
addrmap(&mut self, address: u64, list: &mut Vec<String>)6013e406d2eSAlex Crichton     fn addrmap(&mut self, address: u64, list: &mut Vec<String>) {
6025b076899SAlex Crichton         if !self.objdump.addrmap() {
6033e406d2eSAlex Crichton             return;
6043e406d2eSAlex Crichton         }
6053e406d2eSAlex Crichton         let Some(addrmap) = &mut self.addrmap else {
6063e406d2eSAlex Crichton             return;
6073e406d2eSAlex Crichton         };
6083e406d2eSAlex Crichton         while let Some((addr, pos)) = addrmap.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
6093e406d2eSAlex Crichton             if u64::from(addr) != address {
6103e406d2eSAlex Crichton                 continue;
6113e406d2eSAlex Crichton             }
6123e406d2eSAlex Crichton             if let Some(offset) = pos.file_offset() {
6133e406d2eSAlex Crichton                 list.push(format!("addrmap: {offset:#x}"));
6143e406d2eSAlex Crichton             }
6153e406d2eSAlex Crichton         }
6163e406d2eSAlex Crichton     }
6173e406d2eSAlex Crichton 
traps(&mut self, address: u64, list: &mut Vec<String>)6183e406d2eSAlex Crichton     fn traps(&mut self, address: u64, list: &mut Vec<String>) {
6195b076899SAlex Crichton         if !self.objdump.traps() {
6203e406d2eSAlex Crichton             return;
6213e406d2eSAlex Crichton         }
6223e406d2eSAlex Crichton         let Some(traps) = &mut self.traps else {
6233e406d2eSAlex Crichton             return;
6243e406d2eSAlex Crichton         };
6253e406d2eSAlex Crichton         while let Some((addr, trap)) = traps.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
6263e406d2eSAlex Crichton             if u64::from(addr) != address {
6273e406d2eSAlex Crichton                 continue;
6283e406d2eSAlex Crichton             }
6293e406d2eSAlex Crichton             list.push(format!("trap: {trap:?}"));
6303e406d2eSAlex Crichton         }
6313e406d2eSAlex Crichton     }
6325b076899SAlex Crichton 
stack_maps(&mut self, address: u64, list: &mut Vec<String>)6335b076899SAlex Crichton     fn stack_maps(&mut self, address: u64, list: &mut Vec<String>) {
6345b076899SAlex Crichton         if !self.objdump.stack_maps() {
6355b076899SAlex Crichton             return;
6365b076899SAlex Crichton         }
6375b076899SAlex Crichton         let Some(stack_maps) = &mut self.stack_maps else {
6385b076899SAlex Crichton             return;
6395b076899SAlex Crichton         };
6405b076899SAlex Crichton         while let Some((addr, stack_map)) =
6415b076899SAlex Crichton             stack_maps.next_if(|(addr, _pos)| u64::from(*addr) <= address)
6425b076899SAlex Crichton         {
6435b076899SAlex Crichton             if u64::from(addr) != address {
6445b076899SAlex Crichton                 continue;
6455b076899SAlex Crichton             }
6465b076899SAlex Crichton             list.push(format!(
6475b076899SAlex Crichton                 "stack_map: frame_size={}, frame_offsets={:?}",
6485b076899SAlex Crichton                 stack_map.frame_size(),
6495b076899SAlex Crichton                 stack_map.offsets().collect::<Vec<_>>()
6505b076899SAlex Crichton             ));
6515b076899SAlex Crichton         }
6525b076899SAlex Crichton     }
6532d25f862SChris Fallin 
exception_table(&mut self, address: u64, list: &mut Vec<String>)6542d25f862SChris Fallin     fn exception_table(&mut self, address: u64, list: &mut Vec<String>) {
6552d25f862SChris Fallin         if !self.objdump.exception_tables() {
6562d25f862SChris Fallin             return;
6572d25f862SChris Fallin         }
6582d25f862SChris Fallin         let Some(exception_tables) = &mut self.exception_tables else {
6592d25f862SChris Fallin             return;
6602d25f862SChris Fallin         };
661fa1d6867SChris Fallin         while let Some((addr, frame_offset, handlers)) =
662fa1d6867SChris Fallin             exception_tables.next_if(|(addr, _, _)| u64::from(*addr) <= address)
6632d25f862SChris Fallin         {
6642d25f862SChris Fallin             if u64::from(addr) != address {
6652d25f862SChris Fallin                 continue;
6662d25f862SChris Fallin             }
667fa1d6867SChris Fallin             if let Some(frame_offset) = frame_offset {
668fa1d6867SChris Fallin                 list.push(format!(
669fa1d6867SChris Fallin                     "exception frame offset: SP = FP - 0x{frame_offset:x}",
670fa1d6867SChris Fallin                 ));
671fa1d6867SChris Fallin             }
6722d25f862SChris Fallin             for handler in &handlers {
6732d25f862SChris Fallin                 let tag = match handler.tag {
6742d25f862SChris Fallin                     Some(tag) => format!("tag={tag}"),
6752d25f862SChris Fallin                     None => "default handler".to_string(),
6762d25f862SChris Fallin                 };
6772d25f862SChris Fallin                 let context = match handler.context_sp_offset {
6782d25f862SChris Fallin                     Some(offset) => format!("context at [SP+0x{offset:x}]"),
6792d25f862SChris Fallin                     None => "no dynamic context".to_string(),
6802d25f862SChris Fallin                 };
6812d25f862SChris Fallin                 list.push(format!(
6822d25f862SChris Fallin                     "exception handler: {tag}, {context}, handler=0x{:x}",
6832d25f862SChris Fallin                     handler.handler_offset
6842d25f862SChris Fallin                 ));
6852d25f862SChris Fallin             }
6862d25f862SChris Fallin         }
6872d25f862SChris Fallin     }
68802155232SChris Fallin 
frame_table( &mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>, )68902155232SChris Fallin     fn frame_table(
69002155232SChris Fallin         &mut self,
69102155232SChris Fallin         address: u64,
69202155232SChris Fallin         pre_list: &mut Vec<String>,
69302155232SChris Fallin         post_list: &mut Vec<String>,
69402155232SChris Fallin     ) {
69502155232SChris Fallin         if !self.objdump.frame_tables() {
69602155232SChris Fallin             return;
69702155232SChris Fallin         }
69802155232SChris Fallin         let (Some(frame_table_iter), Some(frame_tables)) =
69902155232SChris Fallin             (&mut self.frame_tables, &self.frame_table_descriptors)
70002155232SChris Fallin         else {
70102155232SChris Fallin             return;
70202155232SChris Fallin         };
70302155232SChris Fallin 
70402155232SChris Fallin         while let Some((addr, pos, frames)) =
70502155232SChris Fallin             frame_table_iter.next_if(|(addr, _, _)| u64::from(*addr) <= address)
70602155232SChris Fallin         {
70702155232SChris Fallin             if u64::from(addr) != address {
70802155232SChris Fallin                 continue;
70902155232SChris Fallin             }
71002155232SChris Fallin             let list = match pos {
71102155232SChris Fallin                 // N.B.: the "post" position means that we are
71202155232SChris Fallin                 // attached to the end of the previous instruction
71302155232SChris Fallin                 // (its "post"); which means that from this
71402155232SChris Fallin                 // instruction's PoV, we print before the instruction
71502155232SChris Fallin                 // (the "pre list"). And vice versa for the "pre"
71602155232SChris Fallin                 // position. Hence the reversal here.
71702155232SChris Fallin                 FrameInstPos::Post => &mut *pre_list,
71802155232SChris Fallin                 FrameInstPos::Pre => &mut *post_list,
71902155232SChris Fallin             };
72002155232SChris Fallin             let pos = match pos {
72102155232SChris Fallin                 FrameInstPos::Post => "after previous inst",
72202155232SChris Fallin                 FrameInstPos::Pre => "before next inst",
72302155232SChris Fallin             };
72402155232SChris Fallin             for (wasm_pc, frame_descriptor, stack_shape) in frames {
72502155232SChris Fallin                 let (frame_descriptor_data, offset) =
72602155232SChris Fallin                     frame_tables.frame_descriptor(frame_descriptor).unwrap();
72702155232SChris Fallin                 let frame_descriptor = FrameStateSlot::parse(frame_descriptor_data).unwrap();
72802155232SChris Fallin 
72902155232SChris Fallin                 let local_shape = Self::describe_local_shape(&frame_descriptor);
73002155232SChris Fallin                 let stack_shape = Self::describe_stack_shape(&frame_descriptor, stack_shape);
73102155232SChris Fallin                 let func_key = frame_descriptor.func_key();
73202155232SChris Fallin                 list.push(format!("debug frame state ({pos}): func key {func_key:?}, wasm PC {wasm_pc}, slot at FP-0x{offset:x}, locals {local_shape}, stack {stack_shape}"));
73302155232SChris Fallin             }
73402155232SChris Fallin         }
73502155232SChris Fallin     }
73602155232SChris Fallin 
breakpoints(&mut self, address: u64, list: &mut Vec<String>)73717fbd3c6SChris Fallin     fn breakpoints(&mut self, address: u64, list: &mut Vec<String>) {
73817fbd3c6SChris Fallin         while let Some((wasm_pc, addr, patch)) = self.breakpoints.next_if(|(_, addr, patch)| {
73917fbd3c6SChris Fallin             u64::try_from(*addr).unwrap() + u64::try_from(patch.len()).unwrap() <= address
74017fbd3c6SChris Fallin         }) {
74117fbd3c6SChris Fallin             if u64::try_from(addr).unwrap() + u64::try_from(patch.len()).unwrap() != address {
74217fbd3c6SChris Fallin                 continue;
74317fbd3c6SChris Fallin             }
74417fbd3c6SChris Fallin             list.push(format!(
74517fbd3c6SChris Fallin                 "breakpoint patch: wasm PC {wasm_pc}, patch bytes {patch:?}"
74617fbd3c6SChris Fallin             ));
74717fbd3c6SChris Fallin         }
74817fbd3c6SChris Fallin     }
74917fbd3c6SChris Fallin 
describe_local_shape(desc: &FrameStateSlot<'_>) -> String75002155232SChris Fallin     fn describe_local_shape(desc: &FrameStateSlot<'_>) -> String {
75102155232SChris Fallin         let mut parts = vec![];
75202155232SChris Fallin         for (offset, ty) in desc.locals() {
75302155232SChris Fallin             parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
75402155232SChris Fallin         }
75502155232SChris Fallin         parts.join(", ")
75602155232SChris Fallin     }
75702155232SChris Fallin 
describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String75802155232SChris Fallin     fn describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String {
75902155232SChris Fallin         let mut parts = vec![];
76002155232SChris Fallin         for (offset, ty) in desc.stack(shape) {
76102155232SChris Fallin             parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
76202155232SChris Fallin         }
76302155232SChris Fallin         parts.reverse();
76402155232SChris Fallin         parts.join(", ")
76502155232SChris Fallin     }
7663e406d2eSAlex Crichton }
767