1#![allow(clippy::missing_errors_doc)]
2use crate::asm::statements::Label;
3use crate::cached_lines::CachedLines;
4use crate::demangle::LabelKind;
5use crate::{
6 CallGraph, Dumpable, Item, RawLines, URange, color, demangle, esafeprintln, get_context_for,
7 safeprintln,
8};
9use crate::opts::{Format, NameDisplay, RedundantLabels, SourcesFrom};
11
12mod statements;
13
14use nom::Parser as _;
15use owo_colors::OwoColorize;
16pub use statements::{Directive, GenericDirective, Instruction, Statement};
17use statements::{Loc, parse_statement};
18use std::borrow::Cow;
19use std::cell::RefCell;
20use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
21use std::ops::Range;
22use std::path::{Display, Path, PathBuf};
23
24type SourceFile = (String, Option<(Source, CachedLines)>);
25
26pub fn parse_file(input: &str) -> anyhow::Result<Vec<Statement<'_>>> {
27 match nom::multi::many0(parse_statement).parse(input) {
29 Ok(("", stmts)) => Ok(stmts),
30 Ok((leftovers, _)) =>
31 {
32 #[allow(clippy::redundant_else)]
33 if leftovers.len() < 1000 {
34 anyhow::bail!("Didn't consume everything, leftovers: {leftovers:?}")
35 } else {
36 let head = &leftovers[..leftovers
37 .char_indices()
38 .nth(200)
39 .expect("Shouldn't have that much unicode here...")
40 .0];
41 anyhow::bail!("Didn't consume everything, leftovers prefix: {head:?}");
42 }
43 }
44 Err(err) => anyhow::bail!("Couldn't parse the .s file: {err}"),
45 }
46}
47
48#[must_use]
49pub fn find_items(lines: &[Statement]) -> BTreeMap<Item, Range<usize>> {
50 let mut res = BTreeMap::new();
51
52 let mut sec_start = 0;
53 let mut item: Option<Item> = None;
54 let mut names = BTreeMap::new();
55
56 for (ix, line) in lines.iter().enumerate() {
57 if line.is_section_start() {
58 if item.is_none() {
59 sec_start = ix;
60 } else {
61 }
73 } else if line.is_global() && sec_start + 3 < ix {
74 sec_start = ix;
81 } else if line.is_end_of_fn() {
82 let sec_end = ix;
83 let range = sec_start..sec_end;
84 if let Some(mut item) = item.take() {
85 item.len = ix - item.len;
86 item.non_blank_len = item.len;
87 res.insert(item, range);
88 }
89 } else if let Statement::Label(label) = line {
90 if let Some(dem) = demangle::demangled(label.id) {
91 let hashed = format!("{dem:?}");
92 let name = format!("{dem:#?}");
93 let name_entry = names.entry(name.clone()).or_insert(0);
94 item = Some(Item {
95 mangled_name: label.id.to_owned(),
96 name,
97 hashed,
98 index: *name_entry,
99 len: ix,
100 non_blank_len: 0,
101 depth: None,
102 });
103 *name_entry += 1;
104 } else if matches!(label.kind, LabelKind::Unknown | LabelKind::Global) {
105 if let Some(mut i) = handle_non_mangled_labels(lines, ix, label, sec_start) {
106 let name_entry = names.entry(i.name.clone()).or_insert(0);
107 i.index = *name_entry;
108 item = Some(i);
109 *name_entry += 1;
110 }
111 }
112 }
113 }
114
115 let globals = lines
120 .iter()
121 .enumerate()
122 .filter_map(|(ix, line)| {
123 if let Statement::Directive(Directive::Global(name)) = line {
124 Some((name, ix))
125 } else {
126 None
127 }
128 })
129 .collect::<HashMap<_, _>>();
130
131 for (end, line) in lines.iter().enumerate() {
132 let name = match line {
133 Statement::Directive(Directive::SetValue(name, _)) => name,
134 Statement::Assignment(name, _) => name,
135 _ => continue,
136 };
137 let Some(start) = globals.get(name).copied() else {
138 continue;
139 };
140
141 let range = start..end + 1;
167 if range.len() > 10 {
168 continue;
171 }
172 let sym = name;
173 if let Some(dem) = demangle::demangled(sym) {
174 let hashed = format!("{dem:?}");
175 let name = format!("{dem:#?}");
176 let name_entry = names.entry(name.clone()).or_insert(0);
177 res.insert(
178 Item {
179 mangled_name: (*sym).to_string(),
180 name,
181 hashed,
182 index: *name_entry,
183 len: range.len(),
184 non_blank_len: range.len(),
185 depth: None,
186 },
187 range,
188 );
189 *name_entry += 1;
190 }
191 }
192
193 res
194}
195
196fn handle_non_mangled_labels(
201 lines: &[Statement],
202 ix: usize,
203 label: &Label,
204 sec_start: usize,
205) -> Option<Item> {
206 match lines.get(sec_start) {
207 Some(Statement::Directive(Directive::SectionStart(ss))) => {
208 const MACOS_TEXT_SECTION: &str = "__TEXT,__text,regular,pure_instructions";
212 const WINDOWS_TEXT_SECTION_PREFIX: &str = ".text,\"xr\",one_only,";
214 let is_mac = *ss == MACOS_TEXT_SECTION;
215 let is_windows = ss.starts_with(WINDOWS_TEXT_SECTION_PREFIX);
216 if is_windows || is_mac {
217 for line in &lines[sec_start..ix] {
219 if let Statement::Directive(Directive::Global(g)) = line {
220 if let Some(item) = get_item_in_section(ix, label, g, is_mac) {
225 return Some(item);
226 }
227 }
228 }
229 None
230 } else {
231 get_item_in_section(ix, label, ss.strip_prefix(".text.")?, false)
233 }
234 }
235 Some(Statement::Directive(Directive::Global(g))) => get_item_in_section(ix, label, g, true),
236 _ => None,
237 }
238}
239
240fn get_item_in_section(ix: usize, label: &Label, ss: &str, strip_underscore: bool) -> Option<Item> {
243 if !ss.starts_with(label.id) {
244 return None;
245 }
246 let name = if strip_underscore && label.id.starts_with('_') {
247 String::from(&label.id[1..])
248 } else {
249 String::from(label.id)
250 };
251 Some(Item {
252 mangled_name: label.id.to_owned(),
253 name: name.clone(),
254 hashed: name,
255 index: 0, len: ix,
257 non_blank_len: 0,
258 depth: None,
259 })
260}
261
262fn used_labels<'a>(stmts: &'_ [Statement<'a>]) -> BTreeSet<&'a str> {
263 stmts
264 .iter()
265 .filter_map(|stmt| match stmt {
266 Statement::Label(_) | Statement::Nothing => None,
267 Statement::Directive(dir) => match dir {
268 Directive::File(_)
269 | Directive::Loc(_)
270 | Directive::Global(_)
271 | Directive::SubsectionsViaSym
272 | Directive::SymIsFun(_) => None,
273 Directive::Data(_, val) | Directive::SetValue(_, val) => Some(*val),
274 Directive::Generic(g) => Some(g.0),
275 Directive::SectionStart(ss) => Some(*ss),
276 },
277 Statement::Instruction(i) => i.args,
278 Statement::Assignment(_, _) => None,
279 Statement::Dunno(s) => Some(s),
280 })
281 .flat_map(demangle::local_labels)
282 .collect::<BTreeSet<_>>()
283}
284
285fn scan_constant(
287 name: &str,
288 sections: &BTreeMap<&str, usize>,
289 body: &[Statement],
290) -> Option<URange> {
291 let start = *sections.get(name)?;
292 let end = start
293 + body[start + 1..]
294 .iter()
295 .take_while(|s| matches!(s, Statement::Directive(Directive::Data(_, _))))
296 .count()
297 + 1;
298 Some(URange { start, end })
299}
300
301fn dump_range(
302 files: &BTreeMap<u64, SourceFile>,
303 fmt: &Format,
304 print_range: Range<usize>,
305 body: &[Statement], ) -> anyhow::Result<()> {
307 let print_range = URange::from(print_range);
308 let mut prev_loc = Loc::default();
309
310 let stmts = &body[print_range];
311 let used = if fmt.redundant_labels == RedundantLabels::Keep {
312 BTreeSet::new()
313 } else {
314 used_labels(stmts)
315 };
316
317 let mut empty_line = false;
318 for (ix, line) in stmts.iter().enumerate() {
319 if fmt.verbosity > 3 {
320 safeprintln!("{line:?}");
321 }
322 if let Statement::Directive(Directive::File(_)) = &line {
323 } else if let Statement::Directive(Directive::Loc(loc)) = &line {
325 if !fmt.rust {
326 continue;
327 }
328 if loc.line == 0 {
329 continue;
330 }
331 if loc == &prev_loc {
332 continue;
333 }
334 prev_loc = *loc;
335 match files.get(&loc.file) {
336 Some((fname, Some((source, file)))) => {
337 if source.show_for(fmt.sources_from) {
338 let pos = format!("\t\t// {fname}:{}", loc.line);
339 safeprintln!("{}", color!(pos, OwoColorize::cyan));
340 if let Some(rust_line) = &file.get(loc.line as usize - 1) {
341 safeprintln!(
342 "\t\t{}",
343 color!(rust_line.trim_start(), OwoColorize::bright_red)
344 );
345 } else {
346 safeprintln!(
347 "\t\t{}",
348 color!(
349 "Corrupted rust-src installation? Try re-adding rust-src component.",
350 OwoColorize::red
351 )
352 );
353 }
354 }
355 }
356 Some((fname, None)) => {
357 if fmt.verbosity > 1 {
358 safeprintln!(
359 "\t\t{} {}",
360 color!("//", OwoColorize::cyan),
361 color!(
362 "Can't locate the file, please open a ticket with cargo-show-asm",
363 OwoColorize::red
364 ),
365 );
366 }
367 let pos = format!("\t\t// {fname}:{}", loc.line);
368 safeprintln!("{}", color!(pos, OwoColorize::cyan));
369 }
370 None => {
371 anyhow::bail!("DWARF file refers to an undefined location {loc:?}");
372 }
373 }
374 empty_line = false;
375 } else if let Statement::Label(Label {
376 kind: kind @ (LabelKind::Local | LabelKind::Temp),
377 id,
378 }) = line
379 {
380 match fmt.redundant_labels {
381 _ if ix == 0 || used.contains(id) => {
384 safeprintln!("{line}");
385 }
386 RedundantLabels::Keep => {
387 safeprintln!("{line}");
388 }
389 RedundantLabels::Blanks => {
390 if !empty_line && *kind != LabelKind::Temp {
391 safeprintln!();
392 empty_line = true;
393 }
394 }
395 RedundantLabels::Strip => {}
396 }
397 } else {
398 if fmt.simplify && line.boring() {
399 continue;
400 }
401
402 empty_line = false;
403 match fmt.name_display {
404 NameDisplay::Full => safeprintln!("{line:#}"),
405 NameDisplay::Short => safeprintln!("{line}"),
406 NameDisplay::Mangled => safeprintln!("{line:-}"),
407 }
408 }
409 }
410
411 Ok(())
412}
413
414fn path_formatter() -> impl for<'p> Fn(&'p Path, &'p mut PathBuf) -> Display<'p> {
416 let current_dir = std::env::current_dir().unwrap_or_default();
417 let home_dir = std::env::home_dir();
418 let home = if std::path::MAIN_SEPARATOR == '/' {
419 "~"
420 } else {
421 "%userprofile%"
422 };
423 move |path, tmp| {
424 if path.is_absolute() {
425 if let Ok(rel) = path.strip_prefix(¤t_dir) {
426 rel
427 } else if let Some(path_in_home) = home_dir
428 .as_ref()
429 .and_then(|home| path.strip_prefix(home).ok())
430 {
431 tmp.clear();
432 tmp.push(home);
433 tmp.push(path_in_home);
434 &*tmp
435 } else {
436 path
437 }
438 } else {
439 path
440 }
441 .display()
442 }
443}
444
445#[derive(Debug, Clone)]
446pub enum Source {
447 Crate,
448 External,
449 Stdlib,
450 Rustc,
451}
452
453impl Source {
454 fn show_for(&self, from: SourcesFrom) -> bool {
455 match self {
456 Source::Crate => true,
457 Source::External => match from {
458 SourcesFrom::ThisWorkspace => false,
459 SourcesFrom::AllCrates | SourcesFrom::AllSources => true,
460 },
461 Source::Rustc | Source::Stdlib => match from {
462 SourcesFrom::ThisWorkspace | SourcesFrom::AllCrates => false,
463 SourcesFrom::AllSources => true,
464 },
465 }
466 }
467}
468
469fn locate_sources(sysroot: &Path, workspace: &Path, path: &Path) -> Option<(Source, PathBuf)> {
486 let mut path = Cow::Borrowed(path);
487 if path.exists() {
489 let source = if path.starts_with(workspace) {
490 Source::Crate
491 } else {
492 Source::External
493 };
494
495 return Some((source, path.into()));
496 }
497
498 let no_rust_src = || {
499 esafeprintln!(
500 "You need to install rustc sources to be able to see the rust annotations, try\n\
501 \trustup component add rust-src"
502 );
503 std::process::exit(1);
504 };
505
506 if (path.starts_with("/rustc/") || path.starts_with("/private/tmp"))
515 && path
516 .as_os_str()
517 .to_str()
518 .is_some_and(|s| s.contains('\\') && s.contains('/'))
519 {
520 let cursed_path = path
521 .as_os_str()
522 .to_str()
523 .expect("They are coming from a text file");
524 path = Cow::Owned(PathBuf::from(cursed_path.replace('\\', "/")));
525 }
526
527 if path.starts_with("/rustc") && path.iter().any(|c| c == "compiler") {
529 let mut source = sysroot.join("lib/rustlib/rustc-src/rust");
530 for part in path.components().skip(3) {
531 source.push(part);
532 }
533
534 if source.exists() {
535 return Some((Source::Rustc, source));
536 } else {
537 no_rust_src();
538 }
539 }
540
541 if path.starts_with("/rustc/") {
543 let mut source = sysroot.join("lib/rustlib/src/rust");
544 for part in path.components().skip(3) {
545 source.push(part);
546 }
547 if source.exists() {
548 return Some((Source::Stdlib, source));
549 } else {
550 no_rust_src();
551 }
552 }
553
554 if path.starts_with("/private/tmp") && path.components().any(|c| c.as_os_str() == "library") {
556 let mut source = sysroot.join("lib/rustlib/src/rust");
557 for part in path.components().skip(5) {
558 source.push(part);
559 }
560 if source.exists() {
561 return Some((Source::Stdlib, source));
562 } else {
563 no_rust_src();
564 }
565 }
566
567 if let Some(ix) = path
569 .components()
570 .position(|c| c.as_os_str() == "cargo" || c.as_os_str() == ".cargo")
571 .and_then(|ix| path.components().nth(ix).zip(Some(ix)))
572 .and_then(|(c, ix)| (c.as_os_str() == "registry").then_some(ix))
573 {
574 #[allow(deprecated)]
576 let mut source = std::env::home_dir().expect("No home dir?");
577
578 source.push(".cargo");
579 for part in path.components().skip(ix) {
580 source.push(part);
581 }
582 if source.exists() {
583 return Some((Source::External, source));
584 } else {
585 panic!(
586 "{path:?} looks like it can be a cargo registry reference but we failed to get it"
587 );
588 }
589 }
590
591 None
592}
593
594fn load_rust_sources(
595 sysroot: &Path,
596 workspace: &Path,
597 statements: &[Statement],
598 fmt: &Format,
599 files: &mut BTreeMap<u64, SourceFile>,
600) {
601 let home_dir = std::env::home_dir();
602 let format_path = path_formatter();
603 let mut tmp = PathBuf::new();
604
605 for line in statements {
606 if let Statement::Directive(Directive::File(f)) = line {
607 files.entry(f.index).or_insert_with(|| {
608 let path = f.path.as_full_path_with_home_dir(home_dir.as_deref());
609 let pretty_path = format_path(&path, &mut tmp).to_string();
610 if fmt.verbosity > 2 {
611 safeprintln!("Reading file #{} {}", f.index, path.display());
612 }
613
614 if let Some((source, filepath)) = locate_sources(sysroot, workspace, &path) {
615 if fmt.verbosity > 3 {
616 safeprintln!("Resolved name is {filepath:?}");
617 }
618 let sources = std::fs::read_to_string(&filepath).expect("Can't read a file");
619 if sources.is_empty() {
620 if fmt.verbosity > 0 {
621 safeprintln!("Ignoring empty file {filepath:?}!");
622 }
623 (pretty_path, None)
624 } else {
625 if fmt.verbosity > 3 {
626 safeprintln!("Got {} bytes", sources.len());
627 }
628 let lines = CachedLines::without_ending(sources);
629 (pretty_path, Some((source, lines)))
630 }
631 } else {
632 if fmt.verbosity > 1 {
633 safeprintln!("File not found {}", path.display());
634 }
635 (pretty_path, None)
636 }
637 });
638 }
639 }
640}
641
642impl RawLines for Statement<'_> {
643 fn lines(&self) -> Option<&str> {
644 match self {
645 Statement::Instruction(i) => i.args,
646 Statement::Directive(Directive::SetValue(_, i)) => Some(i),
647 _ => None,
648 }
649 }
650}
651
652pub struct Asm<'a> {
653 workspace: &'a Path,
654 sysroot: &'a Path,
655 sources: RefCell<BTreeMap<u64, SourceFile>>,
656}
657
658impl<'a> Asm<'a> {
659 pub fn new(workspace: &'a Path, sysroot: &'a Path) -> Self {
660 Self {
661 workspace,
662 sysroot,
663 sources: Default::default(),
664 }
665 }
666}
667
668impl Dumpable for Asm<'_> {
669 type Line<'l> = Statement<'l>;
670
671 fn split_lines(contents: &str) -> anyhow::Result<Vec<Self::Line<'_>>> {
672 parse_file(contents)
673 }
674
675 fn find_items(lines: &[Self::Line<'_>]) -> BTreeMap<Item, Range<usize>> {
676 find_items(lines)
677 }
678
679 fn callgraph<'a>(lines: &[Self::Line<'a>]) -> CallGraph<'a> {
680 let mut graph: HashMap<&str, HashSet<&str>> = HashMap::new();
681 let mut caller: &str = "";
682 for line in lines {
683 match line {
684 Statement::Label(Label { id, kind }) => {
685 if !matches!(kind, LabelKind::Local | LabelKind::Temp) {
686 caller = id;
687 }
688 }
689 Statement::Instruction(Instruction {
690 args: Some(arg), ..
691 }) => {
692 for callee in demangle::GLOBAL_LABELS
693 .captures_iter(arg)
694 .filter_map(|c| c.get(1))
695 {
696 graph.entry(caller).or_default().insert(callee.as_str());
697 }
698 }
699 _ => {}
700 }
701 }
702 CallGraph(graph)
703 }
704
705 fn dump_range(&self, fmt: &Format, lines: &[Self::Line<'_>]) -> anyhow::Result<()> {
706 dump_range(&self.sources.borrow(), fmt, 0..lines.len(), lines)
707 }
708
709 fn extra_context(
710 &self,
711 fmt: &Format,
712 lines: &[Self::Line<'_>],
713 range: Range<usize>,
714 items: &BTreeMap<Item, Range<usize>>,
715 ) -> Vec<Range<usize>> {
716 let mut res = get_context_for(fmt.context, lines, range.clone(), items);
717 if fmt.rust {
718 load_rust_sources(
719 self.sysroot,
720 self.workspace,
721 lines,
722 fmt,
723 &mut self.sources.borrow_mut(),
724 );
725 }
726
727 if fmt.include_constants {
728 let print_range = URange::from(range);
729 let mut pending = vec![print_range];
731 let mut seen: BTreeSet<URange> = BTreeSet::new();
732
733 let constants = lines
735 .iter()
736 .enumerate()
737 .filter_map(|(ix, stmt)| {
738 let Statement::Label(Label { id, .. }) = stmt else {
739 return None;
740 };
741 matches!(
742 lines.get(ix + 1),
743 Some(Statement::Directive(Directive::Data(_, _)))
744 )
745 .then_some((*id, ix))
746 })
747 .collect::<BTreeMap<_, _>>();
748 while let Some(subset) = pending.pop() {
749 seen.insert(subset);
750 for s in &lines[subset] {
751 if let Statement::Instruction(Instruction {
752 args: Some(arg), ..
753 })
754 | Statement::Directive(Directive::Generic(GenericDirective(arg))) = s
755 {
756 for label in crate::demangle::local_labels(arg) {
757 if let Some(constant_range) = scan_constant(label, &constants, lines) {
758 if !seen.contains(&constant_range)
759 && !print_range.fully_contains(constant_range)
760 {
761 pending.push(constant_range);
762 }
763 }
764 }
765 }
766 }
767 }
768 seen.remove(&print_range);
769 for range in &seen {
770 res.push(range.start..range.end);
771 }
772 }
773
774 if fmt.simplify {
775 res.retain(|range| {
776 lines[range.start..range.end]
777 .iter()
778 .any(|s| !(s.boring() || matches!(s, Statement::Nothing | Statement::Label(_))))
779 });
780 }
781
782 res
783 }
784}