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