use std::collections::BTreeMap;
use std::path::Path;
use std::process::Command;
use std::time::Duration;
use super::process;
pub fn run(readelf: &Path, args: &[String], file: &Path) -> Result<String, String> {
let mut command = Command::new(readelf);
command.arg("-W").args(args).arg(file).env("LC_ALL", "C");
let output = process::run(&mut command, Duration::from_secs(60))
.map_err(|e| format!("cannot run {}: {e}", readelf.display()))?;
if !output.success() {
return Err(format!(
"readelf -W {} {} failed ({}):\n{}",
args.join(" "),
file.display(),
output.describe_status(),
output.stderr_text()
));
}
Ok(output.stdout_text())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DynSym {
pub name: String,
pub version: String,
pub kind: String,
pub bind: String,
pub visibility: String,
pub section: String,
}
pub fn parse_dyn_syms(text: &str) -> Vec<DynSym> {
let mut symbols = Vec::new();
let mut in_dynsym = false;
for line in text.lines() {
if line.starts_with("Symbol table '") {
in_dynsym = line.starts_with("Symbol table '.dynsym'");
continue;
}
if !in_dynsym {
continue;
}
let Some((index, rest)) = line.split_once(':') else {
continue;
};
let Ok(index) = index.trim().parse::<u64>() else {
continue;
};
if index == 0 {
continue;
}
let mut fields = rest.split_whitespace();
let (Some(_value), Some(_size), Some(kind), Some(bind), Some(visibility)) = (
fields.next(),
fields.next(),
fields.next(),
fields.next(),
fields.next(),
) else {
continue;
};
let mut section = fields.next().unwrap_or_default().to_string();
if section.starts_with('[') {
for field in fields.by_ref() {
if field.ends_with(']') {
break;
}
}
section = fields.next().unwrap_or_default().to_string();
}
if kind == "SECTION" {
continue;
}
let mut full_name = fields.collect::<Vec<_>>().join(" ");
if full_name.ends_with(')')
&& let Some(open) = full_name.rfind(" (")
{
full_name.truncate(open);
}
let (name, version) = match full_name.find('@') {
Some(at) => (full_name[..at].to_string(), full_name[at..].to_string()),
None => (full_name, String::new()),
};
let section = match section.as_str() {
"UND" | "ABS" | "COM" => section,
_ => "DEF".to_string(),
};
symbols.push(DynSym {
name,
version,
kind: kind.to_string(),
bind: bind.to_string(),
visibility: visibility.to_string(),
section,
});
}
symbols
}
pub fn parse_dynamic(text: &str) -> Vec<(String, String)> {
let mut entries = Vec::new();
for line in text.lines() {
let line = line.trim_start();
if !line.starts_with("0x") {
continue;
}
let (Some(open), Some(close)) = (line.find('('), line.find(')')) else {
continue;
};
if close < open {
continue;
}
entries.push((
line[open + 1..close].to_string(),
line[close + 1..].trim().to_string(),
));
}
entries
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Reloc {
pub section: String,
pub kind: String,
pub symbol: String,
}
pub fn parse_relocs(text: &str) -> Vec<Reloc> {
let mut relocs = Vec::new();
let mut section = String::new();
for line in text.lines() {
if let Some(rest) = line.strip_prefix("Relocation section '") {
section = rest.split('\'').next().unwrap_or_default().to_string();
continue;
}
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() < 3
|| !fields[2].starts_with("R_")
|| !fields[0].bytes().all(|b| b.is_ascii_hexdigit())
{
continue;
}
let symbol = match fields.get(4) {
Some(name) if *name != "+" && *name != "-" => (*name).to_string(),
_ => String::new(),
};
relocs.push(Reloc {
section: section.clone(),
kind: fields[2].to_string(),
symbol,
});
}
relocs
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Section {
pub name: String,
pub kind: String,
pub flags: String,
}
pub fn parse_sections(text: &str) -> Vec<Section> {
let mut sections = Vec::new();
for line in text.lines() {
let trimmed = line.trim_start();
let Some(rest) = trimmed.strip_prefix('[') else {
continue;
};
let Some((index, rest)) = rest.split_once(']') else {
continue;
};
if index.trim().parse::<u64>().is_err() {
continue;
}
let fields: Vec<&str> = rest.split_whitespace().collect();
let (name, kind, flags) = match fields.len() {
10 => (fields[0], fields[1], fields[6]),
9 if fields[0] == "NULL" => continue,
9 => (fields[0], fields[1], ""),
_ => continue,
};
sections.push(Section {
name: name.to_string(),
kind: kind.to_string(),
flags: flags.to_string(),
});
}
sections
}
const SYMBOLIC_TAGS: &[&str] = &[
"NEEDED",
"SONAME",
"RPATH",
"RUNPATH",
"AUXILIARY",
"FILTER",
"FLAGS",
"FLAGS_1",
"PLTREL",
"BIND_NOW",
"TEXTREL",
];
pub fn properties(readelf: &Path, file: &Path) -> Result<Vec<String>, String> {
let arg = |a: &str| vec![a.to_string()];
let mut lines = Vec::new();
for symbol in parse_dyn_syms(&run(readelf, &arg("--dyn-syms"), file)?) {
lines.push(format!(
"dynsym: {}{} {} {} {} {}",
symbol.name,
symbol.version,
symbol.kind,
symbol.bind,
symbol.visibility,
symbol.section
));
}
let mut tags = BTreeMap::new();
for (tag, value) in parse_dynamic(&run(readelf, &arg("-d"), file)?) {
let line = if SYMBOLIC_TAGS.contains(&tag.as_str()) {
format!("dynamic: {tag} {value}")
} else {
format!("dynamic: {tag}")
};
tags.insert(line, ());
}
lines.extend(tags.into_keys());
let mut relocs: BTreeMap<String, usize> = BTreeMap::new();
for reloc in parse_relocs(&run(readelf, &arg("-r"), file)?) {
let symbol = if reloc.symbol.is_empty() {
"-".to_string()
} else {
reloc.symbol
};
*relocs
.entry(format!("reloc: {} {} {symbol}", reloc.section, reloc.kind))
.or_default() += 1;
}
for (reloc, count) in relocs {
lines.push(format!("{reloc} x{count}"));
}
for section in parse_sections(&run(readelf, &arg("-S"), file)?) {
lines.push(format!(
"section: {} {} [{}]",
section.name, section.kind, section.flags
));
}
lines.sort();
Ok(lines)
}
pub fn compare(left: &[String], right: &[String], ignore: &[String]) -> (Vec<String>, Vec<String>) {
let keep = |line: &&String| !ignore.iter().any(|pattern| line.contains(pattern.as_str()));
let mut counts: BTreeMap<&str, isize> = BTreeMap::new();
for line in left.iter().filter(keep) {
*counts.entry(line).or_default() += 1;
}
for line in right.iter().filter(keep) {
*counts.entry(line).or_default() -= 1;
}
let mut only_left = Vec::new();
let mut only_right = Vec::new();
for (line, count) in counts {
for _ in 0..count.max(0) {
only_left.push(line.to_string());
}
for _ in 0..(-count).max(0) {
only_right.push(line.to_string());
}
}
(only_left, only_right)
}