1use crate::disas::{self, Inst};
4use clap::Parser;
5use object::read::elf::ElfFile64;
6use object::{Endianness, Object, ObjectSection, ObjectSymbol};
7use smallvec::SmallVec;
8use std::io::{IsTerminal, Read};
9use std::iter::{self, Peekable};
10use std::path::{Path, PathBuf};
11use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
12use wasmtime::{Engine, Result, bail, error::Context as _};
13use wasmtime_environ::{
14 CompiledTrap, FilePos, FrameInstPos, FrameStackShape, FrameStateSlot, FrameTable,
15 FrameTableDescriptorIndex, ModulePC, StackMap, obj,
16};
17use wasmtime_unwinder::{ExceptionHandler, ExceptionTable};
18
19#[derive(Parser)]
22pub struct ObjdumpCommand {
23 cwasm: Option<PathBuf>,
27
28 #[arg(long)]
30 addresses: bool,
31
32 #[arg(long)]
35 address_jumps: bool,
36
37 #[arg(long, default_value = "wasm", value_name = "KIND")]
39 funcs: Vec<Func>,
40
41 #[arg(long, value_name = "STR")]
43 filter: Option<String>,
44
45 #[arg(long)]
47 bytes: bool,
48
49 #[arg(long, default_value = "auto")]
51 color: ColorChoice,
52
53 #[arg(long, require_equals = true, value_name = "true|false")]
55 addrmap: Option<Option<bool>>,
56
57 #[arg(long, default_value = "10", value_name = "N")]
59 address_width: usize,
60
61 #[arg(long, require_equals = true, value_name = "true|false")]
63 traps: Option<Option<bool>>,
64
65 #[arg(long, require_equals = true, value_name = "true|false")]
67 stack_maps: Option<Option<bool>>,
68
69 #[arg(long, require_equals = true, value_name = "true|false")]
71 exception_tables: Option<Option<bool>>,
72
73 #[arg(long, require_equals = true, value_name = "true|false")]
75 frame_tables: Option<Option<bool>>,
76}
77
78fn optional_flag_with_default(flag: Option<Option<bool>>, default: bool) -> bool {
79 match flag {
80 None => default,
81 Some(None) => true,
82 Some(Some(val)) => val,
83 }
84}
85
86impl ObjdumpCommand {
87 fn addrmap(&self) -> bool {
88 optional_flag_with_default(self.addrmap, false)
89 }
90
91 fn traps(&self) -> bool {
92 optional_flag_with_default(self.traps, true)
93 }
94
95 fn stack_maps(&self) -> bool {
96 optional_flag_with_default(self.stack_maps, true)
97 }
98
99 fn exception_tables(&self) -> bool {
100 optional_flag_with_default(self.exception_tables, true)
101 }
102
103 fn frame_tables(&self) -> bool {
104 optional_flag_with_default(self.frame_tables, true)
105 }
106
107 pub fn execute(self) -> Result<()> {
109 let mut choice = self.color;
111 if choice == ColorChoice::Auto && !std::io::stdout().is_terminal() {
112 choice = ColorChoice::Never;
113 }
114 let mut stdout = StandardStream::stdout(choice);
115
116 let bytes = self.read_cwasm()?;
117 self.disassemble(&bytes, &mut stdout)
118 }
119
120 #[doc(hidden)]
128 pub fn disassemble(&self, bytes: &[u8], stdout: &mut dyn WriteColor) -> Result<()> {
129 let mut color_address = ColorSpec::new();
131 color_address.set_bold(true).set_fg(Some(Color::Yellow));
132 let mut color_bytes = ColorSpec::new();
133 color_bytes.set_fg(Some(Color::Magenta));
134
135 if Engine::detect_precompiled(bytes).is_none() {
137 bail!("not a `*.cwasm` file from wasmtime: {:?}", self.cwasm);
138 }
139
140 let elf = ElfFile64::<Endianness>::parse(bytes)?;
142 let text = elf
143 .section_by_name(".text")
144 .context("missing .text section")?;
145 let text = text.data()?;
146
147 let frame_table_descriptors = elf
148 .section_by_name(obj::ELF_WASMTIME_FRAMES)
149 .and_then(|section| section.data().ok())
150 .and_then(|bytes| FrameTable::parse(bytes, text).ok());
151
152 let mut breakpoints = frame_table_descriptors
153 .iter()
154 .flat_map(|ftd| ftd.breakpoint_patches())
155 .map(|(wasm_pc, patch)| (wasm_pc, patch.offset, SmallVec::from(patch.enable)))
156 .collect::<Vec<_>>();
157 breakpoints.sort_by_key(|(_wasm_pc, native_offset, _patch)| *native_offset);
158 let breakpoints: Box<dyn Iterator<Item = _>> = Box::new(breakpoints.into_iter());
159 let breakpoints = breakpoints.peekable();
160
161 let mut decorator = Decorator {
164 addrmap: elf
165 .section_by_name(obj::ELF_WASMTIME_ADDRMAP)
166 .and_then(|section| section.data().ok())
167 .and_then(|bytes| wasmtime_environ::iterate_address_map(bytes))
168 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
169 traps: elf
170 .section_by_name(obj::ELF_WASMTIME_TRAPS)
171 .and_then(|section| section.data().ok())
172 .and_then(|bytes| wasmtime_environ::iterate_traps(bytes))
173 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
174 stack_maps: elf
175 .section_by_name(obj::ELF_WASMTIME_STACK_MAP)
176 .and_then(|section| section.data().ok())
177 .and_then(|bytes| StackMap::iter(bytes))
178 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
179 exception_tables: elf
180 .section_by_name(obj::ELF_WASMTIME_EXCEPTIONS)
181 .and_then(|section| section.data().ok())
182 .and_then(|bytes| ExceptionTable::parse(bytes).ok())
183 .map(|table| table.into_iter())
184 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
185 frame_tables: elf
186 .section_by_name(obj::ELF_WASMTIME_FRAMES)
187 .and_then(|section| section.data().ok())
188 .and_then(|bytes| FrameTable::parse(bytes, text).ok())
189 .map(|table| table.into_program_points())
190 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
191
192 breakpoints,
193
194 frame_table_descriptors,
195
196 objdump: &self,
197 };
198
199 let mut first = true;
202 for sym in elf.symbols() {
203 let name = match sym.name() {
204 Ok(name) => name,
205 Err(_) => continue,
206 };
207 let bytes = &text[sym.address() as usize..][..sym.size() as usize];
208
209 let kind = if name.starts_with("wasmtime_builtin")
210 || name.starts_with("wasmtime_patchable_builtin")
211 {
212 Func::Builtin
213 } else if name.contains("]::function[") {
214 Func::Wasm
215 } else if name.contains("trampoline")
216 || name.ends_with("_array_call")
217 || name.ends_with("_wasm_call")
218 || name.contains("unsafe-intrinsics-")
219 || name.contains("module_start")
220 {
221 Func::Trampoline
222 } else if name.contains("libcall") || name.starts_with("component") {
223 Func::Libcall
224 } else {
225 panic!("unknown symbol: {name}")
226 };
227
228 if self.funcs.is_empty() {
231 if kind != Func::Wasm {
232 continue;
233 }
234 } else {
235 if !(self.funcs.contains(&Func::All) || self.funcs.contains(&kind)) {
236 continue;
237 }
238 }
239 if let Some(filter) = &self.filter {
240 if !name.contains(filter) {
241 continue;
242 }
243 }
244
245 if first {
247 first = false;
248 } else {
249 writeln!(stdout)?;
250 }
251
252 if self.addresses {
255 stdout.set_color(color_address.clone().set_bold(true))?;
256 write!(stdout, "{:08x} ", sym.address())?;
257 stdout.reset()?;
258 }
259 stdout.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)))?;
260 write!(stdout, "{name}")?;
261 stdout.reset()?;
262 writeln!(stdout, ":")?;
263
264 let mut prev_jump = false;
267 let mut write_offsets = false;
268
269 for inst in disas::disas(&elf, bytes, sym.address())? {
270 let Inst {
271 address,
272 is_jump,
273 is_return,
274 disassembly: disas,
275 bytes,
276 } = inst;
277
278 let mut bytes = bytes.iter().map(Some).chain(iter::repeat(None));
282 let inline_bytes = 9;
283 let width = self.address_width;
284
285 let mut pre_decorations = Vec::new();
298 let mut post_decorations = Vec::new();
299 decorator.decorate(address, &mut pre_decorations, &mut post_decorations);
300
301 let print_whitespace_to_decoration = |stdout: &mut dyn WriteColor| -> Result<()> {
302 write!(stdout, "{:width$} ", "")?;
303 if self.bytes {
304 for _ in 0..inline_bytes + 1 {
305 write!(stdout, " ")?;
306 }
307 }
308 Ok(())
309 };
310
311 let print_decorations =
312 |stdout: &mut dyn WriteColor, decorations: Vec<String>| -> Result<()> {
313 for (i, decoration) in decorations.iter().enumerate() {
314 print_whitespace_to_decoration(stdout)?;
315 let mut color = ColorSpec::new();
316 color.set_fg(Some(Color::Cyan));
317 stdout.set_color(&color)?;
318 let final_decoration = i == decorations.len() - 1;
319 if !final_decoration {
320 write!(stdout, "├")?;
321 } else {
322 write!(stdout, "╰")?;
323 }
324 for (i, line) in decoration.lines().enumerate() {
325 if i == 0 {
326 write!(stdout, "─╼ ")?;
327 } else {
328 print_whitespace_to_decoration(stdout)?;
329 if final_decoration {
330 write!(stdout, " ")?;
331 } else {
332 write!(stdout, "│ ")?;
333 }
334 }
335 writeln!(stdout, "{line}")?;
336 }
337 stdout.reset()?;
338 }
339 Ok(())
340 };
341
342 print_decorations(&mut *stdout, pre_decorations)?;
343
344 for (i, line) in disas.lines().enumerate() {
348 let print_address = self.addresses
349 || (self.address_jumps && (write_offsets || (prev_jump && !is_jump)));
350 if i == 0 && print_address {
351 stdout.set_color(&color_address)?;
352 write!(stdout, "{address:>width$x}: ")?;
353 stdout.reset()?;
354 } else {
355 write!(stdout, "{:width$} ", "")?;
356 }
357
358 if self.bytes {
363 stdout.set_color(&color_bytes)?;
364 for byte in bytes.by_ref().take(inline_bytes) {
365 match byte {
366 Some(byte) => write!(stdout, "{byte:02x} ")?,
367 None => write!(stdout, " ")?,
368 }
369 }
370 write!(stdout, " ")?;
371 stdout.reset()?;
372 }
373
374 writeln!(stdout, "{line}")?;
375 }
376
377 write_offsets |= is_return;
381 prev_jump = is_jump;
382
383 if self.bytes {
387 let mut inline = 0;
388 stdout.set_color(&color_bytes)?;
389 for byte in bytes {
390 let Some(byte) = byte else { break };
391 if inline == 0 {
392 write!(stdout, "{:width$} ", "")?;
393 } else {
394 write!(stdout, " ")?;
395 }
396 write!(stdout, "{byte:02x}")?;
397 inline += 1;
398 if inline == inline_bytes {
399 writeln!(stdout)?;
400 inline = 0;
401 }
402 }
403 stdout.reset()?;
404 if inline > 0 {
405 writeln!(stdout)?;
406 }
407 }
408
409 print_decorations(&mut *stdout, post_decorations)?;
410 }
411 }
412 Ok(())
413 }
414
415 fn read_cwasm(&self) -> Result<Vec<u8>> {
418 if let Some(path) = &self.cwasm {
419 if path != Path::new("-") {
420 return std::fs::read(path).with_context(|| format!("failed to read {path:?}"));
421 }
422 }
423
424 let mut stdin = Vec::new();
425 std::io::stdin()
426 .read_to_end(&mut stdin)
427 .context("failed to read stdin")?;
428 Ok(stdin)
429 }
430}
431
432#[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq)]
433enum Func {
434 All,
435 Wasm,
436 Trampoline,
437 Builtin,
438 Libcall,
439}
440
441struct Decorator<'a> {
442 objdump: &'a ObjdumpCommand,
443 addrmap: Option<Peekable<Box<dyn Iterator<Item = (u32, FilePos)> + 'a>>>,
444 traps: Option<Peekable<Box<dyn Iterator<Item = (u32, CompiledTrap)> + 'a>>>,
445 stack_maps: Option<Peekable<Box<dyn Iterator<Item = (u32, StackMap<'a>)> + 'a>>>,
446 exception_tables:
447 Option<Peekable<Box<dyn Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a>>>,
448 frame_tables: Option<
449 Peekable<
450 Box<
451 dyn Iterator<
452 Item = (
453 u32,
454 FrameInstPos,
455 Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>,
456 ),
457 > + 'a,
458 >,
459 >,
460 >,
461
462 breakpoints: Peekable<Box<dyn Iterator<Item = (ModulePC, usize, SmallVec<[u8; 8]>)>>>,
466
467 frame_table_descriptors: Option<FrameTable<'a>>,
468}
469
470impl Decorator<'_> {
471 fn decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>) {
472 self.addrmap(address, post_list);
473 self.traps(address, post_list);
474 self.stack_maps(address, post_list);
475 self.exception_table(address, pre_list);
476 self.frame_table(address, pre_list, post_list);
477 self.breakpoints(address, pre_list);
478 }
479
480 fn addrmap(&mut self, address: u64, list: &mut Vec<String>) {
481 if !self.objdump.addrmap() {
482 return;
483 }
484 let Some(addrmap) = &mut self.addrmap else {
485 return;
486 };
487 while let Some((addr, pos)) = addrmap.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
488 if u64::from(addr) != address {
489 continue;
490 }
491 if let Some(offset) = pos.file_offset() {
492 list.push(format!("addrmap: {offset:#x}"));
493 }
494 }
495 }
496
497 fn traps(&mut self, address: u64, list: &mut Vec<String>) {
498 if !self.objdump.traps() {
499 return;
500 }
501 let Some(traps) = &mut self.traps else {
502 return;
503 };
504 while let Some((addr, trap)) = traps.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
505 if u64::from(addr) != address {
506 continue;
507 }
508 list.push(format!("trap: {trap:?}"));
509 }
510 }
511
512 fn stack_maps(&mut self, address: u64, list: &mut Vec<String>) {
513 if !self.objdump.stack_maps() {
514 return;
515 }
516 let Some(stack_maps) = &mut self.stack_maps else {
517 return;
518 };
519 while let Some((addr, stack_map)) =
520 stack_maps.next_if(|(addr, _pos)| u64::from(*addr) <= address)
521 {
522 if u64::from(addr) != address {
523 continue;
524 }
525 list.push(format!(
526 "stack_map: frame_size={}, frame_offsets={:?}",
527 stack_map.frame_size(),
528 stack_map.offsets().collect::<Vec<_>>()
529 ));
530 }
531 }
532
533 fn exception_table(&mut self, address: u64, list: &mut Vec<String>) {
534 if !self.objdump.exception_tables() {
535 return;
536 }
537 let Some(exception_tables) = &mut self.exception_tables else {
538 return;
539 };
540 while let Some((addr, frame_offset, handlers)) =
541 exception_tables.next_if(|(addr, _, _)| u64::from(*addr) <= address)
542 {
543 if u64::from(addr) != address {
544 continue;
545 }
546 if let Some(frame_offset) = frame_offset {
547 list.push(format!(
548 "exception frame offset: SP = FP - 0x{frame_offset:x}",
549 ));
550 }
551 for handler in &handlers {
552 let tag = match handler.tag {
553 Some(tag) => format!("tag={tag}"),
554 None => "default handler".to_string(),
555 };
556 let context = match handler.context_sp_offset {
557 Some(offset) => format!("context at [SP+0x{offset:x}]"),
558 None => "no dynamic context".to_string(),
559 };
560 list.push(format!(
561 "exception handler: {tag}, {context}, handler=0x{:x}",
562 handler.handler_offset
563 ));
564 }
565 }
566 }
567
568 fn frame_table(
569 &mut self,
570 address: u64,
571 pre_list: &mut Vec<String>,
572 post_list: &mut Vec<String>,
573 ) {
574 if !self.objdump.frame_tables() {
575 return;
576 }
577 let (Some(frame_table_iter), Some(frame_tables)) =
578 (&mut self.frame_tables, &self.frame_table_descriptors)
579 else {
580 return;
581 };
582
583 while let Some((addr, pos, frames)) =
584 frame_table_iter.next_if(|(addr, _, _)| u64::from(*addr) <= address)
585 {
586 if u64::from(addr) != address {
587 continue;
588 }
589 let list = match pos {
590 FrameInstPos::Post => &mut *pre_list,
597 FrameInstPos::Pre => &mut *post_list,
598 };
599 let pos = match pos {
600 FrameInstPos::Post => "after previous inst",
601 FrameInstPos::Pre => "before next inst",
602 };
603 for (wasm_pc, frame_descriptor, stack_shape) in frames {
604 let (frame_descriptor_data, offset) =
605 frame_tables.frame_descriptor(frame_descriptor).unwrap();
606 let frame_descriptor = FrameStateSlot::parse(frame_descriptor_data).unwrap();
607
608 let local_shape = Self::describe_local_shape(&frame_descriptor);
609 let stack_shape = Self::describe_stack_shape(&frame_descriptor, stack_shape);
610 let func_key = frame_descriptor.func_key();
611 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}"));
612 }
613 }
614 }
615
616 fn breakpoints(&mut self, address: u64, list: &mut Vec<String>) {
617 while let Some((wasm_pc, addr, patch)) = self.breakpoints.next_if(|(_, addr, patch)| {
618 u64::try_from(*addr).unwrap() + u64::try_from(patch.len()).unwrap() <= address
619 }) {
620 if u64::try_from(addr).unwrap() + u64::try_from(patch.len()).unwrap() != address {
621 continue;
622 }
623 list.push(format!(
624 "breakpoint patch: wasm PC {wasm_pc}, patch bytes {patch:?}"
625 ));
626 }
627 }
628
629 fn describe_local_shape(desc: &FrameStateSlot<'_>) -> String {
630 let mut parts = vec![];
631 for (offset, ty) in desc.locals() {
632 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
633 }
634 parts.join(", ")
635 }
636
637 fn describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String {
638 let mut parts = vec![];
639 for (offset, ty) in desc.stack(shape) {
640 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
641 }
642 parts.reverse();
643 parts.join(", ")
644 }
645}