mirror of https://github.com/aya-rs/aya
integration-test: Remove runtime toolchain deps
Move the use of clang and llvm-objcopy from run-time to build-time. This allows the integration tests to run on VMs with simpler userlands. Create a new CI job to build the integration tests separately from running them. Ship them from that job to the runner job using github actions artifacts.reviewable/pr706/r2
parent
5a398504cf
commit
dca5e6c167
@ -0,0 +1,106 @@
|
|||||||
|
// clang-format off
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_core_read.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
char _license[] __attribute__((section("license"), used)) = "GPL";
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u64);
|
||||||
|
__uint(max_entries, 1);
|
||||||
|
} output_map SEC(".maps");
|
||||||
|
|
||||||
|
long set_output(__u64 value) {
|
||||||
|
__u32 key = 0;
|
||||||
|
return bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct relocated_struct_with_scalars {
|
||||||
|
__u8 a;
|
||||||
|
__u8 b;
|
||||||
|
__u8 c;
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((noinline)) int field_global() {
|
||||||
|
struct relocated_struct_with_scalars s = {1, 2, 3};
|
||||||
|
return set_output(__builtin_preserve_access_index(s.b));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/field") int field(void *ctx) {
|
||||||
|
return field_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct relocated_struct_with_pointer {
|
||||||
|
struct relocated_struct_with_pointer *first;
|
||||||
|
struct relocated_struct_with_pointer *second;
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((noinline)) int pointer_global() {
|
||||||
|
struct relocated_struct_with_pointer s = {
|
||||||
|
(struct relocated_struct_with_pointer *)42,
|
||||||
|
(struct relocated_struct_with_pointer *)21,
|
||||||
|
};
|
||||||
|
return set_output((__u64)__builtin_preserve_access_index(s.first));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/pointer") int pointer(void *ctx) {
|
||||||
|
return pointer_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((noinline)) int struct_flavors_global() {
|
||||||
|
struct relocated_struct_with_scalars s = {1, 2, 3};
|
||||||
|
if (bpf_core_field_exists(s.a)) {
|
||||||
|
return set_output(__builtin_preserve_access_index(s.a));
|
||||||
|
} else {
|
||||||
|
return set_output(__builtin_preserve_access_index(s.b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/struct_flavors") int struct_flavors(void *ctx) {
|
||||||
|
return struct_flavors_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_unsigned_32 { U32 = 0xAAAAAAAA };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_unsigned_32_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_32, U32));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/enum_unsigned_32")
|
||||||
|
int enum_unsigned_32(void *ctx) {
|
||||||
|
return enum_unsigned_32_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_signed_32 { S32 = -0x7AAAAAAA };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_signed_32_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_signed_32, S32));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/enum_signed_32") int enum_signed_32(void *ctx) {
|
||||||
|
return enum_signed_32_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_unsigned_64 { U64 = 0xAAAAAAAABBBBBBBB };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_unsigned_64_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_64, U64));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/enum_unsigned_64")
|
||||||
|
int enum_unsigned_64(void *ctx) {
|
||||||
|
return enum_unsigned_64_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_signed_64 { u64 = -0xAAAAAAABBBBBBBB };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_signed_64_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_signed_64, u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("uprobe/enum_signed_64") int enum_signed_64(void *ctx) {
|
||||||
|
return enum_signed_64_global();
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
// clang-format off
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_core_read.h>
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
long set_output(__u64 value) { exit((int)value); }
|
||||||
|
|
||||||
|
struct relocated_struct_with_scalars {
|
||||||
|
__u8 b;
|
||||||
|
__u8 c;
|
||||||
|
__u8 d;
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((noinline)) int field_global() {
|
||||||
|
struct relocated_struct_with_scalars s = {1, 2, 3};
|
||||||
|
return set_output(__builtin_preserve_access_index(s.b));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct relocated_struct_with_pointer {
|
||||||
|
struct relocated_struct_with_pointer *second;
|
||||||
|
struct relocated_struct_with_pointer *first;
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((noinline)) int pointer_global() {
|
||||||
|
struct relocated_struct_with_pointer s = {
|
||||||
|
(struct relocated_struct_with_pointer *)42,
|
||||||
|
(struct relocated_struct_with_pointer *)21,
|
||||||
|
};
|
||||||
|
return set_output((__u64)__builtin_preserve_access_index(s.first));
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((noinline)) int struct_flavors_global() {
|
||||||
|
struct relocated_struct_with_scalars s = {1, 2, 3};
|
||||||
|
if (bpf_core_field_exists(s.b)) {
|
||||||
|
return set_output(__builtin_preserve_access_index(s.b));
|
||||||
|
} else {
|
||||||
|
return set_output(__builtin_preserve_access_index(s.c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_unsigned_32 { U32 = 0xBBBBBBBB };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_unsigned_32_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_32, U32));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_signed_32 { S32 = -0x7BBBBBBB };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_signed_32_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_signed_32, S32));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_unsigned_64 { U64 = 0xCCCCCCCCDDDDDDDD };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_unsigned_64_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_unsigned_64, U64));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relocated_enum_signed_64 { u64 = -0xCCCCCCCDDDDDDDD };
|
||||||
|
|
||||||
|
__attribute__((noinline)) int enum_signed_64_global() {
|
||||||
|
return set_output(bpf_core_enum_value(enum relocated_enum_signed_64, u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoids dead code elimination by the compiler.
|
||||||
|
int main() {
|
||||||
|
field_global();
|
||||||
|
pointer_global();
|
||||||
|
struct_flavors_global();
|
||||||
|
enum_unsigned_32_global();
|
||||||
|
enum_signed_32_global();
|
||||||
|
enum_unsigned_64_global();
|
||||||
|
enum_signed_64_global();
|
||||||
|
}
|
@ -1,405 +1,62 @@
|
|||||||
use anyhow::{anyhow, bail, Context as _, Result};
|
use test_case::test_case;
|
||||||
use std::{
|
|
||||||
process::{Child, ChildStdout, Command, Stdio},
|
use aya::{maps::Array, programs::UProbe, util::KernelVersion, BpfLoader, Btf, Endianness};
|
||||||
thread::sleep,
|
|
||||||
time::Duration,
|
#[test_case("field", false, None, 2)]
|
||||||
};
|
#[test_case("field", true, None, 1)]
|
||||||
|
#[test_case("enum_unsigned_32", false, None, 0xAAAAAAAA)]
|
||||||
use aya::{maps::Array, programs::TracePoint, util::KernelVersion, BpfLoader, Btf, Endianness};
|
#[test_case("enum_unsigned_32", true, None, 0xBBBBBBBB)]
|
||||||
|
#[test_case("pointer", false, None, 42)]
|
||||||
// In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
|
#[test_case("pointer", true, None, 21)]
|
||||||
// special meaning, they just have "nice" bit patterns that can be helpful while debugging.
|
#[test_case("struct_flavors", false, None, 1)]
|
||||||
|
#[test_case("struct_flavors", true, None, 1)]
|
||||||
#[test]
|
#[test_case("enum_signed_32", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7AAAAAAAi32 as u64)]
|
||||||
fn relocate_field() {
|
#[test_case("enum_signed_32", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7BBBBBBBi32 as u64)]
|
||||||
let test = RelocationTest {
|
#[test_case("enum_unsigned_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xAAAAAAAABBBBBBBB)]
|
||||||
local_definition: r#"
|
#[test_case("enum_unsigned_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xCCCCCCCCDDDDDDDD)]
|
||||||
struct foo {
|
#[test_case("enum_signed_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xAAAAAAABBBBBBBBi64 as u64)]
|
||||||
__u8 a;
|
#[test_case("enum_signed_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xCCCCCCCDDDDDDDDi64 as u64)]
|
||||||
__u8 b;
|
fn relocation_tests(
|
||||||
__u8 c;
|
program: &str,
|
||||||
__u8 d;
|
with_relocations: bool,
|
||||||
};
|
required_kernel_version: Option<(KernelVersion, &str)>,
|
||||||
"#,
|
expected: u64,
|
||||||
target_btf: r#"
|
) {
|
||||||
struct foo {
|
if let Some((required_kernel_version, commit)) = required_kernel_version {
|
||||||
__u8 a;
|
let current_kernel_version = KernelVersion::current().unwrap();
|
||||||
__u8 c;
|
if current_kernel_version < required_kernel_version {
|
||||||
__u8 b;
|
eprintln!("skipping test on kernel {current_kernel_version:?}, support for {program} was added in {required_kernel_version:?}; see {commit}");
|
||||||
__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;
|
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()
|
let mut bpf = BpfLoader::new()
|
||||||
|
.btf(
|
||||||
|
with_relocations
|
||||||
|
.then(|| Btf::parse(crate::RELOC_BTF, Endianness::default()).unwrap())
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.load(crate::RELOC_BPF)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(test.run().unwrap(), 42);
|
let program: &mut UProbe = bpf.program_mut(program).unwrap().try_into().unwrap();
|
||||||
assert_eq!(test.run_no_btf().unwrap(), 42);
|
program.load().unwrap();
|
||||||
}
|
program
|
||||||
|
.attach(
|
||||||
#[test]
|
Some("trigger_btf_relocations_program"),
|
||||||
fn relocate_struct_flavors() {
|
0,
|
||||||
let definition = r#"
|
"/proc/self/exe",
|
||||||
struct foo {};
|
None,
|
||||||
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();
|
.unwrap();
|
||||||
assert_eq!(test_no_flavor.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 {
|
trigger_btf_relocations_program();
|
||||||
/// 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>> {
|
|
||||||
use std::io::Read as _;
|
|
||||||
|
|
||||||
let Self {
|
|
||||||
local_definition,
|
|
||||||
relocation_code,
|
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let mut stdout = 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 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> {
|
|
||||||
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 stdout = 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.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 => {}
|
|
||||||
code => bail!("{cmd:?} exited with code {code}"),
|
|
||||||
},
|
|
||||||
None => bail!("{cmd:?} terminated by signal"),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 its bytes.
|
|
||||||
fn compile(source_code: &str) -> Result<ChildStdout> {
|
|
||||||
use std::io::Write as _;
|
|
||||||
|
|
||||||
let mut cmd = Command::new("clang");
|
|
||||||
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 => {}
|
|
||||||
code => bail!("{cmd:?} exited with code {code}"),
|
|
||||||
},
|
|
||||||
None => bail!("{cmd:?} terminated by signal"),
|
|
||||||
}
|
|
||||||
Ok(stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap();
|
||||||
let key = 0;
|
let key = 0;
|
||||||
output_map.get(&key, 0).context("Getting key 0 failed")
|
assert_eq!(output_map.get(&key, 0).unwrap(), expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#[inline(never)]
|
||||||
|
pub extern "C" fn trigger_btf_relocations_program() {
|
||||||
|
core::hint::black_box(trigger_btf_relocations_program);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue