libbpf-cargo-0.23.3/.cargo_vcs_info.json0000644000000001520000000000100134460ustar { "git": { "sha1": "e58db29643eeffc5067186abfa32da3d5d3dcaab" }, "path_in_vcs": "libbpf-cargo" }libbpf-cargo-0.23.3/CHANGELOG.md000064400000000000000000000046561046102023000140640ustar 000000000000000.23.3 ------ - Fixed generation of `Default` impl in presence of large padding arrays 0.23.1 ------ - Added "import injection" escape hatch to generated skeletons 0.23.0 ------ - Removed `novendor` feature in favor of having disableable default feature - Added support for `struct_ops` shadow objects for generated skeletons - Added support for handling custom data sections in generated skeletons - 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 `_types` module as opposed to having multiple modules for various sections (`.bss`, `.rodata`, etc.) - Fixed potential naming issues by escaping reserved keywords used in identifiers - Fixed potential unsoundness issues in generated skeletons by wrapping "unsafe" type in `MaybeUninit` - Added pointer based ("raw") access to datasec type to generated skeletons - Added better handling for bitfields to code generation logic - Updated `libbpf-sys` dependency to `1.4.0` - Bumped minimum Rust version to `1.71` 0.22.0 ------ - Adjusted skeleton creation logic to generate shared and exclusive datasec accessor functions - Removed `Error` enum in favor of `anyhow::Error` - Bumped minimum Rust version to `1.65` 0.21.2 ------ - Added `Default` impl for generated `struct` types containing pointers - Fixed handling of function prototype type declaration inference in BTF and skeleton generation - Improved error reporting in build script usage - Bumped minimum Rust version to `1.64` 0.21.1 ------ - Adjusted named padding members in generated types to have `pub` visibility 0.21.0 ------ - Adjusted skeleton generation code to ensure implementation of `libbpf-rs`'s `SkelBuilder`, `OpenSkel`, and `Skel` traits - Improved error reporting on BPF C file compilation failure 0.20.1 ------ - Switched over to using `libbpf-rs`'s BTF support internally for skeleton generation - Fixed potential build failures on systems defaulting to stack protector usage by passing `-fno-stack-protector` to `clang` 0.20.0 ------ - Fixed mismatch in size of generated types with respect to corresponding C types - Fixed generated skeleton potentially being unstable (changing each time) - Implemented `Sync` for generated skeletons - Made formatting using `rustfmt` optional - Updated various dependencies 0.19.1 ------ - Initial documented release libbpf-cargo-0.23.3/Cargo.lock0000644000000364750000000000100114420ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "camino" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ "serde", ] [[package]] name = "cargo-platform" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", "thiserror", ] [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "clap" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.46", ] [[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "goblin" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68" dependencies = [ "log", "plain", "scroll", ] [[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "itoa" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "libbpf-cargo" version = "0.23.3" dependencies = [ "anyhow", "cargo_metadata", "clap", "goblin", "libbpf-rs", "memmap2", "regex", "semver", "serde", "serde_json", "tempfile", ] [[package]] name = "libbpf-rs" version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1159a07fd5b9aca3f86ba5688394bb9bd75142bcec510052d6f765e98d801b09" dependencies = [ "bitflags", "libbpf-sys", "libc", "strum_macros", "vsprintf", ] [[package]] name = "libbpf-sys" version = "1.4.2+v1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "800bccc21b216764f96334a241a072b842121843bf679f5a03b0c2c21cb339ed" dependencies = [ "cc", "nix", "pkg-config", ] [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memmap2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "scroll" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", "syn 2.0.46", ] [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn 2.0.46", ] [[package]] name = "serde_json" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "strsim" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "strum_macros" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", "proc-macro2", "quote", "rustversion", "syn 1.0.107", ] [[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys", ] [[package]] name = "thiserror" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", "syn 2.0.46", ] [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vsprintf" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63" dependencies = [ "cc", "libc", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" libbpf-cargo-0.23.3/Cargo.toml0000644000000033100000000000100114430ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.71" name = "libbpf-cargo" version = "0.23.3" authors = [ "Daniel Xu ", "Daniel Müller ", ] description = "Cargo plugin to build bpf programs" homepage = "https://github.com/libbpf/libbpf-rs" documentation = "https://docs.rs/crate/libbpf-cargo" readme = "README.md" keywords = [ "bpf", "ebpf", "libbpf", ] license = "LGPL-2.1-only OR BSD-2-Clause" repository = "https://github.com/libbpf/libbpf-rs" [lib] path = "src/lib.rs" [[bin]] name = "cargo-libbpf" path = "src/main.rs" [dependencies.anyhow] version = "1.0.1" [dependencies.cargo_metadata] version = "0.15.0" [dependencies.clap] version = "4.0.32" features = ["derive"] [dependencies.libbpf-rs] version = "0.23" default-features = false [dependencies.memmap2] version = "0.5" [dependencies.regex] version = "1.6.0" features = [ "std", "unicode-perl", ] default-features = false [dependencies.semver] version = "1.0" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.tempfile] version = "3.3" [dev-dependencies.goblin] version = "0.6" [features] default = ["libbpf-rs/default"] [badges.maintenance] status = "actively-developed" libbpf-cargo-0.23.3/Cargo.toml.orig000064400000000000000000000025251046102023000151330ustar 00000000000000[package] name = "libbpf-cargo" description = "Cargo plugin to build bpf programs" repository = "https://github.com/libbpf/libbpf-rs" homepage = "https://github.com/libbpf/libbpf-rs" documentation = "https://docs.rs/crate/libbpf-cargo" readme = "README.md" version = "0.23.3" authors = ["Daniel Xu ", "Daniel Müller "] edition = "2021" rust-version = "1.71" license = "LGPL-2.1-only OR BSD-2-Clause" keywords = ["bpf", "ebpf", "libbpf"] [badges] maintenance = { status = "actively-developed" } # Crate is named libbpf-cargo to be consistent with libbpf-rs. # Binary must be named cargo-${SUBCOMMAND} to interop with cargo. [[bin]] name = "cargo-libbpf" path = "src/main.rs" [lib] path = "src/lib.rs" [features] # By default the crate uses a vendored libbpf, but requires other # necessary libs to be present on the system. default = ["libbpf-rs/default"] [dependencies] anyhow = "1.0.1" cargo_metadata = "0.15.0" libbpf-rs = { version = "0.23", default-features = false, path = "../libbpf-rs" } memmap2 = "0.5" regex = { version = "1.6.0", default-features = false, features = ["std", "unicode-perl"] } semver = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tempfile = "3.3" clap = { version = "4.0.32", features = ["derive"] } [dev-dependencies] goblin = "0.6" vmlinux = { path = "../vmlinux" } libbpf-cargo-0.23.3/LICENSE000064400000000000000000000000361046102023000132440ustar 00000000000000LGPL-2.1-only OR BSD-2-Clause libbpf-cargo-0.23.3/LICENSE.BSD-2-Clause000064400000000000000000000031511046102023000152250ustar 00000000000000Valid-License-Identifier: BSD-2-Clause SPDX-URL: https://spdx.org/licenses/BSD-2-Clause.html Usage-Guide: To use the BSD 2-clause "Simplified" License put the following SPDX tag/value pair into a comment according to the placement guidelines in the licensing rules documentation: SPDX-License-Identifier: BSD-2-Clause License-Text: Copyright (c) . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libbpf-cargo-0.23.3/LICENSE.LGPL-2.1000064400000000000000000000654251046102023000143140ustar 00000000000000Valid-License-Identifier: LGPL-2.1 Valid-License-Identifier: LGPL-2.1+ SPDX-URL: https://spdx.org/licenses/LGPL-2.1.html Usage-Guide: To use this license in source code, put one of the following SPDX tag/value pairs into a comment according to the placement guidelines in the licensing rules documentation. For 'GNU Lesser General Public License (LGPL) version 2.1 only' use: SPDX-License-Identifier: LGPL-2.1 For 'GNU Lesser General Public License (LGPL) version 2.1 or any later version' use: SPDX-License-Identifier: LGPL-2.1+ License-Text: GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. one line to give the library's name and an idea of what it does. Copyright (C) year name of author This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. signature of Ty Coon, 1 April 1990 Ty Coon, President of Vice That's all there is to it! libbpf-cargo-0.23.3/README.md000064400000000000000000000026041046102023000135210ustar 00000000000000[![CI](https://github.com/libbpf/libbpf-rs/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/libbpf/libbpf-rs/actions/workflows/test.yml) [![rustc](https://img.shields.io/badge/rustc-1.71+-blue.svg)](https://blog.rust-lang.org/2023/07/13/Rust-1.71.0.html) # libbpf-cargo [![crates.io badge](https://img.shields.io/crates/v/libbpf-cargo.svg)](https://crates.io/crates/libbpf-cargo) Helps you build and develop BPF programs with standard Rust tooling. - [Changelog](CHANGELOG.md) To use in your project, add into your `Cargo.toml`: ```toml [build-dependencies] libbpf-cargo = "0.23" ``` See [full documentation here](https://docs.rs/libbpf-cargo). This crate adheres to Cargo's [semantic versioning rules][cargo-semver]. At a minimum, it builds with the most recent Rust stable release minus five minor versions ("N - 5"). E.g., assuming the most recent Rust stable is `1.68`, the crate is guaranteed to build with `1.63` and higher. Please note that the generated skeleton files, while guaranteed to be source code compatible according to aforementioned versioning rules, are not guaranteed to be character identical between releases (including patch releases). ## Contributing We welcome all contributions! Please see the [contributor's guide](../CONTRIBUTING.md) for more information. [cargo-semver]: https://doc.rust-lang.org/cargo/reference/resolver.html#semver-compatibility libbpf-cargo-0.23.3/src/build.rs000064400000000000000000000240731046102023000145020ustar 00000000000000use std::collections::HashSet; use std::env::consts::ARCH; use std::ffi::OsStr; use std::ffi::OsString; use std::fs; use std::path::Path; use std::path::PathBuf; use std::process::Command; use anyhow::anyhow; use anyhow::bail; use anyhow::Context; use anyhow::Result; use regex::Regex; use semver::Version; use tempfile::tempdir; use crate::metadata; use crate::metadata::UnprocessedObj; fn check_progs(objs: &[UnprocessedObj]) -> Result<()> { let mut set = HashSet::with_capacity(objs.len()); for obj in objs { // OK to unwrap() file_name() b/c we already checked earlier that this is a valid file let dest = obj .out .as_path() .join(obj.path.as_path().file_name().unwrap()); if !set.insert(dest) { bail!( "Duplicate obj={} detected", obj.path.as_path().file_name().unwrap().to_string_lossy() ); } } Ok(()) } fn extract_version(output: &str) -> Result<&str> { let re = Regex::new(r"clang\s+version\s+(?P\d+\.\d+\.\d+)")?; let captures = re .captures(output) .ok_or_else(|| anyhow!("Failed to run regex on version string"))?; captures.name("version_str").map_or_else( || Err(anyhow!("Failed to find version capture group")), |v| Ok(v.as_str()), ) } /// Extract vendored libbpf header files to a temporary directory. /// /// Directory and enclosed contents will be removed when return object is dropped. #[cfg(feature = "default")] fn extract_libbpf_headers_to_disk(target_dir: &Path) -> Result> { use libbpf_rs::libbpf_sys; use std::fs::OpenOptions; use std::io::Write; let parent_dir = target_dir.join("bpf").join("src"); let dir = parent_dir.join("bpf"); fs::create_dir_all(&dir)?; for (filename, contents) in libbpf_sys::API_HEADERS.iter() { let path = dir.as_path().join(filename); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path)?; file.write_all(contents.as_bytes())?; } Ok(Some(parent_dir)) } #[cfg(not(feature = "default"))] fn extract_libbpf_headers_to_disk(_target_dir: &Path) -> Result> { Ok(None) } fn check_clang(debug: bool, clang: &Path, skip_version_checks: bool) -> Result<()> { let output = Command::new(clang.as_os_str()) .arg("--version") .output() .context("Failed to execute clang")?; if !output.status.success() { bail!("Failed to execute clang binary"); } if skip_version_checks { return Ok(()); } // Example output: // // clang version 10.0.0 // Target: x86_64-pc-linux-gnu // Thread model: posix // InstalledDir: /bin // let output = String::from_utf8_lossy(&output.stdout); let version_str = extract_version(&output)?; let version = Version::parse(version_str)?; if debug { println!("{} is version {}", clang.display(), version); } if version < Version::parse("10.0.0").unwrap() { bail!( "version {} is too old. Use --skip-clang-version-checks to skip version check", version ); } Ok(()) } /// Strip DWARF information from the provided BPF object file. /// /// We rely on the `libbpf` linker here, which removes debug information as a /// side-effect. fn strip_dwarf_info(file: &Path) -> Result<()> { let mut temp_file = file.as_os_str().to_os_string(); temp_file.push(".tmp"); fs::rename(file, &temp_file).context("Failed to rename compiled BPF object file")?; let mut linker = libbpf_rs::Linker::new(file).context("Failed to instantiate libbpf object file linker")?; linker .add_file(temp_file) .context("Failed to add object file to BPF linker")?; linker.link().context("Failed to link object file")?; Ok(()) } /// Concatenate a command and its arguments into a single string. fn concat_command(command: C, args: A) -> OsString where C: AsRef, A: IntoIterator, S: AsRef, { args.into_iter() .fold(command.as_ref().to_os_string(), |mut cmd, arg| { cmd.push(OsStr::new(" ")); cmd.push(arg.as_ref()); cmd }) } /// Format a command with the given list of arguments as a string. fn format_command(command: &Command) -> String { let prog = command.get_program(); let args = command.get_args(); concat_command(prog, args).to_string_lossy().to_string() } /// We're essentially going to run: /// /// clang -g -O2 -target bpf -c -D__TARGET_ARCH_$(ARCH) runqslower.bpf.c -o runqslower.bpf.o /// /// for each prog. fn compile_one( debug: bool, source: &Path, out: &Path, clang: &Path, clang_args: &[OsString], ) -> Result<()> { if debug { println!("Building {}", source.display()); } let mut cmd = Command::new(clang.as_os_str()); cmd.args(clang_args); if !clang_args .iter() .any(|arg| arg.to_string_lossy().contains("__TARGET_ARCH_")) { // We may end up being invoked by a build script, in which case // `CARGO_CFG_TARGET_ARCH` would represent the target architecture. let arch = option_env!("CARGO_CFG_TARGET_ARCH").unwrap_or(ARCH); let arch = match arch { "x86_64" => "x86", "aarch64" => "arm64", "powerpc64" => "powerpc", "s390x" => "s390", x => x, }; cmd.arg(format!("-D__TARGET_ARCH_{arch}")); } cmd.arg("-g") .arg("-O2") .arg("-target") .arg("bpf") .arg("-c") .arg(source.as_os_str()) .arg("-o") .arg(out); let output = cmd.output().context("Failed to execute clang")?; if !output.status.success() { let err = Err(anyhow!(String::from_utf8_lossy(&output.stderr).to_string())) .with_context(|| { format!( "Command `{}` failed ({})", format_command(&cmd), output.status ) }) .with_context(|| { format!( "Failed to compile {} from {}", out.display(), source.display() ) }); return err; } // Compilation with clang may contain DWARF information that references // system specific and temporary paths. That can render our generated // skeletons unstable, potentially rendering them unsuitable for inclusion // in version control systems. So strip this information. strip_dwarf_info(out).with_context(|| format!("Failed to strip object file {}", out.display())) } fn compile( debug: bool, objs: &[UnprocessedObj], clang: &Path, mut clang_args: Vec, target_dir: &Path, ) -> Result<()> { let header_dir = extract_libbpf_headers_to_disk(target_dir)?; if let Some(dir) = header_dir { clang_args.push(OsString::from("-I")); clang_args.push(dir.into_os_string()); } for obj in objs { let stem = obj.path.file_stem().with_context(|| { format!( "Could not calculate destination name for obj={}", obj.path.display() ) })?; let mut dest_name = stem.to_os_string(); dest_name.push(".o"); let mut dest_path = obj.out.to_path_buf(); dest_path.push(&dest_name); fs::create_dir_all(&obj.out)?; compile_one(debug, &obj.path, &dest_path, clang, &clang_args)?; } Ok(()) } fn extract_clang_or_default(clang: Option<&PathBuf>) -> PathBuf { match clang { Some(c) => c.into(), // Searches $PATH None => "clang".into(), } } #[allow(clippy::too_many_arguments)] pub fn build( debug: bool, manifest_path: Option<&PathBuf>, clang: Option<&PathBuf>, clang_args: Vec, skip_clang_version_checks: bool, ) -> Result<()> { let (target_dir, to_compile) = metadata::get(debug, manifest_path)?; if debug && !to_compile.is_empty() { println!("Found bpf progs to compile:"); for obj in &to_compile { println!("\t{obj:?}"); } } else if to_compile.is_empty() { bail!("Did not find any bpf progs to compile"); } check_progs(&to_compile)?; let clang = extract_clang_or_default(clang); check_clang(debug, &clang, skip_clang_version_checks) .with_context(|| anyhow!("{} is invalid", clang.display()))?; compile(debug, &to_compile, &clang, clang_args, &target_dir) .context("Failed to compile progs")?; Ok(()) } // Only used in libbpf-cargo library #[allow(dead_code)] pub fn build_single( debug: bool, source: &Path, out: &Path, clang: Option<&PathBuf>, skip_clang_version_checks: bool, mut clang_args: Vec, ) -> Result<()> { let clang = extract_clang_or_default(clang); check_clang(debug, &clang, skip_clang_version_checks)?; let header_parent_dir = tempdir()?; let header_dir = extract_libbpf_headers_to_disk(header_parent_dir.path())?; if let Some(dir) = header_dir { clang_args.push(OsString::from("-I")); clang_args.push(dir.into_os_string()); } // Explicitly disable stack protector logic, which doesn't work with // BPF. See https://lkml.org/lkml/2020/2/21/1000. clang_args.push(OsString::from("-fno-stack-protector")); compile_one(debug, source, out, &clang, &clang_args)?; Ok(()) } #[test] fn test_extract_version() { let upstream_format = r"clang version 10.0.0 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /bin "; assert_eq!(extract_version(upstream_format).unwrap(), "10.0.0"); let ubuntu_format = r"Ubuntu clang version 11.0.1-++20201121072624+973b95e0a84-1~exp1~20201121063303.19 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /bin "; assert_eq!(extract_version(ubuntu_format).unwrap(), "11.0.1"); assert!(extract_version("askldfjwe").is_err()); assert!(extract_version("my clang version 1.5").is_err()); } libbpf-cargo-0.23.3/src/gen/btf.rs000064400000000000000000000670131046102023000147300ustar 00000000000000use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Write; use std::mem::size_of; use std::num::NonZeroUsize; use std::ops::Deref; use anyhow::anyhow; use anyhow::bail; use anyhow::ensure; use anyhow::Result; use libbpf_rs::btf::types; use libbpf_rs::btf::types::Linkage; use libbpf_rs::btf::types::MemberAttr; use libbpf_rs::btf::BtfKind; use libbpf_rs::btf::BtfType; use libbpf_rs::btf::TypeId; use libbpf_rs::btf_type_match; use libbpf_rs::Btf; use libbpf_rs::HasSize; use libbpf_rs::ReferencesType; use super::canonicalize_internal_map_name; use super::InternalMapType; const ANON_PREFIX: &str = "__anon_"; /// Check whether the provided type is "unsafe" to use. /// /// A type is considered unsafe by this function if it is not valid for /// any bit pattern. fn is_unsafe(ty: BtfType<'_>) -> bool { let ty = ty.skip_mods_and_typedefs(); btf_type_match!(match ty { BtfKind::Int(t) => matches!(t.encoding, types::IntEncoding::Bool), BtfKind::Enum | BtfKind::Enum64 => true, _ => false, }) } fn is_struct_packed(composite: &types::Composite<'_>, btf: &Btf<'_>) -> Result { if !composite.is_struct { return Ok(false); } let align = composite.alignment()?; // Size of a struct has to be a multiple of its alignment if composite.size() % align != 0 { return Ok(true); } // All the non-bitfield fields have to be naturally aligned for m in composite.iter() { let align = btf.type_by_id::>(m.ty).unwrap().alignment()?; if let MemberAttr::Normal { offset } = m.attr { if offset as usize % (align.get() * 8) != 0 { return Ok(true); } } } // Even if original struct was marked as packed, we haven't detected any misalignment, so // there is no effect of packedness for given struct Ok(false) } /// Given a `current_offset` (in bytes) into a struct and a `required_offset` (in bytes) that /// type `type_id` needs to be placed at, returns how much padding must be inserted before /// `type_id`. fn required_padding( current_offset: usize, required_offset: usize, ty: &BtfType<'_>, packed: bool, ) -> Result { ensure!( current_offset <= required_offset, "current offset ({current_offset}) ahead of required offset ({required_offset})" ); let align = if packed { NonZeroUsize::new(1).unwrap() } else { // Assume 32-bit alignment in case we're generating code for 32-bit // arch. Worst case is on a 64-bit arch the compiler will generate // extra padding. The final layout will still be identical to what is // described by BTF. let a = ty.alignment()?; if a.get() > 4 { NonZeroUsize::new(4).unwrap() } else { a } }; // If we aren't aligning to the natural offset, padding needs to be inserted let aligned_offset = (current_offset + align.get() - 1) / align * align.get(); if aligned_offset == required_offset { Ok(0) } else { Ok(required_offset - current_offset) } } struct TypeDeclOpts { func_type: &'static str, } fn type_declaration_impl( ty: BtfType<'_>, anon_types: &AnonTypes, opts: &TypeDeclOpts, ) -> Result { let ty = ty.skip_mods_and_typedefs(); let s = btf_type_match!(match ty { BtfKind::Void => "std::ffi::c_void".to_string(), BtfKind::Int(t) => { let width = match (t.bits + 7) / 8 { 1 => "8", 2 => "16", 4 => "32", 8 => "64", 16 => "128", _ => bail!("Invalid integer width"), }; match t.encoding { types::IntEncoding::Signed => format!("i{width}"), types::IntEncoding::Bool => { assert!(t.bits as usize == (size_of::() * 8)); "bool".to_string() } types::IntEncoding::Char | types::IntEncoding::None => format!("u{width}"), } } BtfKind::Float(t) => { let width = match t.size() { 2 => bail!("Unsupported float width"), 4 => "32", 8 => "64", 12 => bail!("Unsupported float width"), 16 => bail!("Unsupported float width"), _ => bail!("Invalid float width"), }; format!("f{width}") } BtfKind::Ptr(t) => { let pointee_ty = type_declaration_impl(t.referenced_type(), anon_types, opts)?; format!("*mut {pointee_ty}") } BtfKind::Array(t) => { let val_ty = type_declaration_impl(t.contained_type(), anon_types, opts)?; format!("[{}; {}]", val_ty, t.capacity()) } BtfKind::Struct | BtfKind::Union | BtfKind::Enum | BtfKind::Enum64 => anon_types.type_name_or_anon(&ty).into_owned(), BtfKind::Func | BtfKind::FuncProto => opts.func_type.to_string(), BtfKind::Fwd => "std::ffi::c_void".to_string(), BtfKind::Var(t) => type_declaration_impl(t.referenced_type(), anon_types, opts)?, _ => bail!("Invalid type: {ty:?}"), }); Ok(s) } fn type_declaration(ty: BtfType<'_>, anon_types: &AnonTypes) -> Result { let opts = TypeDeclOpts { func_type: "std::ffi::c_void", }; type_declaration_impl(ty, anon_types, &opts) } /// Returns an expression that evaluates to the Default value /// of a type(typeid) in string form. /// /// To be used when creating a impl Default for a structure /// /// Rule of thumb is `ty` must be a type a variable can have. /// /// Type qualifiers are discarded (eg `const`, `volatile`, etc). fn type_default(ty: BtfType<'_>, anon_types: &AnonTypes) -> Result { let ty = ty.skip_mods_and_typedefs(); Ok(btf_type_match!(match ty { BtfKind::Int => format!("{}::default()", type_declaration(ty, anon_types)?), BtfKind::Float => format!("{}::default()", type_declaration(ty, anon_types)?), BtfKind::Ptr => "std::ptr::null_mut()".to_string(), BtfKind::Array(t) => { format!( "[{}; {}]", type_default(t.contained_type(), anon_types) .map_err(|err| anyhow!("in {ty:?}: {err}"))?, t.capacity() ) } BtfKind::Struct | BtfKind::Union | BtfKind::Enum | BtfKind::Enum64 => format!("{}::default()", anon_types.type_name_or_anon(&ty)), BtfKind::Var(t) => format!( "{}::default()", type_declaration(t.referenced_type(), anon_types)? ), _ => bail!("Invalid type: {ty:?}"), })) } fn size_of_type(ty: BtfType<'_>, btf: &Btf<'_>) -> Result { let ty = ty.skip_mods_and_typedefs(); Ok(btf_type_match!(match ty { BtfKind::Int(t) => ((t.bits + 7) / 8).into(), BtfKind::Ptr => btf.ptr_size()?.get(), BtfKind::Array(t) => t.capacity() * size_of_type(t.contained_type(), btf)?, BtfKind::Struct(t) => t.size(), BtfKind::Union(t) => t.size(), BtfKind::Enum(t) => t.size(), BtfKind::Enum64(t) => t.size(), BtfKind::Var(t) => size_of_type(t.referenced_type(), btf)?, BtfKind::DataSec(t) => t.size(), BtfKind::Float(t) => t.size(), _ => bail!("Cannot get size of type_id: {ty:?}"), })) } fn escape_reserved_keyword(identifier: Cow<'_, str>) -> Cow<'_, str> { // A list of keywords that need to be escaped in Rust when used for variable // names or similar (from https://doc.rust-lang.org/reference/keywords.html#keywords, // minus keywords that are already reserved in C). let reserved = [ "Self", "abstract", "as", "async", "await", "become", "box", "crate", "dyn", "enum", "final", "fn", "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref", "self", "super", "trait", "try", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "yield", ]; debug_assert_eq!( reserved.as_slice(), { let mut vec = reserved.to_vec(); vec.sort(); vec }, "please keep reserved keywords sorted", ); if reserved.binary_search(&identifier.as_ref()).is_ok() { Cow::Owned(format!("r#{identifier}")) } else { identifier } } #[derive(Debug, Default)] pub(crate) struct AnonTypes { /// A mapping from type to number, allowing us to assign numbers to types /// consistently. types: RefCell>, } impl AnonTypes { pub fn type_name_or_anon<'s>(&self, ty: &BtfType<'s>) -> Cow<'s, str> { match ty.name() { None => { let mut anon_table = self.types.borrow_mut(); let len = anon_table.len() + 1; // use 1 index anon ids for backwards compat let anon_id = anon_table.entry(ty.type_id()).or_insert(len); format!("{ANON_PREFIX}{anon_id}").into() } Some(n) => n.to_string_lossy(), } } } pub struct GenBtf<'s> { btf: Btf<'s>, anon_types: AnonTypes, } impl<'s> From> for GenBtf<'s> { fn from(btf: Btf<'s>) -> GenBtf<'s> { Self { btf, anon_types: Default::default(), } } } impl<'s> Deref for GenBtf<'s> { type Target = Btf<'s>; fn deref(&self) -> &Self::Target { &self.btf } } impl<'s> GenBtf<'s> { /// Returns the rust-ified type declaration of `ty` in string format. /// /// Rule of thumb is `ty` must be a type a variable can have. /// /// Type qualifiers are discarded (eg `const`, `volatile`, etc). pub fn type_declaration(&self, ty: BtfType<'s>) -> Result { type_declaration(ty, &self.anon_types) } /// Returns an expression that evaluates to the Default value /// of a type(typeid) in string form. /// /// To be used when creating a impl Default for a structure /// /// Rule of thumb is `ty` must be a type a variable can have. /// /// Type qualifiers are discarded (eg `const`, `volatile`, etc). fn type_default(&self, ty: BtfType<'s>) -> Result { type_default(ty, &self.anon_types) } /// Returns rust type definition of `ty` in string format, including dependent types. /// /// `ty` must be a struct, union, enum, or datasec type. pub fn type_definition( &self, ty: BtfType<'s>, processed: &mut HashSet, ) -> Result { let is_terminal = |ty: BtfType<'_>| -> bool { matches!( ty.kind(), BtfKind::Void | BtfKind::Int | BtfKind::Float | BtfKind::Ptr | BtfKind::Array | BtfKind::Fwd | BtfKind::Typedef | BtfKind::Volatile | BtfKind::Const | BtfKind::Restrict | BtfKind::Func | BtfKind::FuncProto | BtfKind::Var | BtfKind::DeclTag | BtfKind::TypeTag, ) }; ensure!( !is_terminal(ty), "Tried to print type definition for terminal type" ); // Process dependent types until there are none left. // // When we hit a terminal, we write out some stuff. A non-terminal adds more types to // the queue. let mut def = String::new(); let mut dependent_types = vec![ty]; while !dependent_types.is_empty() { let ty = dependent_types.remove(0); if !processed.insert(ty.type_id()) { continue; } btf_type_match!(match ty { BtfKind::Composite(t) => self.type_definition_for_composites(&mut def, &mut dependent_types, t)?, BtfKind::Enum(t) => self.type_definition_for_enums(&mut def, t)?, BtfKind::DataSec(t) => self.type_definition_for_datasec(&mut def, &mut dependent_types, t)?, _ => bail!("Invalid type: {:?}", ty.kind()), }); } Ok(def) } pub fn struct_ops_type_definition(&self, processed: &mut HashSet) -> Result { let mut def = String::new(); let mut dependent_types = vec![]; let mut vars = vec![]; // Take all the struct_ops datasec entries and collect their variables // (and dependent types). for ty in self.type_by_kind::>() { let name = match ty.name() { Some(s) => s.to_str()?, None => "", }; if !matches!( canonicalize_internal_map_name(name), Some(InternalMapType::StructOps) ) { continue; } for var in ty.iter() { let var = self .type_by_id::>(var.ty) .ok_or_else(|| anyhow!("datasec type does not point to a variable"))?; if var.linkage() == types::Linkage::Static { // do not output Static Var continue; } let () = vars.push(*var); if let Some(next_ty) = next_type(*var)? { let () = dependent_types.push(next_ty); } } } // Emit a single struct_ops definition containing all variables // discovered earlier. write!( def, r#" #[derive(Debug, Clone)] #[repr(C)] pub struct struct_ops {{ "# )?; for var in vars.iter() { writeln!( def, r#" pub {var_name}: *mut {var_type},"#, var_name = var.name().unwrap().to_string_lossy(), var_type = self.type_declaration(*var)? )?; } writeln!(def, "}}")?; write!( def, r#" impl struct_ops {{ "# )?; for var in vars.iter() { write!( def, r#" pub fn {var_name}(&self) -> &{var_type} {{ // SAFETY: The library ensures that the member is pointing to // valid data. unsafe {{ self.{var_name}.as_ref() }}.unwrap() }} pub fn {var_name}_mut(&mut self) -> &mut {var_type} {{ // SAFETY: The library ensures that the member is pointing to // valid data. unsafe {{ self.{var_name}.as_mut() }}.unwrap() }} "#, var_name = var.name().unwrap().to_string_lossy(), var_type = self.type_declaration(*var)? )?; } writeln!(def, "}}")?; let vars = vars .into_iter() .map(|ty| ty.next_type().unwrap().type_id()) .collect::>(); while !dependent_types.is_empty() { let ty = dependent_types.remove(0); if !processed.insert(ty.type_id()) { continue; } btf_type_match!(match ty { BtfKind::Composite(t) => { if vars.contains(&ty.type_id()) { let opts = TypeDeclOpts { func_type: "libbpf_rs::libbpf_sys::bpf_program", }; self.type_definition_for_composites_with_opts( &mut def, &mut dependent_types, t, &opts, )? } else { self.type_definition_for_composites(&mut def, &mut dependent_types, t)? } } BtfKind::Enum(t) => self.type_definition_for_enums(&mut def, t)?, _ => bail!("Invalid type: {:?}", ty.kind()), }); } Ok(def) } fn type_definition_for_composites<'a>( &'a self, def: &mut String, dependent_types: &mut Vec>, t: types::Composite<'_>, ) -> Result<()> { let opts = TypeDeclOpts { func_type: "std::ffi::c_void", }; self.type_definition_for_composites_with_opts(def, dependent_types, t, &opts) } fn type_definition_for_composites_with_opts<'a>( &'a self, def: &mut String, dependent_types: &mut Vec>, t: types::Composite<'_>, opts: &TypeDeclOpts, ) -> Result<()> { let packed = is_struct_packed(&t, &self.btf)?; // fields in the aggregate let mut agg_content: Vec = Vec::new(); // structs with arrays > 32 length need to impl Default // rather than #[derive(Default)] let mut impl_default: Vec = Vec::new(); // output for impl Default let mut gen_impl_default = false; // whether to output impl Default or use #[derive] let mut offset = 0; // In bytes for member in t.iter() { let member_offset = match member.attr { MemberAttr::Normal { offset } => offset, // Bitfields are tricky to get correct, if at all possible. For // now we just skip them, which results in them being covered by // padding bytes. MemberAttr::BitField { .. } => continue, }; let field_ty = self .type_by_id::>(member.ty) .unwrap() .skip_mods_and_typedefs(); if let Some(next_ty_id) = next_type(field_ty)? { dependent_types.push(next_ty_id); } let field_name = if let Some(name) = member.name { escape_reserved_keyword(name.to_string_lossy()) } else { // Only anonymous unnamed unions should ever have no name set. // We just name them the same as their anonymous type. As there // can only be one member of this very type, there can't be a // conflict. self.anon_types.type_name_or_anon(&field_ty) }; // Add padding as necessary if t.is_struct { let padding = required_padding( offset, member_offset as usize / 8, &self.type_by_id::>(member.ty).unwrap(), packed, )?; if padding != 0 { agg_content.push(format!(r#" pub __pad_{offset}: [u8; {padding}],"#,)); impl_default.push(format!( r#" __pad_{offset}: [u8::default(); {padding}]"#, )); if padding > 32 { gen_impl_default = true; } } if let Some(ft) = self.type_by_id::>(field_ty.type_id()) { if ft.capacity() > 32 { gen_impl_default = true } } // Rust does not implement `Default` for pointers, no matter if // the pointee implements it, and it also doesn't do it for // `MaybeUninit` constructs, which we use for "unsafe" types. if self .type_by_id::>(field_ty.type_id()) .is_some() || is_unsafe(field_ty) { gen_impl_default = true } } match self.type_default(field_ty) { Ok(mut def) => { if is_unsafe(field_ty) { def = format!("std::mem::MaybeUninit::new({def})") } impl_default.push(format!( r#" {field_name}: {field_ty_str}"#, field_ty_str = def )); } Err(e) => { if gen_impl_default || !t.is_struct { return Err(e.context("Could not construct a necessary Default Impl")); } } }; // Set `offset` to end of current var offset = (member_offset / 8) as usize + size_of_type(field_ty, &self.btf)?; let field_ty_str = type_declaration_impl(field_ty, &self.anon_types, opts)?; let field_ty_str = if is_unsafe(field_ty) { Cow::Owned(format!("std::mem::MaybeUninit<{field_ty_str}>")) } else { Cow::Borrowed(field_ty_str.as_str()) }; agg_content.push(format!(r#" pub {field_name}: {field_ty_str},"#)); } if t.is_struct { let struct_size = t.size(); let padding = required_padding(offset, struct_size, &t, packed)?; if padding != 0 { agg_content.push(format!(r#" pub __pad_{offset}: [u8; {padding}],"#,)); impl_default.push(format!( r#" __pad_{offset}: [u8::default(); {padding}]"#, )); if padding > 32 { gen_impl_default = true; } } } if !gen_impl_default && t.is_struct { writeln!(def, r#"#[derive(Debug, Default, Copy, Clone)]"#)?; } else if t.is_struct { writeln!(def, r#"#[derive(Debug, Copy, Clone)]"#)?; } else { writeln!(def, r#"#[derive(Copy, Clone)]"#)?; } let aggregate_type = if t.is_struct { "struct" } else { "union" }; let packed_repr = if packed { ", packed" } else { "" }; writeln!(def, r#"#[repr(C{packed_repr})]"#)?; writeln!( def, r#"pub {agg_type} {name} {{"#, agg_type = aggregate_type, name = self.anon_types.type_name_or_anon(&t), )?; for field in agg_content { writeln!(def, "{field}")?; } writeln!(def, "}}")?; // if required write a Default implementation for this struct if gen_impl_default { writeln!( def, r#"impl Default for {} {{"#, self.anon_types.type_name_or_anon(&t), )?; writeln!(def, r#" fn default() -> Self {{"#)?; writeln!( def, r#" {} {{"#, self.anon_types.type_name_or_anon(&t) )?; for impl_def in impl_default { writeln!(def, r#"{impl_def},"#)?; } writeln!(def, r#" }}"#)?; writeln!(def, r#" }}"#)?; writeln!(def, r#"}}"#)?; } else if !t.is_struct { // write a Debug implementation for a union writeln!( def, r#"impl std::fmt::Debug for {} {{"#, self.anon_types.type_name_or_anon(&t), )?; writeln!( def, r#" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"# )?; writeln!(def, r#" write!(f, "(???)")"#)?; writeln!(def, r#" }}"#)?; writeln!(def, r#"}}"#)?; // write a Default implementation for a union writeln!( def, r#"impl Default for {} {{"#, self.anon_types.type_name_or_anon(&t), )?; writeln!(def, r#" fn default() -> Self {{"#)?; writeln!( def, r#" {} {{"#, self.anon_types.type_name_or_anon(&t) )?; writeln!(def, r#"{},"#, impl_default[0])?; writeln!(def, r#" }}"#)?; writeln!(def, r#" }}"#)?; writeln!(def, r#"}}"#)?; } Ok(()) } fn type_definition_for_enums(&self, def: &mut String, t: types::Enum<'_>) -> Result<()> { let repr_size = match t.size() { 1 => "8", 2 => "16", 4 => "32", 8 => "64", 16 => "128", _ => bail!("Invalid enum size: {}", t.size()), }; let mut signed = "u"; for value in t.iter() { if value.value < 0 { signed = "i"; break; } } writeln!( def, r#"#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]"# )?; writeln!(def, r#"#[repr({signed}{repr_size})]"#)?; writeln!( def, r#"pub enum {name} {{"#, name = self.anon_types.type_name_or_anon(&t), )?; for (i, value) in t.iter().enumerate() { if i == 0 { writeln!(def, r#" #[default]"#)?; } writeln!( def, r#" {name} = {value},"#, name = value.name.unwrap().to_string_lossy(), value = value.value, )?; } writeln!(def, "}}")?; Ok(()) } fn type_definition_for_datasec<'a>( &'a self, def: &mut String, dependent_types: &mut Vec>, t: types::DataSec<'_>, ) -> Result<()> { let mut sec_name = match t.name().map(|s| s.to_string_lossy()) { None => bail!("Datasec name is empty"), Some(s) if !s.starts_with('.') => bail!("Datasec name is invalid: {s}"), Some(s) => s.into_owned(), }; sec_name.remove(0); let sec_name = sec_name.replace('.', "_"); writeln!(def, r#"#[derive(Debug, Copy, Clone)]"#)?; writeln!(def, r#"#[repr(C)]"#)?; writeln!(def, r#"pub struct {sec_name} {{"#)?; let mut offset: u32 = 0; for datasec_var in t.iter() { let var = self .type_by_id::>(datasec_var.ty) .ok_or_else(|| anyhow!("BTF is invalid! Datasec var does not point to a var"))?; if var.linkage() == Linkage::Static { // do not output Static Var continue; } if let Some(next_ty) = next_type(*var)? { dependent_types.push(next_ty); } let padding = required_padding(offset as usize, datasec_var.offset as usize, &var, false)?; if padding != 0 { writeln!(def, r#" __pad_{offset}: [u8; {padding}],"#)?; } // Set `offset` to end of current var offset = datasec_var.offset + datasec_var.size as u32; writeln!( def, r#" pub {var_name}: {var_type},"#, var_name = var.name().unwrap().to_string_lossy(), var_type = self.type_declaration(*var)? )?; } writeln!(def, "}}")?; Ok(()) } } fn next_type(mut t: BtfType<'_>) -> Result>> { loop { match t.kind() { BtfKind::Struct | BtfKind::Union | BtfKind::Enum | BtfKind::Enum64 | BtfKind::DataSec => return Ok(Some(t)), BtfKind::Array => { let a = types::Array::try_from(t).unwrap(); t = a.contained_type() } _ => match t.next_type() { Some(next) => t = next, None => return Ok(None), }, } } } libbpf-cargo-0.23.3/src/gen/mod.rs000064400000000000000000001007141046102023000147300ustar 00000000000000pub mod btf; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashSet; use std::ffi::c_void; use std::ffi::CStr; use std::ffi::CString; use std::fmt::Display; use std::fmt::Formatter; use std::fmt::Result as FmtResult; use std::fmt::Write as fmt_write; use std::fs::File; use std::io::stdout; use std::io::ErrorKind; use std::io::Write; use std::mem::size_of; use std::ops::Deref; use std::os::raw::c_ulong; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; use std::ptr; use anyhow::bail; use anyhow::ensure; use anyhow::Context; use anyhow::Result; use libbpf_rs::btf::types; use libbpf_rs::libbpf_sys; use libbpf_rs::Btf; use memmap2::Mmap; use crate::metadata; use crate::metadata::UnprocessedObj; use self::btf::GenBtf; /// Escape certain characters in a "raw" name of a section, for example. fn escape_raw_name(name: &str) -> String { name.replace('.', "_") } #[derive(Debug, PartialEq)] pub(crate) enum InternalMapType<'name> { Data, CustomData(&'name str), Rodata, CustomRodata(&'name str), Bss, CustomBss(&'name str), Kconfig, StructOps, } impl Display for InternalMapType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::Data => write!(f, "data"), Self::CustomData(name) => write!(f, "data_{}", escape_raw_name(name)), Self::Rodata => write!(f, "rodata"), Self::CustomRodata(name) => write!(f, "rodata_{}", escape_raw_name(name)), Self::Bss => write!(f, "bss"), Self::CustomBss(name) => write!(f, "bss_{}", escape_raw_name(name)), Self::Kconfig => write!(f, "kconfig"), Self::StructOps => write!(f, "struct_ops"), } } } #[repr(transparent)] pub(crate) struct BpfObj(ptr::NonNull); impl BpfObj { #[inline] pub fn as_mut_ptr(&mut self) -> *mut libbpf_sys::bpf_object { self.0.as_ptr() } } impl Deref for BpfObj { type Target = libbpf_sys::bpf_object; fn deref(&self) -> &Self::Target { // SAFETY: Our `bpf_object` pointer is always valid. unsafe { self.0.as_ref() } } } impl Drop for BpfObj { fn drop(&mut self) { unsafe { libbpf_sys::bpf_object__close(self.as_mut_ptr()) } } } pub enum OutputDest<'a> { Stdout, /// Infer a filename and place file in specified directory Directory(&'a Path), #[allow(dead_code)] /// File to place output in // Only constructed in libbpf-cargo library File(&'a Path), } macro_rules! gen_bpf_object_iter { ($name:ident, $iter_ty:ty, $next_fn:expr) => { struct $name { obj: *mut libbpf_sys::bpf_object, last: *mut $iter_ty, } impl $name { fn new(obj: *mut libbpf_sys::bpf_object) -> $name { $name { obj, last: ptr::null_mut(), } } } impl Iterator for $name { type Item = *mut $iter_ty; fn next(&mut self) -> Option { self.last = unsafe { $next_fn(self.obj, self.last) }; if self.last.is_null() { None } else { Some(self.last) } } } }; } gen_bpf_object_iter!( MapIter, libbpf_sys::bpf_map, libbpf_sys::bpf_object__next_map ); gen_bpf_object_iter!( ProgIter, libbpf_sys::bpf_program, libbpf_sys::bpf_object__next_program ); /// Try running `rustfmt` over `s` and return result. /// /// If no `rustfmt` binary could be found the content is left untouched, as /// it's only meant as a cosmetic brush up, without change of semantics. fn try_rustfmt<'code>(s: &'code str, rustfmt_path: Option<&PathBuf>) -> Result> { let result = if let Some(r) = rustfmt_path { Command::new(r) } else { Command::new("rustfmt") } .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn(); match result { Ok(mut cmd) => { // Send input in via stdin cmd.stdin.take().unwrap().write_all(s.as_bytes())?; // Extract output let output = cmd .wait_with_output() .context("Failed to execute rustfmt")?; ensure!( output.status.success(), "Failed to rustfmt: {}", output.status ); Ok(output.stdout.into()) } Err(err) if err.kind() == ErrorKind::NotFound => { // No `rustfmt` is present. Just skip formatting altogether. Ok(Cow::Borrowed(s.as_bytes())) } Err(err) => panic!("failed to spawn rustfmt: {err}"), } } fn capitalize_first_letter(s: &str) -> String { if s.is_empty() { return "".to_string(); } s.split('_').fold(String::new(), |mut acc, ts| { acc += &ts.chars().next().unwrap().to_uppercase().to_string(); if ts.len() > 1 { acc += &ts[1..]; } acc }) } fn get_raw_map_name(map: *const libbpf_sys::bpf_map) -> Result { let name_ptr = unsafe { libbpf_sys::bpf_map__name(map) }; ensure!(!name_ptr.is_null(), "Map name unknown"); Ok(unsafe { CStr::from_ptr(name_ptr) }.to_str()?.to_string()) } pub(crate) fn canonicalize_internal_map_name(s: &str) -> Option> { if s.ends_with(".data") { Some(InternalMapType::Data) } else if s.ends_with(".rodata") { Some(InternalMapType::Rodata) } else if s.ends_with(".bss") { Some(InternalMapType::Bss) } else if s.ends_with(".kconfig") { Some(InternalMapType::Kconfig) } else if s.ends_with(".struct_ops") { Some(InternalMapType::StructOps) } else if s.ends_with(".struct_ops.link") { // The `*.link` extension really only sets an additional flag in lower // layers. For our intents and purposes both can be treated similarly. Some(InternalMapType::StructOps) // Custom data sections don't prepend bpf_object name, so we can match from // start of name. // See https://github.com/libbpf/libbpf/blob/20ea95b4505c477af3b6ff6ce9d19cee868ddc5d/src/libbpf.c#L1789-L1794 } else if s.starts_with(".data.") { let name = s.get(".data.".len()..).unwrap(); Some(InternalMapType::CustomData(name)) } else if s.starts_with(".rodata.") { let name = s.get(".rodata.".len()..).unwrap(); Some(InternalMapType::CustomRodata(name)) } else if s.starts_with(".bss.") { let name = s.get(".bss.".len()..).unwrap(); Some(InternalMapType::CustomBss(name)) } else { eprintln!("Warning: unrecognized map: {s}"); None } } /// Same as `get_raw_map_name` except the name is canonicalized fn get_map_name(map: *const libbpf_sys::bpf_map) -> Result> { let name = get_raw_map_name(map)?; if unsafe { !libbpf_sys::bpf_map__is_internal(map) } { Ok(Some(escape_raw_name(&name))) } else { Ok(canonicalize_internal_map_name(&name).map(|map| map.to_string())) } } fn get_prog_name(prog: *const libbpf_sys::bpf_program) -> Result { let name_ptr = unsafe { libbpf_sys::bpf_program__name(prog) }; ensure!(!name_ptr.is_null(), "Prog name unknown"); Ok(unsafe { CStr::from_ptr(name_ptr) }.to_str()?.to_string()) } fn map_is_mmapable(map: *const libbpf_sys::bpf_map) -> bool { (unsafe { libbpf_sys::bpf_map__map_flags(map) } & libbpf_sys::BPF_F_MMAPABLE) > 0 } fn map_is_datasec(map: *const libbpf_sys::bpf_map) -> bool { let internal = unsafe { libbpf_sys::bpf_map__is_internal(map) }; let mmapable = map_is_mmapable(map); internal && mmapable } fn map_is_readonly(map: *const libbpf_sys::bpf_map) -> bool { assert!(map_is_mmapable(map)); // BPF_F_RDONLY_PROG means readonly from prog side (unsafe { libbpf_sys::bpf_map__map_flags(map) } & libbpf_sys::BPF_F_RDONLY_PROG) > 0 } fn gen_skel_c_skel_constructor(skel: &mut String, object: &mut BpfObj, name: &str) -> Result<()> { write!( skel, r#" fn build_skel_config() -> libbpf_rs::Result> {{ let mut builder = libbpf_rs::__internal_skel::ObjectSkeletonConfigBuilder::new(DATA); builder .name("{name}") "#, )?; for map in MapIter::new(object.as_mut_ptr()) { let raw_name = get_raw_map_name(map)?; let mmaped = if map_is_mmapable(map) { "true" } else { "false" }; write!( skel, r#" .map("{raw_name}", {mmaped}) "#, )?; } for prog in ProgIter::new(object.as_mut_ptr()) { let name = get_prog_name(prog)?; write!( skel, r#" .prog("{name}") "#, )?; } writeln!(skel, ";")?; write!( skel, r#" builder.build() }} "# )?; Ok(()) } fn gen_skel_map_defs( skel: &mut String, object: &mut BpfObj, obj_name: &str, open: bool, ) -> Result<()> { let mut gen = |mutable| -> Result<()> { if MapIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } let (struct_suffix, mut_prefix, map_fn) = if mutable { ("Mut", "mut ", "map_mut") } else { ("", "", "map") }; let (struct_name, inner_ty, return_ty) = if open { ( format!("Open{obj_name}Maps{struct_suffix}"), "libbpf_rs::OpenObject", "libbpf_rs::OpenMap", ) } else { ( format!("{obj_name}Maps{struct_suffix}"), "libbpf_rs::Object", "libbpf_rs::Map", ) }; write!( skel, r#" pub struct {struct_name}<'a> {{ inner: &'a {mut_prefix}{inner_ty}, }} impl {struct_name}<'_> {{ "#, )?; for map in MapIter::new(object.as_mut_ptr()) { let map_name = match get_map_name(map)? { Some(n) => n, None => continue, }; write!( skel, r#" pub fn {map_name}(&{mut_prefix}self) -> &{mut_prefix}{return_ty} {{ self.inner.{map_fn}("{raw_map_name}").unwrap() }} "#, map_name = map_name, raw_map_name = get_raw_map_name(map)?, return_ty = return_ty, mut_prefix = mut_prefix, map_fn = map_fn )?; } writeln!(skel, "}}")?; Ok(()) }; let () = gen(true)?; let () = gen(false)?; Ok(()) } fn gen_skel_prog_defs( skel: &mut String, object: &mut BpfObj, obj_name: &str, open: bool, mutable: bool, ) -> Result<()> { if ProgIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } let (struct_suffix, mut_prefix, prog_fn) = if mutable { ("Mut", "mut ", "prog_mut") } else { ("", "", "prog") }; let (struct_name, inner_ty, return_ty) = if open { ( format!("Open{obj_name}Progs{struct_suffix}"), "libbpf_rs::OpenObject", "libbpf_rs::OpenProgram", ) } else { ( format!("{obj_name}Progs{struct_suffix}"), "libbpf_rs::Object", "libbpf_rs::Program", ) }; write!( skel, r#" pub struct {struct_name}<'a> {{ inner: &'a {mut_prefix}{inner_ty}, }} impl {struct_name}<'_> {{ "#, )?; for prog in ProgIter::new(object.as_mut_ptr()) { write!( skel, r#" pub fn {prog_name}(&{mut_prefix}self) -> &{mut_prefix}{return_ty} {{ self.inner.{prog_fn}("{prog_name}").unwrap() }} "#, prog_name = get_prog_name(prog)?, return_ty = return_ty, mut_prefix = mut_prefix, prog_fn = prog_fn )?; } writeln!(skel, "}}")?; Ok(()) } fn gen_skel_datasec_types(skel: &mut String, object: &BpfObj) -> Result<()> { let btf = if let Some(btf) = Btf::from_bpf_object(object)? { btf } else { return Ok(()); }; let btf = GenBtf::from(btf); let mut processed = HashSet::new(); for ty in btf.type_by_kind::>() { let name = match ty.name() { Some(s) => s.to_str()?, None => "", }; if matches!( canonicalize_internal_map_name(name), None | Some(InternalMapType::StructOps) ) { continue; }; let sec_def = btf.type_definition(*ty, &mut processed)?; write!(skel, "{sec_def}")?; } Ok(()) } fn gen_skel_struct_ops_types( skel: &mut String, object: &BpfObj, /*, programs: &mut HashMap*/ ) -> Result<()> { if let Some(btf) = Btf::from_bpf_object(object)? { let btf = GenBtf::from(btf); let mut processed = HashSet::new(); let def = btf.struct_ops_type_definition(&mut processed)?; write!(skel, "{def}")?; } else { write!( skel, r#" #[derive(Debug, Clone)] #[repr(C)] pub struct struct_ops {{}} "# )?; } Ok(()) } fn gen_skel_map_getters( skel: &mut String, object: &mut BpfObj, obj_name: &str, open: bool, ) -> Result<()> { let mut gen = |mutable| -> Result<()> { if MapIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } let (struct_suffix, mut_prefix, map_fn) = if mutable { ("Mut", "mut ", "maps_mut") } else { ("", "", "maps") }; let return_ty = if open { format!("Open{obj_name}Maps{struct_suffix}") } else { format!("{obj_name}Maps{struct_suffix}") }; write!( skel, r#" pub fn {map_fn}(&{mut_prefix}self) -> {return_ty}<'_> {{ {return_ty} {{ inner: &{mut_prefix}self.obj, }} }} "#, )?; Ok(()) }; let () = gen(true)?; let () = gen(false)?; Ok(()) } fn gen_skel_struct_ops_getters( skel: &mut String, object: &mut BpfObj, obj_name: &str, ) -> Result<()> { if MapIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } write!( skel, r#" pub fn struct_ops_raw(&self) -> *const {obj_name}_types::struct_ops {{ &self.struct_ops }} pub fn struct_ops(&self) -> &{obj_name}_types::struct_ops {{ &self.struct_ops }} "#, )?; Ok(()) } fn gen_skel_prog_getters( skel: &mut String, object: &mut BpfObj, obj_name: &str, open: bool, ) -> Result<()> { let mut gen = |mutable| -> Result<()> { if ProgIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } let (struct_suffix, mut_prefix, prog_fn) = if mutable { ("Mut", "mut ", "progs_mut") } else { ("", "", "progs") }; let return_ty = if open { format!("Open{obj_name}Progs{struct_suffix}") } else { format!("{obj_name}Progs{struct_suffix}") }; write!( skel, r#" pub fn {prog_fn}(&{mut_prefix}self) -> {return_ty}<'_> {{ {return_ty} {{ inner: &{mut_prefix}self.obj, }} }} "#, )?; Ok(()) }; let () = gen(true)?; let () = gen(false)?; Ok(()) } fn gen_skel_datasec_getters( skel: &mut String, object: &mut BpfObj, obj_name: &str, loaded: bool, ) -> Result<()> { for (idx, map) in MapIter::new(object.as_mut_ptr()).enumerate() { if !map_is_datasec(map) { continue; } let name = match get_map_name(map)? { Some(n) => n, None => continue, }; let struct_name = format!("{obj_name}_types::{name}"); let immutable = loaded && map_is_readonly(map); let mutabilities = if immutable { [false].as_ref() } else { [false, true].as_ref() }; for mutable in mutabilities { let (ref_suffix, ptr_suffix, fn_suffix) = if *mutable { ("mut", "mut", "_mut") } else { ("", "const", "") }; write!( skel, r#" pub fn {name}{fn_suffix}_raw(&{ref_suffix} self) -> *{ptr_suffix} {struct_name} {{ self.skel_config.map_mmap_ptr{fn_suffix}({idx}).unwrap().cast::<{struct_name}>() }} pub fn {name}{fn_suffix}(&{ref_suffix} self) -> &{ref_suffix} {struct_name} {{ unsafe {{&{ref_suffix}*self.{name}{fn_suffix}_raw()}} }} "# )?; } } Ok(()) } fn gen_skel_link_defs(skel: &mut String, object: &mut BpfObj, obj_name: &str) -> Result<()> { if ProgIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } write!( skel, r#" #[derive(Default)] pub struct {obj_name}Links {{ "#, )?; for prog in ProgIter::new(object.as_mut_ptr()) { write!( skel, r#"pub {}: Option, "#, get_prog_name(prog)? )?; } writeln!(skel, "}}")?; Ok(()) } fn gen_skel_link_getter(skel: &mut String, object: &mut BpfObj, obj_name: &str) -> Result<()> { if ProgIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } write!( skel, r#"pub links: {obj_name}Links, "# )?; Ok(()) } fn open_bpf_object(name: &str, data: &[u8]) -> Result { let cname = CString::new(name)?; let obj_opts = libbpf_sys::bpf_object_open_opts { sz: size_of::() as libbpf_sys::size_t, object_name: cname.as_ptr(), ..Default::default() }; let object = unsafe { libbpf_sys::bpf_object__open_mem( data.as_ptr() as *const c_void, data.len() as c_ulong, &obj_opts, ) }; ensure!(!object.is_null(), "Failed to bpf_object__open_mem()"); Ok(BpfObj(ptr::NonNull::new(object).unwrap())) } fn gen_skel_attach(skel: &mut String, object: &mut BpfObj, obj_name: &str) -> Result<()> { if ProgIter::new(object.as_mut_ptr()).next().is_none() { return Ok(()); } write!( skel, r#" fn attach(&mut self) -> libbpf_rs::Result<()> {{ let ret = unsafe {{ libbpf_sys::bpf_object__attach_skeleton(self.skel_config.get()) }}; if ret != 0 {{ return Err(libbpf_rs::Error::from_raw_os_error(-ret)); }} self.links = {obj_name}Links {{ "#, )?; for (idx, prog) in ProgIter::new(object.as_mut_ptr()).enumerate() { let prog_name = get_prog_name(prog)?; write!( skel, r#"{prog_name}: core::ptr::NonNull::new(self.skel_config.prog_link_ptr({idx})?) .map(|ptr| unsafe {{ libbpf_rs::Link::from_ptr(ptr) }}), "# )?; } write!( skel, r#" }}; Ok(()) }} "#, )?; Ok(()) } fn gen_skel_struct_ops_init(object: &mut BpfObj) -> Result { let mut def = String::new(); for map in MapIter::new(object.as_mut_ptr()) { let type_ = unsafe { libbpf_sys::bpf_map__type(map) }; if type_ != libbpf_sys::BPF_MAP_TYPE_STRUCT_OPS { continue; } let raw_name = get_raw_map_name(map)?; write!( def, r#" skel.struct_ops.{raw_name} = skel.maps_mut().{raw_name}().initial_value_mut().unwrap().as_mut_ptr().cast(); "#, )?; } Ok(def) } /// Generate contents of a single skeleton fn gen_skel_contents(_debug: bool, raw_obj_name: &str, obj_file_path: &Path) -> Result { let mut skel = String::new(); write!( skel, r#"// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) // // THIS FILE IS AUTOGENERATED BY CARGO-LIBBPF-GEN! pub use self::imp::*; #[allow(dead_code)] #[allow(non_snake_case)] #[allow(non_camel_case_types)] #[allow(clippy::absolute_paths)] #[allow(clippy::transmute_ptr_to_ref)] #[allow(clippy::upper_case_acronyms)] #[warn(single_use_lifetimes)] mod imp {{ #[allow(unused_imports)] use super::*; use libbpf_rs::libbpf_sys; use libbpf_rs::skel::OpenSkel; use libbpf_rs::skel::Skel; use libbpf_rs::skel::SkelBuilder; "# )?; // The name we'll always hand to libbpf // // Note it's important this remains consistent b/c libbpf infers map/prog names from this name let libbpf_obj_name = format!("{raw_obj_name}_bpf"); // We'll use `obj_name` as the rust-ified object name let obj_name = capitalize_first_letter(raw_obj_name); // Open bpf_object so we can iterate over maps and progs let file = File::open(obj_file_path) .with_context(|| format!("failed to open BPF object `{}`", obj_file_path.display()))?; let mmap = unsafe { Mmap::map(&file)? }; let mut object = open_bpf_object(&libbpf_obj_name, &mmap)?; gen_skel_c_skel_constructor(&mut skel, &mut object, &libbpf_obj_name)?; #[allow(clippy::uninlined_format_args)] write!( skel, r#" #[derive(Default)] pub struct {name}SkelBuilder {{ pub obj_builder: libbpf_rs::ObjectBuilder, }} impl<'a> SkelBuilder<'a> for {name}SkelBuilder {{ type Output = Open{name}Skel<'a>; fn open(self) -> libbpf_rs::Result> {{ let opts = *self.obj_builder.opts(); self.open_opts(opts) }} fn open_opts(self, open_opts: libbpf_sys::bpf_object_open_opts) -> libbpf_rs::Result> {{ let mut skel_config = build_skel_config()?; let ret = unsafe {{ libbpf_sys::bpf_object__open_skeleton(skel_config.get(), &open_opts) }}; if ret != 0 {{ return Err(libbpf_rs::Error::from_raw_os_error(-ret)); }} let obj = unsafe {{ libbpf_rs::OpenObject::from_ptr(skel_config.object_ptr())? }}; #[allow(unused_mut)] let mut skel = Open{name}Skel {{ obj, // SAFETY: Our `struct_ops` type contains only pointers, // which are allowed to be NULL. // TODO: Generate and use a `Default` representation // instead, to cut down on unsafe code. struct_ops: unsafe {{ std::mem::zeroed() }}, skel_config }}; {struct_ops_init} Ok(skel) }} fn object_builder(&self) -> &libbpf_rs::ObjectBuilder {{ &self.obj_builder }} fn object_builder_mut(&mut self) -> &mut libbpf_rs::ObjectBuilder {{ &mut self.obj_builder }} }} "#, name = obj_name, struct_ops_init = gen_skel_struct_ops_init(&mut object)?, )?; gen_skel_map_defs(&mut skel, &mut object, &obj_name, true)?; gen_skel_prog_defs(&mut skel, &mut object, &obj_name, true, false)?; gen_skel_prog_defs(&mut skel, &mut object, &obj_name, true, true)?; write!( skel, r#" pub mod {raw_obj_name}_types {{ #[allow(unused_imports)] use super::*; "# )?; gen_skel_datasec_types(&mut skel, &object)?; gen_skel_struct_ops_types(&mut skel, &object)?; writeln!(skel, "}}")?; write!( skel, r#" pub struct Open{name}Skel<'a> {{ pub obj: libbpf_rs::OpenObject, pub struct_ops: {raw_obj_name}_types::struct_ops, skel_config: libbpf_rs::__internal_skel::ObjectSkeletonConfig<'a>, }} impl<'a> OpenSkel for Open{name}Skel<'a> {{ type Output = {name}Skel<'a>; fn load(mut self) -> libbpf_rs::Result<{name}Skel<'a>> {{ let ret = unsafe {{ libbpf_sys::bpf_object__load_skeleton(self.skel_config.get()) }}; if ret != 0 {{ return Err(libbpf_rs::Error::from_raw_os_error(-ret)); }} let obj = unsafe {{ libbpf_rs::Object::from_ptr(self.obj.take_ptr())? }}; Ok({name}Skel {{ obj, struct_ops: self.struct_ops, skel_config: self.skel_config, {links} }}) }} fn open_object(&self) -> &libbpf_rs::OpenObject {{ &self.obj }} fn open_object_mut(&mut self) -> &mut libbpf_rs::OpenObject {{ &mut self.obj }} "#, name = &obj_name, links = if ProgIter::new(object.as_mut_ptr()).next().is_some() { format!(r#"links: {obj_name}Links::default()"#) } else { "".to_string() } )?; writeln!(skel, "}}")?; writeln!(skel, "impl Open{name}Skel<'_> {{", name = &obj_name)?; gen_skel_prog_getters(&mut skel, &mut object, &obj_name, true)?; gen_skel_map_getters(&mut skel, &mut object, &obj_name, true)?; gen_skel_datasec_getters(&mut skel, &mut object, raw_obj_name, false)?; writeln!(skel, "}}")?; gen_skel_map_defs(&mut skel, &mut object, &obj_name, false)?; gen_skel_prog_defs(&mut skel, &mut object, &obj_name, false, false)?; gen_skel_prog_defs(&mut skel, &mut object, &obj_name, false, true)?; gen_skel_link_defs(&mut skel, &mut object, &obj_name)?; write!( skel, r#" pub struct {name}Skel<'a> {{ pub obj: libbpf_rs::Object, struct_ops: {raw_obj_name}_types::struct_ops, skel_config: libbpf_rs::__internal_skel::ObjectSkeletonConfig<'a>, "#, name = &obj_name, )?; gen_skel_link_getter(&mut skel, &mut object, &obj_name)?; write!( skel, r#" }} unsafe impl Send for {name}Skel<'_> {{}} unsafe impl Sync for {name}Skel<'_> {{}} impl Skel for {name}Skel<'_> {{ fn object(&self) -> &libbpf_rs::Object {{ &self.obj }} fn object_mut(&mut self) -> &mut libbpf_rs::Object {{ &mut self.obj }} "#, name = &obj_name, )?; gen_skel_attach(&mut skel, &mut object, &obj_name)?; writeln!(skel, "}}")?; write!(skel, "impl {name}Skel<'_> {{", name = &obj_name)?; gen_skel_prog_getters(&mut skel, &mut object, &obj_name, false)?; gen_skel_map_getters(&mut skel, &mut object, &obj_name, false)?; gen_skel_struct_ops_getters(&mut skel, &mut object, raw_obj_name)?; gen_skel_datasec_getters(&mut skel, &mut object, raw_obj_name, true)?; writeln!(skel, "}}")?; // Coerce to &[u8] just to be safe, as we'll be using debug formatting let bytes: &[u8] = &mmap; write!( skel, r#" const DATA: &[u8] = &{bytes:?}; "# )?; writeln!(skel, "}}")?; Ok(skel) } /// Generate a single skeleton fn gen_skel( debug: bool, name: &str, obj: &Path, out: OutputDest<'_>, rustfmt_path: Option<&PathBuf>, ) -> Result<()> { ensure!(!name.is_empty(), "Object file has no name"); let skel = gen_skel_contents(debug, name, obj)?; let skel = try_rustfmt(&skel, rustfmt_path)?; match out { OutputDest::Stdout => stdout().write_all(&skel)?, OutputDest::Directory(dir) => { let path = dir.join(format!("{name}.skel.rs")); let mut file = File::create(path)?; file.write_all(&skel)?; } OutputDest::File(file) => { let mut file = File::create(file)?; file.write_all(&skel)?; } }; Ok(()) } /// Generate mod.rs in src/bpf directory of each project. /// /// Each `UnprocessedObj` in `objs` must belong to same project. pub fn gen_mods(objs: &[UnprocessedObj], rustfmt_path: Option<&PathBuf>) -> Result<()> { if objs.is_empty() { return Ok(()); } let mut path = objs[0].path.clone(); path.pop(); path.push("mod.rs"); let mut contents = String::new(); write!( contents, r#" // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)" // // THIS FILE IS AUTOGENERATED BY CARGO-LIBBPF-GEN! "# )?; for obj in objs { write!( contents, r#" #[path = "{name}.skel.rs"] mod {name}_skel; "#, name = obj.name )?; } for obj in objs { write!( contents, r#" pub use {}_skel::*; "#, obj.name )?; } let mut file = File::create(path)?; file.write_all(&try_rustfmt(&contents, rustfmt_path)?)?; Ok(()) } pub fn gen_single( debug: bool, obj_file: &Path, output: OutputDest<'_>, rustfmt_path: Option<&PathBuf>, ) -> Result<()> { let filename = match obj_file.file_name() { Some(n) => n, None => bail!( "Could not determine file name for object file: {}", obj_file.to_string_lossy() ), }; let name = match filename.to_str() { Some(n) => { ensure!( n.ends_with(".o"), "Object file does not have `.o` suffix: {n}" ); n.split('.').next().unwrap() } None => bail!( "Object file name is not valid unicode: {}", filename.to_string_lossy() ), }; let () = gen_skel(debug, name, obj_file, output, rustfmt_path).with_context(|| { format!( "Failed to generate skeleton for {}", obj_file.to_string_lossy(), ) })?; Ok(()) } fn gen_project( debug: bool, manifest_path: Option<&PathBuf>, rustfmt_path: Option<&PathBuf>, ) -> Result<()> { let (_target_dir, to_gen) = metadata::get(debug, manifest_path)?; if debug && !to_gen.is_empty() { println!("Found bpf objs to gen skel:"); for obj in &to_gen { println!("\t{obj:?}"); } } else if to_gen.is_empty() { bail!("Did not find any bpf objects to generate skeleton"); } // Map to store package_name -> [UnprocessedObj] let mut package_objs: BTreeMap> = BTreeMap::new(); for obj in to_gen { let mut obj_file_path = obj.out.clone(); obj_file_path.push(format!("{}.bpf.o", obj.name)); let mut skel_path = obj.path.clone(); skel_path.pop(); let () = gen_skel( debug, &obj.name, obj_file_path.as_path(), OutputDest::Directory(skel_path.as_path()), rustfmt_path, ) .with_context(|| { format!( "Failed to generate project skeleton for {}", obj.path.as_path().display() ) })?; match package_objs.get_mut(&obj.package) { Some(v) => v.push(obj.clone()), None => { package_objs.insert(obj.package.clone(), vec![obj.clone()]); } }; } for (package, objs) in package_objs { let () = gen_mods(&objs, rustfmt_path) .with_context(|| format!("Failed to generate mod.rs for package={package}"))?; } Ok(()) } pub fn gen( debug: bool, manifest_path: Option<&PathBuf>, rustfmt_path: Option<&PathBuf>, object: Option<&PathBuf>, ) -> Result<()> { if manifest_path.is_some() && object.is_some() { bail!("--manifest-path and --object cannot be used together"); } if let Some(obj_file) = object { gen_single(debug, obj_file, OutputDest::Stdout, rustfmt_path) } else { gen_project(debug, manifest_path, rustfmt_path) } } libbpf-cargo-0.23.3/src/lib.rs000064400000000000000000000213131046102023000141430ustar 00000000000000//! libbpf-cargo helps you develop and build eBPF (BPF) programs with standard rust tooling. //! //! libbpf-cargo supports two interfaces: //! * [`SkeletonBuilder`] API, for use with [build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) //! * `cargo-libbpf` cargo subcommand, for use with `cargo` //! //! The **build script interface is recommended** over the cargo subcommand interface because: //! * once set up, you cannot forget to update the generated skeletons if your source changes //! * build scripts are standard practice for projects that include codegen //! * newcomers to your project can `cargo build` and it will "just work" //! //! The following sections in this document describe the `cargo-libbpf` plugin. See the API //! reference for documentation on the build script interface. //! //! # Configuration //! //! cargo-libbpf consumes the following Cargo.toml configuration options: //! //! ```text //! [package.metadata.libbpf] //! prog_dir = "src/other_bpf_dir" # default: /src/bpf //! target_dir = "other_target_dir" # default: /bpf //! ``` //! //! * `prog_dir`: path relative to package Cargo.toml to search for bpf progs //! * `target_dir`: path relative to workspace target directory to place compiled bpf progs //! //! # Subcommands //! //! ## build //! //! `cargo libbpf build` compiles `.bpf.c` C files into corresponding `.bpf.o` ELF //! object files. Each object file may contain one or more BPF programs, maps, and associated //! metadata. The object file may then be handed over to `libbpf-rs` for loading and interaction. //! //! cargo-libbpf-build enforces a few conventions: //! //! * source file names must be in the `.bpf.c` format //! * object file names will be generated in `.bpf.o` format //! * there may not be any two identical `.bpf.c` file names in any two projects in a //! cargo workspace //! //! ## gen //! //! `cargo libbpf gen` generates a skeleton module for each BPF object file in the project. Each //! `.bpf.o` object file will have its own module. One `mod.rs` file is also generated. All //! output files are placed into `package.metadata.libbpf.prog_dir`. //! //! Be careful to run cargo-libbpf-build before running cargo-libbpf-gen. cargo-libbpf-gen reads //! object files from `package.metadata.libbpf.target_dir`. //! //! ## make //! //! `cargo libbpf make` sequentially runs cargo-libbpf-build, cargo-libbpf-gen, and `cargo //! build`. This is a convenience command so you don't forget any steps. Alternatively, you could //! write a Makefile for your project. #![allow(clippy::let_unit_value)] #![warn( elided_lifetimes_in_paths, single_use_lifetimes, clippy::absolute_paths, clippy::wildcard_imports )] #![deny(unsafe_op_in_unsafe_fn)] use std::ffi::OsStr; use std::ffi::OsString; use std::path::Path; use std::path::PathBuf; use anyhow::anyhow; use anyhow::Context as _; use anyhow::Result; use tempfile::tempdir; use tempfile::TempDir; // libbpf-cargo binary is the primary consumer of the following modules. As such, // we do not use all the symbols. Silence any unused code warnings. #[allow(dead_code)] mod build; #[allow(dead_code)] mod gen; #[allow(dead_code)] mod make; #[allow(dead_code)] mod metadata; #[cfg(test)] mod test; /// `SkeletonBuilder` builds and generates a single skeleton. /// /// This interface is meant to be used in build scripts. /// /// # Examples /// /// ```no_run /// use libbpf_cargo::SkeletonBuilder; /// /// SkeletonBuilder::new() /// .source("myobject.bpf.c") /// .debug(true) /// .clang("/opt/clang/clang") /// .build_and_generate("/output/path") /// .unwrap(); /// ``` pub struct SkeletonBuilder { debug: bool, source: Option, obj: Option, clang: Option, clang_args: Vec, skip_clang_version_check: bool, rustfmt: PathBuf, dir: Option, } impl Default for SkeletonBuilder { fn default() -> Self { Self::new() } } impl SkeletonBuilder { pub fn new() -> Self { SkeletonBuilder { debug: false, source: None, obj: None, clang: None, clang_args: Vec::new(), skip_clang_version_check: false, rustfmt: "rustfmt".into(), dir: None, } } /// Point the [`SkeletonBuilder`] to a source file for compilation /// /// Default is None pub fn source>(&mut self, source: P) -> &mut SkeletonBuilder { self.source = Some(source.as_ref().to_path_buf()); self } /// Point the [`SkeletonBuilder`] to an object file for generation /// /// Default is None pub fn obj>(&mut self, obj: P) -> &mut SkeletonBuilder { self.obj = Some(obj.as_ref().to_path_buf()); self } /// Turn debug output on or off /// /// Default is off pub fn debug(&mut self, debug: bool) -> &mut SkeletonBuilder { self.debug = debug; self } /// Specify which `clang` binary to use /// /// Default searches `$PATH` for `clang` pub fn clang>(&mut self, clang: P) -> &mut SkeletonBuilder { self.clang = Some(clang.as_ref().to_path_buf()); self } /// Pass additional arguments to `clang` when building BPF object file /// /// # Examples /// /// ```no_run /// use libbpf_cargo::SkeletonBuilder; /// /// SkeletonBuilder::new() /// .source("myobject.bpf.c") /// .clang_args([ /// "-DMACRO=value", /// "-I/some/include/dir", /// ]) /// .build_and_generate("/output/path") /// .unwrap(); /// ``` pub fn clang_args(&mut self, args: A) -> &mut SkeletonBuilder where A: IntoIterator, S: AsRef, { self.clang_args = args .into_iter() .map(|arg| arg.as_ref().to_os_string()) .collect(); self } /// Specify whether or not to skip clang version check /// /// Default is `false` pub fn skip_clang_version_check(&mut self, skip: bool) -> &mut SkeletonBuilder { self.skip_clang_version_check = skip; self } /// Specify which `rustfmt` binary to use /// /// Default searches `$PATH` for `rustfmt` pub fn rustfmt>(&mut self, rustfmt: P) -> &mut SkeletonBuilder { self.rustfmt = rustfmt.as_ref().to_path_buf(); self } /// Build BPF programs and generate the skeleton at path `output` pub fn build_and_generate>(&mut self, output: P) -> Result<()> { self.build()?; self.generate(output)?; Ok(()) } // Build BPF programs without generating a skeleton. // // [`SkeletonBuilder::source`] must be set for this to succeed. pub fn build(&mut self) -> Result<()> { let source = self .source .as_ref() .ok_or_else(|| anyhow!("No source file provided"))?; let filename = source .file_name() .ok_or_else(|| anyhow!("Missing file name"))? .to_str() .ok_or_else(|| anyhow!("Invalid unicode in file name"))?; if !filename.ends_with(".bpf.c") { return Err(anyhow!( "Source `{}` does not have .bpf.c suffix", source.display() )); } if self.obj.is_none() { let name = filename.split('.').next().unwrap(); let dir = tempdir().context("failed to create temporary directory")?; let objfile = dir.path().join(format!("{name}.o")); self.obj = Some(objfile); // Hold onto tempdir so that it doesn't get deleted early self.dir = Some(dir); } build::build_single( self.debug, source, // Unwrap is safe here since we guarantee that obj.is_some() above self.obj.as_ref().unwrap(), self.clang.as_ref(), self.skip_clang_version_check, self.clang_args.clone(), ) .with_context(|| format!("failed to build `{}`", source.display()))?; Ok(()) } // Generate a skeleton at path `output` without building BPF programs. // // [`SkeletonBuilder::obj`] must be set for this to succeed. pub fn generate>(&mut self, output: P) -> Result<()> { let objfile = self.obj.as_ref().ok_or_else(|| anyhow!("No object file"))?; gen::gen_single( self.debug, objfile, gen::OutputDest::File(output.as_ref()), Some(&self.rustfmt), ) .with_context(|| format!("failed to generate `{}`", objfile.display()))?; Ok(()) } } libbpf-cargo-0.23.3/src/main.rs000064400000000000000000000103471046102023000143260ustar 00000000000000#![allow(clippy::let_unit_value)] #![warn(clippy::absolute_paths)] use std::ffi::OsString; use std::path::PathBuf; use anyhow::Result; use clap::Args; use clap::Parser; use clap::Subcommand; #[doc(hidden)] mod build; mod gen; mod make; mod metadata; #[doc(hidden)] #[derive(Debug, Parser)] #[command(version, about)] #[command(propagate_version = true)] struct Opt { #[command(subcommand)] wrapper: Wrapper, /// Enable debug output. #[clap(short, long, global = true)] debug: bool, } // cargo invokes subcommands with the first argument as // the subcommand name. ie. // // cargo ${command} --help // // into // // cargo-${command} ${command} --help // // so we must have a dummy subcommand here to eat the arg. #[doc(hidden)] #[derive(Debug, Subcommand)] enum Wrapper { #[command(subcommand)] Libbpf(Command), } /// A grouping of clang specific options. #[derive(Debug, Args)] pub struct ClangOpts { /// Path to clang binary #[arg(long, value_parser)] clang_path: Option, /// Additional arguments to pass to `clang`. #[arg(long, value_parser)] clang_args: Vec, /// Skip clang version checks #[arg(long)] skip_clang_version_checks: bool, } /// cargo-libbpf is a cargo subcommand that helps develop and build eBPF (BPF) programs. #[doc(hidden)] #[derive(Debug, Subcommand)] enum Command { /// Build bpf programs Build { #[arg(long, value_parser)] /// Path to top level Cargo.toml manifest_path: Option, #[command(flatten)] clang_opts: ClangOpts, }, /// Generate skeleton files Gen { #[arg(long, value_parser)] /// Path to top level Cargo.toml manifest_path: Option, #[arg(long, value_parser)] /// Path to rustfmt binary rustfmt_path: Option, #[arg(long, value_parser)] /// Generate skeleton for the specified object file and print results to stdout /// /// When specified, skeletons for the rest of the project will not be generated object: Option, }, /// Build project Make { #[arg(long, value_parser)] /// Path to top level Cargo.toml manifest_path: Option, #[command(flatten)] clang_opts: ClangOpts, #[arg(short, long)] /// Quiet output quiet: bool, /// Arguments to pass to `cargo build` /// /// Example: cargo libbpf build -- --package mypackage cargo_build_args: Vec, #[arg(long, value_parser)] /// Path to rustfmt binary rustfmt_path: Option, }, } #[doc(hidden)] fn main() -> Result<()> { let opts = Opt::parse(); let Opt { wrapper, debug } = opts; match wrapper { Wrapper::Libbpf(cmd) => match cmd { Command::Build { manifest_path, clang_opts: ClangOpts { clang_path, clang_args, skip_clang_version_checks, }, } => build::build( debug, manifest_path.as_ref(), clang_path.as_ref(), clang_args, skip_clang_version_checks, ), Command::Gen { manifest_path, rustfmt_path, object, } => gen::gen( debug, manifest_path.as_ref(), rustfmt_path.as_ref(), object.as_ref(), ), Command::Make { manifest_path, clang_opts: ClangOpts { clang_path, clang_args, skip_clang_version_checks, }, quiet, cargo_build_args, rustfmt_path, } => make::make( debug, manifest_path.as_ref(), clang_path.as_ref(), clang_args, skip_clang_version_checks, quiet, cargo_build_args, rustfmt_path.as_ref(), ), }, } } libbpf-cargo-0.23.3/src/make.rs000064400000000000000000000025471046102023000143220ustar 00000000000000use std::ffi::OsString; use std::path::PathBuf; use std::process::Command; use anyhow::bail; use anyhow::Context; use anyhow::Result; use crate::build; use crate::gen; #[allow(clippy::too_many_arguments)] pub fn make( debug: bool, manifest_path: Option<&PathBuf>, clang: Option<&PathBuf>, clang_args: Vec, skip_clang_version_checks: bool, quiet: bool, cargo_build_args: Vec, rustfmt_path: Option<&PathBuf>, ) -> Result<()> { if !quiet { println!("Compiling BPF objects"); } build::build( debug, manifest_path, clang, clang_args, skip_clang_version_checks, ) .context("Failed to compile BPF objects")?; if !quiet { println!("Generating skeletons"); } gen::gen(debug, manifest_path, None, rustfmt_path).context("Failed to generate skeletons")?; let mut cmd = Command::new("cargo"); cmd.arg("build"); if quiet { cmd.arg("--quiet"); } for arg in cargo_build_args { cmd.arg(arg); } let status = cmd.status().context("Failed to spawn child")?; if !status.success() { let reason = match status.code() { Some(rc) => format!("exit code {rc}"), None => "killed by signal".to_string(), }; bail!("Failed to `cargo build`: {reason}"); } Ok(()) } libbpf-cargo-0.23.3/src/metadata.rs000064400000000000000000000117541046102023000151650ustar 00000000000000use std::fs; use std::path::Path; use std::path::PathBuf; use anyhow::bail; use anyhow::Context as _; use anyhow::Result; use cargo_metadata::MetadataCommand; use cargo_metadata::Package; use serde::Deserialize; use serde_json::value::Value; #[derive(Default, Deserialize)] struct LibbpfPackageMetadata { prog_dir: Option, target_dir: Option, } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] struct PackageMetadata { #[serde(default)] libbpf: LibbpfPackageMetadata, } #[derive(Debug, Clone)] pub struct UnprocessedObj { /// Package the object belongs to pub package: String, /// Path to .c pub path: PathBuf, /// Where to place compiled object pub out: PathBuf, /// Object name (eg: `runqslower.bpf.c` -> `runqslower`) pub name: String, } fn get_package( debug: bool, package: &Package, workspace_target_dir: &Path, ) -> Result> { if debug { println!("Metadata for package={}", package.name); println!("\t{}", package.metadata); } let package_metadata = if package.metadata != Value::Null { let PackageMetadata { libbpf } = serde_json::from_value(package.metadata.clone())?; libbpf } else { LibbpfPackageMetadata::default() }; // Respect custom target directories specified by package let mut package_root = package.manifest_path.clone().into_std_path_buf(); // Remove "Cargo.toml" package_root.pop(); if let Some(d) = package_metadata.prog_dir { if debug { println!("Custom prog_dir={}", d.to_string_lossy()); } // Add requested path package_root.push(d); } else { // Add default path package_root.push("src/bpf"); }; // Respect custom target directories specified by package let mut target_dir = workspace_target_dir.to_path_buf(); if let Some(d) = package_metadata.target_dir { if debug { println!("Custom target_dir={}", d.to_string_lossy()); } // Add requested path target_dir.push(d); } else { // Add default path target_dir.push("bpf"); }; // Get an iterator to the input directory. If directory is missing, // skip the current project let dir_iter = match fs::read_dir(&package_root) { Ok(d) => d, Err(e) => { if let Some(ec) = e.raw_os_error() { // ENOENT == 2 if ec == 2 { return Ok(vec![]); } else { bail!( "Invalid directory: {}: {}", package_root.to_string_lossy(), e ); } } else { return Err(e.into()); } } }; Ok(dir_iter .filter_map(|file| { let path = match file { Ok(f) => f.path(), Err(_) => return None, }; if !path.is_file() { return None; } // Only take files with extension ".bpf.c" if let Some(file_name) = path.as_path().file_name() { if file_name.to_string_lossy().ends_with(".bpf.c") { let name = path .as_path() .file_stem() // remove ".c" suffix .unwrap() // we already know it's a file .to_string_lossy() .rsplit_once('.') .map(|f| f.0) // take portion of string prior to .bpf .unwrap() // Already know it has enough '.'s .to_string(); return Some(UnprocessedObj { package: package.name.clone(), out: target_dir.clone(), path, name, }); } } None }) .collect()) } /// Returns the `target_directory` and a list of objects to compile. pub fn get(debug: bool, manifest_path: Option<&PathBuf>) -> Result<(PathBuf, Vec)> { let mut cmd = MetadataCommand::new(); if let Some(path) = manifest_path { cmd.manifest_path(path); } let metadata = cmd.exec().context("Failed to get cargo metadata")?; if metadata.workspace_members.is_empty() { bail!("Failed to find targets") } let target_directory = metadata.target_directory.clone().into_std_path_buf(); let mut v: Vec = Vec::new(); for id in &metadata.workspace_members { for package in &metadata.packages { if id == &package.id { let vv = &mut get_package(debug, package, &target_directory) .with_context(|| format!("Failed to process package={}", package.name))?; let () = v.append(vv); } } } Ok((metadata.target_directory.into_std_path_buf(), v)) } libbpf-cargo-0.23.3/src/test.rs000064400000000000000000002127371046102023000143700ustar 00000000000000use std::collections::HashSet; 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 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 crate::build::build; use crate::gen::btf::GenBtf; use crate::make::make; use crate::SkeletonBuilder; /// Creates a temporary directory and initializes a default cargo project inside. /// /// Returns temp directory object to hold directory open, the path to the cargo /// project directory, and the path to the project Cargo.toml fn setup_temp_project() -> (TempDir, PathBuf, PathBuf) { let dir = tempdir().expect("failed to create tempdir"); let proj_dir = dir.path().join("proj"); // Create default rust project 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) } /// Creates a temporary directory and initializes a cargo workspace with two projects /// inside. Similar to `setup_temp_project`, just that here there's 2 projects> /// /// /// Returns temp directory object to hold directory open, the path to the cargo /// workspace directory, path to first project, and path to second project. 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"); // Create first project 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()); // Create second project 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()); // Populate workspace Cargo.toml 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) } /// Validate if bpf object file at `path` is a valid bpf object file 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"), } } /// Returns the path to the local libbpf-rs fn get_libbpf_rs_path() -> PathBuf { // The `CARGO_MANIFEST_DIR` environment variable points to the // libbpf-cargo directory, at build time. 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") } /// Add vmlinux header into `project`'s src/bpf dir fn add_vmlinux_header(project: &Path) { let mut vmlinux = OpenOptions::new() .create(true) .truncate(true) .write(true) .open(project.join("src/bpf/vmlinux.h")) .expect("failed to open vmlinux.h"); let () = vmlinux .write_all(vmlinux::VMLINUX) .expect("failed to write vmlinux.h"); } #[test] fn test_build_default() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // No bpf progs yet build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); // Add a prog let _prog_file = File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file"); build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap(); // Validate generated object file 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(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); } #[test] fn test_build_custom() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add custom build rules 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"); // No bpf progs yet build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); // Add a prog 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(true, Some(&cargo_toml), None, Vec::new(), true).unwrap(); // Validate generated object file 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(); // Add a metadata section that isn't for libbpf. let mut cargo_toml_file = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); let deb_metadata = 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"); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); // Add a prog let _prog_file = File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file"); build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap(); // Validate generated object file 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(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); build(true, Some(&cargo_toml), None, Vec::new(), true).unwrap_err(); let _prog_file = File::create(proj_dir.join("src/bpf/prog_BAD_EXTENSION.c")) .expect("failed to create prog file"); build(true, Some(&cargo_toml), None, Vec::new(), true).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(true, Some(&cargo_toml), None, Vec::new(), true).unwrap(); } #[test] fn test_build_workspace() { let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace(); // No bpf progs yet build(true, Some(&workspace_cargo_toml), None, Vec::new(), true).unwrap_err(); // Create bpf prog for project one 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 bpf prog for project two 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(true, Some(&workspace_cargo_toml), None, Vec::new(), true).unwrap(); } #[test] fn test_build_workspace_collision() { let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace(); // Create bpf prog for project one 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 bpf prog for project two, same name as project one 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(true, Some(&workspace_cargo_toml), None, Vec::new(), true).unwrap_err(); } #[test] fn test_make_basic() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog let _prog_file = File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file"); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); // Validate generated object file validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path()); // Check that skeleton exists (other tests will check for skeleton validity) 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 bpf prog for project one 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 bpf prog for project two, same name as project one 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( true, Some(&workspace_cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); // Validate generated object files 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(), ); // Check that skeleton exists (other tests will check for skeleton validity) 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()); } #[test] fn test_skeleton_empty_source() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog let _prog_file = File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file"); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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#" #![warn(elided_lifetimes_in_paths)] mod bpf; use bpf::*; use libbpf_rs::skel::SkelBuilder; use libbpf_rs::skel::OpenSkel; fn main() {{ let builder = ProgSkelBuilder::default(); let _skel = builder .open() .expect("failed to open skel") .load() .expect("failed to load skel"); }} "#, ) .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_basic() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include 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; }} "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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#" #![warn(elided_lifetimes_in_paths)] mod bpf; 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_skel = builder .open() .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 _open_map_mut = open_skel.maps_mut().mymap(); let _open_prog_mut = open_skel.progs_mut().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(); let _map_mut = skel.maps_mut().mymap(); let _prog_mut = skel.progs_mut().this_is_my_prog(); // Check that attach() is generated skel.attach().expect("failed to attach progs"); // Check that Option field is generated let _mylink = skel.links.this_is_my_prog.unwrap(); }} "#, ) .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_generate_datasec_static() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include SEC("kprobe/foo") int this_is_my_prog(u64 *ctx) {{ bpf_printk("this should not cause an error"); return 0; }} "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); } #[test] fn test_skeleton_datasec() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include 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; }} "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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#" #![warn(elided_lifetimes_in_paths)] mod bpf; use bpf::*; use libbpf_rs::skel::SkelBuilder; use libbpf_rs::skel::OpenSkel; fn main() {{ let builder = ProgSkelBuilder::default(); let mut open_skel = builder .open() .expect("failed to open skel"); // Check that we set rodata vars before load open_skel.rodata_mut().myconst = std::ptr::null_mut(); // We can always set bss vars open_skel.bss_mut().myglobal = 42; open_skel.data_custom_mut().mycustomdata = 1337; open_skel.bss_custom_mut().mycustombss = 12; assert_eq!(open_skel.rodata_custom_1().mycustomrodata, 43); let mut skel = open_skel .load() .expect("failed to load skel"); // We can always set bss vars skel.bss_mut().myglobal = 24; skel.data_custom_mut().mycustomdata += 1; skel.bss_custom_mut().mycustombss += 1; assert_eq!(skel.rodata_custom_1().mycustomrodata, 43); // Read only for rodata after load let _rodata: &prog_types::rodata = skel.rodata(); }} "#, ) .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_basic() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include 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; }} "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); // Generate skeleton file let skel = NamedTempFile::new().unwrap(); SkeletonBuilder::new() .source(proj_dir.join("src/bpf/prog.bpf.c")) .debug(true) .build_and_generate(skel.path()) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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::*; use libbpf_rs::skel::SkelBuilder; use libbpf_rs::skel::OpenSkel; use libbpf_rs::skel::Skel; fn main() {{ let builder = ProgSkelBuilder::default(); let mut open_skel = builder .open() .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 _open_map_mut = open_skel.maps_mut().mymap(); let _open_prog_mut = open_skel.progs_mut().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(); let _map_mut = skel.maps_mut().mymap(); let _prog_mut = skel.progs_mut().this_is_my_prog(); // Check that attach() is generated skel.attach().expect("failed to attach progs"); // Check that Option field is generated let _mylink = skel.links.this_is_my_prog.unwrap(); }} "#, skel_path = skel.path().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(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #ifndef PURPOSE #error "what is my purpose?" #endif "#, ) .expect("failed to write prog.bpf.c"); let skel = NamedTempFile::new().unwrap(); // Should fail b/c `PURPOSE` not defined SkeletonBuilder::new() .source(proj_dir.join("src/bpf/prog.bpf.c")) .debug(true) .clang("clang") .build_and_generate(skel.path()) .unwrap_err(); // Should succeed b/c we defined the macro SkeletonBuilder::new() .source(proj_dir.join("src/bpf/prog.bpf.c")) .debug(true) .clang("clang") .clang_args(["-DPURPOSE=you_pass_the_butter"]) .build_and_generate(skel.path()) .unwrap(); } #[test] fn test_skeleton_builder_arrays_ptrs() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include 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; "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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#" #![warn(elided_lifetimes_in_paths)] mod bpf; use bpf::*; use libbpf_rs::skel::SkelBuilder; fn main() {{ let builder = ProgSkelBuilder::default(); let open_skel = builder .open() .expect("failed to open skel"); // That everything exists and compiled okay let _ = open_skel.rodata().my_array[0].x; let _ = open_skel.rodata().my_array[0].y[1].b; let _ = open_skel.rodata().my_array[0].z[0].a; let _ = open_skel.rodata().my_ptr; }} "#, ) .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_generate_struct_with_pointer() { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, r#" #include "vmlinux.h" #include struct list {{ struct list *next; }}; struct list l; "#, ) .expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); make( true, Some(&cargo_toml), None, Vec::new(), true, true, Vec::new(), None, ) .unwrap(); let mut cargo = OpenOptions::new() .append(true) .open(&cargo_toml) .expect("failed to open Cargo.toml"); // Make test project use our development libbpf-rs version writeln!( cargo, 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#" #![warn(elided_lifetimes_in_paths)] mod bpf; use bpf::*; use libbpf_rs::skel::SkelBuilder; fn main() {{ let builder = ProgSkelBuilder::default(); let _open_skel = builder .open() .expect("failed to open skel"); }} "#, ) .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()); } /// Check that skeleton creation is deterministic, i.e., that no temporary paths /// change the output between invocations. #[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 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 = NamedTempFile::new().unwrap(); SkeletonBuilder::new() .source(proj_dir.join("src/bpf/prog.bpf.c")) .debug(true) .clang("clang") .build_and_generate(skel1.path()) .unwrap(); let skel1 = read_to_string(skel1.path()).unwrap(); let skel2 = NamedTempFile::new().unwrap(); SkeletonBuilder::new() .source(proj_dir.join("src/bpf/prog.bpf.c")) .debug(true) .clang("clang") .build_and_generate(skel2.path()) .unwrap(); let skel2 = read_to_string(skel2.path()).unwrap(); assert_eq!(skel1, skel2); } // -- TEST RUST GENERATION OF BTF PROGRAMS -- /// Searches the Btf struct for a BtfType /// returns type identifier if found /// fails calling test if not found, or if duplicates exist /// /// usage: -- search for basic struct/union/enum with exact match to name /// find_type_in_btf!(, /// , /// <&str search name>); /// eg: /// let my_type = find_type_in_btf!(btf, Struct, "name"); /// or: -- search for basic struct/union/enum/var containing name substring /// find_type_in_btf!(, /// , /// <&str search name>, /// true); /// eg: /// let my_type = find_type_in_btf!(btf, Datasec, "bss", true); /// or: -- search for a Variable name in a Datasec -- exact match /// find_type_in_btf!(, /// Var, /// <&str search name>); /// eg /// let let my_type = find_type_in_btf!(btf, Var, "name"); macro_rules! find_type_in_btf { // match for a named BtfType::Var inside all vars in a Datasec ($btf:ident, Var, $name:literal) => {{ let mut asserted_type: Option> = None; for ty in $btf.type_by_kind::>() { 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()); // No duplicates asserted_type = Some(var_ty); } } } asserted_type.unwrap() }}; // match for a named BtfType. ($btf:ident, $btf_type:path, $name:literal) => {{ find_type_in_btf!($btf, $btf_type, $name, false) }}; // match for a named BtfType. // If substr == true then test for substring rather than exact match ($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()); // No duplicates asserted_type = Some(ty); } } asserted_type.unwrap() }}; } /// Boiler plate code to build a struct Btf from a raw string /// returns struct Btf if able to compile /// fails calling test if unable to compile fn build_btf_mmap(prog_text: &str) -> Mmap { let (_dir, proj_dir, cargo_toml) = setup_temp_project(); // Add prog dir create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir"); // Add a prog 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, "{prog_text}").expect("failed to write prog.bpf.c"); // Lay down the necessary header files add_vmlinux_header(&proj_dir); // Build the .bpf.o build(true, Some(&cargo_toml), None, Vec::new(), true).expect("failed to compile"); let obj = OpenOptions::new() .read(true) .open(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path()) .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::>().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); } /// Tests the type_definition output of a type_id against a given expected output /// Will trim leading and trailing whitespace from both expected output and from /// the generated type_definition /// fails calling text if type_definition does not match expected_output #[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 = r#" #include "vmlinux.h" #include struct Foo { u64 type; void* mod; }; struct Foo foo = {{0}}; "#; let expected_output = r#" #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct Foo { pub r#type: u64, pub r#mod: *mut std::ffi::c_void, } impl Default for Foo { fn default() -> Self { Foo { r#type: u64::default(), r#mod: 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 = r#" #include "vmlinux.h" #include int myglobal = 1; struct Foo { int x; char y[10]; void *z; }; struct Foo foo = {{0}}; "#; let expected_output = 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 { Foo { 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 = r#" #include "vmlinux.h" #include 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 = r#" #include "vmlinux.h" #include struct Foo { int x; } __attribute__((aligned(16))); struct Foo foo = {1}; "#; let expected_output = 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 = r#" #include "vmlinux.h" #include struct Foo { int x; } __attribute__((aligned(64))); struct Foo foo = {1}; "#; let expected_output = 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 { Foo { 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_basic_long_array() { let prog_text = r#" #include "vmlinux.h" #include int myglobal = 1; struct Foo { int x; char y[33]; void *z; }; struct Foo foo = {{0}}; "#; let expected_output = 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 { Foo { 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); // Find our types 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 = r#" #include "vmlinux.h" #include 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; "#; // Note how there's 6 bytes of padding. It's not necessary on 64 bit archs but // we've assumed 32 bit arch during padding generation. let expected_output = 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 { Foo { 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); // Find our struct 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 = r#" #include "vmlinux.h" #include struct with_func_proto { struct with_func_proto *next; void (*func)(struct with_func_proto *); }; struct with_func_proto w; "#; let expected_output = 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 { with_func_proto { next: std::ptr::null_mut(), func: std::ptr::null_mut(), } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include 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; "#; // Note how there's 6 bytes of padding. It's not necessary on 64 bit archs but // we've assumed 32 bit arch during padding generation. let expected_output = 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 { Foo { 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 { Bar { x: u16::default(), y: [u16::default(); 33], } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Foo { int x; char y; __s32 z[2]; } __attribute__((packed)); struct Foo foo; "#; let expected_output = 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); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Foo { int x; char y; __s32 z[33]; } __attribute__((packed)); struct Foo foo; "#; let expected_output = 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 { Foo { x: i32::default(), y: i8::default(), z: [i32::default(); 33], } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Foo { unsigned short x: 2; unsigned short y: 3; }; struct Foo foo; "#; let expected_output = 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 = r#" #include "vmlinux.h" #include 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 = 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, pub __pad_18: [u8; 6], } impl Default for Foo { fn default() -> Self { Foo { 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 = r#" #include "vmlinux.h" #include enum Foo { Zero = 0, One, seven = 7, }; enum Foo foo; "#; let expected_output = r#" #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[repr(u32)] pub enum Foo { #[default] Zero = 0, One = 1, seven = 7, } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct let enum_foo = find_type_in_btf!(btf, types::Enum<'_>, "Foo"); assert_definition(&btf, &enum_foo, expected_output); } #[test] fn test_btf_dump_definition_union() { let prog_text = r#" #include "vmlinux.h" #include union Foo { int x; __u32 y; char z[128]; }; union Foo foo; "#; let expected_output = 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 { Foo { x: i32::default(), } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Bar { u16 x; }; struct Foo { struct Bar bar; struct Bar bartwo; }; struct Foo foo; "#; let expected_output = 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); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Foo { int x; char y[10]; void *z; }; struct Foo foo = {0}; const int myconstglobal = 0; "#; let bss_output = 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 { Foo { x: i32::default(), y: [i8::default(); 10], z: std::ptr::null_mut(), } } } "#; let rodata_output = 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); // Find out types 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 = r#" #include "vmlinux.h" #include 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 = r#" #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct bss_custom { pub bss_array: [i32; 1], } "#; let data_custom_output = r#" #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct data_custom { pub data_array: [i32; 1], } "#; let rodata_custom_output = 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 = r#" #include "vmlinux.h" #include struct Foo { int x; char y[33]; void *z; }; struct Foo foo = {0}; const int myconstglobal = 0; "#; let bss_output = 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 { Foo { x: i32::default(), y: [i8::default(); 33], z: std::ptr::null_mut(), } } } "#; let rodata_output = 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); // Find our types 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 = r#" #include "vmlinux.h" #include 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 = 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 { Foo { x: i32::default(), y: [i8::default(); 10], z: std::ptr::null_mut(), } } } "#; let rodata_output = 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); // Find our types 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 = r#" #include "vmlinux.h" #include 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 = 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 { Foo { x: i32::default(), y: [i8::default(); 33], z: std::ptr::null_mut(), } } } "#; let rodata_output = 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); // Find our types 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 = r#" #include "vmlinux.h" #include 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 = r#" #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct Foo { pub x: i32, pub bar: __anon_1, pub __pad_36: [u8; 4], pub baz: __anon_2, pub w: i32, pub __pad_52: [u8; 4], } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_1 { pub y: [u8; 10], pub z: [u16; 16], } impl std::fmt::Debug for __anon_1 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_1 { fn default() -> Self { __anon_1 { y: [u8::default(); 10], } } } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_2 { pub w: u32, pub u: *mut u64, } impl std::fmt::Debug for __anon_2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_2 { fn default() -> Self { __anon_2 { w: u32::default(), } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include 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 = r#" #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct Foo { pub x: i32, pub bar: __anon_1, pub baz: __anon_2, pub w: i32, pub __pad_68: [u8; 4], } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct __anon_1 { pub y: [u8; 10], pub z: [u16; 16], } #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct __anon_2 { pub w: u32, pub __pad_4: [u8; 4], pub u: *mut u64, } impl Default for __anon_2 { fn default() -> Self { __anon_2 { 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); // Find our struct 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 = r#" #include "vmlinux.h" #include 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 = r#" #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct Foo { pub x: i32, pub bar: __anon_1, pub zerg: __anon_2, pub baz: __anon_3, pub w: i32, pub __pad_76: [u8; 4], pub flarg: __anon_4, } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct __anon_1 { pub y: [u8; 10], pub z: [u16; 16], } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_2 { pub a: *mut i8, pub b: i32, } impl std::fmt::Debug for __anon_2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_2 { fn default() -> Self { __anon_2 { a: std::ptr::null_mut(), } } } #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct __anon_3 { pub w: u32, pub __pad_4: [u8; 4], pub u: *mut u64, } impl Default for __anon_3 { fn default() -> Self { __anon_3 { w: u32::default(), __pad_4: [u8::default(); 4], u: std::ptr::null_mut(), } } } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_4 { pub c: u8, pub d: [u64; 5], } impl std::fmt::Debug for __anon_4 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_4 { fn default() -> Self { __anon_4 { c: u8::default(), } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find our struct 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 = r#" #include "vmlinux.h" #include struct Foo { union { char *name; void *tp; }; }; struct Foo foo = {{0}}; "#; let expected_output = r#" #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct Foo { pub __anon_1: __anon_1, } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_1 { pub name: *mut i8, pub tp: *mut std::ffi::c_void, } impl std::fmt::Debug for __anon_1 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_1 { fn default() -> Self { __anon_1 { 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_definition_anon_enum() { let prog_text = r#" #include "vmlinux.h" #include typedef enum { FOO = 1, } test_t; struct Foo { test_t test; }; struct Foo foo; "#; let expected_output = r#" #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct Foo { pub test: std::mem::MaybeUninit<__anon_1>, } impl Default for Foo { fn default() -> Self { Foo { test: std::mem::MaybeUninit::new(__anon_1::default()), } } } #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[repr(u32)] pub enum __anon_1 { #[default] FOO = 1, } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); // Find the struct 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 = r#" #include "vmlinux.h" #include struct Foo { s32 a; u16 b; s16 c; bool d; char e; }; struct Foo foo; "#; let expected_output = r#" #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct Foo { pub a: i32, pub b: u16, pub c: i16, pub d: std::mem::MaybeUninit, pub e: i8, } impl Default for Foo { fn default() -> Self { Foo { 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); // Find the struct 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 = r#" #include "vmlinux.h" #include // 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 = r#" #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct bpf_sock_tuple_5_15 { pub __anon_1: __anon_1, pub __pad_36: [u8; 4], pub __anon_2: __anon_2, } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_1 { pub ipv4: __anon_3, pub ipv6: __anon_4, } impl std::fmt::Debug for __anon_1 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_1 { fn default() -> Self { __anon_1 { ipv4: __anon_3::default(), } } } #[derive(Copy, Clone)] #[repr(C)] pub union __anon_2 { pub a: i32, pub b: *mut i8, } impl std::fmt::Debug for __anon_2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "(???)") } } impl Default for __anon_2 { fn default() -> Self { __anon_2 { a: i32::default(), } } } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct __anon_3 { pub saddr: u32, pub daddr: u32, pub sport: u16, pub dport: u16, } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct __anon_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); // Find the struct 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_struct_ops_mixed() { let prog_text = r#" #include "vmlinux.h" #include #include 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 = r#" #[derive(Debug, Clone)] #[repr(C)] pub struct struct_ops { pub dummy_1: *mut bpf_dummy_ops, pub dummy_2: *mut bpf_dummy_ops, } impl struct_ops { pub fn dummy_1(&self) -> &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 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) -> &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 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, } impl Default for bpf_dummy_ops { fn default() -> Self { bpf_dummy_ops { test_1: std::ptr::null_mut(), test_2: std::ptr::null_mut(), } } } "#; let mmap = build_btf_mmap(prog_text); let btf = btf_from_mmap(&mmap); let mut processed = HashSet::new(); let def = btf.struct_ops_type_definition(&mut processed).unwrap(); assert_output(&def, expected_output); } #[test] fn test_btf_dump_float() { let prog_text = 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") ); }