xref: /wasmtime-44.0.1/src/commands/objdump.rs (revision 9500c417)
1 //! Implementation of the `wasmtime objdump` CLI command.
2 
3 use capstone::InsnGroupType::{CS_GRP_JUMP, CS_GRP_RET};
4 use clap::Parser;
5 use cranelift_codegen::isa::lookup_by_name;
6 use cranelift_codegen::settings::Flags;
7 use object::read::elf::ElfFile64;
8 use object::{Architecture, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol};
9 use pulley_interpreter::decode::{Decoder, DecodingError, OpVisitor};
10 use pulley_interpreter::disas::Disassembler;
11 use smallvec::SmallVec;
12 use std::io::{IsTerminal, Read, Write};
13 use std::iter::{self, Peekable};
14 use std::path::{Path, PathBuf};
15 use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
16 use wasmtime::{Engine, Result, bail, error::Context as _};
17 use wasmtime_environ::{
18     FilePos, FrameInstPos, FrameStackShape, FrameStateSlot, FrameTable, FrameTableDescriptorIndex,
19     ModulePC, StackMap, Trap, obj,
20 };
21 use wasmtime_unwinder::{ExceptionHandler, ExceptionTable};
22 
23 /// A helper utility in wasmtime to explore the compiled object file format of
24 /// a `*.cwasm` file.
25 #[derive(Parser)]
26 pub struct ObjdumpCommand {
27     /// The path to a compiled `*.cwasm` file.
28     ///
29     /// If this is `-` or not provided then stdin is used as input.
30     cwasm: Option<PathBuf>,
31 
32     /// Whether or not to display function/instruction addresses.
33     #[arg(long)]
34     addresses: bool,
35 
36     /// Whether or not to try to only display addresses of instruction jump
37     /// targets.
38     #[arg(long)]
39     address_jumps: bool,
40 
41     /// What functions should be printed
42     #[arg(long, default_value = "wasm", value_name = "KIND")]
43     funcs: Vec<Func>,
44 
45     /// String filter to apply to function names to only print some functions.
46     #[arg(long, value_name = "STR")]
47     filter: Option<String>,
48 
49     /// Whether or not instruction bytes are disassembled.
50     #[arg(long)]
51     bytes: bool,
52 
53     /// Whether or not to use color.
54     #[arg(long, default_value = "auto")]
55     color: ColorChoice,
56 
57     /// Whether or not to interleave instructions with address maps.
58     #[arg(long, require_equals = true, value_name = "true|false")]
59     addrmap: Option<Option<bool>>,
60 
61     /// Column width of how large an address is rendered as.
62     #[arg(long, default_value = "10", value_name = "N")]
63     address_width: usize,
64 
65     /// Whether or not to show information about what instructions can trap.
66     #[arg(long, require_equals = true, value_name = "true|false")]
67     traps: Option<Option<bool>>,
68 
69     /// Whether or not to show information about stack maps.
70     #[arg(long, require_equals = true, value_name = "true|false")]
71     stack_maps: Option<Option<bool>>,
72 
73     /// Whether or not to show information about exception tables.
74     #[arg(long, require_equals = true, value_name = "true|false")]
75     exception_tables: Option<Option<bool>>,
76 
77     /// Whether or not to show information about frame tables.
78     #[arg(long, require_equals = true, value_name = "true|false")]
79     frame_tables: Option<Option<bool>>,
80 }
81 
optional_flag_with_default(flag: Option<Option<bool>>, default: bool) -> bool82 fn optional_flag_with_default(flag: Option<Option<bool>>, default: bool) -> bool {
83     match flag {
84         None => default,
85         Some(None) => true,
86         Some(Some(val)) => val,
87     }
88 }
89 
90 impl ObjdumpCommand {
addrmap(&self) -> bool91     fn addrmap(&self) -> bool {
92         optional_flag_with_default(self.addrmap, false)
93     }
94 
traps(&self) -> bool95     fn traps(&self) -> bool {
96         optional_flag_with_default(self.traps, true)
97     }
98 
stack_maps(&self) -> bool99     fn stack_maps(&self) -> bool {
100         optional_flag_with_default(self.stack_maps, true)
101     }
102 
exception_tables(&self) -> bool103     fn exception_tables(&self) -> bool {
104         optional_flag_with_default(self.exception_tables, true)
105     }
106 
frame_tables(&self) -> bool107     fn frame_tables(&self) -> bool {
108         optional_flag_with_default(self.frame_tables, true)
109     }
110 
111     /// Executes the command.
execute(self) -> Result<()>112     pub fn execute(self) -> Result<()> {
113         // Setup stdout handling color options. Also build some variables used
114         // below to configure colors of certain items.
115         let mut choice = self.color;
116         if choice == ColorChoice::Auto && !std::io::stdout().is_terminal() {
117             choice = ColorChoice::Never;
118         }
119         let mut stdout = StandardStream::stdout(choice);
120 
121         let mut color_address = ColorSpec::new();
122         color_address.set_bold(true).set_fg(Some(Color::Yellow));
123         let mut color_bytes = ColorSpec::new();
124         color_bytes.set_fg(Some(Color::Magenta));
125 
126         let bytes = self.read_cwasm()?;
127 
128         // Double-check this is a `*.cwasm`
129         if Engine::detect_precompiled(&bytes).is_none() {
130             bail!("not a `*.cwasm` file from wasmtime: {:?}", self.cwasm);
131         }
132 
133         // Parse the input as an ELF file, extract the `.text` section.
134         let elf = ElfFile64::<Endianness>::parse(&bytes)?;
135         let text = elf
136             .section_by_name(".text")
137             .context("missing .text section")?;
138         let text = text.data()?;
139 
140         let frame_table_descriptors = elf
141             .section_by_name(obj::ELF_WASMTIME_FRAMES)
142             .and_then(|section| section.data().ok())
143             .and_then(|bytes| FrameTable::parse(bytes, text).ok());
144 
145         let mut breakpoints = frame_table_descriptors
146             .iter()
147             .flat_map(|ftd| ftd.breakpoint_patches())
148             .map(|(wasm_pc, patch)| (wasm_pc, patch.offset, SmallVec::from(patch.enable)))
149             .collect::<Vec<_>>();
150         breakpoints.sort_by_key(|(_wasm_pc, native_offset, _patch)| *native_offset);
151         let breakpoints: Box<dyn Iterator<Item = _>> = Box::new(breakpoints.into_iter());
152         let breakpoints = breakpoints.peekable();
153 
154         // Build the helper that'll get used to attach decorations/annotations
155         // to various instructions.
156         let mut decorator = Decorator {
157             addrmap: elf
158                 .section_by_name(obj::ELF_WASMTIME_ADDRMAP)
159                 .and_then(|section| section.data().ok())
160                 .and_then(|bytes| wasmtime_environ::iterate_address_map(bytes))
161                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
162             traps: elf
163                 .section_by_name(obj::ELF_WASMTIME_TRAPS)
164                 .and_then(|section| section.data().ok())
165                 .and_then(|bytes| wasmtime_environ::iterate_traps(bytes))
166                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
167             stack_maps: elf
168                 .section_by_name(obj::ELF_WASMTIME_STACK_MAP)
169                 .and_then(|section| section.data().ok())
170                 .and_then(|bytes| StackMap::iter(bytes))
171                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
172             exception_tables: elf
173                 .section_by_name(obj::ELF_WASMTIME_EXCEPTIONS)
174                 .and_then(|section| section.data().ok())
175                 .and_then(|bytes| ExceptionTable::parse(bytes).ok())
176                 .map(|table| table.into_iter())
177                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
178             frame_tables: elf
179                 .section_by_name(obj::ELF_WASMTIME_FRAMES)
180                 .and_then(|section| section.data().ok())
181                 .and_then(|bytes| FrameTable::parse(bytes, text).ok())
182                 .map(|table| table.into_program_points())
183                 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
184 
185             breakpoints,
186 
187             frame_table_descriptors,
188 
189             objdump: &self,
190         };
191 
192         // Iterate over all symbols which will be functions for a cwasm and
193         // we'll disassemble them all.
194         let mut first = true;
195         for sym in elf.symbols() {
196             let name = match sym.name() {
197                 Ok(name) => name,
198                 Err(_) => continue,
199             };
200             let bytes = &text[sym.address() as usize..][..sym.size() as usize];
201 
202             let kind = if name.starts_with("wasmtime_builtin")
203                 || name.starts_with("wasmtime_patchable_builtin")
204             {
205                 Func::Builtin
206             } else if name.contains("]::function[") {
207                 Func::Wasm
208             } else if name.contains("trampoline")
209                 || name.ends_with("_array_call")
210                 || name.ends_with("_wasm_call")
211             {
212                 Func::Trampoline
213             } else if name.contains("libcall") || name.starts_with("component") {
214                 Func::Libcall
215             } else {
216                 panic!("unknown symbol: {name}")
217             };
218 
219             // Apply any filters, if provided, to this function to look at just
220             // one function in the disassembly.
221             if self.funcs.is_empty() {
222                 if kind != Func::Wasm {
223                     continue;
224                 }
225             } else {
226                 if !(self.funcs.contains(&Func::All) || self.funcs.contains(&kind)) {
227                     continue;
228                 }
229             }
230             if let Some(filter) = &self.filter {
231                 if !name.contains(filter) {
232                     continue;
233                 }
234             }
235 
236             // Place a blank line between functions.
237             if first {
238                 first = false;
239             } else {
240                 writeln!(stdout)?;
241             }
242 
243             // Print the function's address, if so desired. Then print the
244             // function name.
245             if self.addresses {
246                 stdout.set_color(color_address.clone().set_bold(true))?;
247                 write!(stdout, "{:08x} ", sym.address())?;
248                 stdout.reset()?;
249             }
250             stdout.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)))?;
251             write!(stdout, "{name}")?;
252             stdout.reset()?;
253             writeln!(stdout, ":")?;
254 
255             // Tracking variables for rough heuristics of printing targets of
256             // jump instructions for `--address-jumps` mode.
257             let mut prev_jump = false;
258             let mut write_offsets = false;
259 
260             for inst in self.disas(&elf, bytes, sym.address())? {
261                 let Inst {
262                     address,
263                     is_jump,
264                     is_return,
265                     disassembly: disas,
266                     bytes,
267                 } = inst;
268 
269                 // Generate an infinite list of bytes to make printing below
270                 // easier, but only limit `inline_bytes` to get printed before
271                 // an instruction.
272                 let mut bytes = bytes.iter().map(Some).chain(iter::repeat(None));
273                 let inline_bytes = 9;
274                 let width = self.address_width;
275 
276                 // Collect any "decorations" or annotations for this
277                 // instruction. This includes the address map, stack
278                 // maps, exception handlers, etc.
279                 //
280                 // Once they're collected then we print them before or
281                 // after the instruction attempting to use some
282                 // unicode characters to make it easier to read/scan.
283                 //
284                 // Note that some decorations occur "before" an
285                 // instruction: for example, exception handler entries
286                 // logically occur at the return point after a call,
287                 // so "before" the instruction following the call.
288                 let mut pre_decorations = Vec::new();
289                 let mut post_decorations = Vec::new();
290                 decorator.decorate(address, &mut pre_decorations, &mut post_decorations);
291 
292                 let print_whitespace_to_decoration = |stdout: &mut StandardStream| -> Result<()> {
293                     write!(stdout, "{:width$}  ", "")?;
294                     if self.bytes {
295                         for _ in 0..inline_bytes + 1 {
296                             write!(stdout, "   ")?;
297                         }
298                     }
299                     Ok(())
300                 };
301 
302                 let print_decorations =
303                     |stdout: &mut StandardStream, decorations: Vec<String>| -> Result<()> {
304                         for (i, decoration) in decorations.iter().enumerate() {
305                             print_whitespace_to_decoration(stdout)?;
306                             let mut color = ColorSpec::new();
307                             color.set_fg(Some(Color::Cyan));
308                             stdout.set_color(&color)?;
309                             let final_decoration = i == decorations.len() - 1;
310                             if !final_decoration {
311                                 write!(stdout, "├")?;
312                             } else {
313                                 write!(stdout, "╰")?;
314                             }
315                             for (i, line) in decoration.lines().enumerate() {
316                                 if i == 0 {
317                                     write!(stdout, "─╼ ")?;
318                                 } else {
319                                     print_whitespace_to_decoration(stdout)?;
320                                     if final_decoration {
321                                         write!(stdout, "    ")?;
322                                     } else {
323                                         write!(stdout, "│   ")?;
324                                     }
325                                 }
326                                 writeln!(stdout, "{line}")?;
327                             }
328                             stdout.reset()?;
329                         }
330                         Ok(())
331                     };
332 
333                 print_decorations(&mut stdout, pre_decorations)?;
334 
335                 // Some instructions may disassemble to multiple lines, such as
336                 // `br_table` with Pulley. Handle separate lines per-instruction
337                 // here.
338                 for (i, line) in disas.lines().enumerate() {
339                     let print_address = self.addresses
340                         || (self.address_jumps && (write_offsets || (prev_jump && !is_jump)));
341                     if i == 0 && print_address {
342                         stdout.set_color(&color_address)?;
343                         write!(stdout, "{address:>width$x}: ")?;
344                         stdout.reset()?;
345                     } else {
346                         write!(stdout, "{:width$}  ", "")?;
347                     }
348 
349                     // If we're printing inline bytes then print up to
350                     // `inline_bytes` of instruction data, and any remaining
351                     // data will go on the next line, if any, or after the
352                     // instruction below.
353                     if self.bytes {
354                         stdout.set_color(&color_bytes)?;
355                         for byte in bytes.by_ref().take(inline_bytes) {
356                             match byte {
357                                 Some(byte) => write!(stdout, "{byte:02x} ")?,
358                                 None => write!(stdout, "   ")?,
359                             }
360                         }
361                         write!(stdout, "  ")?;
362                         stdout.reset()?;
363                     }
364 
365                     writeln!(stdout, "{line}")?;
366                 }
367 
368                 // Flip write_offsets to true once we've seen a `ret`, as
369                 // instructions that follow the return are often related to trap
370                 // tables.
371                 write_offsets |= is_return;
372                 prev_jump = is_jump;
373 
374                 // After the instruction is printed then finish printing the
375                 // instruction bytes if any are present. Still limit to
376                 // `inline_bytes` per line.
377                 if self.bytes {
378                     let mut inline = 0;
379                     stdout.set_color(&color_bytes)?;
380                     for byte in bytes {
381                         let Some(byte) = byte else { break };
382                         if inline == 0 {
383                             write!(stdout, "{:width$}  ", "")?;
384                         } else {
385                             write!(stdout, " ")?;
386                         }
387                         write!(stdout, "{byte:02x}")?;
388                         inline += 1;
389                         if inline == inline_bytes {
390                             writeln!(stdout)?;
391                             inline = 0;
392                         }
393                     }
394                     stdout.reset()?;
395                     if inline > 0 {
396                         writeln!(stdout)?;
397                     }
398                 }
399 
400                 print_decorations(&mut stdout, post_decorations)?;
401             }
402         }
403         Ok(())
404     }
405 
406     /// Disassembles `func` contained within `elf` returning a list of
407     /// instructions that represent the function.
disas(&self, elf: &ElfFile64<'_, Endianness>, func: &[u8], addr: u64) -> Result<Vec<Inst>>408     fn disas(&self, elf: &ElfFile64<'_, Endianness>, func: &[u8], addr: u64) -> Result<Vec<Inst>> {
409         let cranelift_target = match elf.architecture() {
410             Architecture::X86_64 => "x86_64",
411             Architecture::Aarch64 => "aarch64",
412             Architecture::S390x => "s390x",
413             Architecture::Riscv64 => {
414                 let e_flags = match elf.flags() {
415                     FileFlags::Elf { e_flags, .. } => e_flags,
416                     _ => bail!("not an ELF file"),
417                 };
418                 if e_flags & (obj::EF_WASMTIME_PULLEY32 | obj::EF_WASMTIME_PULLEY64) != 0 {
419                     return self.disas_pulley(func, addr);
420                 } else {
421                     "riscv64"
422                 }
423             }
424             other => bail!("unknown architecture {other:?}"),
425         };
426         let builder =
427             lookup_by_name(cranelift_target).context("failed to load cranelift ISA builder")?;
428         let flags = cranelift_codegen::settings::builder();
429         let isa = builder.finish(Flags::new(flags))?;
430         let isa = &*isa;
431         let capstone = isa
432             .to_capstone()
433             .context("failed to create a capstone disassembler")?;
434 
435         let insts = capstone
436             .disasm_all(func, addr)?
437             .into_iter()
438             .map(|inst| {
439                 let detail = capstone.insn_detail(&inst).ok();
440                 let detail = detail.as_ref();
441                 let is_jump = detail
442                     .map(|d| {
443                         d.groups()
444                             .iter()
445                             .find(|g| g.0 as u32 == CS_GRP_JUMP)
446                             .is_some()
447                     })
448                     .unwrap_or(false);
449 
450                 let is_return = detail
451                     .map(|d| {
452                         d.groups()
453                             .iter()
454                             .find(|g| g.0 as u32 == CS_GRP_RET)
455                             .is_some()
456                     })
457                     .unwrap_or(false);
458 
459                 let disassembly = match (inst.mnemonic(), inst.op_str()) {
460                     (Some(i), Some(o)) => {
461                         if o.is_empty() {
462                             format!("{i}")
463                         } else {
464                             format!("{i:7} {o}")
465                         }
466                     }
467                     (Some(i), None) => format!("{i}"),
468                     _ => unreachable!(),
469                 };
470 
471                 let address = inst.address();
472                 Inst {
473                     address,
474                     is_jump,
475                     is_return,
476                     bytes: inst.bytes().to_vec(),
477                     disassembly,
478                 }
479             })
480             .collect::<Vec<_>>();
481         Ok(insts)
482     }
483 
484     /// Same as `dias` above, but just for Pulley.
disas_pulley(&self, func: &[u8], addr: u64) -> Result<Vec<Inst>>485     fn disas_pulley(&self, func: &[u8], addr: u64) -> Result<Vec<Inst>> {
486         let mut result = vec![];
487 
488         let mut disas = Disassembler::new(func);
489         disas.offsets(false);
490         disas.hexdump(false);
491         disas.start_offset(usize::try_from(addr).unwrap());
492         let mut decoder = Decoder::new();
493         let mut last_disas_pos = 0;
494         loop {
495             let start_addr = disas.bytecode().position();
496 
497             match decoder.decode_one(&mut disas) {
498                 // If we got EOF at the initial position, then we're done disassembling.
499                 Err(DecodingError::UnexpectedEof { position }) if position == start_addr => break,
500 
501                 // Otherwise, propagate the error.
502                 Err(e) => {
503                     return Err(e).context("failed to disassembly pulley bytecode");
504                 }
505 
506                 Ok(()) => {
507                     let bytes_range = start_addr..disas.bytecode().position();
508                     let disassembly = disas.disas()[last_disas_pos..].trim();
509                     last_disas_pos = disas.disas().len();
510                     let address = u64::try_from(start_addr).unwrap() + addr;
511                     let is_jump = disassembly.contains("jump") || disassembly.contains("br_");
512                     let is_return = disassembly == "ret";
513                     result.push(Inst {
514                         bytes: func[bytes_range].to_vec(),
515                         address,
516                         is_jump,
517                         is_return,
518                         disassembly: disassembly.to_string(),
519                     });
520                 }
521             }
522         }
523 
524         Ok(result)
525     }
526 
527     /// Helper to read the input bytes of the `*.cwasm` handling stdin
528     /// automatically.
read_cwasm(&self) -> Result<Vec<u8>>529     fn read_cwasm(&self) -> Result<Vec<u8>> {
530         if let Some(path) = &self.cwasm {
531             if path != Path::new("-") {
532                 return std::fs::read(path).with_context(|| format!("failed to read {path:?}"));
533             }
534         }
535 
536         let mut stdin = Vec::new();
537         std::io::stdin()
538             .read_to_end(&mut stdin)
539             .context("failed to read stdin")?;
540         Ok(stdin)
541     }
542 }
543 
544 /// Helper structure to package up metadata about an instruction.
545 struct Inst {
546     address: u64,
547     is_jump: bool,
548     is_return: bool,
549     disassembly: String,
550     bytes: Vec<u8>,
551 }
552 
553 #[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq)]
554 enum Func {
555     All,
556     Wasm,
557     Trampoline,
558     Builtin,
559     Libcall,
560 }
561 
562 struct Decorator<'a> {
563     objdump: &'a ObjdumpCommand,
564     addrmap: Option<Peekable<Box<dyn Iterator<Item = (u32, FilePos)> + 'a>>>,
565     traps: Option<Peekable<Box<dyn Iterator<Item = (u32, Trap)> + 'a>>>,
566     stack_maps: Option<Peekable<Box<dyn Iterator<Item = (u32, StackMap<'a>)> + 'a>>>,
567     exception_tables:
568         Option<Peekable<Box<dyn Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a>>>,
569     frame_tables: Option<
570         Peekable<
571             Box<
572                 dyn Iterator<
573                         Item = (
574                             u32,
575                             FrameInstPos,
576                             Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>,
577                         ),
578                     > + 'a,
579             >,
580         >,
581     >,
582 
583     // Breakpoint table, sorted by native offset instead so we can
584     // display inline with disassembly (the table in the image is
585     // sorted by Wasm PC).
586     breakpoints: Peekable<Box<dyn Iterator<Item = (ModulePC, usize, SmallVec<[u8; 8]>)>>>,
587 
588     frame_table_descriptors: Option<FrameTable<'a>>,
589 }
590 
591 impl Decorator<'_> {
decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>)592     fn decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>) {
593         self.addrmap(address, post_list);
594         self.traps(address, post_list);
595         self.stack_maps(address, post_list);
596         self.exception_table(address, pre_list);
597         self.frame_table(address, pre_list, post_list);
598         self.breakpoints(address, pre_list);
599     }
600 
addrmap(&mut self, address: u64, list: &mut Vec<String>)601     fn addrmap(&mut self, address: u64, list: &mut Vec<String>) {
602         if !self.objdump.addrmap() {
603             return;
604         }
605         let Some(addrmap) = &mut self.addrmap else {
606             return;
607         };
608         while let Some((addr, pos)) = addrmap.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
609             if u64::from(addr) != address {
610                 continue;
611             }
612             if let Some(offset) = pos.file_offset() {
613                 list.push(format!("addrmap: {offset:#x}"));
614             }
615         }
616     }
617 
traps(&mut self, address: u64, list: &mut Vec<String>)618     fn traps(&mut self, address: u64, list: &mut Vec<String>) {
619         if !self.objdump.traps() {
620             return;
621         }
622         let Some(traps) = &mut self.traps else {
623             return;
624         };
625         while let Some((addr, trap)) = traps.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
626             if u64::from(addr) != address {
627                 continue;
628             }
629             list.push(format!("trap: {trap:?}"));
630         }
631     }
632 
stack_maps(&mut self, address: u64, list: &mut Vec<String>)633     fn stack_maps(&mut self, address: u64, list: &mut Vec<String>) {
634         if !self.objdump.stack_maps() {
635             return;
636         }
637         let Some(stack_maps) = &mut self.stack_maps else {
638             return;
639         };
640         while let Some((addr, stack_map)) =
641             stack_maps.next_if(|(addr, _pos)| u64::from(*addr) <= address)
642         {
643             if u64::from(addr) != address {
644                 continue;
645             }
646             list.push(format!(
647                 "stack_map: frame_size={}, frame_offsets={:?}",
648                 stack_map.frame_size(),
649                 stack_map.offsets().collect::<Vec<_>>()
650             ));
651         }
652     }
653 
exception_table(&mut self, address: u64, list: &mut Vec<String>)654     fn exception_table(&mut self, address: u64, list: &mut Vec<String>) {
655         if !self.objdump.exception_tables() {
656             return;
657         }
658         let Some(exception_tables) = &mut self.exception_tables else {
659             return;
660         };
661         while let Some((addr, frame_offset, handlers)) =
662             exception_tables.next_if(|(addr, _, _)| u64::from(*addr) <= address)
663         {
664             if u64::from(addr) != address {
665                 continue;
666             }
667             if let Some(frame_offset) = frame_offset {
668                 list.push(format!(
669                     "exception frame offset: SP = FP - 0x{frame_offset:x}",
670                 ));
671             }
672             for handler in &handlers {
673                 let tag = match handler.tag {
674                     Some(tag) => format!("tag={tag}"),
675                     None => "default handler".to_string(),
676                 };
677                 let context = match handler.context_sp_offset {
678                     Some(offset) => format!("context at [SP+0x{offset:x}]"),
679                     None => "no dynamic context".to_string(),
680                 };
681                 list.push(format!(
682                     "exception handler: {tag}, {context}, handler=0x{:x}",
683                     handler.handler_offset
684                 ));
685             }
686         }
687     }
688 
frame_table( &mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>, )689     fn frame_table(
690         &mut self,
691         address: u64,
692         pre_list: &mut Vec<String>,
693         post_list: &mut Vec<String>,
694     ) {
695         if !self.objdump.frame_tables() {
696             return;
697         }
698         let (Some(frame_table_iter), Some(frame_tables)) =
699             (&mut self.frame_tables, &self.frame_table_descriptors)
700         else {
701             return;
702         };
703 
704         while let Some((addr, pos, frames)) =
705             frame_table_iter.next_if(|(addr, _, _)| u64::from(*addr) <= address)
706         {
707             if u64::from(addr) != address {
708                 continue;
709             }
710             let list = match pos {
711                 // N.B.: the "post" position means that we are
712                 // attached to the end of the previous instruction
713                 // (its "post"); which means that from this
714                 // instruction's PoV, we print before the instruction
715                 // (the "pre list"). And vice versa for the "pre"
716                 // position. Hence the reversal here.
717                 FrameInstPos::Post => &mut *pre_list,
718                 FrameInstPos::Pre => &mut *post_list,
719             };
720             let pos = match pos {
721                 FrameInstPos::Post => "after previous inst",
722                 FrameInstPos::Pre => "before next inst",
723             };
724             for (wasm_pc, frame_descriptor, stack_shape) in frames {
725                 let (frame_descriptor_data, offset) =
726                     frame_tables.frame_descriptor(frame_descriptor).unwrap();
727                 let frame_descriptor = FrameStateSlot::parse(frame_descriptor_data).unwrap();
728 
729                 let local_shape = Self::describe_local_shape(&frame_descriptor);
730                 let stack_shape = Self::describe_stack_shape(&frame_descriptor, stack_shape);
731                 let func_key = frame_descriptor.func_key();
732                 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}"));
733             }
734         }
735     }
736 
breakpoints(&mut self, address: u64, list: &mut Vec<String>)737     fn breakpoints(&mut self, address: u64, list: &mut Vec<String>) {
738         while let Some((wasm_pc, addr, patch)) = self.breakpoints.next_if(|(_, addr, patch)| {
739             u64::try_from(*addr).unwrap() + u64::try_from(patch.len()).unwrap() <= address
740         }) {
741             if u64::try_from(addr).unwrap() + u64::try_from(patch.len()).unwrap() != address {
742                 continue;
743             }
744             list.push(format!(
745                 "breakpoint patch: wasm PC {wasm_pc}, patch bytes {patch:?}"
746             ));
747         }
748     }
749 
describe_local_shape(desc: &FrameStateSlot<'_>) -> String750     fn describe_local_shape(desc: &FrameStateSlot<'_>) -> String {
751         let mut parts = vec![];
752         for (offset, ty) in desc.locals() {
753             parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
754         }
755         parts.join(", ")
756     }
757 
describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String758     fn describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String {
759         let mut parts = vec![];
760         for (offset, ty) in desc.stack(shape) {
761             parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
762         }
763         parts.reverse();
764         parts.join(", ")
765     }
766 }
767