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, Write};
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 FilePos, FrameInstPos, FrameStackShape, FrameStateSlot, FrameTable, FrameTableDescriptorIndex,
15 ModulePC, StackMap, Trap, 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;
112 if choice == ColorChoice::Auto && !std::io::stdout().is_terminal() {
113 choice = ColorChoice::Never;
114 }
115 let mut stdout = StandardStream::stdout(choice);
116
117 let mut color_address = ColorSpec::new();
118 color_address.set_bold(true).set_fg(Some(Color::Yellow));
119 let mut color_bytes = ColorSpec::new();
120 color_bytes.set_fg(Some(Color::Magenta));
121
122 let bytes = self.read_cwasm()?;
123
124 if Engine::detect_precompiled(&bytes).is_none() {
126 bail!("not a `*.cwasm` file from wasmtime: {:?}", self.cwasm);
127 }
128
129 let elf = ElfFile64::<Endianness>::parse(&bytes)?;
131 let text = elf
132 .section_by_name(".text")
133 .context("missing .text section")?;
134 let text = text.data()?;
135
136 let frame_table_descriptors = elf
137 .section_by_name(obj::ELF_WASMTIME_FRAMES)
138 .and_then(|section| section.data().ok())
139 .and_then(|bytes| FrameTable::parse(bytes, text).ok());
140
141 let mut breakpoints = frame_table_descriptors
142 .iter()
143 .flat_map(|ftd| ftd.breakpoint_patches())
144 .map(|(wasm_pc, patch)| (wasm_pc, patch.offset, SmallVec::from(patch.enable)))
145 .collect::<Vec<_>>();
146 breakpoints.sort_by_key(|(_wasm_pc, native_offset, _patch)| *native_offset);
147 let breakpoints: Box<dyn Iterator<Item = _>> = Box::new(breakpoints.into_iter());
148 let breakpoints = breakpoints.peekable();
149
150 let mut decorator = Decorator {
153 addrmap: elf
154 .section_by_name(obj::ELF_WASMTIME_ADDRMAP)
155 .and_then(|section| section.data().ok())
156 .and_then(|bytes| wasmtime_environ::iterate_address_map(bytes))
157 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
158 traps: elf
159 .section_by_name(obj::ELF_WASMTIME_TRAPS)
160 .and_then(|section| section.data().ok())
161 .and_then(|bytes| wasmtime_environ::iterate_traps(bytes))
162 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
163 stack_maps: elf
164 .section_by_name(obj::ELF_WASMTIME_STACK_MAP)
165 .and_then(|section| section.data().ok())
166 .and_then(|bytes| StackMap::iter(bytes))
167 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
168 exception_tables: elf
169 .section_by_name(obj::ELF_WASMTIME_EXCEPTIONS)
170 .and_then(|section| section.data().ok())
171 .and_then(|bytes| ExceptionTable::parse(bytes).ok())
172 .map(|table| table.into_iter())
173 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
174 frame_tables: elf
175 .section_by_name(obj::ELF_WASMTIME_FRAMES)
176 .and_then(|section| section.data().ok())
177 .and_then(|bytes| FrameTable::parse(bytes, text).ok())
178 .map(|table| table.into_program_points())
179 .map(|i| (Box::new(i) as Box<dyn Iterator<Item = _>>).peekable()),
180
181 breakpoints,
182
183 frame_table_descriptors,
184
185 objdump: &self,
186 };
187
188 let mut first = true;
191 for sym in elf.symbols() {
192 let name = match sym.name() {
193 Ok(name) => name,
194 Err(_) => continue,
195 };
196 let bytes = &text[sym.address() as usize..][..sym.size() as usize];
197
198 let kind = if name.starts_with("wasmtime_builtin")
199 || name.starts_with("wasmtime_patchable_builtin")
200 {
201 Func::Builtin
202 } else if name.contains("]::function[") {
203 Func::Wasm
204 } else if name.contains("trampoline")
205 || name.ends_with("_array_call")
206 || name.ends_with("_wasm_call")
207 || name.contains("unsafe-intrinsics-")
208 {
209 Func::Trampoline
210 } else if name.contains("libcall") || name.starts_with("component") {
211 Func::Libcall
212 } else {
213 panic!("unknown symbol: {name}")
214 };
215
216 if self.funcs.is_empty() {
219 if kind != Func::Wasm {
220 continue;
221 }
222 } else {
223 if !(self.funcs.contains(&Func::All) || self.funcs.contains(&kind)) {
224 continue;
225 }
226 }
227 if let Some(filter) = &self.filter {
228 if !name.contains(filter) {
229 continue;
230 }
231 }
232
233 if first {
235 first = false;
236 } else {
237 writeln!(stdout)?;
238 }
239
240 if self.addresses {
243 stdout.set_color(color_address.clone().set_bold(true))?;
244 write!(stdout, "{:08x} ", sym.address())?;
245 stdout.reset()?;
246 }
247 stdout.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)))?;
248 write!(stdout, "{name}")?;
249 stdout.reset()?;
250 writeln!(stdout, ":")?;
251
252 let mut prev_jump = false;
255 let mut write_offsets = false;
256
257 for inst in disas::disas(&elf, bytes, sym.address())? {
258 let Inst {
259 address,
260 is_jump,
261 is_return,
262 disassembly: disas,
263 bytes,
264 } = inst;
265
266 let mut bytes = bytes.iter().map(Some).chain(iter::repeat(None));
270 let inline_bytes = 9;
271 let width = self.address_width;
272
273 let mut pre_decorations = Vec::new();
286 let mut post_decorations = Vec::new();
287 decorator.decorate(address, &mut pre_decorations, &mut post_decorations);
288
289 let print_whitespace_to_decoration = |stdout: &mut StandardStream| -> Result<()> {
290 write!(stdout, "{:width$} ", "")?;
291 if self.bytes {
292 for _ in 0..inline_bytes + 1 {
293 write!(stdout, " ")?;
294 }
295 }
296 Ok(())
297 };
298
299 let print_decorations =
300 |stdout: &mut StandardStream, decorations: Vec<String>| -> Result<()> {
301 for (i, decoration) in decorations.iter().enumerate() {
302 print_whitespace_to_decoration(stdout)?;
303 let mut color = ColorSpec::new();
304 color.set_fg(Some(Color::Cyan));
305 stdout.set_color(&color)?;
306 let final_decoration = i == decorations.len() - 1;
307 if !final_decoration {
308 write!(stdout, "├")?;
309 } else {
310 write!(stdout, "╰")?;
311 }
312 for (i, line) in decoration.lines().enumerate() {
313 if i == 0 {
314 write!(stdout, "─╼ ")?;
315 } else {
316 print_whitespace_to_decoration(stdout)?;
317 if final_decoration {
318 write!(stdout, " ")?;
319 } else {
320 write!(stdout, "│ ")?;
321 }
322 }
323 writeln!(stdout, "{line}")?;
324 }
325 stdout.reset()?;
326 }
327 Ok(())
328 };
329
330 print_decorations(&mut stdout, pre_decorations)?;
331
332 for (i, line) in disas.lines().enumerate() {
336 let print_address = self.addresses
337 || (self.address_jumps && (write_offsets || (prev_jump && !is_jump)));
338 if i == 0 && print_address {
339 stdout.set_color(&color_address)?;
340 write!(stdout, "{address:>width$x}: ")?;
341 stdout.reset()?;
342 } else {
343 write!(stdout, "{:width$} ", "")?;
344 }
345
346 if self.bytes {
351 stdout.set_color(&color_bytes)?;
352 for byte in bytes.by_ref().take(inline_bytes) {
353 match byte {
354 Some(byte) => write!(stdout, "{byte:02x} ")?,
355 None => write!(stdout, " ")?,
356 }
357 }
358 write!(stdout, " ")?;
359 stdout.reset()?;
360 }
361
362 writeln!(stdout, "{line}")?;
363 }
364
365 write_offsets |= is_return;
369 prev_jump = is_jump;
370
371 if self.bytes {
375 let mut inline = 0;
376 stdout.set_color(&color_bytes)?;
377 for byte in bytes {
378 let Some(byte) = byte else { break };
379 if inline == 0 {
380 write!(stdout, "{:width$} ", "")?;
381 } else {
382 write!(stdout, " ")?;
383 }
384 write!(stdout, "{byte:02x}")?;
385 inline += 1;
386 if inline == inline_bytes {
387 writeln!(stdout)?;
388 inline = 0;
389 }
390 }
391 stdout.reset()?;
392 if inline > 0 {
393 writeln!(stdout)?;
394 }
395 }
396
397 print_decorations(&mut stdout, post_decorations)?;
398 }
399 }
400 Ok(())
401 }
402
403 fn read_cwasm(&self) -> Result<Vec<u8>> {
406 if let Some(path) = &self.cwasm {
407 if path != Path::new("-") {
408 return std::fs::read(path).with_context(|| format!("failed to read {path:?}"));
409 }
410 }
411
412 let mut stdin = Vec::new();
413 std::io::stdin()
414 .read_to_end(&mut stdin)
415 .context("failed to read stdin")?;
416 Ok(stdin)
417 }
418}
419
420#[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq)]
421enum Func {
422 All,
423 Wasm,
424 Trampoline,
425 Builtin,
426 Libcall,
427}
428
429struct Decorator<'a> {
430 objdump: &'a ObjdumpCommand,
431 addrmap: Option<Peekable<Box<dyn Iterator<Item = (u32, FilePos)> + 'a>>>,
432 traps: Option<Peekable<Box<dyn Iterator<Item = (u32, Trap)> + 'a>>>,
433 stack_maps: Option<Peekable<Box<dyn Iterator<Item = (u32, StackMap<'a>)> + 'a>>>,
434 exception_tables:
435 Option<Peekable<Box<dyn Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a>>>,
436 frame_tables: Option<
437 Peekable<
438 Box<
439 dyn Iterator<
440 Item = (
441 u32,
442 FrameInstPos,
443 Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>,
444 ),
445 > + 'a,
446 >,
447 >,
448 >,
449
450 breakpoints: Peekable<Box<dyn Iterator<Item = (ModulePC, usize, SmallVec<[u8; 8]>)>>>,
454
455 frame_table_descriptors: Option<FrameTable<'a>>,
456}
457
458impl Decorator<'_> {
459 fn decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>) {
460 self.addrmap(address, post_list);
461 self.traps(address, post_list);
462 self.stack_maps(address, post_list);
463 self.exception_table(address, pre_list);
464 self.frame_table(address, pre_list, post_list);
465 self.breakpoints(address, pre_list);
466 }
467
468 fn addrmap(&mut self, address: u64, list: &mut Vec<String>) {
469 if !self.objdump.addrmap() {
470 return;
471 }
472 let Some(addrmap) = &mut self.addrmap else {
473 return;
474 };
475 while let Some((addr, pos)) = addrmap.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
476 if u64::from(addr) != address {
477 continue;
478 }
479 if let Some(offset) = pos.file_offset() {
480 list.push(format!("addrmap: {offset:#x}"));
481 }
482 }
483 }
484
485 fn traps(&mut self, address: u64, list: &mut Vec<String>) {
486 if !self.objdump.traps() {
487 return;
488 }
489 let Some(traps) = &mut self.traps else {
490 return;
491 };
492 while let Some((addr, trap)) = traps.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
493 if u64::from(addr) != address {
494 continue;
495 }
496 list.push(format!("trap: {trap:?}"));
497 }
498 }
499
500 fn stack_maps(&mut self, address: u64, list: &mut Vec<String>) {
501 if !self.objdump.stack_maps() {
502 return;
503 }
504 let Some(stack_maps) = &mut self.stack_maps else {
505 return;
506 };
507 while let Some((addr, stack_map)) =
508 stack_maps.next_if(|(addr, _pos)| u64::from(*addr) <= address)
509 {
510 if u64::from(addr) != address {
511 continue;
512 }
513 list.push(format!(
514 "stack_map: frame_size={}, frame_offsets={:?}",
515 stack_map.frame_size(),
516 stack_map.offsets().collect::<Vec<_>>()
517 ));
518 }
519 }
520
521 fn exception_table(&mut self, address: u64, list: &mut Vec<String>) {
522 if !self.objdump.exception_tables() {
523 return;
524 }
525 let Some(exception_tables) = &mut self.exception_tables else {
526 return;
527 };
528 while let Some((addr, frame_offset, handlers)) =
529 exception_tables.next_if(|(addr, _, _)| u64::from(*addr) <= address)
530 {
531 if u64::from(addr) != address {
532 continue;
533 }
534 if let Some(frame_offset) = frame_offset {
535 list.push(format!(
536 "exception frame offset: SP = FP - 0x{frame_offset:x}",
537 ));
538 }
539 for handler in &handlers {
540 let tag = match handler.tag {
541 Some(tag) => format!("tag={tag}"),
542 None => "default handler".to_string(),
543 };
544 let context = match handler.context_sp_offset {
545 Some(offset) => format!("context at [SP+0x{offset:x}]"),
546 None => "no dynamic context".to_string(),
547 };
548 list.push(format!(
549 "exception handler: {tag}, {context}, handler=0x{:x}",
550 handler.handler_offset
551 ));
552 }
553 }
554 }
555
556 fn frame_table(
557 &mut self,
558 address: u64,
559 pre_list: &mut Vec<String>,
560 post_list: &mut Vec<String>,
561 ) {
562 if !self.objdump.frame_tables() {
563 return;
564 }
565 let (Some(frame_table_iter), Some(frame_tables)) =
566 (&mut self.frame_tables, &self.frame_table_descriptors)
567 else {
568 return;
569 };
570
571 while let Some((addr, pos, frames)) =
572 frame_table_iter.next_if(|(addr, _, _)| u64::from(*addr) <= address)
573 {
574 if u64::from(addr) != address {
575 continue;
576 }
577 let list = match pos {
578 FrameInstPos::Post => &mut *pre_list,
585 FrameInstPos::Pre => &mut *post_list,
586 };
587 let pos = match pos {
588 FrameInstPos::Post => "after previous inst",
589 FrameInstPos::Pre => "before next inst",
590 };
591 for (wasm_pc, frame_descriptor, stack_shape) in frames {
592 let (frame_descriptor_data, offset) =
593 frame_tables.frame_descriptor(frame_descriptor).unwrap();
594 let frame_descriptor = FrameStateSlot::parse(frame_descriptor_data).unwrap();
595
596 let local_shape = Self::describe_local_shape(&frame_descriptor);
597 let stack_shape = Self::describe_stack_shape(&frame_descriptor, stack_shape);
598 let func_key = frame_descriptor.func_key();
599 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}"));
600 }
601 }
602 }
603
604 fn breakpoints(&mut self, address: u64, list: &mut Vec<String>) {
605 while let Some((wasm_pc, addr, patch)) = self.breakpoints.next_if(|(_, addr, patch)| {
606 u64::try_from(*addr).unwrap() + u64::try_from(patch.len()).unwrap() <= address
607 }) {
608 if u64::try_from(addr).unwrap() + u64::try_from(patch.len()).unwrap() != address {
609 continue;
610 }
611 list.push(format!(
612 "breakpoint patch: wasm PC {wasm_pc}, patch bytes {patch:?}"
613 ));
614 }
615 }
616
617 fn describe_local_shape(desc: &FrameStateSlot<'_>) -> String {
618 let mut parts = vec![];
619 for (offset, ty) in desc.locals() {
620 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
621 }
622 parts.join(", ")
623 }
624
625 fn describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String {
626 let mut parts = vec![];
627 for (offset, ty) in desc.stack(shape) {
628 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
629 }
630 parts.reverse();
631 parts.join(", ")
632 }
633}