integration-test: compile without touching disk

pull/644/head
Tamir Duberstein 1 year ago
parent 3d463a3610
commit c76d5a9950
No known key found for this signature in database

@ -18,7 +18,6 @@ object = { version = "0.31", default-features = false, features = [
"elf",
] }
rbpf = "0.2.0"
tempfile = "3.3.0"
tokio = { version = "1.24", default-features = false, features = ["time"] }
[build-dependencies]

@ -1,6 +1,9 @@
use anyhow::{bail, Context as _, Result};
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
use tempfile::TempDir;
use anyhow::{anyhow, bail, Context as _, Result};
use std::{
process::{Child, ChildStdout, Command, Stdio},
thread::sleep,
time::Duration,
};
use aya::{maps::Array, programs::TracePoint, util::KernelVersion, BpfLoader, Btf, Endianness};
@ -215,9 +218,15 @@ impl RelocationTest {
/// - Generate the source eBPF filling a template
/// - Compile it with clang
fn build_ebpf(&self) -> Result<Vec<u8>> {
let local_definition = self.local_definition;
let relocation_code = self.relocation_code;
let (_tmp_dir, compiled_file) = compile(&format!(
use std::io::Read as _;
let Self {
local_definition,
relocation_code,
..
} = self;
let mut stdout = compile(&format!(
r#"
#include <linux/bpf.h>
@ -250,23 +259,29 @@ impl RelocationTest {
char _license[] __attribute__((section("license"), used)) = "GPL";
"#
))
.context("Failed to compile eBPF program")?;
let bytecode =
std::fs::read(compiled_file).context("Error reading compiled eBPF program")?;
Ok(bytecode)
.context("failed to compile eBPF program")?;
let mut output = Vec::new();
stdout.read_to_end(&mut output)?;
Ok(output)
}
/// - Generate the target BTF source with a mock main()
/// - Compile it with clang
/// - Extract the BTF with llvm-objcopy
fn build_btf(&self) -> Result<Btf> {
let target_btf = self.target_btf;
let relocation_code = self.relocation_code;
use std::io::Read as _;
let Self {
target_btf,
relocation_code,
..
} = self;
// BTF files can be generated and inspected with these commands:
// $ clang -c -g -O2 -target bpf target.c
// $ pahole --btf_encode_detached=target.btf -V target.o
// $ bpftool btf dump file ./target.btf format c
let (tmp_dir, compiled_file) = compile(&format!(
let stdout = compile(&format!(
r#"
#include <linux/bpf.h>
@ -280,14 +295,20 @@ impl RelocationTest {
}}
"#
))
.context("Failed to compile BTF")?;
.context("failed to compile BTF")?;
let mut cmd = Command::new("llvm-objcopy");
cmd.current_dir(tmp_dir.path())
.args(["--dump-section", ".BTF=target.btf"])
.arg(compiled_file);
let status = cmd
.status()
.with_context(|| format!("Failed to run {cmd:?}"))?;
cmd.args(["--dump-section", ".BTF=-", "-"])
.stdin(stdout)
.stdout(Stdio::piped());
let mut child = cmd
.spawn()
.with_context(|| format!("failed to spawn {cmd:?}"))?;
let Child { stdout, .. } = &mut child;
let mut stdout = stdout.take().ok_or(anyhow!("failed to open stdout"))?;
let status = child
.wait()
.with_context(|| format!("failed to wait for {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => {}
@ -295,25 +316,39 @@ impl RelocationTest {
},
None => bail!("{cmd:?} terminated by signal"),
}
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
.context("Error parsing generated BTF")?;
Ok(btf)
let mut output = Vec::new();
stdout.read_to_end(&mut output)?;
Btf::parse(output.as_slice(), Endianness::default())
.context("failed to parse generated BTF")
}
}
/// Compile an eBPF program and return the path of the compiled object.
/// Also returns a TempDir handler, dropping it will clear the created dicretory.
fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
let source = tmp_dir.path().join("source.c");
std::fs::write(&source, source_code).context("Writing bpf program failed")?;
/// Compile an eBPF program and return its bytes.
fn compile(source_code: &str) -> Result<ChildStdout> {
use std::io::Write as _;
let mut cmd = Command::new("clang");
cmd.current_dir(&tmp_dir)
.args(["-c", "-g", "-O2", "-target", "bpf"])
.arg(&source);
let status = cmd
.status()
.with_context(|| format!("Failed to run {cmd:?}"))?;
cmd.args([
"-c", "-g", "-O2", "-target", "bpf", "-x", "c", "-", "-o", "-",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped());
let mut child = cmd
.spawn()
.with_context(|| format!("failed to spawn {cmd:?}"))?;
let Child { stdin, stdout, .. } = &mut child;
{
let mut stdin = stdin.take().ok_or(anyhow!("failed to open stdin"))?;
stdin
.write_all(source_code.as_bytes())
.context("failed to write to stdin")?;
}
let stdout = stdout.take().ok_or(anyhow!("failed to open stdout"))?;
let status = child
.wait()
.with_context(|| format!("failed to wait for {cmd:?}"))?;
match status.code() {
Some(code) => match code {
0 => {}
@ -321,7 +356,7 @@ fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
},
None => bail!("{cmd:?} terminated by signal"),
}
Ok((tmp_dir, source.with_extension("o")))
Ok(stdout)
}
struct RelocationTestRunner {

Loading…
Cancel
Save