Skip to content

Commit

Permalink
libbpf-cargo: Stop generation of duplicate types
Browse files Browse the repository at this point in the history
When a type is present in more than one section in the ELF binary (e.g.,
in .bss as well as .rodata), the generation code ends up generating
multiple Rust correspondents as well. That is horrible for
interoperability, because there is no language-level connection between
these types and so the corresponding objects are not interchangeable in
any way.
This change fixes this problem by putting all generated types in a
single Rust module, '<project>_types', as opposed to having them be
emitted into modules for each of the "data sections".

Closes: #590
  • Loading branch information
d-e-s-o authored and danielocfb committed Feb 16, 2024
1 parent e23c74d commit 73c7900
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 32 deletions.
8 changes: 4 additions & 4 deletions examples/capable/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod capable {
include!(concat!(env!("OUT_DIR"), "/capable.skel.rs"));
}

use capable::capable_rodata_types::uniqueness;
use capable::capable_types::uniqueness;
use capable::*;

static CAPS: phf::Map<i32, &'static str> = phf_map! {
Expand Down Expand Up @@ -103,7 +103,7 @@ struct Command {
debug: bool,
}

unsafe impl Plain for capable_bss_types::event {}
unsafe impl Plain for capable_types::event {}

fn bump_memlock_rlimit() -> Result<()> {
let rlimit = libc::rlimit {
Expand Down Expand Up @@ -133,7 +133,7 @@ fn print_banner(extra_fields: bool) {
}
}

fn _handle_event(opts: Command, event: capable_bss_types::event) {
fn _handle_event(opts: Command, event: capable_types::event) {
let now = if let Ok(now) = OffsetDateTime::now_local() {
let format = format_description!("[hour]:[minute]:[second]");
now.format(&format)
Expand Down Expand Up @@ -203,7 +203,7 @@ fn main() -> Result<()> {

print_banner(opts.extra_fields);
let handle_event = move |_cpu: i32, data: &[u8]| {
let mut event = capable_bss_types::event::default();
let mut event = capable_types::event::default();
plain::copy_from_bytes(&mut event, data).expect("Data buffer was too short");
_handle_event(opts, event);
};
Expand Down
4 changes: 2 additions & 2 deletions examples/runqslower/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct Command {
verbose: bool,
}

unsafe impl Plain for runqslower_bss_types::event {}
unsafe impl Plain for runqslower_types::event {}

fn bump_memlock_rlimit() -> Result<()> {
let rlimit = libc::rlimit {
Expand All @@ -52,7 +52,7 @@ fn bump_memlock_rlimit() -> Result<()> {
}

fn handle_event(_cpu: i32, data: &[u8]) {
let mut event = runqslower_bss_types::event::default();
let mut event = runqslower_types::event::default();
plain::copy_from_bytes(&mut event, data).expect("Data buffer was too short");

let now = if let Ok(now) = OffsetDateTime::now_local() {
Expand Down
2 changes: 2 additions & 0 deletions libbpf-cargo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Unreleased
- Adjusted `SkeletonBuilder::clang_args` to accept an iterator of
arguments instead of a string
- Added `--clang-args` argument to `make` and `build` sub-commands
- Put all generated types into single `<project>_types` module as opposed to
having multiple modules for various sections (`.bss`, `.rodata`, etc.)
- Fixed potential unsoundness issues in generated skeletons by wrapping "unsafe"
type in `MaybeUninit`
- Updated `libbpf-sys` dependency to `1.3.0`
Expand Down
28 changes: 13 additions & 15 deletions libbpf-cargo/src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,30 +406,28 @@ fn gen_skel_datasec_defs(skel: &mut String, obj_name: &str, object: &[u8]) -> Re
};
let btf = GenBtf::from(btf);

write!(
skel,
r#"
pub mod {obj_name}_types {{
"#
)?;

let mut processed = HashSet::new();
for ty in btf.type_by_kind::<types::DataSec<'_>>() {
let name = match ty.name() {
Some(s) => s.to_str()?,
None => "",
};
let sec_ident = match canonicalize_internal_map_name(name) {
Some(n) => n,
None => continue,
};

write!(
skel,
r#"
pub mod {obj_name}_{sec_ident}_types {{
"#
)?;
if canonicalize_internal_map_name(name).is_none() {
continue;
}

let mut processed = HashSet::new();
let sec_def = btf.type_definition(*ty, &mut processed)?;
write!(skel, "{sec_def}")?;

writeln!(skel, "}}")?;
}

writeln!(skel, "}}")?;
Ok(())
}

Expand Down Expand Up @@ -532,7 +530,7 @@ fn gen_skel_datasec_getters(
Some(n) => n,
None => continue,
};
let struct_name = format!("{obj_name}_{name}_types::{name}");
let struct_name = format!("{obj_name}_types::{name}");
let immutable = loaded && map_is_readonly(map);
let mutabilities = if immutable {
[false].as_ref()
Expand Down
2 changes: 1 addition & 1 deletion libbpf-cargo/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ fn test_skeleton_datasec() {
skel.bss_mut().myglobal = 24;
// Read only for rodata after load
let _rodata: &prog_rodata_types::rodata = skel.rodata();
let _rodata: &prog_types::rodata = skel.rodata();
}}
"#,
)
Expand Down
19 changes: 9 additions & 10 deletions libbpf-rs/src/skeleton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,20 +366,19 @@ pub trait SkelBuilder<'a> {
///
/// The type of the value returned by each of these methods will be specific to your BPF program.
/// A common convention is to define a single global variable in the BPF program with a struct type
/// containing a field for each configuration parameter <sup>\[[source]\]</sup>. libbpf-rs
/// containing a field for each configuration parameter <sup>\[[source]\]</sup>. libbpf-rs
/// auto-generates this pattern for you without you having to define such a struct type in your BPF
/// program. It does this by examining each of the global variables in your BPF program's `.bss`,
/// `.data`, and `.rodata` sections and then creating rust struct types `<yourprogram>_bss_types`,
/// `<yourprogram>_data_types`, and `<yourprogram>_rodata_types`. Since these struct types are
/// specific to the layout of your BPF program, they are not documented in this crate. However you
/// can see documentation for them by running `cargo doc` in your own project and looking at the
/// `imp` module. You can also view their implementation by looking at the generated skeleton rust
/// source file. The use of these methods can also be seen in the examples 'capable', 'runqslower',
/// and 'tproxy'.
/// `.data`, and `.rodata` sections and then creating Rust struct types. Since these struct types
/// are specific to the layout of your BPF program, they are not documented in this crate. However
/// you can see documentation for them by running `cargo doc` in your own project and looking at
/// the `imp` module. You can also view their implementation by looking at the generated skeleton
/// rust source file. The use of these methods can also be seen in the examples 'capable',
/// 'runqslower', and 'tproxy'.
///
/// If you ever doubt whether libbpf-rs has placed a particular variable in the correct struct
/// type, you can see which section each global variable is stored in by examing the output of the
/// following command (after a successful build):
/// type, you can see which section each global variable is stored in by examining the output of
/// the following command (after a successful build):
///
/// ```sh
/// bpf-objdump --syms ./target/bpf/*.bpf.o
Expand Down

0 comments on commit 73c7900

Please sign in to comment.