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