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