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 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;
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 || name.contains("module_start")
209 {
210 Func::Trampoline
211 } else if name.contains("libcall") || name.starts_with("component") {
212 Func::Libcall
213 } else {
214 panic!("unknown symbol: {name}")
215 };
216
217 if self.funcs.is_empty() {
220 if kind != Func::Wasm {
221 continue;
222 }
223 } else {
224 if !(self.funcs.contains(&Func::All) || self.funcs.contains(&kind)) {
225 continue;
226 }
227 }
228 if let Some(filter) = &self.filter {
229 if !name.contains(filter) {
230 continue;
231 }
232 }
233
234 if first {
236 first = false;
237 } else {
238 writeln!(stdout)?;
239 }
240
241 if self.addresses {
244 stdout.set_color(color_address.clone().set_bold(true))?;
245 write!(stdout, "{:08x} ", sym.address())?;
246 stdout.reset()?;
247 }
248 stdout.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)))?;
249 write!(stdout, "{name}")?;
250 stdout.reset()?;
251 writeln!(stdout, ":")?;
252
253 let mut prev_jump = false;
256 let mut write_offsets = false;
257
258 for inst in disas::disas(&elf, bytes, sym.address())? {
259 let Inst {
260 address,
261 is_jump,
262 is_return,
263 disassembly: disas,
264 bytes,
265 } = inst;
266
267 let mut bytes = bytes.iter().map(Some).chain(iter::repeat(None));
271 let inline_bytes = 9;
272 let width = self.address_width;
273
274 let mut pre_decorations = Vec::new();
287 let mut post_decorations = Vec::new();
288 decorator.decorate(address, &mut pre_decorations, &mut post_decorations);
289
290 let print_whitespace_to_decoration = |stdout: &mut StandardStream| -> Result<()> {
291 write!(stdout, "{:width$} ", "")?;
292 if self.bytes {
293 for _ in 0..inline_bytes + 1 {
294 write!(stdout, " ")?;
295 }
296 }
297 Ok(())
298 };
299
300 let print_decorations =
301 |stdout: &mut StandardStream, decorations: Vec<String>| -> Result<()> {
302 for (i, decoration) in decorations.iter().enumerate() {
303 print_whitespace_to_decoration(stdout)?;
304 let mut color = ColorSpec::new();
305 color.set_fg(Some(Color::Cyan));
306 stdout.set_color(&color)?;
307 let final_decoration = i == decorations.len() - 1;
308 if !final_decoration {
309 write!(stdout, "├")?;
310 } else {
311 write!(stdout, "╰")?;
312 }
313 for (i, line) in decoration.lines().enumerate() {
314 if i == 0 {
315 write!(stdout, "─╼ ")?;
316 } else {
317 print_whitespace_to_decoration(stdout)?;
318 if final_decoration {
319 write!(stdout, " ")?;
320 } else {
321 write!(stdout, "│ ")?;
322 }
323 }
324 writeln!(stdout, "{line}")?;
325 }
326 stdout.reset()?;
327 }
328 Ok(())
329 };
330
331 print_decorations(&mut stdout, pre_decorations)?;
332
333 for (i, line) in disas.lines().enumerate() {
337 let print_address = self.addresses
338 || (self.address_jumps && (write_offsets || (prev_jump && !is_jump)));
339 if i == 0 && print_address {
340 stdout.set_color(&color_address)?;
341 write!(stdout, "{address:>width$x}: ")?;
342 stdout.reset()?;
343 } else {
344 write!(stdout, "{:width$} ", "")?;
345 }
346
347 if self.bytes {
352 stdout.set_color(&color_bytes)?;
353 for byte in bytes.by_ref().take(inline_bytes) {
354 match byte {
355 Some(byte) => write!(stdout, "{byte:02x} ")?,
356 None => write!(stdout, " ")?,
357 }
358 }
359 write!(stdout, " ")?;
360 stdout.reset()?;
361 }
362
363 writeln!(stdout, "{line}")?;
364 }
365
366 write_offsets |= is_return;
370 prev_jump = is_jump;
371
372 if self.bytes {
376 let mut inline = 0;
377 stdout.set_color(&color_bytes)?;
378 for byte in bytes {
379 let Some(byte) = byte else { break };
380 if inline == 0 {
381 write!(stdout, "{:width$} ", "")?;
382 } else {
383 write!(stdout, " ")?;
384 }
385 write!(stdout, "{byte:02x}")?;
386 inline += 1;
387 if inline == inline_bytes {
388 writeln!(stdout)?;
389 inline = 0;
390 }
391 }
392 stdout.reset()?;
393 if inline > 0 {
394 writeln!(stdout)?;
395 }
396 }
397
398 print_decorations(&mut stdout, post_decorations)?;
399 }
400 }
401 Ok(())
402 }
403
404 fn read_cwasm(&self) -> Result<Vec<u8>> {
407 if let Some(path) = &self.cwasm {
408 if path != Path::new("-") {
409 return std::fs::read(path).with_context(|| format!("failed to read {path:?}"));
410 }
411 }
412
413 let mut stdin = Vec::new();
414 std::io::stdin()
415 .read_to_end(&mut stdin)
416 .context("failed to read stdin")?;
417 Ok(stdin)
418 }
419}
420
421#[derive(clap::ValueEnum, Clone, Copy, PartialEq, Eq)]
422enum Func {
423 All,
424 Wasm,
425 Trampoline,
426 Builtin,
427 Libcall,
428}
429
430struct Decorator<'a> {
431 objdump: &'a ObjdumpCommand,
432 addrmap: Option<Peekable<Box<dyn Iterator<Item = (u32, FilePos)> + 'a>>>,
433 traps: Option<Peekable<Box<dyn Iterator<Item = (u32, CompiledTrap)> + 'a>>>,
434 stack_maps: Option<Peekable<Box<dyn Iterator<Item = (u32, StackMap<'a>)> + 'a>>>,
435 exception_tables:
436 Option<Peekable<Box<dyn Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a>>>,
437 frame_tables: Option<
438 Peekable<
439 Box<
440 dyn Iterator<
441 Item = (
442 u32,
443 FrameInstPos,
444 Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>,
445 ),
446 > + 'a,
447 >,
448 >,
449 >,
450
451 breakpoints: Peekable<Box<dyn Iterator<Item = (ModulePC, usize, SmallVec<[u8; 8]>)>>>,
455
456 frame_table_descriptors: Option<FrameTable<'a>>,
457}
458
459impl Decorator<'_> {
460 fn decorate(&mut self, address: u64, pre_list: &mut Vec<String>, post_list: &mut Vec<String>) {
461 self.addrmap(address, post_list);
462 self.traps(address, post_list);
463 self.stack_maps(address, post_list);
464 self.exception_table(address, pre_list);
465 self.frame_table(address, pre_list, post_list);
466 self.breakpoints(address, pre_list);
467 }
468
469 fn addrmap(&mut self, address: u64, list: &mut Vec<String>) {
470 if !self.objdump.addrmap() {
471 return;
472 }
473 let Some(addrmap) = &mut self.addrmap else {
474 return;
475 };
476 while let Some((addr, pos)) = addrmap.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
477 if u64::from(addr) != address {
478 continue;
479 }
480 if let Some(offset) = pos.file_offset() {
481 list.push(format!("addrmap: {offset:#x}"));
482 }
483 }
484 }
485
486 fn traps(&mut self, address: u64, list: &mut Vec<String>) {
487 if !self.objdump.traps() {
488 return;
489 }
490 let Some(traps) = &mut self.traps else {
491 return;
492 };
493 while let Some((addr, trap)) = traps.next_if(|(addr, _pos)| u64::from(*addr) <= address) {
494 if u64::from(addr) != address {
495 continue;
496 }
497 list.push(format!("trap: {trap:?}"));
498 }
499 }
500
501 fn stack_maps(&mut self, address: u64, list: &mut Vec<String>) {
502 if !self.objdump.stack_maps() {
503 return;
504 }
505 let Some(stack_maps) = &mut self.stack_maps else {
506 return;
507 };
508 while let Some((addr, stack_map)) =
509 stack_maps.next_if(|(addr, _pos)| u64::from(*addr) <= address)
510 {
511 if u64::from(addr) != address {
512 continue;
513 }
514 list.push(format!(
515 "stack_map: frame_size={}, frame_offsets={:?}",
516 stack_map.frame_size(),
517 stack_map.offsets().collect::<Vec<_>>()
518 ));
519 }
520 }
521
522 fn exception_table(&mut self, address: u64, list: &mut Vec<String>) {
523 if !self.objdump.exception_tables() {
524 return;
525 }
526 let Some(exception_tables) = &mut self.exception_tables else {
527 return;
528 };
529 while let Some((addr, frame_offset, handlers)) =
530 exception_tables.next_if(|(addr, _, _)| u64::from(*addr) <= address)
531 {
532 if u64::from(addr) != address {
533 continue;
534 }
535 if let Some(frame_offset) = frame_offset {
536 list.push(format!(
537 "exception frame offset: SP = FP - 0x{frame_offset:x}",
538 ));
539 }
540 for handler in &handlers {
541 let tag = match handler.tag {
542 Some(tag) => format!("tag={tag}"),
543 None => "default handler".to_string(),
544 };
545 let context = match handler.context_sp_offset {
546 Some(offset) => format!("context at [SP+0x{offset:x}]"),
547 None => "no dynamic context".to_string(),
548 };
549 list.push(format!(
550 "exception handler: {tag}, {context}, handler=0x{:x}",
551 handler.handler_offset
552 ));
553 }
554 }
555 }
556
557 fn frame_table(
558 &mut self,
559 address: u64,
560 pre_list: &mut Vec<String>,
561 post_list: &mut Vec<String>,
562 ) {
563 if !self.objdump.frame_tables() {
564 return;
565 }
566 let (Some(frame_table_iter), Some(frame_tables)) =
567 (&mut self.frame_tables, &self.frame_table_descriptors)
568 else {
569 return;
570 };
571
572 while let Some((addr, pos, frames)) =
573 frame_table_iter.next_if(|(addr, _, _)| u64::from(*addr) <= address)
574 {
575 if u64::from(addr) != address {
576 continue;
577 }
578 let list = match pos {
579 FrameInstPos::Post => &mut *pre_list,
586 FrameInstPos::Pre => &mut *post_list,
587 };
588 let pos = match pos {
589 FrameInstPos::Post => "after previous inst",
590 FrameInstPos::Pre => "before next inst",
591 };
592 for (wasm_pc, frame_descriptor, stack_shape) in frames {
593 let (frame_descriptor_data, offset) =
594 frame_tables.frame_descriptor(frame_descriptor).unwrap();
595 let frame_descriptor = FrameStateSlot::parse(frame_descriptor_data).unwrap();
596
597 let local_shape = Self::describe_local_shape(&frame_descriptor);
598 let stack_shape = Self::describe_stack_shape(&frame_descriptor, stack_shape);
599 let func_key = frame_descriptor.func_key();
600 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}"));
601 }
602 }
603 }
604
605 fn breakpoints(&mut self, address: u64, list: &mut Vec<String>) {
606 while let Some((wasm_pc, addr, patch)) = self.breakpoints.next_if(|(_, addr, patch)| {
607 u64::try_from(*addr).unwrap() + u64::try_from(patch.len()).unwrap() <= address
608 }) {
609 if u64::try_from(addr).unwrap() + u64::try_from(patch.len()).unwrap() != address {
610 continue;
611 }
612 list.push(format!(
613 "breakpoint patch: wasm PC {wasm_pc}, patch bytes {patch:?}"
614 ));
615 }
616 }
617
618 fn describe_local_shape(desc: &FrameStateSlot<'_>) -> String {
619 let mut parts = vec![];
620 for (offset, ty) in desc.locals() {
621 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
622 }
623 parts.join(", ")
624 }
625
626 fn describe_stack_shape(desc: &FrameStateSlot<'_>, shape: FrameStackShape) -> String {
627 let mut parts = vec![];
628 for (offset, ty) in desc.stack(shape) {
629 parts.push(format!("{ty:?} @ slot+0x{:x}", offset.offset()));
630 }
631 parts.reverse();
632 parts.join(", ")
633 }
634}