mirror of https://github.com/aya-rs/aya
Merge branch 'aya-rs:main' into lsm_sleepable
commit
1f2006bfde
@ -0,0 +1,28 @@
|
|||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u64);
|
||||||
|
__uint(max_entries, 2);
|
||||||
|
} RESULTS SEC(".maps");
|
||||||
|
|
||||||
|
static __u64
|
||||||
|
inc_cb(void *map, __u32 *key, void *val,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
__u64 *value = val;
|
||||||
|
*value += 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/test_text_64_64_reloc")
|
||||||
|
int test_text_64_64_reloc(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
bpf_for_each_map_elem(&RESULTS, inc_cb, NULL, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::hint;
|
||||||
|
|
||||||
|
use aya_bpf::{
|
||||||
|
macros::{map, uprobe},
|
||||||
|
maps::Array,
|
||||||
|
programs::ProbeContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[map]
|
||||||
|
static mut RESULTS: Array<u64> = Array::with_max_entries(3, 0);
|
||||||
|
|
||||||
|
#[uprobe]
|
||||||
|
pub fn test_64_32_call_relocs(_ctx: ProbeContext) {
|
||||||
|
// this will link set_result and do a forward call
|
||||||
|
set_result(0, hint::black_box(1));
|
||||||
|
|
||||||
|
// set_result is already linked, this will just do the forward call
|
||||||
|
set_result(1, hint::black_box(2));
|
||||||
|
|
||||||
|
// this will link set_result_backward after set_result. Then will do a
|
||||||
|
// backward call to set_result.
|
||||||
|
set_result_backward(2, hint::black_box(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn set_result(index: u32, value: u64) {
|
||||||
|
unsafe {
|
||||||
|
if let Some(v) = RESULTS.get_ptr_mut(index) {
|
||||||
|
*v = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn set_result_backward(index: u32, value: u64) {
|
||||||
|
set_result(index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
unsafe { core::hint::unreachable_unchecked() }
|
||||||
|
}
|
@ -0,0 +1,313 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
|
||||||
|
|
||||||
|
use super::integration_test;
|
||||||
|
|
||||||
|
// In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
|
||||||
|
// special meaning, they just have "nice" bit patterns that can be helpful while debugging.
|
||||||
|
|
||||||
|
#[integration_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[integration_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[integration_test]
|
||||||
|
fn relocate_enum_signed() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[integration_test]
|
||||||
|
fn relocate_enum64() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[integration_test]
|
||||||
|
fn relocate_enum64_signed() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[integration_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility code for running relocation tests:
|
||||||
|
/// - Generates the eBPF program using probided local definition and relocation code
|
||||||
|
/// - Generates the BTF from the target btf code
|
||||||
|
struct RelocationTest {
|
||||||
|
/// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
|
||||||
|
local_definition: &'static str,
|
||||||
|
/// Target data structure definition. What the vmlinux would actually contain.
|
||||||
|
target_btf: &'static str,
|
||||||
|
/// Code executed by the eBPF program to test the relocation.
|
||||||
|
/// The format should be:
|
||||||
|
// __u8 memory[] = { ... };
|
||||||
|
// __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
|
||||||
|
//
|
||||||
|
// The generated code will be executed by attaching a tracepoint to sched_switch
|
||||||
|
// and emitting `__u32 value` an a map. See the code template below for more details.
|
||||||
|
relocation_code: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelocationTest {
|
||||||
|
/// Build a RelocationTestRunner
|
||||||
|
fn build(&self) -> Result<RelocationTestRunner> {
|
||||||
|
Ok(RelocationTestRunner {
|
||||||
|
ebpf: self.build_ebpf()?,
|
||||||
|
btf: self.build_btf()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// - 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!(
|
||||||
|
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__((section("tracepoint/bpf_prog"), used))
|
||||||
|
int bpf_prog(void *ctx) {{
|
||||||
|
__u32 key = 0;
|
||||||
|
__u64 value = 0;
|
||||||
|
{relocation_code}
|
||||||
|
bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// - 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;
|
||||||
|
// 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!(
|
||||||
|
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")?;
|
||||||
|
Command::new("llvm-objcopy")
|
||||||
|
.current_dir(tmp_dir.path())
|
||||||
|
.args(["--dump-section", ".BTF=target.btf"])
|
||||||
|
.arg(compiled_file)
|
||||||
|
.status()
|
||||||
|
.context("Failed to run llvm-objcopy")?
|
||||||
|
.success()
|
||||||
|
.then_some(())
|
||||||
|
.context("Failed to extract BTF")?;
|
||||||
|
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
|
||||||
|
.context("Error parsing generated BTF")?;
|
||||||
|
Ok(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")?;
|
||||||
|
Command::new("clang")
|
||||||
|
.current_dir(&tmp_dir)
|
||||||
|
.args(["-c", "-g", "-O2", "-target", "bpf"])
|
||||||
|
.arg(&source)
|
||||||
|
.status()
|
||||||
|
.context("Failed to run clang")?
|
||||||
|
.success()
|
||||||
|
.then_some(())
|
||||||
|
.context("Failed to compile eBPF source")?;
|
||||||
|
Ok((tmp_dir, source.with_extension("o")))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RelocationTestRunner {
|
||||||
|
ebpf: Vec<u8>,
|
||||||
|
btf: Btf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelocationTestRunner {
|
||||||
|
/// Run test and return the output value
|
||||||
|
fn run(&self) -> Result<u64> {
|
||||||
|
self.run_internal(true).context("Error running with BTF")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run without loading 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")?;
|
||||||
|
// Attach to sched_switch and wait some time to make sure it executed at least once
|
||||||
|
program
|
||||||
|
.attach("sched", "sched_switch")
|
||||||
|
.context("attach failed")?;
|
||||||
|
sleep(Duration::from_millis(1000));
|
||||||
|
// To inspect the loaded eBPF bytecode, increse the timeout and run:
|
||||||
|
// $ sudo bpftool prog dump xlated name bpf_prog
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -1,313 +1,70 @@
|
|||||||
use anyhow::{Context, Result};
|
use std::{process::exit, time::Duration};
|
||||||
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
|
|
||||||
use tempfile::TempDir;
|
|
||||||
|
|
||||||
use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
|
use aya::{
|
||||||
|
include_bytes_aligned,
|
||||||
use super::{integration_test, IntegrationTest};
|
programs::{ProgramError, UProbe},
|
||||||
|
Bpf,
|
||||||
// In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
|
|
||||||
// special meaning, they just have "nice" bit patterns that can be helpful while debugging.
|
|
||||||
|
|
||||||
#[integration_test]
|
|
||||||
fn relocate_field() {
|
|
||||||
let test = RelocationTest {
|
|
||||||
local_definition: r#"
|
|
||||||
struct foo {
|
|
||||||
__u8 a;
|
|
||||||
__u8 b;
|
|
||||||
__u8 c;
|
|
||||||
__u8 d;
|
|
||||||
};
|
};
|
||||||
"#,
|
use integration_test_macros::integration_test;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[integration_test]
|
#[integration_test]
|
||||||
fn relocate_enum() {
|
fn relocations() {
|
||||||
let test = RelocationTest {
|
let bpf = load_and_attach(
|
||||||
local_definition: r#"
|
"test_64_32_call_relocs",
|
||||||
enum foo { D = 0xAAAAAAAA };
|
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/relocations"),
|
||||||
"#,
|
);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[integration_test]
|
trigger_relocations_program();
|
||||||
fn relocate_enum_signed() {
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[integration_test]
|
let m = aya::maps::Array::<_, u64>::try_from(bpf.map("RESULTS").unwrap()).unwrap();
|
||||||
fn relocate_enum64() {
|
assert_eq!(m.get(&0, 0).unwrap(), 1);
|
||||||
let test = RelocationTest {
|
assert_eq!(m.get(&1, 0).unwrap(), 2);
|
||||||
local_definition: r#"
|
assert_eq!(m.get(&2, 0).unwrap(), 3);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[integration_test]
|
#[integration_test]
|
||||||
fn relocate_enum64_signed() {
|
fn text_64_64_reloc() {
|
||||||
let test = RelocationTest {
|
let mut bpf = load_and_attach(
|
||||||
local_definition: r#"
|
"test_text_64_64_reloc",
|
||||||
enum foo { D = -0xAAAAAAABBBBBBBB };
|
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/text_64_64_reloc.o"),
|
||||||
"#,
|
);
|
||||||
target_btf: r#"
|
|
||||||
enum foo { D = -0xCCCCCCCDDDDDDDD } e1;
|
let mut m = aya::maps::Array::<_, u64>::try_from(bpf.map_mut("RESULTS").unwrap()).unwrap();
|
||||||
"#,
|
m.set(0, 1, 0).unwrap();
|
||||||
relocation_code: r#"
|
m.set(1, 2, 0).unwrap();
|
||||||
#define BPF_ENUMVAL_VALUE 1
|
|
||||||
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
|
trigger_relocations_program();
|
||||||
"#,
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
}
|
|
||||||
.build()
|
assert_eq!(m.get(&0, 0).unwrap(), 2);
|
||||||
.unwrap();
|
assert_eq!(m.get(&1, 0).unwrap(), 3);
|
||||||
assert_eq!(test.run().unwrap() as i64, -0xCCCCCCCDDDDDDDDi64);
|
}
|
||||||
assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
|
|
||||||
}
|
fn load_and_attach(name: &str, bytes: &[u8]) -> Bpf {
|
||||||
|
let mut bpf = Bpf::load(bytes).unwrap();
|
||||||
|
|
||||||
|
let prog: &mut UProbe = bpf.program_mut(name).unwrap().try_into().unwrap();
|
||||||
|
if let Err(ProgramError::LoadError {
|
||||||
|
io_error,
|
||||||
|
verifier_log,
|
||||||
|
}) = prog.load()
|
||||||
|
{
|
||||||
|
println!("Failed to load program `{name}`: {io_error}. Verifier log:\n{verifier_log:#}");
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
#[integration_test]
|
prog.attach(
|
||||||
fn relocate_pointer() {
|
Some("trigger_relocations_program"),
|
||||||
let test = RelocationTest {
|
0,
|
||||||
local_definition: r#"
|
"/proc/self/exe",
|
||||||
struct foo {};
|
None,
|
||||||
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();
|
.unwrap();
|
||||||
assert_eq!(test.run().unwrap(), 42);
|
|
||||||
assert_eq!(test.run_no_btf().unwrap(), 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility code for running relocation tests:
|
bpf
|
||||||
/// - Generates the eBPF program using probided local definition and relocation code
|
|
||||||
/// - Generates the BTF from the target btf code
|
|
||||||
struct RelocationTest {
|
|
||||||
/// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
|
|
||||||
local_definition: &'static str,
|
|
||||||
/// Target data structure definition. What the vmlinux would actually contain.
|
|
||||||
target_btf: &'static str,
|
|
||||||
/// Code executed by the eBPF program to test the relocation.
|
|
||||||
/// The format should be:
|
|
||||||
// __u8 memory[] = { ... };
|
|
||||||
// __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
|
|
||||||
//
|
|
||||||
// The generated code will be executed by attaching a tracepoint to sched_switch
|
|
||||||
// and emitting `__u32 value` an a map. See the code template below for more details.
|
|
||||||
relocation_code: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelocationTest {
|
#[no_mangle]
|
||||||
/// Build a RelocationTestRunner
|
#[inline(never)]
|
||||||
fn build(&self) -> Result<RelocationTestRunner> {
|
pub extern "C" fn trigger_relocations_program() {}
|
||||||
Ok(RelocationTestRunner {
|
|
||||||
ebpf: self.build_ebpf()?,
|
|
||||||
btf: self.build_btf()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// - 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!(
|
|
||||||
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__((section("tracepoint/bpf_prog"), used))
|
|
||||||
int bpf_prog(void *ctx) {{
|
|
||||||
__u32 key = 0;
|
|
||||||
__u64 value = 0;
|
|
||||||
{relocation_code}
|
|
||||||
bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// - 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;
|
|
||||||
// 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!(
|
|
||||||
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")?;
|
|
||||||
Command::new("llvm-objcopy")
|
|
||||||
.current_dir(tmp_dir.path())
|
|
||||||
.args(["--dump-section", ".BTF=target.btf"])
|
|
||||||
.arg(compiled_file)
|
|
||||||
.status()
|
|
||||||
.context("Failed to run llvm-objcopy")?
|
|
||||||
.success()
|
|
||||||
.then_some(())
|
|
||||||
.context("Failed to extract BTF")?;
|
|
||||||
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
|
|
||||||
.context("Error parsing generated BTF")?;
|
|
||||||
Ok(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")?;
|
|
||||||
Command::new("clang")
|
|
||||||
.current_dir(&tmp_dir)
|
|
||||||
.args(["-c", "-g", "-O2", "-target", "bpf"])
|
|
||||||
.arg(&source)
|
|
||||||
.status()
|
|
||||||
.context("Failed to run clang")?
|
|
||||||
.success()
|
|
||||||
.then_some(())
|
|
||||||
.context("Failed to compile eBPF source")?;
|
|
||||||
Ok((tmp_dir, source.with_extension("o")))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RelocationTestRunner {
|
|
||||||
ebpf: Vec<u8>,
|
|
||||||
btf: Btf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelocationTestRunner {
|
|
||||||
/// Run test and return the output value
|
|
||||||
fn run(&self) -> Result<u64> {
|
|
||||||
self.run_internal(true).context("Error running with BTF")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run without loading 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")?;
|
|
||||||
// Attach to sched_switch and wait some time to make sure it executed at least once
|
|
||||||
program
|
|
||||||
.attach("sched", "sched_switch")
|
|
||||||
.context("attach failed")?;
|
|
||||||
sleep(Duration::from_millis(1000));
|
|
||||||
// To inspect the loaded eBPF bytecode, increse the timeout and run:
|
|
||||||
// $ sudo bpftool prog dump xlated name bpf_prog
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue