Skip to content

Commit b7136cf

Browse files
committed
build: generate vmlinux at compile time
Previously SeaBee relied on the kernel version to decide which eBPF code to compile and load. This method proved unreliable since many distros backport features to old kernel versions. In order to make sure that our code always builds correctly, this commit strips vmlinux.h from the respoitory and adds code to the build.rs script to generate vmlinux.h and detect features in it. This enables our test cases to pass on rocky 9 linux. Signed-off-by: Alan Wandke <[email protected]>
1 parent 6fcc185 commit b7136cf

23 files changed

+194
-295096
lines changed

Cargo.lock

Lines changed: 2 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ ctrlc = { version = "3.4", features = ["termination"] }
1212
libbpf-rs = { version = "0.25.0-beta.1", default-features = false, features = ["vendored"] }
1313
libtest-mimic = "0.7"
1414
nix = "0.28"
15-
procfs = { version = "0.17", default-features = false }
1615
serde = { version = "1.0", features = ["derive"] }
1716
serde_json = "1.0"
1817
strum = "0.26"

bpf/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,3 @@ bindgen = { version = "0.69", default-features = false, features = ["runtime"] }
2323
doxygen-rs = "0.4"
2424
libbpf-rs.workspace = true
2525
libbpf-cargo = { version = "0.25.0-beta.1", default-features = false}
26-
procfs.workspace = true

bpf/build.rs

Lines changed: 105 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
use std::env;
3-
use std::fs;
2+
use std::collections::HashSet;
3+
use std::io::{BufRead, BufReader, Write};
44
use std::path::{Path, PathBuf};
5+
use std::process::{Command, Stdio};
6+
use std::{env, fs};
57

6-
use anyhow::{Context, Result};
8+
use anyhow::{anyhow, Context, Result};
79
use libbpf_cargo::SkeletonBuilder;
8-
use procfs::KernelVersion;
10+
11+
const VMLINUX_FEATURES: [&str; 2] = ["bpf_map_create", "bpf_map_alloc_security"];
912

1013
/// Tells Cargo to rerun the build if the supplied file has changed
1114
fn track_file(header: &str) {
1215
println!("cargo:rerun-if-changed={header}");
1316
}
1417

1518
/// Converts a BPF source code path to a skeleton path for [libbpf_rs]
16-
fn get_skel_path(src_file: &Path, version: Option<&KernelVersion>) -> String {
19+
fn get_skel_path(src_file: &Path) -> PathBuf {
1720
if let Some(bpf) = src_file.file_stem() {
1821
if let Some(stem) = Path::new(bpf).file_stem() {
19-
let mut skel_path = String::new();
20-
skel_path.push_str(&stem.to_string_lossy());
21-
// if a kernel version is specified, add that at the end of the name
22-
if let Some(version) = version {
23-
skel_path.push_str(&format!(
24-
"_{}_{}_{}",
25-
version.major, version.minor, version.patch
26-
));
27-
}
28-
skel_path.push_str(".skel.rs");
29-
return skel_path;
22+
let mut skel = PathBuf::new();
23+
skel.push(stem);
24+
skel.set_extension("skel.rs");
25+
return skel;
3026
}
3127
}
3228
panic!(
@@ -35,53 +31,14 @@ fn get_skel_path(src_file: &Path, version: Option<&KernelVersion>) -> String {
3531
);
3632
}
3733

38-
/// Converts [KernelVersion] to BPF_CODE_VERSION to be compared against the
39-
/// `KERNEL_VERSION` macro for conditional compilation
40-
pub fn kernel_version_to_bpf_code_version(version: &KernelVersion) -> u32 {
41-
((version.major as u32) << 16)
42-
+ ((version.minor as u32) << 8)
43-
+ std::cmp::max(version.patch as u32, 255)
44-
}
45-
4634
/// Uses [libbpf_cargo::SkeletonBuilder] to compile BPF source code to a skeleton
47-
fn compile_bpf_obj(
48-
src_file: &PathBuf,
49-
out_path: &Path,
50-
versions: Option<&[KernelVersion]>,
51-
) -> Result<()> {
52-
let mut clang_args: Vec<String> = vec![
53-
"-Iinclude".to_string(),
54-
format!("-I{}", out_path.to_string_lossy()),
55-
];
56-
let mut skel_build = SkeletonBuilder::new();
57-
skel_build.source(src_file);
58-
// if multiple versions are specified
59-
if let Some(versions) = versions {
60-
// compile the default case (every version before the first specified)
61-
clang_args.push("-DBPF_CODE_VERSION=0".to_owned());
62-
skel_build
63-
.clang_args(&clang_args)
64-
.build_and_generate(out_path.join(get_skel_path(src_file, None)))?;
65-
clang_args.pop();
66-
// compile each version of the skeleton separately
67-
for version in versions {
68-
clang_args.push(format!(
69-
"-DBPF_CODE_VERSION={}",
70-
kernel_version_to_bpf_code_version(version)
71-
));
72-
skel_build
73-
.clang_args(&clang_args)
74-
.build_and_generate(out_path.join(get_skel_path(src_file, Some(version))))?;
75-
clang_args.pop();
76-
}
77-
}
78-
// otherwise, assume all versions are supported with the same skeleton
79-
else {
80-
skel_build
81-
.clang_args(&clang_args)
82-
.build_and_generate(out_path.join(get_skel_path(src_file, None)))?;
83-
}
84-
Ok(())
35+
fn compile_bpf_obj(src_file: &PathBuf, out_path: &Path) -> Result<PathBuf> {
36+
let bpf_skel_path = out_path.join(get_skel_path(src_file));
37+
SkeletonBuilder::new()
38+
.source(src_file)
39+
.clang_args([&format!("-I{}", out_path.to_string_lossy()), "-Iinclude"])
40+
.build_and_generate(&bpf_skel_path)?;
41+
Ok(bpf_skel_path)
8542
}
8643

8744
#[derive(Debug)]
@@ -148,14 +105,11 @@ fn generate_header_bindings(hdr_file: &Path, out_path: &Path) -> Result<PathBuf>
148105
///
149106
/// Copied from https://github.com/libbpf/libbpf-rs/blob/aacaec1b7dfaa4bf9112d2f4168d77dfceee499f/libbpf-cargo/src/build.rs#L55
150107
fn extract_libbpf_headers_to_disk(target_dir: &Path) -> Result<Option<PathBuf>> {
151-
use std::fs::OpenOptions;
152-
use std::io::Write;
153-
154108
let dir = target_dir.join("bpf");
155109
fs::create_dir_all(&dir)?;
156110
for (filename, contents) in libbpf_rs::libbpf_sys::API_HEADERS.iter() {
157111
let path = dir.as_path().join(filename);
158-
let mut file = OpenOptions::new()
112+
let mut file = fs::OpenOptions::new()
159113
.write(true)
160114
.create(true)
161115
.truncate(true)
@@ -170,12 +124,7 @@ fn extract_libbpf_headers_to_disk(target_dir: &Path) -> Result<Option<PathBuf>>
170124
///
171125
/// You can pass a vector of filenames to explicitly ignore if they are known to cause
172126
/// problems for either Clang or bindgen (e.g. "vmlinux.h")
173-
fn build(
174-
out_path: &Path,
175-
base_path: &str,
176-
ignore_files: Vec<&str>,
177-
versions: Option<&[KernelVersion]>,
178-
) -> Result<()> {
127+
fn build(out_path: &Path, base_path: &str, ignore_files: Vec<&str>) -> Result<()> {
179128
for path in fs::read_dir(base_path)?
180129
.filter_map(|r| r.ok())
181130
.map(|r| r.path())
@@ -189,7 +138,7 @@ fn build(
189138
continue;
190139
}
191140
if path_str.ends_with(".bpf.c") {
192-
compile_bpf_obj(&path, out_path, versions)?;
141+
compile_bpf_obj(&path, out_path)?;
193142
}
194143
if path_str.ends_with(".h") {
195144
generate_header_bindings(&path, out_path)?;
@@ -198,31 +147,97 @@ fn build(
198147
Ok(())
199148
}
200149

150+
// Creates vmlinux (truncates if exists)
151+
fn generate_vmlinux(out_path: &Path) -> Result<PathBuf> {
152+
let vmlinux_path = out_path.join("bpf/vmlinux.h");
153+
let vmlinux_file = fs::File::create(&vmlinux_path)?;
154+
// bpftool is installed in the update_test_dependencies.sh which
155+
// is run as part of the update_root_dependencies.sh
156+
let status = Command::new("bpftool")
157+
.args([
158+
"btf",
159+
"dump",
160+
"file",
161+
"/sys/kernel/btf/vmlinux",
162+
"format",
163+
"c",
164+
])
165+
.stdout(Stdio::from(vmlinux_file))
166+
.status()?;
167+
if !status.success() {
168+
return Err(anyhow!(
169+
"failed to generate vmlinux using bpftool: {}",
170+
status
171+
));
172+
}
173+
174+
Ok(vmlinux_path)
175+
}
176+
177+
fn detect_vmlinux_features(vmlinux: &PathBuf) -> Result<HashSet<String>> {
178+
// detect features
179+
let file = fs::File::open(vmlinux)?;
180+
let reader = BufReader::new(file);
181+
let mut found = HashSet::new();
182+
for line in reader.lines() {
183+
let line = line?;
184+
for feat in VMLINUX_FEATURES {
185+
if line.contains(feat) {
186+
found.insert(feat.to_string());
187+
}
188+
}
189+
}
190+
191+
// validate features
192+
if found.contains(VMLINUX_FEATURES[0]) && found.contains(VMLINUX_FEATURES[1]) {
193+
return Err(anyhow!(
194+
"Conflicting function definitions: '{}' and '{}'",
195+
VMLINUX_FEATURES[0],
196+
VMLINUX_FEATURES[1]
197+
));
198+
} else if !found.contains(VMLINUX_FEATURES[0]) && !found.contains(VMLINUX_FEATURES[1]) {
199+
return Err(anyhow!(
200+
"Kernel no supported. No function definition found for '{}' or '{}'",
201+
VMLINUX_FEATURES[0],
202+
VMLINUX_FEATURES[1]
203+
));
204+
}
205+
206+
Ok(found)
207+
}
208+
209+
fn export_features_to_header(features: HashSet<String>, out_path: &Path) -> Result<()> {
210+
let vmlinux_features_path = out_path.join("bpf/vmlinux_features.h");
211+
let mut f = fs::File::create(vmlinux_features_path)?;
212+
213+
writeln!(f, "// Auto-generated header from build.rs")?;
214+
215+
for flag in features {
216+
let macro_name = flag.to_uppercase();
217+
writeln!(f, "#define HAS_{}", macro_name)?;
218+
}
219+
220+
Ok(())
221+
}
222+
201223
fn main() -> Result<()> {
202224
let out_path = PathBuf::from(env::var_os("OUT_DIR").context("OUT_DIR must be set")?);
203225
extract_libbpf_headers_to_disk(&out_path)?;
226+
// Create vmlinux and do feature detection based on it
227+
let vmlinux =
228+
generate_vmlinux(&out_path).map_err(|e| anyhow!("failed to generate vmlinux.h: {e}"))?;
229+
let features = detect_vmlinux_features(&vmlinux)?;
230+
export_features_to_header(features, &out_path)?;
231+
204232
// Build common
205233
build(
206234
&out_path,
207235
"include",
208-
vec![
209-
"logging.h",
210-
"vmlinux.h",
211-
"vmlinux_6_0_18.h",
212-
"vmlinux_6_11_4.h",
213-
"seabee_maps.h",
214-
"seabee_utils.h",
215-
],
216-
None,
236+
vec!["logging.h", "seabee_maps.h", "seabee_utils.h"],
217237
)?;
218238
// Build bpf code
219-
build(
220-
&out_path,
221-
"src/seabee",
222-
vec!["seabee_log.h"],
223-
Some(&[KernelVersion::new(6, 1, 0), KernelVersion::new(6, 9, 0)]),
224-
)?;
225-
build(&out_path, "src/kernel_api", vec![], None)?;
226-
build(&out_path, "src/tests", vec![], None)?;
239+
build(&out_path, "src/seabee", vec!["seabee_log.h"])?;
240+
build(&out_path, "src/kernel_api", vec![])?;
241+
build(&out_path, "src/tests", vec![])?;
227242
Ok(())
228243
}

bpf/include/logging.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* @file logging.h
66
*/
77

8-
#include "vmlinux.h"
9-
#include "bpf/bpf_helpers.h"
8+
#include <bpf/vmlinux.h>
9+
#include <bpf/bpf_helpers.h>
1010

1111
#include "logging_types.h"
1212
#include "shared_rust_types.h"

0 commit comments

Comments
 (0)