Skip to main content

quasar_cli/
dump.rs

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") // disassemble
39        .arg("-C") // demangle
40        .arg("--no-show-raw-insn"); // cleaner output
41
42    if source {
43        cmd.arg("-S"); // interleave source
44    }
45
46    if let Some(ref sym) = function {
47        cmd.arg(format!("--disassemble-symbols={sym}"));
48    }
49
50    cmd.arg(&so_path);
51
52    // If piping to a pager, let it handle output directly
53    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            // Print with minimal framing
74            for line in &lines {
75                println!("{line}");
76            }
77
78            // Summary
79            let insn_count = lines
80                .iter()
81                .filter(|l| {
82                    let trimmed = l.trim();
83                    // Instruction lines start with an address (hex digits followed by colon)
84                    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
135/// Find llvm-objdump in Solana platform-tools (newest version first)
136fn 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}