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