Find Ghidra installed from a Linux package

Linux packages put Ghidra in /opt/ghidra (Arch and its derivatives) or /usr/share/ghidra (Debian, Kali) with only a start script on PATH, so Deep mode said Ghidra wasn't there (#85). The engine table lists both folders and the Ghidra lookup falls back to them.
This commit is contained in:
bflatastic
2026-11-01 22:49:34 +07:00
parent 348949ca76
commit eb27cbc0ea
3 changed files with 27 additions and 6 deletions
+7 -3
View File
@@ -26,7 +26,7 @@ pub fn can_install() -> bool {
}
/// Ghidra's headless launcher: in the folder the person picked (Ghidra's own folder, or one that
/// holds it), then the managed install, then PATH.
/// holds it), then the managed install, then PATH, then where system packages put it.
pub fn headless(install_dir: Option<&Path>, store: &Path) -> Option<PathBuf> {
if let Some(dir) = install_dir {
if dir.is_file() {
@@ -36,7 +36,9 @@ pub fn headless(install_dir: Option<&Path>, store: &Path) -> Option<PathBuf> {
return Some(p);
}
}
in_child(&store.join(ID), launcher).or_else(|| crate::find_on_path(&["analyzeHeadless.bat", "analyzeHeadless"]))
in_child(&store.join(ID), launcher)
.or_else(|| crate::find_on_path(&["analyzeHeadless.bat", "analyzeHeadless"]))
.or_else(|| crate::well_known(ID).into_iter().find(|p| p.is_file()))
}
/// The Java runtime to run Ghidra with: the one the person set, else the managed one. `None` lets
@@ -150,7 +152,9 @@ mod tests {
let d = tempfile::tempdir().unwrap();
let store = d.path().join("store");
let exe = if cfg!(windows) { "analyzeHeadless.bat" } else { "analyzeHeadless" };
assert!(headless(None, &store).is_none() || crate::find_on_path(&[exe]).is_some());
// None, unless this computer has Ghidra on PATH or from a system package.
let installed = crate::find_on_path(&[exe]).is_some() || crate::well_known(ID).iter().any(|p| p.is_file());
assert!(headless(None, &store).is_none() || installed);
// The managed install: the zip's own top folder under <store>/ghidra.
let managed = store.join("ghidra/ghidra_12.1.4_PUBLIC/support").join(exe);
touch(&managed);
+11 -2
View File
@@ -82,7 +82,7 @@ fn in_managed(dir: &Path, exes: &[&str]) -> Option<PathBuf> {
}
/// Standard install locations for programs that don't put themselves on PATH (browsers on
/// Windows and macOS), most common first.
/// Windows and macOS, Ghidra from Linux packages), most common first.
pub fn well_known(id: &str) -> Vec<PathBuf> {
engine(id).map(ExternalEngine::known_install_paths).unwrap_or_default()
}
@@ -135,7 +135,9 @@ mod tests {
// Nothing but folders: still none, not a panic.
let empty = tempfile::tempdir().unwrap();
std::fs::create_dir_all(empty.path().join("ghidra").join("ghidra_12.1.4_PUBLIC")).unwrap();
assert!(locate("ghidra", &NoOverrides, empty.path()).is_none());
// (Unless this computer has Ghidra on PATH or from a system package.)
let installed = find_on_path(&[exe]).is_some() || well_known("ghidra").iter().any(|p| p.is_file());
assert!(locate("ghidra", &NoOverrides, empty.path()).is_none() || installed);
}
#[test]
@@ -146,5 +148,12 @@ mod tests {
assert!(paths.iter().any(|p| p.to_string_lossy().contains("Chrome")));
assert!(paths.iter().any(|p| p.to_string_lossy().contains("Edge")));
}
// #85: Linux packages put Ghidra in its own folder with only a start script on PATH.
let ghidra = well_known("ghidra");
if cfg!(target_os = "linux") {
assert_eq!(ghidra, [PathBuf::from("/opt/ghidra/support/analyzeHeadless"), PathBuf::from("/usr/share/ghidra/support/analyzeHeadless")]);
} else {
assert!(ghidra.is_empty());
}
}
}
+9 -1
View File
@@ -144,8 +144,16 @@ const fn mac(base: Base, rel: &'static str) -> KnownPath {
KnownPath { os: "macos", base, rel }
}
const fn linux(dir: &'static str, rel: &'static str) -> KnownPath {
KnownPath { os: "linux", base: Base::Dir(dir), rel }
}
const NO_PATHS: &[KnownPath] = &[];
/// Linux packages put Ghidra in a folder of its own and only a start script on PATH (`/usr/bin/ghidra`):
/// Arch and its derivatives in `/opt/ghidra`, Debian and Kali in `/usr/share/ghidra` (#85).
const GHIDRA_PATHS: &[KnownPath] = &[linux("/opt/ghidra", "support/analyzeHeadless"), linux("/usr/share/ghidra", "support/analyzeHeadless")];
/// Browsers on Windows and macOS rarely put themselves on PATH.
const CHROMIUM_PATHS: &[KnownPath] = &[
win("ProgramFiles", "Google/Chrome/Application/chrome.exe"),
@@ -322,7 +330,7 @@ pub const ENGINES: &[ExternalEngine] = &[
needed_for: "Deep mode: richer decompiled code",
exes: &["analyzeHeadless"],
exes_windows: &["analyzeHeadless.bat"],
known_paths: NO_PATHS,
known_paths: GHIDRA_PATHS,
path_setting: false,
install: Install::Guided { pins: GHIDRA_PINS, how: GHIDRA_SETUP },
},