use capstone::prelude::*;
use goblin::Object;
use std::{
fs,
io::Write,
path::Path,
process::{Command, Stdio},
};
pub fn test_no_args(path: &str) {
let output = Command::new(path).output().expect("Failed to run binary");
assert!(!output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stdout.is_empty(), "stdout: {}", stdout);
assert!(
stderr.contains(format!("Usage: {} command [args]", path).as_str()),
"stderr: {}",
stderr
);
assert!(
stderr.contains("Error: EINVAL: Invalid argument"),
"stderr: {}",
stderr
);
}
pub fn test_no_exec(path: &str) {
let output = Command::new(path)
.arg("bla")
.output()
.expect("Failed to run binary");
assert!(!output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stdout.is_empty(), "stdout: {}", stdout);
assert!(
stderr.contains("Error: ENOENT: No such file or directory"),
"stderr: {}",
stderr
);
}
fn disass(path: &str) -> Vec<String> {
let capstone = Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.syntax(arch::x86::ArchSyntax::Att)
.detail(true)
.build()
.expect("Failed to create Capstone object");
let buffer = fs::read(path).expect("Failed to read binary");
let mut ret = Vec::new();
match Object::parse(&buffer).expect("Failed to parse binary") {
Object::Elf(elf) => {
for ph in elf.program_headers.iter() {
if ph.is_executable() {
let code = &buffer[ph.file_range()];
let instructions = capstone
.disasm_all(code, elf.header.e_entry)
.expect("Failed to disassemble code");
for ins in instructions.iter() {
let ins_str = format!(
"{}\t{}",
ins.mnemonic().unwrap_or(""),
ins.op_str().unwrap_or("")
);
ret.push(String::from(ins_str.trim()));
}
}
}
}
_ => assert!(false, "Unsupported binary format."),
}
ret
}
pub fn test_cov(path: &str) {
let test_bin = "/bin/ls";
let output = Command::new(path)
.arg(&test_bin)
.output()
.expect("Failed to run binary");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success());
let disass = disass(test_bin);
for line in stderr.lines() {
if (line.contains("j") || line.contains("call") || line.contains("push"))
&& line.contains("0x")
{
continue;
}
if line.contains("+++") || line.contains("---") {
continue;
}
if let Some((_, insn)) = line.split_once(' ') {
let insn_trimmed = insn.trim();
assert!(
disass.contains(&insn_trimmed.to_string()),
"Disassembly does not contain: {}",
insn_trimmed
);
}
}
}
pub struct Cleanup<'a> {
pub path: &'a Path,
}
impl<'a> Drop for Cleanup<'a> {
fn drop(&mut self) {
if self.path.exists() {
fs::remove_file(self.path).expect("Failed to clean up compiled binary");
}
let path = self.path.with_extension("c.cov");
if path.exists() {
fs::remove_file(path).expect("Failed to clean up compiled binary");
}
}
}
fn build_example(dwarf_version: &str) -> (String, String) {
let root = env!("CARGO_MANIFEST_DIR");
let example_dir = format!("{}/tests/example", root);
let example_path = format!("{}/example.c", &example_dir);
let example_bin = format!("{}/example", &example_dir);
let output = Command::new("gcc")
.arg(&dwarf_version)
.arg("-no-pie")
.arg("-o")
.arg(&example_bin)
.arg(&example_path)
.output()
.expect("Failed to compile binary");
assert!(
output.status.success(),
"Compilation failed: {}",
String::from_utf8_lossy(&output.stderr)
);
(example_path, example_bin)
}
pub fn build_example_no_dwarf() -> (String, String) {
build_example("-g0")
}
pub fn build_example_gdwarf4() -> (String, String) {
build_example("-gdwarf-4")
}
pub fn build_example_gdwarf5() -> (String, String) {
build_example("-gdwarf-5")
}
pub fn run_cli_commands(
binary: &str,
target: &str,
commands: &[&str],
) -> (String, String, bool) {
let mut child = Command::new(binary)
.arg(target)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to start CLI process");
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for command in commands {
writeln!(stdin, "{}", command).expect("Failed to write to stdin");
}
let output = child.wait_with_output().expect("Failed to read output");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(stdout, stderr, output.status.success())
}