Skip to content

Commit d45f669

Browse files
committed
libbpf-cargo: Support on-the-fly vmlinux.h extraction
This adds the `extract_vmlinux_header` method to `SkelBuilder`, which lets libbpf-cargo auto-generate the "vmlinux.h" header from the running kernel using bpftool. This would be particularly useful when packaging a crate with "cargo package", as it doesn't need the header file to be added to the repository. Signed-off-by: Daiki Ueno <[email protected]>
1 parent ef3e33b commit d45f669

File tree

4 files changed

+102
-9
lines changed

4 files changed

+102
-9
lines changed

libbpf-cargo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ path = "src/lib.rs"
2828
# When turned on, link against system-installed libbpf instead of building
2929
# and linking against vendored libbpf sources
3030
novendor = ["libbpf-sys/novendor"]
31+
bpftool = []
3132

3233
[dependencies]
3334
anyhow = "1.0.1"

libbpf-cargo/src/build.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,11 @@ fn compile(debug: bool, objs: &[UnprocessedObj], clang: &Path, target_dir: &Path
242242
Ok(())
243243
}
244244

245-
fn extract_clang_or_default(clang: Option<&PathBuf>) -> PathBuf {
246-
match clang {
245+
fn extract_command_or_default(command: Option<&PathBuf>, default: &str) -> PathBuf {
246+
match command {
247247
Some(c) => c.into(),
248248
// Searches $PATH
249-
None => "clang".into(),
249+
None => default.into(),
250250
}
251251
}
252252

@@ -269,7 +269,7 @@ pub fn build(
269269

270270
check_progs(&to_compile)?;
271271

272-
let clang = extract_clang_or_default(clang);
272+
let clang = extract_command_or_default(clang, "clang");
273273
check_clang(debug, &clang, skip_clang_version_checks)
274274
.with_context(|| anyhow!("{} is invalid", clang.display()))?;
275275
compile(debug, &to_compile, &clang, &target_dir).context("Failed to compile progs")?;
@@ -286,8 +286,10 @@ pub fn build_single(
286286
clang: Option<&PathBuf>,
287287
skip_clang_version_checks: bool,
288288
options: &str,
289+
bpftool: Option<&PathBuf>,
290+
vmlinux_h: Option<&PathBuf>,
289291
) -> Result<()> {
290-
let clang = extract_clang_or_default(clang);
292+
let clang = extract_command_or_default(clang, "clang");
291293
check_clang(debug, &clang, skip_clang_version_checks)?;
292294
let header_parent_dir = tempdir()?;
293295
let header_dir = extract_libbpf_headers_to_disk(header_parent_dir.path())?;
@@ -297,6 +299,21 @@ pub fn build_single(
297299
options.to_string()
298300
};
299301

302+
if let Some(vmlinux_h) = vmlinux_h {
303+
let file = fs::File::create(vmlinux_h)?;
304+
let bpftool = extract_command_or_default(bpftool, "bpftool");
305+
Command::new(bpftool)
306+
.arg("btf")
307+
.arg("dump")
308+
.arg("file")
309+
.arg("/sys/kernel/btf/vmlinux")
310+
.arg("format")
311+
.arg("c")
312+
.stdout(file)
313+
.status()?;
314+
compiler_options += &format!(" -I{}", vmlinux_h.parent().unwrap().display());
315+
}
316+
300317
// Explicitly disable stack protector logic, which doesn't work with
301318
// BPF. See https://lkml.org/lkml/2020/2/21/1000.
302319
compiler_options += " -fno-stack-protector";

libbpf-cargo/src/lib.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ pub struct SkeletonBuilder {
120120
clang: Option<PathBuf>,
121121
clang_args: String,
122122
skip_clang_version_check: bool,
123+
bpftool: Option<PathBuf>,
124+
extract_vmlinux_header: bool,
125+
vmlinux_h: Option<PathBuf>,
123126
rustfmt: PathBuf,
124127
dir: Option<TempDir>,
125128
}
@@ -139,6 +142,9 @@ impl SkeletonBuilder {
139142
clang: None,
140143
clang_args: String::new(),
141144
skip_clang_version_check: false,
145+
bpftool: None,
146+
extract_vmlinux_header: false,
147+
vmlinux_h: None,
142148
rustfmt: "rustfmt".into(),
143149
dir: None,
144150
}
@@ -202,6 +208,22 @@ impl SkeletonBuilder {
202208
self
203209
}
204210

211+
/// Specify which `bpftool` binary to use
212+
///
213+
/// Default searchs `$PATH` for `bpftool`
214+
pub fn bpftool<P: AsRef<Path>>(&mut self, bpftool: P) -> &mut SkeletonBuilder {
215+
self.bpftool = Some(bpftool.as_ref().to_path_buf());
216+
self
217+
}
218+
219+
/// Tell whether to extract vmlinux.h from the running kernel
220+
///
221+
/// Default is off
222+
pub fn extract_vmlinux_header(&mut self, extract: bool) -> &mut SkeletonBuilder {
223+
self.extract_vmlinux_header = extract;
224+
self
225+
}
226+
205227
/// Specify which `rustfmt` binary to use
206228
///
207229
/// Default searches `$PATH` for `rustfmt`
@@ -240,15 +262,25 @@ impl SkeletonBuilder {
240262
)));
241263
}
242264

243-
if self.obj.is_none() {
244-
let name = filename.split('.').next().unwrap();
265+
if self.obj.is_none() || self.extract_vmlinux_header {
245266
let dir = tempdir().map_err(|e| Error::Build(e.into()))?;
246-
let objfile = dir.path().join(format!("{name}.o"));
247-
self.obj = Some(objfile);
248267
// Hold onto tempdir so that it doesn't get deleted early
249268
self.dir = Some(dir);
250269
}
251270

271+
if self.obj.is_none() {
272+
let name = filename.split('.').next().unwrap();
273+
// Unwrap is safe here since we guarantee that dir.is_some() above
274+
let objfile = self.dir.as_ref().unwrap().path().join(format!("{name}.o"));
275+
self.obj = Some(objfile);
276+
}
277+
278+
if self.extract_vmlinux_header {
279+
// Unwrap is safe here since we guarantee that dir.is_some() above
280+
let vmlinux_h = self.dir.as_ref().unwrap().path().join("vmlinux.h");
281+
self.vmlinux_h = Some(vmlinux_h);
282+
}
283+
252284
build::build_single(
253285
self.debug,
254286
source,
@@ -257,6 +289,8 @@ impl SkeletonBuilder {
257289
self.clang.as_ref(),
258290
self.skip_clang_version_check,
259291
&self.clang_args,
292+
self.bpftool.as_ref(),
293+
self.vmlinux_h.as_ref(),
260294
)
261295
.map_err(Error::Build)?;
262296

libbpf-cargo/src/test.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,47 @@ fn test_skeleton_builder_clang_opts() {
850850
.unwrap();
851851
}
852852

853+
#[cfg(feature = "bpftool")]
854+
#[test]
855+
fn test_skeleton_builder_bpftool() {
856+
let (_dir, proj_dir, _cargo_toml) = setup_temp_project();
857+
858+
// Add prog dir
859+
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
860+
861+
// Add a prog
862+
let mut prog = OpenOptions::new()
863+
.write(true)
864+
.create(true)
865+
.open(proj_dir.join("src/bpf/prog.bpf.c"))
866+
.expect("failed to open prog.bpf.c");
867+
868+
write!(
869+
prog,
870+
r#"
871+
#include "vmlinux.h"
872+
"#,
873+
)
874+
.expect("failed to write prog.bpf.c");
875+
876+
let skel = NamedTempFile::new().unwrap();
877+
878+
// Should fail b/c vmlinux.h is not present
879+
SkeletonBuilder::new()
880+
.source(proj_dir.join("src/bpf/prog.bpf.c"))
881+
.debug(true)
882+
.build_and_generate(skel.path())
883+
.unwrap_err();
884+
885+
// Should succeed b/c vmlinux.h is extracted
886+
SkeletonBuilder::new()
887+
.source(proj_dir.join("src/bpf/prog.bpf.c"))
888+
.debug(true)
889+
.extract_vmlinux_header(true)
890+
.build_and_generate(skel.path())
891+
.unwrap();
892+
}
893+
853894
#[test]
854895
fn test_skeleton_builder_arrays_ptrs() {
855896
let (_dir, proj_dir, cargo_toml) = setup_temp_project();

0 commit comments

Comments
 (0)