Move install nyx functionality from build script to runtime

It did not make a lot of sense to have that in the buildscript, as that will make nyx_runner dependent on the target directory.
This also allows specifying a binary to trace at runtime
This commit is contained in:
David Venhoff 2025-09-07 14:36:02 +02:00
parent 57ff2f187c
commit 2d88a8539f
8 changed files with 156 additions and 118 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target /target
*.log *.log
/nyx_data

31
Cargo.lock generated
View File

@ -130,7 +130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff"
dependencies = [ dependencies = [
"clap", "clap",
"heck", "heck 0.4.1",
"indexmap", "indexmap",
"log", "log",
"proc-macro2", "proc-macro2",
@ -180,18 +180,19 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.42" version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.42" version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -199,6 +200,18 @@ dependencies = [
"strsim", "strsim",
] ]
[[package]]
name = "clap_derive"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.5" version = "0.7.5"
@ -426,6 +439,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.10.0" version = "2.10.0"
@ -673,7 +692,7 @@ dependencies = [
name = "qemu-nyx-runner" name = "qemu-nyx-runner"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"client", "clap",
"csv", "csv",
"libnyx", "libnyx",
"pt-dump-decoder", "pt-dump-decoder",

View File

@ -8,7 +8,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
client = { path = "client" }
libnyx = { git = "https://git.cs.tu-dortmund.de/david.venhoff/libnyx-fork" } libnyx = { git = "https://git.cs.tu-dortmund.de/david.venhoff/libnyx-fork" }
pt-dump-decoder = { path = "pt-dump-decoder" } pt-dump-decoder = { path = "pt-dump-decoder" }
csv = "1.3.1" csv = "1.3.1"
clap = { version = "4.5.47", features = ["derive"] }

101
build.rs
View File

@ -1,101 +0,0 @@
use std::env;
use std::path::Path;
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=client");
let out_dir = env::var("OUT_DIR").unwrap();
let client_bin = build_client(&out_dir);
setup_nyx(&out_dir);
create_nyx_workdir(&out_dir, &client_bin);
}
#[track_caller]
fn shell(cwd: impl AsRef<Path>, command_string: &str) {
println!(
"cargo:warning=Running ({}) {command_string}",
cwd.as_ref().display()
);
let cwd = cwd.as_ref();
let mut parts = command_string.split(" ");
let command_name = parts.next().unwrap();
let mut command = Command::new(command_name);
command.current_dir(cwd);
command.stdout(std::process::Stdio::inherit());
command.stderr(std::process::Stdio::inherit());
command.args(parts);
let mut process = command.spawn().unwrap();
let exit_status = process.wait().unwrap();
if !exit_status.success() {
panic!(
"Command failed with {exit_status}: {}> {command_string}",
cwd.display()
);
}
}
/// Builds the client and returns the path to its binary
fn build_client(out_dir: &str) -> String {
// Change the target dir to avoid a cargo target dir deadlock
shell(
"client",
&format!("cargo build --release --target-dir {out_dir}/client_target"),
);
format!("{out_dir}/client_target/release/client")
}
/// Downloads and compiles qemu-nyx and packer and compiles them for the current architecture
/// This means that cross-compilation is not supported
fn setup_nyx(out_dir: &str) {
let packer_path = std::path::PathBuf::from(format!("{out_dir}/packer"));
let qemu_path = std::path::PathBuf::from(format!("{out_dir}/QEMU-Nyx"));
if !packer_path.exists() {
shell(out_dir, "git clone https://github.com/nyx-fuzz/packer/");
}
if !qemu_path.exists() {
println!("cargo:warning=Cloning and building QEMU-Nyx. This may take a while...");
shell(
out_dir,
"git clone https://github.com/nyx-fuzz/QEMU-Nyx --depth 1",
);
let is_debug_build = match env::var("DEBUG").unwrap().as_str() {
"true" => true,
"false" => false,
other => panic!("Invalid value for DEBUG: {other}"),
};
let qemu_compile_mode = if is_debug_build {
"debug_static"
} else {
"lto"
};
shell(
qemu_path,
&format!("bash compile_qemu_nyx.sh {qemu_compile_mode}"),
);
}
}
fn create_nyx_workdir(out_dir: &str, client_bin: &str) {
// Create the directory and move required binaries to it
shell(
out_dir,
&format!(
"python3 packer/packer/nyx_packer.py {client_bin} build afl processor_trace --fast_reload_mode --purge"
),
);
// Create the nyx config
shell(
out_dir,
"python3 packer/packer/nyx_config_gen.py build Kernel",
);
// Pass the path to the build directory to src/main.rs
println!("cargo:rustc-env=NYX_SHAREDIR={out_dir}/build")
}

View File

@ -30,7 +30,7 @@ const BENCHMARKS: &[&str] = &[
]; ];
fn main() { fn main() {
println!("cargo:rerun-if-changed=coremark/"); println!("cargo:rerun-if-changed=build.rs");
let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap();
Command::new("git") Command::new("git")

View File

@ -1,21 +1,23 @@
use crate::benchmark::{Benchmark, ToCsv}; use crate::benchmark::{Benchmark, ToCsv};
use crate::nyx;
use csv::Writer; use csv::Writer;
use libnyx::{NyxConfig, NyxProcess, NyxProcessRole, NyxReturnValue}; use libnyx::{NyxConfig, NyxProcess, NyxProcessRole, NyxReturnValue};
use pt_dump_decoder::{AnalyzeData, analyze_dump}; use pt_dump_decoder::{AnalyzeData, analyze_dump};
use std::error::Error; use std::error::Error;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf};
const WORKDIR_PATH: &str = "/tmp/wdir"; const WORKDIR_PATH: &str = "/tmp/wdir";
// The sharedir path gets set by the build script
const SHAREDIR_PATH: &str = env!("NYX_SHAREDIR");
pub struct NyxRunner(NyxProcess); pub struct NyxRunner(NyxProcess);
impl NyxRunner { impl NyxRunner {
pub fn setup() -> Result<Self, Box<dyn Error>> { pub fn setup(client_bin: &Path) -> Result<Self, Box<dyn Error>> {
let _ = std::fs::remove_dir_all(WORKDIR_PATH); let _ = std::fs::remove_dir_all(WORKDIR_PATH);
let sharedir = nyx::setup_nyx(&PathBuf::from("nyx_data"), client_bin)?;
let sharedir = sharedir.to_str().expect("Expected unicode path");
let mut nyx_config = NyxConfig::load(SHAREDIR_PATH)?; let mut nyx_config = NyxConfig::load(sharedir)?;
nyx_config.set_workdir_path(WORKDIR_PATH.to_string()); nyx_config.set_workdir_path(WORKDIR_PATH.to_string());
nyx_config.set_input_buffer_size(0x2000); nyx_config.set_input_buffer_size(0x2000);
nyx_config.set_nyx_debug_log_path("qemu_nyx.log".to_string()); nyx_config.set_nyx_debug_log_path("qemu_nyx.log".to_string());

View File

@ -1,19 +1,27 @@
//! Translated from libnyx/test.c //! Translated from libnyx/test.c
mod benchmark; mod benchmark;
mod benchmark_baseline;
mod benchmark_nyx_pt; mod benchmark_nyx_pt;
mod nyx;
use crate::benchmark::{Benchmark, ToCsv}; use crate::benchmark::{Benchmark, ToCsv};
use crate::benchmark_baseline::Baseline;
use crate::benchmark_nyx_pt::NyxRunner; use crate::benchmark_nyx_pt::NyxRunner;
use std::error::Error; use std::error::Error;
use std::path::PathBuf;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use clap::Parser;
#[derive(Debug, Parser)]
#[command(about="Tool to execute a program with nyx and trace it with intel pt")]
struct Args {
/// Path to the client binary to execute
client: PathBuf
}
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let nyx_runner = NyxRunner::setup()?; let args = Args::parse();
let nyx_runner = NyxRunner::setup(&args.client)?;
benchmark(nyx_runner)?; benchmark(nyx_runner)?;
benchmark(Baseline)?;
Ok(()) Ok(())
} }

109
src/nyx.rs Normal file
View File

@ -0,0 +1,109 @@
//! This module contains functionality to download and setup nyx,
//! so that it can be used to execute a binary.
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{fs, io};
/// Downloads and installs nyx
///
/// [out_dir] Is the directory to which all tools get downloaded and
/// where the working directory gets created.
///
/// [client] Is the path to the client binary.
///
/// Returns the path to the working directory
pub fn setup_nyx(out_dir: &Path, client: &Path) -> Result<PathBuf, io::Error> {
fn inner(out_dir: &Path, client: &Path) -> Result<PathBuf, io::Error> {
if out_dir.exists() {
println!("Nyx is already installed, skipping setup");
} else {
println!("Installing nxx...");
fs::create_dir(out_dir)?;
setup_nyx_tools(&out_dir)?;
}
create_nyx_workdir(&out_dir, &client)
}
// Clean up the out directory if something went wrong
match inner(out_dir, client) {
Ok(path) => Ok(path),
Err(err) => {
fs::remove_dir_all(out_dir)?;
Err(err)
}
}
}
#[track_caller]
fn shell(cwd: impl AsRef<Path>, command_string: &str) -> Result<(), io::Error> {
println!("Running ({}) {command_string}", cwd.as_ref().display());
let cwd = cwd.as_ref();
let mut parts = command_string.split(" ");
let command_name = parts.next().unwrap();
let mut command = Command::new(command_name);
command.current_dir(cwd);
command.stdout(std::process::Stdio::inherit());
command.stderr(std::process::Stdio::inherit());
command.args(parts);
let mut process = command.spawn()?;
let exit_status = process.wait()?;
if !exit_status.success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Command ({command_string}) failed with {exit_status}"),
));
}
Ok(())
}
/// Downloads and compiles qemu-nyx and packer and compiles them for the current architecture
/// This means that cross-compilation is not supported
fn setup_nyx_tools(out_dir: &Path) -> Result<(), io::Error> {
let packer_path = out_dir.join("packer");
let qemu_path = out_dir.join("QEMU-Nyx");
if !packer_path.exists() {
shell(out_dir, "git clone https://github.com/nyx-fuzz/packer/")?;
}
if !qemu_path.exists() {
println!("cargo:warning=Cloning and building QEMU-Nyx. This may take a while...");
shell(
out_dir,
"git clone https://github.com/nyx-fuzz/QEMU-Nyx --depth 1",
)?;
// Compile with maximum optimizations
let qemu_compile_mode = "lto";
shell(
qemu_path,
&format!("bash compile_qemu_nyx.sh {qemu_compile_mode}"),
)?;
}
Ok(())
}
fn create_nyx_workdir(out_dir: &Path, client_bin: &Path) -> Result<PathBuf, io::Error> {
let client_bin = fs::canonicalize(client_bin)?;
let client_bin = client_bin.to_str().expect("Expected unicode path");
// Create the directory and move required binaries to it
shell(
out_dir,
&format!(
"python3 packer/packer/nyx_packer.py {client_bin} build afl processor_trace --fast_reload_mode --purge"
),
)?;
// Create the nyx config
shell(
out_dir,
"python3 packer/packer/nyx_config_gen.py build Kernel",
)?;
Ok(out_dir.join("build"))
}