1use {
2 crate::{config::QuasarConfig, error::CliResult, style, utils},
3 std::{
4 path::PathBuf,
5 process::{Command, Stdio},
6 },
7};
8
9pub fn run(elf_path: Option<PathBuf>, function: Option<String>, source: bool) -> CliResult {
10 let so_path = match elf_path {
11 Some(p) => p,
12 None => find_so()?,
13 };
14
15 if !so_path.exists() {
16 eprintln!(
17 " {}",
18 style::fail(&format!("file not found: {}", so_path.display()))
19 );
20 std::process::exit(1);
21 }
22
23 let objdump = find_objdump().unwrap_or_else(|| {
24 eprintln!(
25 " {}",
26 style::fail("llvm-objdump not found in Solana platform-tools.")
27 );
28 eprintln!();
29 eprintln!(" Looked in ~/.cache/solana/*/platform-tools/llvm/bin/");
30 eprintln!(
31 " Install platform-tools: {}",
32 style::bold("solana-install init")
33 );
34 std::process::exit(1);
35 });
36
37 let mut cmd = Command::new(&objdump);
38 cmd.arg("-d") .arg("-C") .arg("--no-show-raw-insn"); if source {
43 cmd.arg("-S"); }
45
46 if let Some(ref sym) = function {
47 cmd.arg(format!("--disassemble-symbols={sym}"));
48 }
49
50 cmd.arg(&so_path);
51
52 let output = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).output();
54
55 match output {
56 Ok(o) if o.status.success() => {
57 let stdout = String::from_utf8_lossy(&o.stdout);
58 let lines: Vec<&str> = stdout.lines().collect();
59
60 if lines.is_empty() || (function.is_some() && lines.len() <= 2) {
61 if let Some(sym) = function {
62 eprintln!(" {}", style::fail(&format!("symbol not found: {sym}")));
63 eprintln!(
64 " {}",
65 style::dim("Try a mangled or partial name, e.g. 'entrypoint'")
66 );
67 } else {
68 eprintln!(" {}", style::fail("no disassembly output"));
69 }
70 std::process::exit(1);
71 }
72
73 for line in &lines {
75 println!("{line}");
76 }
77
78 let insn_count = lines
80 .iter()
81 .filter(|l| {
82 let trimmed = l.trim();
83 trimmed.split(':').next().is_some_and(|addr| {
85 !addr.is_empty() && addr.trim().chars().all(|c| c.is_ascii_hexdigit())
86 })
87 })
88 .count();
89
90 eprintln!(
91 "\n {} {} instructions ({})",
92 style::dim("sBPF"),
93 insn_count,
94 style::dim(&so_path.display().to_string()),
95 );
96
97 Ok(())
98 }
99 Ok(o) => {
100 let stderr = String::from_utf8_lossy(&o.stderr);
101 eprintln!(" {}", style::fail("llvm-objdump failed"));
102 if !stderr.trim().is_empty() {
103 eprintln!(" {}", stderr.trim());
104 }
105 std::process::exit(1);
106 }
107 Err(e) => {
108 eprintln!(
109 " {}",
110 style::fail(&format!("failed to run {}: {e}", objdump.display()))
111 );
112 std::process::exit(1);
113 }
114 }
115}
116
117fn find_so() -> Result<PathBuf, crate::error::CliError> {
118 let config = QuasarConfig::load()?;
119 match utils::find_so(&config, true) {
120 Some(p) => Ok(p),
121 None => {
122 eprintln!(
123 " {}",
124 style::fail("no .so found in target/deploy/ or target/profile/")
125 );
126 eprintln!(
127 " {}",
128 style::dim("Run `quasar build` first or pass a path: `quasar dump <path>`")
129 );
130 std::process::exit(1);
131 }
132 }
133}
134
135fn find_objdump() -> Option<PathBuf> {
137 let home = dirs::home_dir()?;
138 let cache = home.join(".cache/solana");
139 if !cache.exists() {
140 return None;
141 }
142
143 let mut versions: Vec<_> = std::fs::read_dir(&cache)
144 .ok()?
145 .flatten()
146 .filter_map(|e| {
147 let path = e.path();
148 let name = path.file_name()?.to_str()?;
149 let ver = name.strip_prefix('v')?;
150 let num: f64 = ver.parse().ok()?;
151 let objdump = path.join("platform-tools/llvm/bin/llvm-objdump");
152 if objdump.exists() {
153 Some((num, objdump))
154 } else {
155 None
156 }
157 })
158 .collect();
159 versions.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
160 versions.into_iter().next().map(|(_, path)| path)
161}