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 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 82 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 { 91 fn addrmap(&self) -> bool { 92 optional_flag_with_default(self.addrmap, false) 93 } 94 95 fn traps(&self) -> bool { 96 optional_flag_with_default(self.traps, true) 97 } 98 99 fn stack_maps(&self) -> bool { 100 optional_flag_with_default(self.stack_maps, true) 101 } 102 103 fn exception_tables(&self) -> bool { 104 optional_flag_with_default(self.exception_tables, true) 105 } 106 107 fn frame_tables(&self) -> bool { 108 optional_flag_with_default(self.frame_tables, true) 109 } 110 111 /// Executes the command. 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. 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. 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. 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<(u32, 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 = (u32, usize, SmallVec<[u8; 8]>)>>>, 587 588 frame_table_descriptors: Option<FrameTable<'a>>, 589 } 590 591 impl Decorator<'_> { 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 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 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 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 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 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 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 750 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 758 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