Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/commands/objdump.rs
3052 views
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
768