use std::collections::HashSet;
use std::env;
use std::fs::create_dir;
use std::fs::read;
use std::fs::read_to_string;
use std::fs::write;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use goblin::Object;
use indoc::indoc;
use libbpf_rs::btf::types;
use libbpf_rs::btf::BtfType;
use libbpf_rs::Btf;
use memmap2::Mmap;
use tempfile::tempdir;
use tempfile::NamedTempFile;
use tempfile::TempDir;
use test_fork::fork;
use test_log::test;
use crate::build::build_project;
use crate::build::BpfObjBuilder;
use crate::make::make;
use crate::r#gen::GenBtf;
use crate::r#gen::GenStructOps;
use crate::SkeletonBuilder;
fn setup_temp_project() -> (TempDir, PathBuf, PathBuf) {
let dir = tempdir().expect("failed to create tempdir");
let proj_dir = dir.path().join("proj");
let status = Command::new("cargo")
.arg("new")
.arg("--quiet")
.arg("--bin")
.arg(proj_dir.into_os_string())
.status()
.expect("failed to create new cargo project");
assert!(status.success());
let proj_dir = dir.path().join("proj");
let mut cargo_toml = proj_dir.clone();
cargo_toml.push("Cargo.toml");
(dir, proj_dir, cargo_toml)
}
fn setup_temp_workspace() -> (TempDir, PathBuf, PathBuf, PathBuf, PathBuf) {
let dir = tempdir().expect("failed to create tempdir");
let workspace_cargo_toml = dir.path().join("Cargo.toml");
let path_one = dir.path().join("one");
let status_one = Command::new("cargo")
.arg("new")
.arg("--quiet")
.arg("--bin")
.arg(path_one.clone().into_os_string())
.status()
.expect("failed to create new cargo project 1");
assert!(status_one.success());
let path_two = dir.path().join("two");
let status_two = Command::new("cargo")
.arg("new")
.arg("--quiet")
.arg("--bin")
.arg(path_two.clone().into_os_string())
.status()
.expect("failed to create new cargo project 2");
assert!(status_two.success());
let mut cargo_toml_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&workspace_cargo_toml)
.expect("failed to open workspace Cargo.toml");
writeln!(cargo_toml_file, r#"[workspace]"#).expect("write to workspace Cargo.toml failed");
writeln!(cargo_toml_file, r#"members = ["one", "two"]"#)
.expect("write to workspace Cargo.toml failed");
let dir_pathbuf = dir.path().to_path_buf();
(dir, dir_pathbuf, workspace_cargo_toml, path_one, path_two)
}
fn validate_bpf_o(path: &Path) {
let buffer = read(path)
.unwrap_or_else(|_| panic!("failed to read object file at path={}", path.display()));
match Object::parse(&buffer).expect("failed to parse object file") {
Object::Elf(_) => (),
_ => panic!("wrong object file format"),
}
}
fn get_libbpf_rs_path() -> PathBuf {
let libcargo_dir = env!("CARGO_MANIFEST_DIR");
Path::new(&libcargo_dir)
.parent()
.expect("failed to get parent of libbpf-cargo directory")
.join("libbpf-rs")
.canonicalize()
.expect("failed to canonicalize libbpf-rs")
}
fn write_vmlinux_header(dir: &Path) {
let mut vmlinux = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(dir.join("vmlinux.h"))
.expect("failed to open vmlinux.h");
let () = vmlinux
.write_all(vmlinux::VMLINUX)
.expect("failed to write vmlinux.h");
}
fn add_vmlinux_header(project: &Path) {
write_vmlinux_header(&project.join("src/bpf"))
}
#[test]
fn test_build_default() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
let _prog_file =
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap();
validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
}
#[test]
fn test_build_invalid_prog() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let mut prog_file =
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
writeln!(prog_file, "1").expect("write to prog file failed");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
}
#[test]
fn test_build_custom() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
let mut cargo_toml_file = OpenOptions::new()
.append(true)
.open(&cargo_toml)
.expect("failed to open Cargo.toml");
writeln!(cargo_toml_file, "[package.metadata.libbpf]").expect("write to Cargo.toml failed");
writeln!(cargo_toml_file, r#"prog_dir = "src/other_bpf_dir""#)
.expect("write to Cargo.toml failed");
writeln!(cargo_toml_file, r#"target_dir = "other_target_dir""#)
.expect("write to Cargo.toml failed");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
create_dir(proj_dir.join("src/other_bpf_dir")).expect("failed to create prog dir");
let _prog_file = File::create(proj_dir.join("src/other_bpf_dir/prog.bpf.c"))
.expect("failed to create prog file");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap();
validate_bpf_o(
proj_dir
.as_path()
.join("target/other_target_dir/prog.bpf.o")
.as_path(),
);
}
#[test]
fn test_unknown_metadata_section() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
let mut cargo_toml_file = OpenOptions::new()
.append(true)
.open(&cargo_toml)
.expect("failed to open Cargo.toml");
let deb_metadata = indoc! {r#"
[package.metadata.deb]
prog_dir = "some value that should be ignored"
some_other_val = true
"#};
cargo_toml_file
.write_all(deb_metadata.as_bytes())
.expect("write to Cargo.toml failed");
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
let _prog_file =
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap();
validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
}
#[test]
fn test_enforce_file_extension() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
let _prog_file = File::create(proj_dir.join("src/bpf/prog_BAD_EXTENSION.c"))
.expect("failed to create prog file");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
let _prog_file_again = File::create(proj_dir.join("src/bpf/prog_GOOD_EXTENSION.bpf.c"))
.expect("failed to create prog file");
build_project(Some(&cargo_toml), None, Vec::new()).unwrap();
}
#[test]
fn test_build_workspace() {
let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace();
build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap_err();
create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog1.bpf.c"))
.expect("failed to create prog file 1");
create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog2.bpf.c"))
.expect("failed to create prog file 2");
build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap();
}
#[test]
fn test_build_workspace_collision() {
let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace();
create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog.bpf.c"))
.expect("failed to create prog file 1");
create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog.bpf.c"))
.expect("failed to create prog file 2");
build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap_err();
}
#[test]
fn test_make_basic() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file =
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
make(Some(&cargo_toml), None, Vec::new(), Vec::new(), None).unwrap();
validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
assert!(proj_dir
.as_path()
.join("src/bpf/prog.skel.rs")
.as_path()
.exists());
}
#[test]
fn test_make_workspace() {
let (_dir, workspace_dir, workspace_cargo_toml, proj_one_dir, proj_two_dir) =
setup_temp_workspace();
create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog1.bpf.c"))
.expect("failed to create prog file 1");
create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog2.bpf.c"))
.expect("failed to create prog file 2");
make(
Some(&workspace_cargo_toml),
None,
Vec::new(),
Vec::new(),
None,
)
.unwrap();
validate_bpf_o(
workspace_dir
.as_path()
.join("target/bpf/prog1.bpf.o")
.as_path(),
);
validate_bpf_o(
workspace_dir
.as_path()
.join("target/bpf/prog2.bpf.o")
.as_path(),
);
assert!(proj_one_dir
.as_path()
.join("src/bpf/prog1.skel.rs")
.as_path()
.exists());
assert!(proj_two_dir
.as_path()
.join("src/bpf/prog2.skel.rs")
.as_path()
.exists());
}
fn build_rust_project_from_bpf_c_impl(bpf_c: &str, rust: &str, run: bool) -> (TempDir, PathBuf) {
let (dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let mut prog = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(proj_dir.join("src/bpf/prog.bpf.c"))
.expect("failed to open prog.bpf.c");
prog.write_all(bpf_c.as_bytes())
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
make(Some(&cargo_toml), None, Vec::new(), Vec::new(), None).unwrap();
let mut cargo = OpenOptions::new()
.append(true)
.open(&cargo_toml)
.expect("failed to open Cargo.toml");
writeln!(
cargo,
indoc! {r#"
libbpf-rs = {{ path = "{}" }}
"#},
get_libbpf_rs_path().as_path().display()
)
.expect("failed to write to Cargo.toml");
let mut source = OpenOptions::new()
.write(true)
.truncate(true)
.open(proj_dir.join("src/main.rs"))
.expect("failed to open main.rs");
source
.write_all(rust.as_bytes())
.expect("failed to write to main.rs");
let status = Command::new("cargo")
.arg(if run { "run" } else { "build" })
.arg("--quiet")
.arg("--manifest-path")
.arg(cargo_toml.into_os_string())
.env("RUSTFLAGS", "-Dwarnings")
.status()
.expect("failed to run cargo");
assert!(status.success());
(dir, proj_dir.join("target/debug/proj"))
}
fn build_rust_project_from_bpf_c(bpf_c: &str, rust: &str) -> (TempDir, PathBuf) {
let run = false;
build_rust_project_from_bpf_c_impl(bpf_c, rust, run)
}
fn run_rust_project_from_bpf_c(bpf_c: &str, rust: &str) {
let run = true;
build_rust_project_from_bpf_c_impl(bpf_c, rust, run);
}
#[test]
fn test_skeleton_empty_source() {
let bpf_c = String::new();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let _skel = builder
.open(&mut open_object)
.expect("failed to open skel")
.load()
.expect("failed to load skel");
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_basic() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, u64);
} mymap SEC(".maps");
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{
return 0;
}
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
use libbpf_rs::skel::Skel;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
// Check that we can grab handles to open maps/progs
let _open_map = &open_skel.maps.mymap;
let _open_prog = &open_skel.progs.this_is_my_prog;
let mut skel = open_skel.load().expect("failed to load skel");
// Check that we can grab handles to loaded maps/progs
let _map = &skel.maps.mymap;
let _prog = &skel.progs.this_is_my_prog;
// Check that attach() is generated
skel.attach().expect("failed to attach progs");
// Check that Option<Link> field is generated
let _mylink = skel.links.this_is_my_prog.unwrap();
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_generate_bpf_objs_section() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{
return 0;
}
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
let _skel = open_skel.load().expect("failed to load skel");
}
"#}
.to_string();
let (_dir, bin) = build_rust_project_from_bpf_c(&bpf_c, &rust);
let buffer = read(&bin)
.unwrap_or_else(|_| panic!("failed to read binary file at path={}", &bin.display()));
let elf = goblin::elf::Elf::parse(&buffer).unwrap();
let (section_idx, _) = elf
.section_headers
.iter()
.enumerate()
.find(|(_i, sh)| &elf.shdr_strtab[sh.sh_name] == ".bpf.objs")
.unwrap();
let syms: Vec<_> = elf
.syms
.iter()
.filter(|sym| sym.st_shndx == section_idx && sym.st_size > 0)
.collect();
assert_eq!(syms.len(), 1, "expected one symbol in .bpf.objs");
let sym = syms.first().unwrap();
let mut bpf_o = NamedTempFile::new().unwrap();
let sym_start = sym.st_value as usize;
let sym_end = sym_start + (sym.st_size as usize);
bpf_o
.as_file_mut()
.write_all(&buffer[sym_start..sym_end])
.unwrap();
validate_bpf_o(bpf_o.path());
}
#[test]
fn test_skeleton_generate_datasec_static() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{
bpf_printk("this should not cause an error");
return 0;
}
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use bpf::*;
fn main() {
let _builder = ProgSkelBuilder::default();
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_datasec() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
int myglobal = 0;
void * const myconst = 0;
int mycustomdata SEC(".data.custom");
int mycustombss SEC(".bss.custom");
const int mycustomrodata SEC(".rodata.custom.1") = 43;
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{
return 0;
}
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let mut open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
// Check that we set rodata vars before load
open_skel.maps.rodata_data.as_deref_mut().unwrap().myconst = std::ptr::null_mut();
// We can always set bss vars
open_skel.maps.bss_data.as_deref_mut().unwrap().myglobal = 42;
open_skel.maps.data_custom_data.as_deref_mut().unwrap().mycustomdata = 1337;
open_skel.maps.bss_custom_data.as_deref_mut().unwrap().mycustombss = 12;
assert_eq!(open_skel.maps.rodata_custom_1_data.as_deref_mut().unwrap().mycustomrodata, 43);
let mut skel = open_skel.load().expect("failed to load skel");
// We can always set bss vars
skel.maps.bss_data.as_deref_mut().unwrap().myglobal = 24;
skel.maps.data_custom_data.as_deref_mut().unwrap().mycustomdata += 1;
skel.maps.bss_custom_data.as_deref_mut().unwrap().mycustombss += 1;
assert_eq!(skel.maps.rodata_custom_1_data.as_deref().unwrap().mycustomrodata, 43);
// Read only for rodata after load
let _rodata: &types::rodata = skel.maps.rodata_data.as_deref().unwrap();
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_builder_basic() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let mut prog = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(proj_dir.join("src/bpf/prog.bpf.c"))
.expect("failed to open prog.bpf.c");
write!(
prog,
indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct unique_key {{
int cap;
u32 tgid;
u64 cgroupid;
}};
struct {{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, struct unique_key);
__type(value, u64);
}} mymap SEC(".maps");
struct {{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__uint(key_size, 8);
__type(value, u64);
}} mymap2 SEC(".maps");
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{{
return 0;
}}
"#}
)
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
let skel = proj_dir.join("src/bpf/skel.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.build_and_generate(&skel)
.unwrap();
let mut cargo = OpenOptions::new()
.append(true)
.open(&cargo_toml)
.expect("failed to open Cargo.toml");
writeln!(
cargo,
indoc! {r#"
libbpf-rs = {{ path = "{}" }}
"#},
get_libbpf_rs_path().as_path().display()
)
.expect("failed to write to Cargo.toml");
let mut source = OpenOptions::new()
.write(true)
.truncate(true)
.open(proj_dir.join("src/main.rs"))
.expect("failed to open main.rs");
write!(
source,
r#"
#[path = "{skel_path}"]
mod skel;
use std::mem::MaybeUninit;
use skel::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
use libbpf_rs::skel::Skel;
fn main() {{
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
// Check that we can grab handles to open maps/progs
let _open_map = &open_skel.maps.mymap;
let _open_map2 = &open_skel.maps.mymap2;
let _open_prog = &open_skel.progs.this_is_my_prog;
let mut skel = open_skel.load().expect("failed to load skel");
// Check that we can grab handles to loaded maps/progs
let _map = &skel.maps.mymap;
let _map2 = &skel.maps.mymap2;
let _prog = &skel.progs.this_is_my_prog;
// Check that attach() is generated
skel.attach().expect("failed to attach progs");
// Check that Option<Link> field is generated
let _mylink = skel.links.this_is_my_prog.unwrap();
let _key = types::unique_key::default();
}}
"#,
skel_path = skel.display(),
)
.expect("failed to write to main.rs");
let status = Command::new("cargo")
.arg("build")
.arg("--quiet")
.arg("--manifest-path")
.arg(cargo_toml.into_os_string())
.env("RUSTFLAGS", "-Dwarnings")
.status()
.expect("failed to spawn cargo-build");
assert!(status.success());
}
#[test]
fn test_skeleton_builder_clang_opts() {
let (_dir, proj_dir, _cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let mut prog = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(proj_dir.join("src/bpf/prog.bpf.c"))
.expect("failed to open prog.bpf.c");
write!(
prog,
indoc! {r#"
#ifndef PURPOSE
#error "what is my purpose?"
#endif
"#},
)
.expect("failed to write prog.bpf.c");
let skel = proj_dir.join("src/bpf/skel.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.build_and_generate(&skel)
.unwrap_err();
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.clang_args(["-DPURPOSE=you_pass_the_butter"])
.build_and_generate(&skel)
.unwrap();
}
#[test]
fn test_skeleton_builder_arrays_ptrs() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct inner {
int a;
};
struct mystruct {
int x;
struct {
int b;
} y[2];
struct inner z[2];
};
const volatile struct mystruct my_array[1] = { {0} };
struct mystruct * const my_ptr = NULL;
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
let rodata = open_skel.maps.rodata_data.as_deref().unwrap();
// That everything exists and compiled okay
let _ = rodata.my_array[0].x;
let _ = rodata.my_array[0].y[1].b;
let _ = rodata.my_array[0].z[0].a;
let _ = rodata.my_ptr;
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_enum_with_same_value_variants() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
enum Foo {
Zero = 0,
ZeroDup = Zero,
One,
};
enum Foo foo;
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use bpf::*;
fn main() {
let zero1 = types::Foo::Zero;
let zero2 = types::Foo::ZeroDup;
assert_eq!(zero1, zero2);
assert_ne!(types::Foo::Zero, types::Foo::One);
}
"#}
.to_string();
run_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_generate_struct_with_pointer() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct list {
struct list *next;
};
struct list l;
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let _open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_generate_struct_with_pointer_array() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct vm_area_struct;
struct vmacache {
struct vm_area_struct *vmas[4];
};
struct vmacache c;
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let _open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_builder_multiple_anon() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct unique_key {
struct {
u32 foo;
u64 bar;
} foobar;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, struct unique_key);
__type(value, u64);
} mymap SEC(".maps");
struct Foo {
int x;
struct {
u8 y[10];
u16 z[16];
} bar;
struct {
u32 w;
u64 *u;
} baz;
int w;
};
struct Foo foo;
SEC("kprobe/foo")
int this_is_my_prog(u64 *ctx)
{
return 0;
}
"#}
.to_string();
let rust = indoc! {r#"
mod bpf;
use std::mem::MaybeUninit;
use bpf::*;
use libbpf_rs::skel::SkelBuilder;
use libbpf_rs::skel::OpenSkel;
fn main() {
let builder = ProgSkelBuilder::default();
let mut open_object = MaybeUninit::uninit();
let open_skel = builder
.open(&mut open_object)
.expect("failed to open skel");
let _skel = open_skel.load().expect("failed to load skel");
let _key = types::unique_key::default();
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
fn test_skeleton_multipl_kfuncs() {
let bpf_c = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
extern int bpf_dynptr_from_xdp(
struct xdp_md* xdp,
__u64 flags,
struct bpf_dynptr* ptr__uninit) __ksym;
extern void* bpf_dynptr_slice(
const struct bpf_dynptr* ptr,
__u32 offset,
void* buffer,
__u32 buffer__szk) __ksym;
SEC("xdp")
int steering(struct xdp_md* xdp) {
u8 ethb[sizeof(struct ethhdr)];
struct bpf_dynptr ptr;
struct ethhdr* eth;
if (bpf_dynptr_from_xdp(xdp, 0, &ptr)) {
return XDP_PASS;
}
eth = bpf_dynptr_slice(&ptr, 0, ethb, sizeof(ethb));
(void)eth;
}
"#}
.to_string();
let rust = indoc! {r#"
#![warn(elided_lifetimes_in_paths)]
mod bpf;
use bpf::*;
fn main() {
let _builder = ProgSkelBuilder::default();
}
"#}
.to_string();
build_rust_project_from_bpf_c(&bpf_c, &rust);
}
#[test]
#[ignore = "may fail on some systems; depends on kernel headers that have been seen to be broken"]
fn test_skeleton_builder_deterministic() {
let (_dir, proj_dir, _cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
write(
proj_dir.join("src/bpf/prog.bpf.c"),
r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
} sock_map SEC(".maps");
SEC("sk_reuseport")
long prog_select_sk(struct sk_reuseport_md *reuse_md)
{
unsigned int index = 0;
bpf_sk_select_reuseport(reuse_md, &sock_map, &index, 0);
return SK_PASS;
}
"#,
)
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
let skel1 = proj_dir.join("src/bpf/skel1.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.build_and_generate(&skel1)
.unwrap();
let skel1 = read_to_string(skel1).unwrap();
let skel2 = proj_dir.join("src/bpf/skel2.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.build_and_generate(&skel2)
.unwrap();
let skel2 = read_to_string(skel2).unwrap();
assert_eq!(skel1, skel2);
}
#[fork]
#[test]
fn test_skeleton_builder_reference_obj() {
let (_dir, proj_dir, _cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
write(
proj_dir.join("src/bpf/prog.bpf.c"),
r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
} sock_map SEC(".maps");
SEC("sk_reuseport")
long prog_select_sk(struct sk_reuseport_md *reuse_md)
{
unsigned int index = 0;
bpf_sk_select_reuseport(reuse_md, &sock_map, &index, 0);
return SK_PASS;
}
"#,
)
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
let obj = proj_dir.join("src/bpf/prog.o");
let skel = proj_dir.join("src/bpf/skel.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.obj(&obj)
.reference_obj(true)
.build_and_generate(&skel)
.unwrap();
let skel_content = read_to_string(&skel).unwrap();
assert!(skel_content.contains("struct ProgSkel<"));
assert!(skel_content.contains("include_bytes!"));
assert!(!skel_content.contains("127, 69, 76, 70"));
unsafe {
env::set_var("OUT_DIR", proj_dir.join("src/bpf"));
}
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.reference_obj(true)
.build_and_generate(&skel)
.unwrap();
let skel = read_to_string(&skel).unwrap();
assert!(skel.contains("struct ProgSkel<"));
}
#[test]
fn test_skeleton_builder_default_inlines_bytes() {
let (_dir, proj_dir, _cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
write(
proj_dir.join("src/bpf/prog.bpf.c"),
r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
} sock_map SEC(".maps");
SEC("sk_reuseport")
long prog_select_sk(struct sk_reuseport_md *reuse_md)
{
unsigned int index = 0;
bpf_sk_select_reuseport(reuse_md, &sock_map, &index, 0);
return SK_PASS;
}
"#,
)
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
let skel = proj_dir.join("src/bpf/skel.rs");
SkeletonBuilder::new()
.source(proj_dir.join("src/bpf/prog.bpf.c"))
.build_and_generate(&skel)
.unwrap();
let skel = read_to_string(skel).unwrap();
assert!(!skel.contains("include_bytes!"));
assert!(skel.contains("127, 69, 76, 70"));
}
#[test]
fn test_skeleton_duplicate_struct() {
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
let prog1 = proj_dir.join("src/bpf/prog1.bpf.c");
write(
&prog1,
r#"
#include "vmlinux.h"
struct foobar {
int bar;
} foo;
"#,
)
.expect("failed to write prog.bpf.c");
let prog2 = proj_dir.join("src/bpf/prog2.bpf.c");
write(
&prog2,
r#"
#include "vmlinux.h"
typedef int nonsensicaltypedeftoint;
struct foobar {
nonsensicaltypedeftoint bar;
} baz;
"#,
)
.expect("failed to write prog.bpf.c");
add_vmlinux_header(&proj_dir);
let bpf_o = proj_dir.join("prog.bpf.o");
let _output = BpfObjBuilder::default()
.build_many([&prog1, &prog2], &bpf_o)
.unwrap();
let skel = proj_dir.join("src/bpf/skel.rs");
let () = SkeletonBuilder::new().obj(&bpf_o).generate(&skel).unwrap();
let mut cargo = OpenOptions::new()
.append(true)
.open(&cargo_toml)
.expect("failed to open Cargo.toml");
writeln!(
cargo,
indoc! {r#"
libbpf-rs = {{ path = "{}" }}
"#},
get_libbpf_rs_path().as_path().display()
)
.expect("failed to write to Cargo.toml");
let mut source = OpenOptions::new()
.write(true)
.truncate(true)
.open(proj_dir.join("src/main.rs"))
.expect("failed to open main.rs");
write!(
source,
r#"
#[path = "{skel_path}"]
mod skel;
use skel::*;
fn main() {{
let _foobar1 = types::foobar::default();
let _foobar2 = types::foobar_2::default();
}}
"#,
skel_path = skel.display(),
)
.expect("failed to write to main.rs");
let status = Command::new("cargo")
.arg("build")
.arg("--quiet")
.arg("--manifest-path")
.arg(cargo_toml.into_os_string())
.env("RUSTFLAGS", "-Dwarnings")
.status()
.expect("failed to spawn cargo-build");
assert!(status.success());
}
macro_rules! find_type_in_btf {
($btf:ident, Var, $name:literal) => {{
let mut asserted_type: Option<BtfType<'_>> = None;
for ty in $btf.type_by_kind::<types::DataSec<'_>>() {
for var in ty.iter() {
let var_ty = $btf
.type_by_id(var.ty)
.expect("Failed to lookup datasec var");
let t = types::Var::try_from(var_ty).unwrap_or_else(|_| {
panic!("Datasec var didn't point to a var. Instead: {}", var.ty)
});
if t.name().map(|o| o.to_str().unwrap()) == Some($name) {
assert!(asserted_type.is_none()); asserted_type = Some(var_ty);
}
}
}
asserted_type.unwrap()
}};
($btf:ident, $btf_type:path, $name:literal) => {{
find_type_in_btf!($btf, $btf_type, $name, false)
}};
($btf:ident, $btf_type:path, $name:literal, $substr:expr) => {{
let mut asserted_type: Option<$btf_type> = None;
for ty in $btf.type_by_kind::<$btf_type>() {
let name = ty.name().map(|o| o.to_str().unwrap());
let found = if $substr {
name.map(|n| n.contains($name)).unwrap_or(false)
} else {
name == Some($name)
};
if found {
assert!(asserted_type.is_none()); asserted_type = Some(ty);
}
}
asserted_type.unwrap()
}};
}
fn build_btf_mmap(prog_text: &str) -> Mmap {
let dir = tempdir().expect("failed to create tempdir");
let dir = dir.path();
let bpf_c = dir.join("prog.bpf.c");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&bpf_c)
.expect("failed to open prog.bpf.c");
let () = file
.write_all(prog_text.as_bytes())
.expect("failed to write prog.bpf.c");
let () = write_vmlinux_header(dir);
let bpf_o = dir.join("prog.bpf.o");
let _output = BpfObjBuilder::default().build(&bpf_c, &bpf_o).unwrap();
let obj = OpenOptions::new()
.read(true)
.open(bpf_o)
.expect("failed to open object file");
unsafe { Mmap::map(&obj) }.expect("Failed to mmap object file")
}
fn btf_from_mmap(mmap: &Mmap) -> GenBtf<'_> {
let btf = Btf::from_raw("prog", mmap)
.expect("Failed to initialize Btf")
.expect("Did not find .BTF section");
assert_ne!(btf.type_by_kind::<BtfType<'_>>().count(), 0);
GenBtf::from(btf)
}
#[track_caller]
fn assert_output(actual_output: &str, expected_output: &str) {
let ao = actual_output.trim_end().trim_start();
let eo = expected_output.trim_end().trim_start();
println!("---------------");
println!("expected output");
println!("---------------");
println!("{eo}");
println!("-------------");
println!("actual output");
println!("-------------");
println!("{ao}");
assert!(eo == ao);
}
#[track_caller]
fn assert_definition(btf: &GenBtf<'_>, btf_item: &BtfType<'_>, expected_output: &str) {
let actual_output = btf
.type_definition(*btf_item, &mut HashSet::new())
.expect("Failed to generate struct Foo defn");
assert_output(&actual_output, expected_output)
}
#[test]
fn test_btf_dump_reserved_keyword_escaping() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
u64 type;
void* mod;
void* self;
};
struct Foo foo = {{0}};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub r#type: u64,
pub r#mod: *mut std::ffi::c_void,
pub slf: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
r#type: u64::default(),
r#mod: std::ptr::null_mut(),
slf: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_basic() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
int myglobal = 1;
struct Foo {
int x;
char y[10];
void *z;
};
struct Foo foo = {{0}};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 10],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 10],
z: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
let foo = find_type_in_btf!(btf, Var, "foo");
let myglobal = find_type_in_btf!(btf, Var, "myglobal");
assert_eq!(
"Foo",
btf.type_declaration(foo)
.expect("Failed to generate foo decl")
);
assert_eq!(
"i32",
btf.type_declaration(myglobal)
.expect("Failed to generate myglobal decl")
);
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_fwd() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct sometypethatdoesnotexist *m;
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let m = find_type_in_btf!(btf, types::Var<'_>, "m");
assert_eq!(
"*mut std::ffi::c_void",
btf.type_declaration(*m)
.expect("Failed to generate foo decl")
);
}
#[test]
fn test_btf_dump_align() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
} __attribute__((aligned(16)));
struct Foo foo = {1};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub __pad_4: [u8; 12],
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_insane_align() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
} __attribute__((aligned(64)));
struct Foo foo = {1};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub __pad_4: [u8; 60],
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
__pad_4: [u8::default(); 60],
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_align_trailing_bitfield() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
__u8 x;
__u32 y : 23;
};
struct Bar {
__u8 x;
__u32 y : 23;
} __attribute__((__packed__));
struct Baz {
__u8 x;
__u32 y : 20;
__u32 z : 3;
};
struct Foo foo = {};
struct Bar bar = {};
struct Baz baz = {};
"#};
let expected_foo_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: u8,
pub __pad_1: [u8; 3],
}
"#};
let expected_bar_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub x: u8,
pub __pad_1: [u8; 3],
}
"#};
let expected_baz_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Baz {
pub x: u8,
pub __pad_1: [u8; 3],
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_foo_output);
let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
assert_definition(&btf, &struct_bar, expected_bar_output);
let struct_baz = find_type_in_btf!(btf, types::Struct<'_>, "Baz");
assert_definition(&btf, &struct_baz, expected_baz_output);
}
#[test]
fn test_btf_dump_basic_long_array() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
int myglobal = 1;
struct Foo {
int x;
char y[33];
void *z;
};
struct Foo foo = {{0}};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 33],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 33],
z: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
let foo = find_type_in_btf!(btf, Var, "foo");
let myglobal = find_type_in_btf!(btf, Var, "myglobal");
assert_eq!(
"Foo",
btf.type_declaration(foo)
.expect("Failed to generate foo decl")
);
assert_eq!(
"i32",
btf.type_declaration(myglobal)
.expect("Failed to generate myglobal decl")
);
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_struct_definition() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Bar {
u16 x;
};
struct Foo {
int *ip;
int **ipp;
struct Bar bar;
struct Bar *pb;
volatile u64 v;
const volatile s64 cv;
char * restrict r;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub ip: *mut i32,
pub ipp: *mut *mut i32,
pub bar: Bar,
pub __pad_18: [u8; 6],
pub pb: *mut Bar,
pub v: u64,
pub cv: i64,
pub r: *mut i8,
}
impl Default for Foo {
fn default() -> Self {
Self {
ip: std::ptr::null_mut(),
ipp: std::ptr::null_mut(),
bar: Bar::default(),
__pad_18: [u8::default(); 6],
pb: std::ptr::null_mut(),
v: u64::default(),
cv: i64::default(),
r: std::ptr::null_mut(),
}
}
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub x: u16,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_struct_definition_func_proto() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct with_func_proto {
struct with_func_proto *next;
void (*func)(struct with_func_proto *);
};
struct with_func_proto w;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct with_func_proto {
pub next: *mut with_func_proto,
pub func: *mut std::ffi::c_void,
}
impl Default for with_func_proto {
fn default() -> Self {
Self {
next: std::ptr::null_mut(),
func: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "with_func_proto");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_struct_definition_long_array() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Bar {
u16 x;
u16 y[33];
};
struct Foo {
int *ip;
int **ipp;
struct Bar bar;
struct Bar *pb;
volatile u64 v;
const volatile s64 cv;
char * restrict r;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub ip: *mut i32,
pub ipp: *mut *mut i32,
pub bar: Bar,
pub __pad_84: [u8; 4],
pub pb: *mut Bar,
pub v: u64,
pub cv: i64,
pub r: *mut i8,
}
impl Default for Foo {
fn default() -> Self {
Self {
ip: std::ptr::null_mut(),
ipp: std::ptr::null_mut(),
bar: Bar::default(),
__pad_84: [u8::default(); 4],
pb: std::ptr::null_mut(),
v: u64::default(),
cv: i64::default(),
r: std::ptr::null_mut(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub x: u16,
pub y: [u16; 33],
}
impl Default for Bar {
fn default() -> Self {
Self {
x: u16::default(),
y: [u16::default(); 33],
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_packed_struct() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y;
__s32 z[2];
} __attribute__((packed));
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
pub struct Foo {
pub x: i32,
pub y: i8,
pub z: [i32; 2],
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_packed_struct_long_array() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y;
__s32 z[33];
} __attribute__((packed));
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct Foo {
pub x: i32,
pub y: i8,
pub z: [i32; 33],
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: i8::default(),
z: [i32::default(); 33],
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_bitfield_1() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
unsigned short x: 2;
unsigned short y: 3;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub __pad_0: [u8; 2],
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_bitfield_2() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
const char *name;
void *kset;
unsigned int state_initialized: 1;
unsigned int state_in_sysfs: 1;
unsigned int state_add_uevent_sent: 1;
unsigned int state_remove_uevent_sent: 1;
unsigned int uevent_suppress: 1;
bool flag;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub name: *mut i8,
pub kset: *mut std::ffi::c_void,
pub __pad_16: [u8; 1],
pub flag: std::mem::MaybeUninit<bool>,
pub __pad_18: [u8; 6],
}
impl Default for Foo {
fn default() -> Self {
Self {
name: std::ptr::null_mut(),
kset: std::ptr::null_mut(),
__pad_16: [u8::default(); 1],
flag: std::mem::MaybeUninit::new(bool::default()),
__pad_18: [u8::default(); 6],
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_enum() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
enum Foo {
Zero = 0,
One,
Seven = 7,
ZeroDup = Zero,
Infinite = 0xffffffff,
};
struct Bar {
enum Foo foo;
};
struct Bar bar;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct Foo(pub u32);
#[allow(non_upper_case_globals)]
impl Foo {
pub const Zero: Self = Self(0);
pub const One: Self = Self(1);
pub const Seven: Self = Self(7);
pub const ZeroDup: Self = Self(0);
pub const Infinite: Self = Self(4294967295);
}
impl Default for Foo {
fn default() -> Self { Self::Zero }
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
assert_definition(&btf, &struct_bar, expected_output);
}
#[test]
fn test_btf_dump_definition_enum_signed() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
enum Foo {
Zero = 0,
Infinite = -1,
};
struct Bar {
enum Foo foo;
};
struct Bar bar;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct Foo(pub i32);
#[allow(non_upper_case_globals)]
impl Foo {
pub const Zero: Self = Self(0);
pub const Infinite: Self = Self(-1);
}
impl Default for Foo {
fn default() -> Self { Self::Zero }
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
assert_definition(&btf, &struct_bar, expected_output);
}
#[test]
fn test_btf_dump_definition_enum64() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
enum Foo {
Zero = 0,
Infinite = 0xffffffffffffffff,
};
struct Bar {
enum Foo foo;
};
struct Bar bar;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct Foo(pub u64);
#[allow(non_upper_case_globals)]
impl Foo {
pub const Zero: Self = Self(0);
pub const Infinite: Self = Self(18446744073709551615);
}
impl Default for Foo {
fn default() -> Self { Self::Zero }
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
assert_definition(&btf, &struct_bar, expected_output);
}
#[test]
fn test_btf_dump_definition_enum64_signed() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
enum Foo {
Zero = 0,
Whatevs = -922337854775808,
};
struct Bar {
enum Foo foo;
};
struct Bar bar;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct Foo(pub i64);
#[allow(non_upper_case_globals)]
impl Foo {
pub const Zero: Self = Self(0);
pub const Whatevs: Self = Self(-922337854775808);
}
impl Default for Foo {
fn default() -> Self { Self::Zero }
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
assert_definition(&btf, &struct_bar, expected_output);
}
#[test]
fn test_btf_dump_definition_union() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
union Foo {
int x;
__u32 y;
char z[128];
};
union Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Copy, Clone)]
#[repr(C)]
pub union Foo {
pub x: i32,
pub y: u32,
pub z: [i8; 128],
}
impl std::fmt::Debug for Foo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let union_foo = find_type_in_btf!(btf, types::Union<'_>, "Foo");
assert_definition(&btf, &union_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_shared_dependent_types() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Bar {
u16 x;
};
struct Foo {
struct Bar bar;
struct Bar bartwo;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub bar: Bar,
pub bartwo: Bar,
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub x: u16,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_datasec() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y[10];
void *z;
};
struct Foo foo = {0};
const int myconstglobal = 0;
"#};
let bss_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 10],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 10],
z: std::ptr::null_mut(),
}
}
}
"#};
let rodata_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct rodata {
pub myconstglobal: i32,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);
assert_definition(&btf, &bss, bss_output);
assert_definition(&btf, &rodata, rodata_output);
}
#[test]
fn test_btf_dump_definition_datasec_custom() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
int bss_array[1] SEC(".bss.custom");
int data_array[1] SEC(".data.custom");
const int rodata_array[1] SEC(".rodata.custom.1");
"#};
let bss_custom_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss_custom {
pub bss_array: [i32; 1],
}
"#};
let data_custom_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct data_custom {
pub data_array: [i32; 1],
}
"#};
let rodata_custom_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct rodata_custom_1 {
pub rodata_array: [i32; 1],
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let bss_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".bss.custom", false);
let data_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".data.custom", false);
let rodata_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".rodata.custom.1", false);
assert_definition(&btf, &bss_custom, bss_custom_output);
assert_definition(&btf, &data_custom, data_custom_output);
assert_definition(&btf, &rodata_custom, rodata_custom_output);
}
#[test]
fn test_btf_dump_definition_datasec_long_array() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y[33];
void *z;
};
struct Foo foo = {0};
const int myconstglobal = 0;
"#};
let bss_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss {
pub foo: Foo,
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 33],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 33],
z: std::ptr::null_mut(),
}
}
}
"#};
let rodata_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct rodata {
pub myconstglobal: i32,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);
assert_definition(&btf, &bss, bss_output);
assert_definition(&btf, &rodata, rodata_output);
}
#[test]
fn test_btf_dump_definition_datasec_multiple() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y[10];
void *z;
};
struct Foo foo = {0};
struct Foo foo2 = {0};
struct Foo foo3 = {0};
const int ci = 0;
const int ci2 = 0;
const int ci3 = 0;
"#};
let bss_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss {
pub foo: Foo,
pub foo2: Foo,
pub foo3: Foo,
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 10],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 10],
z: std::ptr::null_mut(),
}
}
}
"#};
let rodata_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct rodata {
pub ci: i32,
pub ci2: i32,
pub ci3: i32,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);
assert_definition(&btf, &bss, bss_output);
assert_definition(&btf, &rodata, rodata_output);
}
#[test]
fn test_btf_dump_definition_datasec_multiple_long_array() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
char y[33];
void *z;
};
struct Foo foo = {0};
struct Foo foo2 = {0};
struct Foo foo3 = {0};
const int ci = 0;
const int ci2 = 0;
const int ci3 = 0;
"#};
let bss_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss {
pub foo: Foo,
pub foo2: Foo,
pub foo3: Foo,
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub y: [i8; 33],
pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
fn default() -> Self {
Self {
x: i32::default(),
y: [i8::default(); 33],
z: std::ptr::null_mut(),
}
}
}
"#};
let rodata_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct rodata {
pub ci: i32,
pub ci2: i32,
pub ci3: i32,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);
assert_definition(&btf, &bss, bss_output);
assert_definition(&btf, &rodata, rodata_output);
}
#[test]
fn test_btf_dump_definition_struct_inner_anon_union() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
union {
u8 y[10];
u16 z[16];
} bar;
union {
u32 w;
u64 *u;
} baz;
int w;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub bar: __anon_Foo_1,
pub __pad_36: [u8; 4],
pub baz: __anon_Foo_2,
pub w: i32,
pub __pad_52: [u8; 4],
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_1 {
pub y: [u8; 10],
pub z: [u16; 16],
}
impl std::fmt::Debug for __anon_Foo_1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_1 {
fn default() -> Self {
Self {
y: [u8::default(); 10],
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_2 {
pub w: u32,
pub u: *mut u64,
}
impl std::fmt::Debug for __anon_Foo_2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_2 {
fn default() -> Self {
Self {
w: u32::default(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_struct_inner_anon_struct() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
struct {
u8 y[10];
u16 z[16];
} bar;
struct {
u32 w;
u64 *u;
} baz;
int w;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub bar: __anon_Foo_1,
pub baz: __anon_Foo_2,
pub w: i32,
pub __pad_68: [u8; 4],
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct __anon_Foo_1 {
pub y: [u8; 10],
pub z: [u16; 16],
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct __anon_Foo_2 {
pub w: u32,
pub __pad_4: [u8; 4],
pub u: *mut u64,
}
impl Default for __anon_Foo_2 {
fn default() -> Self {
Self {
w: u32::default(),
__pad_4: [u8::default(); 4],
u: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_struct_inner_anon_struct_and_union() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
int x;
struct {
u8 y[10];
u16 z[16];
} bar;
union {
char *a;
int b;
} zerg;
struct {
u32 w;
u64 *u;
} baz;
int w;
union {
u8 c;
u64 d[5];
} flarg;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub x: i32,
pub bar: __anon_Foo_1,
pub zerg: __anon_Foo_2,
pub baz: __anon_Foo_3,
pub w: i32,
pub __pad_76: [u8; 4],
pub flarg: __anon_Foo_4,
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct __anon_Foo_1 {
pub y: [u8; 10],
pub z: [u16; 16],
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_2 {
pub a: *mut i8,
pub b: i32,
}
impl std::fmt::Debug for __anon_Foo_2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_2 {
fn default() -> Self {
Self {
a: std::ptr::null_mut(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct __anon_Foo_3 {
pub w: u32,
pub __pad_4: [u8; 4],
pub u: *mut u64,
}
impl Default for __anon_Foo_3 {
fn default() -> Self {
Self {
w: u32::default(),
__pad_4: [u8::default(); 4],
u: std::ptr::null_mut(),
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_4 {
pub c: u8,
pub d: [u64; 5],
}
impl std::fmt::Debug for __anon_Foo_4 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_4 {
fn default() -> Self {
Self {
c: u8::default(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_anon_union_member() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
union {
char *name;
void *tp;
};
};
struct Foo foo = {{0}};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub __anon_Foo_1: __anon_Foo_1,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_1 {
pub name: *mut i8,
pub tp: *mut std::ffi::c_void,
}
impl std::fmt::Debug for __anon_Foo_1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_1 {
fn default() -> Self {
Self {
name: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_anon_member_tree() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
union {
struct {
char *name;
void *tp;
};
struct Bar {
union {
struct {
char *name;
void *trp;
};
struct Baz {
char *name;
void *trp;
} baz;
};
} bar;
};
};
struct Foo foo = {0};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub __anon_Foo_1: __anon_Foo_1,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Foo_1 {
pub __anon_Foo_2: __anon_Foo_2,
pub bar: Bar,
}
impl std::fmt::Debug for __anon_Foo_1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Foo_1 {
fn default() -> Self {
Self {
__anon_Foo_2: __anon_Foo_2::default(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct __anon_Foo_2 {
pub name: *mut i8,
pub tp: *mut std::ffi::c_void,
}
impl Default for __anon_Foo_2 {
fn default() -> Self {
Self {
name: std::ptr::null_mut(),
tp: std::ptr::null_mut(),
}
}
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Bar {
pub __anon_Bar_1: __anon_Bar_1,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_Bar_1 {
pub __anon_Bar_2: __anon_Bar_2,
pub baz: Baz,
}
impl std::fmt::Debug for __anon_Bar_1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_Bar_1 {
fn default() -> Self {
Self {
__anon_Bar_2: __anon_Bar_2::default(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct __anon_Bar_2 {
pub name: *mut i8,
pub trp: *mut std::ffi::c_void,
}
impl Default for __anon_Bar_2 {
fn default() -> Self {
Self {
name: std::ptr::null_mut(),
trp: std::ptr::null_mut(),
}
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Baz {
pub name: *mut i8,
pub trp: *mut std::ffi::c_void,
}
impl Default for Baz {
fn default() -> Self {
Self {
name: std::ptr::null_mut(),
trp: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_anon_enum() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
typedef enum {
FOO = 1,
} test_t;
struct Foo {
test_t test;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub test: __anon_Foo_1,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct __anon_Foo_1(pub u32);
#[allow(non_upper_case_globals)]
impl __anon_Foo_1 {
pub const FOO: Self = Self(1);
}
impl Default for __anon_Foo_1 {
fn default() -> Self { Self::FOO }
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_int_encodings() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
struct Foo {
s32 a;
u16 b;
s16 c;
bool d;
char e;
};
struct Foo foo;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
pub a: i32,
pub b: u16,
pub c: i16,
pub d: std::mem::MaybeUninit<bool>,
pub e: i8,
}
impl Default for Foo {
fn default() -> Self {
Self {
a: i32::default(),
b: u16::default(),
c: i16::default(),
d: std::mem::MaybeUninit::new(bool::default()),
e: i8::default(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
assert_definition(&btf, &struct_foo, expected_output);
}
#[test]
fn test_btf_dump_definition_unnamed_union() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
// re-typed 'struct bpf_sock_tuple tup' from vmlinux as of kernel 5.15
// with a little bit added for additional complexity testing
struct bpf_sock_tuple_5_15 {
union {
struct {
__be32 saddr;
__be32 daddr;
__be16 sport;
__be16 dport;
} ipv4;
struct {
__be32 saddr[4];
__be32 daddr[4];
__be16 sport;
__be16 dport;
} ipv6;
};
union {
int a;
char *b;
};
};
struct bpf_sock_tuple_5_15 tup;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct bpf_sock_tuple_5_15 {
pub __anon_bpf_sock_tuple_5_15_1: __anon_bpf_sock_tuple_5_15_1,
pub __pad_36: [u8; 4],
pub __anon_bpf_sock_tuple_5_15_2: __anon_bpf_sock_tuple_5_15_2,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_bpf_sock_tuple_5_15_1 {
pub ipv4: __anon_bpf_sock_tuple_5_15_3,
pub ipv6: __anon_bpf_sock_tuple_5_15_4,
}
impl std::fmt::Debug for __anon_bpf_sock_tuple_5_15_1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_bpf_sock_tuple_5_15_1 {
fn default() -> Self {
Self {
ipv4: __anon_bpf_sock_tuple_5_15_3::default(),
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union __anon_bpf_sock_tuple_5_15_2 {
pub a: i32,
pub b: *mut i8,
}
impl std::fmt::Debug for __anon_bpf_sock_tuple_5_15_2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(???)")
}
}
impl Default for __anon_bpf_sock_tuple_5_15_2 {
fn default() -> Self {
Self {
a: i32::default(),
}
}
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct __anon_bpf_sock_tuple_5_15_3 {
pub saddr: u32,
pub daddr: u32,
pub sport: u16,
pub dport: u16,
}
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct __anon_bpf_sock_tuple_5_15_4 {
pub saddr: [u32; 4],
pub daddr: [u32; 4],
pub sport: u16,
pub dport: u16,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_bpf_sock_tuple = find_type_in_btf!(btf, types::Struct<'_>, "bpf_sock_tuple_5_15");
assert_definition(&btf, &struct_bpf_sock_tuple, expected_output);
}
#[test]
fn test_btf_dump_definition_empty_union() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
struct struct_with_empty_union {
int member;
union {};
};
struct struct_with_empty_union s;
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
pub struct struct_with_empty_union {
pub member: i32,
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let struct_with_empty_union =
find_type_in_btf!(btf, types::Struct<'_>, "struct_with_empty_union");
assert_definition(&btf, &struct_with_empty_union, expected_output);
}
#[test]
fn test_btf_dump_definition_struct_ops_mixed() {
let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
SEC("struct_ops/test_1")
int BPF_PROG(test_1, struct bpf_dummy_ops_state *state)
{
return 0;
}
SEC(".struct_ops")
struct bpf_dummy_ops dummy_1 = {
.test_1 = (void *)test_1,
};
SEC(".struct_ops.link")
struct bpf_dummy_ops dummy_2 = {
.test_1 = (void *)test_1,
};
"#};
let expected_output = indoc! {r#"
#[derive(Debug, Clone)]
#[repr(C)]
pub struct StructOps {
pub dummy_1: *mut types::bpf_dummy_ops,
pub dummy_2: *mut types::bpf_dummy_ops,
}
impl StructOps {
pub fn dummy_1(&self) -> &types::bpf_dummy_ops {
// SAFETY: The library ensures that the member is pointing to
// valid data.
unsafe { self.dummy_1.as_ref() }.unwrap()
}
pub fn dummy_1_mut(&mut self) -> &mut types::bpf_dummy_ops {
// SAFETY: The library ensures that the member is pointing to
// valid data.
unsafe { self.dummy_1.as_mut() }.unwrap()
}
pub fn dummy_2(&self) -> &types::bpf_dummy_ops {
// SAFETY: The library ensures that the member is pointing to
// valid data.
unsafe { self.dummy_2.as_ref() }.unwrap()
}
pub fn dummy_2_mut(&mut self) -> &mut types::bpf_dummy_ops {
// SAFETY: The library ensures that the member is pointing to
// valid data.
unsafe { self.dummy_2.as_mut() }.unwrap()
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bpf_dummy_ops {
pub test_1: *mut libbpf_rs::libbpf_sys::bpf_program,
pub test_2: *mut libbpf_rs::libbpf_sys::bpf_program,
pub test_sleepable: *mut libbpf_rs::libbpf_sys::bpf_program,
}
impl Default for bpf_dummy_ops {
fn default() -> Self {
Self {
test_1: std::ptr::null_mut(),
test_2: std::ptr::null_mut(),
test_sleepable: std::ptr::null_mut(),
}
}
}
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let mut processed = HashSet::new();
let mut def = String::new();
let ops = GenStructOps::new(&btf).unwrap();
let () = ops.gen_struct_ops_def(&mut def).unwrap();
let () = ops.gen_dependent_types(&mut processed, &mut def).unwrap();
assert_output(&def, expected_output);
}
#[test]
fn test_btf_dump_float() {
let prog_text = indoc! {r#"
float f = 2.16;
double d = 12.15;
"#};
let mmap = build_btf_mmap(prog_text);
let btf = btf_from_mmap(&mmap);
let f = find_type_in_btf!(btf, Var, "f");
let d = find_type_in_btf!(btf, Var, "d");
assert_eq!(
"f32",
btf.type_declaration(f).expect("Failed to generate f decl")
);
assert_eq!(
"f64",
btf.type_declaration(d).expect("Failed to generate d decl")
);
}