leviso_elf/
analyze.rs

1//! ELF binary analysis using readelf.
2
3use anyhow::Result;
4use std::collections::HashSet;
5use std::path::Path;
6use std::process::Command;
7
8use crate::paths::find_library;
9
10/// Extract library dependencies from an ELF binary using readelf.
11///
12/// This is architecture-independent - readelf reads the ELF headers directly
13/// without executing the binary, unlike ldd which uses the host dynamic linker.
14pub fn get_library_dependencies(binary_path: &Path) -> Result<Vec<String>> {
15    let output = Command::new("readelf")
16        .args(["-d"])
17        .arg(binary_path)
18        .output();
19
20    let output = match output {
21        Ok(o) => o,
22        Err(_) => return Ok(Vec::new()), // readelf not found or failed
23    };
24
25    if !output.status.success() {
26        // Not an ELF binary or readelf failed - return empty list
27        return Ok(Vec::new());
28    }
29
30    let stdout = String::from_utf8_lossy(&output.stdout);
31    parse_readelf_output(&stdout)
32}
33
34/// Parse readelf -d output to extract NEEDED library names.
35///
36/// Example readelf output:
37/// ```text
38/// Dynamic section at offset 0x2d0e0 contains 28 entries:
39///   Tag        Type                         Name/Value
40///  0x0000000000000001 (NEEDED)             Shared library: [libtinfo.so.6]
41///  0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
42/// ```
43pub fn parse_readelf_output(output: &str) -> Result<Vec<String>> {
44    let mut libs = Vec::new();
45
46    for line in output.lines() {
47        // Look for lines containing "(NEEDED)" and "Shared library:"
48        if line.contains("(NEEDED)") && line.contains("Shared library:") {
49            // Extract library name from [libname.so.X]
50            if let Some(start) = line.find('[') {
51                if let Some(end) = line.find(']') {
52                    let lib_name = &line[start + 1..end];
53                    libs.push(lib_name.to_string());
54                }
55            }
56        }
57    }
58
59    Ok(libs)
60}
61
62/// Recursively get all library dependencies (including transitive).
63///
64/// Some libraries depend on other libraries. We need to copy all of them.
65/// The `extra_lib_paths` parameter is passed to `find_library` for each lookup.
66pub fn get_all_dependencies(
67    source_root: &Path,
68    binary_path: &Path,
69    extra_lib_paths: &[&str],
70) -> Result<HashSet<String>> {
71    let mut all_libs = HashSet::new();
72    let mut to_process = vec![binary_path.to_path_buf()];
73    let mut processed = HashSet::new();
74
75    while let Some(path) = to_process.pop() {
76        if processed.contains(&path) {
77            continue;
78        }
79        processed.insert(path.clone());
80
81        let deps = get_library_dependencies(&path)?;
82        for lib_name in deps {
83            if all_libs.insert(lib_name.clone()) {
84                // New library - find it and check its dependencies too
85                if let Some(lib_path) = find_library(source_root, &lib_name, extra_lib_paths) {
86                    to_process.push(lib_path);
87                }
88            }
89        }
90    }
91
92    Ok(all_libs)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_parse_readelf_output() {
101        let output = r#"
102Dynamic section at offset 0x2d0e0 contains 28 entries:
103  Tag        Type                         Name/Value
104 0x0000000000000001 (NEEDED)             Shared library: [libtinfo.so.6]
105 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
106 0x000000000000000c (INIT)               0x5000
107"#;
108        let libs = parse_readelf_output(output).unwrap();
109        assert_eq!(libs, vec!["libtinfo.so.6", "libc.so.6"]);
110    }
111
112    #[test]
113    fn test_parse_readelf_empty() {
114        let output = "not an ELF file";
115        let libs = parse_readelf_output(output).unwrap();
116        assert!(libs.is_empty());
117    }
118}