123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- use anyhow::{bail, Context as _, Result};
- use procfs::KernelVersion;
- use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
- use tempfile::TempDir;
- use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
- #[test]
- fn relocate_field() {
- let test = RelocationTest {
- local_definition: r#"
- struct foo {
- __u8 a;
- __u8 b;
- __u8 c;
- __u8 d;
- };
- "#,
- target_btf: r#"
- struct foo {
- __u8 a;
- __u8 c;
- __u8 b;
- __u8 d;
- } s1;
- "#,
- relocation_code: r#"
- __u8 memory[] = {1, 2, 3, 4};
- struct foo *ptr = (struct foo *) &memory;
- value = __builtin_preserve_access_index(ptr->c);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap(), 2);
- assert_eq!(test.run_no_btf().unwrap(), 3);
- }
- #[test]
- fn relocate_enum() {
- let test = RelocationTest {
- local_definition: r#"
- enum foo { D = 0xAAAAAAAA };
- "#,
- target_btf: r#"
- enum foo { D = 0xBBBBBBBB } e1;
- "#,
- relocation_code: r#"
- #define BPF_ENUMVAL_VALUE 1
- value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap(), 0xBBBBBBBB);
- assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA);
- }
- #[test]
- fn relocate_enum_signed() {
- let kernel_version = KernelVersion::current().unwrap();
- if kernel_version < KernelVersion::new(6, 0, 0) {
- eprintln!("skipping test on kernel {kernel_version:?}, support for signed enum was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
- return;
- }
- let test = RelocationTest {
- local_definition: r#"
- enum foo { D = -0x7AAAAAAA };
- "#,
- target_btf: r#"
- enum foo { D = -0x7BBBBBBB } e1;
- "#,
- relocation_code: r#"
- #define BPF_ENUMVAL_VALUE 1
- value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap() as i64, -0x7BBBBBBBi64);
- assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64);
- }
- #[test]
- fn relocate_enum64() {
- let kernel_version = KernelVersion::current().unwrap();
- if kernel_version < KernelVersion::new(6, 0, 0) {
- eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
- return;
- }
- let test = RelocationTest {
- local_definition: r#"
- enum foo { D = 0xAAAAAAAABBBBBBBB };
- "#,
- target_btf: r#"
- enum foo { D = 0xCCCCCCCCDDDDDDDD } e1;
- "#,
- relocation_code: r#"
- #define BPF_ENUMVAL_VALUE 1
- value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap(), 0xCCCCCCCCDDDDDDDD);
- assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB);
- }
- #[test]
- fn relocate_enum64_signed() {
- let kernel_version = KernelVersion::current().unwrap();
- if kernel_version < KernelVersion::new(6, 0, 0) {
- eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
- return;
- }
- let test = RelocationTest {
- local_definition: r#"
- enum foo { D = -0xAAAAAAABBBBBBBB };
- "#,
- target_btf: r#"
- enum foo { D = -0xCCCCCCCDDDDDDDD } e1;
- "#,
- relocation_code: r#"
- #define BPF_ENUMVAL_VALUE 1
- value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap() as i64, -0xCCCCCCCDDDDDDDDi64);
- assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
- }
- #[test]
- fn relocate_pointer() {
- let test = RelocationTest {
- local_definition: r#"
- struct foo {};
- struct bar { struct foo *f; };
- "#,
- target_btf: r#"
- struct foo {};
- struct bar { struct foo *f; };
- "#,
- relocation_code: r#"
- __u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0};
- struct bar* ptr = (struct bar *) &memory;
- value = (__u64) __builtin_preserve_access_index(ptr->f);
- "#,
- }
- .build()
- .unwrap();
- assert_eq!(test.run().unwrap(), 42);
- assert_eq!(test.run_no_btf().unwrap(), 42);
- }
- #[test]
- fn relocate_struct_flavors() {
- let definition = r#"
- struct foo {};
- struct bar { struct foo *f; };
- struct bar___cafe { struct foo *e; struct foo *f; };
- "#;
- let relocation_code = r#"
- __u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0};
- struct bar* ptr = (struct bar *) &memory;
- if (__builtin_preserve_field_info((((typeof(struct bar___cafe) *)0)->e), 2)) {
- value = (__u64) __builtin_preserve_access_index(((struct bar___cafe *)ptr)->e);
- } else {
- value = (__u64) __builtin_preserve_access_index(ptr->f);
- }
- "#;
- let test_no_flavor = RelocationTest {
- local_definition: definition,
- target_btf: definition,
- relocation_code,
- }
- .build()
- .unwrap();
- assert_eq!(test_no_flavor.run_no_btf().unwrap(), 42);
- }
- struct RelocationTest {
-
- local_definition: &'static str,
-
- target_btf: &'static str,
-
-
-
-
-
-
-
- relocation_code: &'static str,
- }
- impl RelocationTest {
-
- fn build(&self) -> Result<RelocationTestRunner> {
- Ok(RelocationTestRunner {
- ebpf: self.build_ebpf()?,
- btf: self.build_btf()?,
- })
- }
-
-
- 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!(
- r#"
- #include <linux/bpf.h>
- static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
- {local_definition}
- struct {{
- int (*type)[BPF_MAP_TYPE_ARRAY];
- __u32 *key;
- __u64 *value;
- int (*max_entries)[1];
- }} output_map
- __attribute__((section(".maps"), used));
- __attribute__ ((noinline)) int bpf_func() {{
- __u32 key = 0;
- __u64 value = 0;
- {relocation_code}
- bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
- return 0;
- }}
- __attribute__((section("tracepoint/bpf_prog"), used))
- int bpf_prog(void *ctx) {{
- bpf_func();
- return 0;
- }}
- 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)
- }
-
-
-
- fn build_btf(&self) -> Result<Btf> {
- let target_btf = self.target_btf;
- let relocation_code = self.relocation_code;
-
-
-
-
- let (tmp_dir, compiled_file) = compile(&format!(
- r#"
- #include <linux/bpf.h>
- {target_btf}
- int main() {{
- __u64 value = 0;
- // This is needed to make sure to emit BTF for the defined types,
- // it could be dead code eliminated if we don't.
- {relocation_code};
- return value;
- }}
- "#
- ))
- .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:?}"))?;
- match status.code() {
- Some(code) => match code {
- 0 => {}
- code => bail!("{cmd:?} exited with code {code}"),
- },
- 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)
- }
- }
- 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")?;
- 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:?}"))?;
- match status.code() {
- Some(code) => match code {
- 0 => {}
- code => bail!("{cmd:?} exited with code {code}"),
- },
- None => bail!("{cmd:?} terminated by signal"),
- }
- Ok((tmp_dir, source.with_extension("o")))
- }
- struct RelocationTestRunner {
- ebpf: Vec<u8>,
- btf: Btf,
- }
- impl RelocationTestRunner {
-
- fn run(&self) -> Result<u64> {
- self.run_internal(true).context("Error running with BTF")
- }
-
- fn run_no_btf(&self) -> Result<u64> {
- self.run_internal(false)
- .context("Error running without BTF")
- }
- fn run_internal(&self, with_relocations: bool) -> Result<u64> {
- let mut loader = BpfLoader::new();
- if with_relocations {
- loader.btf(Some(&self.btf));
- } else {
- loader.btf(None);
- }
- let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
- let program: &mut TracePoint = bpf
- .program_mut("bpf_prog")
- .context("bpf_prog not found")?
- .try_into()
- .context("program not a tracepoint")?;
- program.load().context("Loading tracepoint failed")?;
-
- program
- .attach("sched", "sched_switch")
- .context("attach failed")?;
- sleep(Duration::from_millis(1000));
-
-
- let output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap();
- let key = 0;
- output_map.get(&key, 0).context("Getting key 0 failed")
- }
- }
|