1#![doc = include_str!("../README.md")]
2
3use opts::{Format, NameDisplay, ToDump};
4use std::{
5 array,
6 collections::{BTreeMap, BTreeSet},
7 ops::Range,
8 path::{Path, PathBuf},
9};
10
11pub mod asm;
12pub mod cached_lines;
13pub mod demangle;
14#[cfg(feature = "disasm")]
15pub mod disasm;
16pub mod llvm;
17pub mod mca;
18pub mod mir;
19pub mod opts;
20
21#[macro_export]
22macro_rules! color {
23 ($item:expr, $color:expr) => {
24 owo_colors::OwoColorize::if_supports_color(&$item, owo_colors::Stream::Stdout, $color)
25 };
26}
27
28#[macro_export]
35macro_rules! safeprintln {
36 ($($x:expr),* $(,)?) => {{
37 use std::io::Write;
38 if writeln!(std::io::stdout(), $($x),*).is_err() {
39 std::process::exit(0);
40 }
41 }};
42}
43
44#[macro_export]
45macro_rules! safeprint {
46 ($($x:expr),* $(,)?) => {{
47 use std::io::Write;
48 if write!(std::io::stdout(), $($x),*).is_err() {
49 std::process::exit(0);
50 }
51 }};
52}
53
54#[macro_export]
55macro_rules! esafeprintln {
56 ($($x:expr),* $(,)?) => {{
57 use std::io::Write;
58 if writeln!(std::io::stderr(), $($x),*).is_err() {
59 std::process::exit(0);
60 }
61 }};
62}
63
64#[macro_export]
65macro_rules! esafeprint {
66 ($($x:expr),* $(,)?) => {{
67 use std::io::Write;
68 if write!(std::io::stderr(), $($x),*).is_err() {
69 std::process::exit(0);
70 }
71 }};
72}
73
74pub fn read_sources(names: &[PathBuf]) -> anyhow::Result<Vec<String>> {
78 names
79 .iter()
80 .map(|name| {
81 let bytes = std::fs::read(name)?;
82 Ok(String::from_utf8_lossy(&bytes).into_owned())
86 })
87 .collect()
88}
89
90#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
91pub struct Item {
92 pub name: String,
96 pub hashed: String,
98 pub index: usize,
100 pub len: usize,
102 pub non_blank_len: usize,
104 pub mangled_name: String,
106}
107
108pub fn suggest_name<'a>(
109 search: &str,
110 fmt: &Format,
111 items: impl IntoIterator<Item = &'a Item>,
112) -> ! {
113 let mut count = 0usize;
114 let names: BTreeMap<&String, Vec<usize>> =
115 items.into_iter().fold(BTreeMap::new(), |mut m, item| {
116 count += 1;
117 let entry = match fmt.name_display {
118 NameDisplay::Full => &item.hashed,
119 NameDisplay::Short => &item.name,
120 NameDisplay::Mangled => &item.mangled_name,
121 };
122 m.entry(entry).or_default().push(item.non_blank_len);
123 m
124 });
125
126 if fmt.verbosity > 0 {
127 if names.is_empty() {
128 if search.is_empty() {
129 safeprintln!(
130 "This target defines no functions (or cargo-show-asm can't find them)"
131 );
132 } else {
133 safeprintln!("No matching functions, try relaxing your search request");
134 }
135 safeprintln!("You can pass --everything to see the demangled contents of a file");
136 } else {
137 safeprintln!("Try one of those by name or a sequence number");
138 }
139 }
140
141 #[allow(clippy::cast_sign_loss)]
142 #[allow(clippy::cast_precision_loss)]
143 let width = (count as f64).log10().ceil() as usize;
144
145 let mut ix = 0;
146 for (name, lens) in &names {
147 safeprintln!(
148 "{ix:width$} {:?} {:?}",
149 color!(name, owo_colors::OwoColorize::green),
150 color!(lens, owo_colors::OwoColorize::cyan),
151 );
152 ix += lens.len();
153 }
154
155 std::process::exit(1);
156}
157
158#[must_use]
162pub fn pick_dump_item<K: Clone>(
163 goal: ToDump,
164 fmt: &Format,
165 items: &BTreeMap<Item, K>,
166) -> Option<K> {
167 match goal {
168 ToDump::Everything => None,
170
171 ToDump::ByIndex { value } => {
173 if let Some(range) = items.values().nth(value) {
174 Some(range.clone())
175 } else {
176 let actual = items.len();
177 esafeprintln!("You asked to display item #{value} (zero based), but there's only {actual} items");
178 std::process::exit(1);
179 }
180 }
181
182 ToDump::Function { function, nth } => {
184 let filtered = items
185 .iter()
186 .filter(|(item, _range)| item.name.contains(&function))
187 .collect::<Vec<_>>();
188
189 let range = if nth.is_none() && filtered.len() == 1 {
190 filtered
191 .first()
192 .expect("Must have one item as checked above")
193 .1
194 .clone()
195 } else if let Some(range) = nth.and_then(|nth| filtered.get(nth)) {
196 range.1.clone()
197 } else if let Some(value) = nth {
198 let filtered = filtered.len();
199 esafeprintln!("You asked to display item #{value} (zero based), but there's only {filtered} matching items");
200 std::process::exit(1);
201 } else {
202 if filtered.is_empty() {
203 esafeprintln!("Can't find any items matching {function:?}");
204 } else {
205 suggest_name(&function, fmt, filtered.iter().map(|x| x.0));
206 }
207 std::process::exit(1);
208 };
209 Some(range)
210 }
211
212 ToDump::Unspecified => {
213 let mut items_values = items.values();
214 if let [Some(item), None] = array::from_fn(|_| items_values.next()) {
215 Some(item.clone())
217 } else {
218 let items = items.keys();
220 suggest_name("", fmt, items);
221 }
222 }
223 }
224}
225
226trait RawLines {
227 fn lines(&self) -> Option<&str>;
228}
229
230impl RawLines for &str {
231 fn lines(&self) -> Option<&str> {
232 Some(self)
233 }
234}
235
236fn get_context_for<R: RawLines>(
238 depth: usize,
239 all_stmts: &[R],
240 self_range: Range<usize>,
241 items: &BTreeMap<Item, Range<usize>>,
242) -> Vec<Range<usize>> {
243 let mut out = Vec::new();
244 if depth == 0 {
245 return out;
246 }
247 let items = items
248 .iter()
249 .map(|(item, range)| (item.mangled_name.as_str(), range.clone()))
250 .collect::<BTreeMap<_, _>>();
251 let mut pending = vec![(self_range.clone(), depth)];
252 let mut processed = BTreeSet::new();
253 while let Some((range, depth)) = pending.pop() {
254 for raw in all_stmts[range]
255 .iter()
256 .filter_map(R::lines)
257 .filter_map(demangle::global_reference)
258 {
259 if !processed.insert(raw) {
260 continue;
261 }
262 if let Some(range) = items.get(raw) {
263 if range == &self_range {
264 continue;
265 }
266 if depth > 0 {
267 pending.push((range.clone(), depth - 1));
268 }
269 out.push(range.clone());
270 }
271 }
272 }
273 out.sort_by_key(|r| r.start);
274 out
275}
276
277pub trait Dumpable {
278 type Line<'a>;
279 fn split_lines(contents: &str) -> anyhow::Result<Vec<Self::Line<'_>>>;
281
282 fn find_items(lines: &[Self::Line<'_>]) -> BTreeMap<Item, Range<usize>>;
284
285 fn init(&mut self, _lines: &[Self::Line<'_>]) {}
287
288 fn dump_range(&self, fmt: &Format, lines: &[Self::Line<'_>]) -> anyhow::Result<()>;
290
291 fn extra_context(
293 &self,
294 fmt: &Format,
295 lines: &[Self::Line<'_>],
296 range: Range<usize>,
297 items: &BTreeMap<Item, Range<usize>>,
298 ) -> Vec<Range<usize>> {
299 #![allow(unused_variables)]
300 Vec::new()
301 }
302}
303
304pub fn dump_function<T: Dumpable>(
306 dumpable: &mut T,
307 goal: ToDump,
308 path: &Path,
309 fmt: &Format,
310) -> anyhow::Result<()> {
311 let raw_bytes = std::fs::read(path)?;
314 let contents = String::from_utf8_lossy(&raw_bytes[..]);
315
316 let lines = T::split_lines(&contents)?;
317 let items = T::find_items(&lines);
318 dumpable.init(&lines);
319
320 match pick_dump_item(goal, fmt, &items) {
321 Some(range) => {
322 let context = T::extra_context(dumpable, fmt, &lines, range.clone(), &items);
323 dumpable.dump_range(fmt, &lines[range])?;
324
325 if !context.is_empty() {
326 safeprintln!(
327 "\n======================= Additional context ========================="
328 );
329 for range in context {
330 safeprintln!("");
331 dumpable.dump_range(fmt, &lines[range])?;
332 }
333 }
334 }
335 None => {
336 if fmt.rust {
337 T::extra_context(dumpable, fmt, &lines, 0..lines.len(), &items);
339 }
340 dumpable.dump_range(fmt, &lines)?;
341 }
342 }
343 Ok(())
344}
345
346#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
348pub struct URange {
349 start: usize,
350 end: usize,
351}
352
353impl From<Range<usize>> for URange {
354 fn from(Range { start, end }: Range<usize>) -> Self {
355 Self { start, end }
356 }
357}
358
359impl<T> std::ops::Index<URange> for [T] {
360 type Output = [T];
361 fn index(&self, index: URange) -> &Self::Output {
362 &self[index.start..index.end]
363 }
364}
365
366impl URange {
367 pub fn fully_contains(&self, other: Self) -> bool {
368 self.start >= other.start && self.end <= other.end
369 }
370}