diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bc2cbc..877eb32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,61 @@ jobs: - run: cargo test ${{ matrix.args }} shell: alpine.sh {0} + test-android: + name: Test on Android + strategy: + matrix: + include: + - rust-target: aarch64-linux-android + os-arch: amd64 + + runs-on: ubuntu-20.04 + + env: + CARGO_BUILD_TARGET: ${{ matrix.rust-target }} + CARGO_TERM_VERBOSE: 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get -y install \ + gawk \ + flex \ + bison \ + clang \ + automake \ + autopoint \ + gcc-multilib \ + build-essential + + - name: Install Rust stable for ${{ matrix.rust-target }} + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust-target }} + + - name: Install Android NDK + uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r26d + # local-cache: true + # add-to-path: false + + - name: Cargo test no run + run: cargo test --no-run + if: matrix.rust-target == 'aarch64-linux-android' + env: + RUSTFLAGS: -C linker=${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang + AR_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar + CC_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang + CXX_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++ + test-libbpf-rs: # check that libbpf-rs, one of the main consumers of the library, works with # this version of libbpf-sys @@ -174,6 +229,7 @@ jobs: needs: - test-gnu - test-musl + - test-android - test-libbpf-rs runs-on: ubuntu-22.04 steps: diff --git a/android/AndroidFixup.h b/android/AndroidFixup.h new file mode 100644 index 0000000..a777ab9 --- /dev/null +++ b/android/AndroidFixup.h @@ -0,0 +1,28 @@ +/* + * Copyright 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_FIXUP_H +#define ANDROID_FIXUP_H +static inline void *rawmemchr(const void *s, int c) { + const unsigned char *ptr = s; + while (1) { + if (*ptr == c) + return (void *)ptr; + ptr++; + } +} +/* workaround for canonicalize_file_name */ +#define canonicalize_file_name(path) realpath(path, NULL) +#endif /* ANDROID_FIXUP_H */ \ No newline at end of file diff --git a/android/android.h b/android/android.h new file mode 100644 index 0000000..43b6ce8 --- /dev/null +++ b/android/android.h @@ -0,0 +1,4 @@ +#pragma once +#define __user +#define __force +typedef unsigned __poll_t; \ No newline at end of file diff --git a/android/argp.h b/android/argp.h new file mode 100644 index 0000000..3558d15 --- /dev/null +++ b/android/argp.h @@ -0,0 +1,21 @@ +/* + * Copyright 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ELFUTILS_ARGP_H +#define ELFUTILS_ARGP_H +// We don't have an implementation, but elfutils unconditionally includes this, +// and relies on its transitive includes in places. +#include +#endif /* ELFUTILS_ARGP_H */ \ No newline at end of file diff --git a/android/libintl.h b/android/libintl.h new file mode 100644 index 0000000..a5d10c0 --- /dev/null +++ b/android/libintl.h @@ -0,0 +1,21 @@ +/* + * Copyright 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ELFUTILS_LIBINTL_H +#define ELFUTILS_LIBINTL_H +/* no internalization */ +#define gettext(x) (x) +#define dgettext(x, y) (y) +#endif /* ELFUTILS_LIBINTL_H */ \ No newline at end of file diff --git a/bindings.h b/bindings.h index a5a3a98..2b8c7ee 100644 --- a/bindings.h +++ b/bindings.h @@ -11,3 +11,8 @@ #include "libbpf/src/btf.h" #include "libbpf/src/libbpf.h" #endif /* __LIBBPF_SYS_NOVENDOR */ + +#if defined (__aarch64__) || defined (__arm__) +#include +typedef va_list __va_list_tag; +#endif diff --git a/build.rs b/build.rs index 09e44a1..e7815fe 100644 --- a/build.rs +++ b/build.rs @@ -2,11 +2,11 @@ use std::env; use std::ffi; -use std::fs; use std::fs::read_dir; use std::path; use std::path::Path; use std::process; +use std::process::ExitStatus; use nix::fcntl; @@ -70,7 +70,6 @@ fn generate_bindings(src_dir: path::PathBuf) { .allowlist_function("perf_.+") .allowlist_function("ring_buffer_.+") .allowlist_function("user_ring_buffer_.+") - .allowlist_function("vdprintf") .allowlist_type("bpf_.+") .allowlist_type("btf_.+") .allowlist_type("xdp_.+") @@ -79,6 +78,10 @@ fn generate_bindings(src_dir: path::PathBuf) { .allowlist_var("BTF_.+") .allowlist_var("XDP_.+") .allowlist_var("PERF_.+") + .allowlist_type("__va_list_tag") + .blocklist_type("vdprintf") + .blocklist_type("libbpf_print_fn_t") + .blocklist_function("libbpf_set_print") .parse_callbacks(Box::new(ignored_macros)) .header("bindings.h") .clang_arg(format!("-I{}", src_dir.join("libbpf/include").display())) @@ -114,16 +117,18 @@ fn main() { generate_bindings(src_dir.clone()); - let vendored_libbpf = cfg!(feature = "vendored-libbpf"); - let vendored_libelf = cfg!(feature = "vendored-libelf"); - let vendored_zlib = cfg!(feature = "vendored-zlib"); + let android = build_android(); + + let vendored_libbpf = cfg!(feature = "vendored-libbpf") || android; + let vendored_libelf = cfg!(feature = "vendored-libelf") || android; + let vendored_zlib = cfg!(feature = "vendored-zlib") || android; println!("Using feature vendored-libbpf={}", vendored_libbpf); println!("Using feature vendored-libelf={}", vendored_libelf); println!("Using feature vendored-zlib={}", vendored_zlib); - let static_libbpf = cfg!(feature = "static-libbpf"); - let static_libelf = cfg!(feature = "static-libelf"); - let static_zlib = cfg!(feature = "static-zlib"); + let static_libbpf = cfg!(feature = "static-libbpf") || android; + let static_libelf = cfg!(feature = "static-libelf") || android; + let static_zlib = cfg!(feature = "static-zlib") || android; println!("Using feature static-libbpf={}", static_libbpf); println!("Using feature static-libelf={}", static_libelf); println!("Using feature static-zlib={}", static_zlib); @@ -146,10 +151,10 @@ fn main() { pkg_check("flex"); pkg_check("bison"); pkg_check("gawk"); + pkg_check("aclocal"); } let (compiler, mut cflags) = if vendored_libbpf || vendored_libelf || vendored_zlib { - pkg_check("make"); pkg_check("pkg-config"); let compiler = cc::Build::new().try_get_compiler().expect( @@ -208,96 +213,103 @@ fn main() { } } -fn make_zlib(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) { - let src_dir = src_dir.join("zlib"); +fn make_zlib(compiler: &cc::Tool, src_dir: &path::Path, _: &path::Path) { // lock README such that if two crates are trying to compile // this at the same time (eg libbpf-rs libbpf-cargo) // they wont trample each other - let file = std::fs::File::open(src_dir.join("README")).unwrap(); - let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap(); - - let status = process::Command::new("./configure") - .arg("--static") - .arg("--prefix") - .arg(".") - .arg("--libdir") - .arg(out_dir) - .env("CC", compiler.path()) - .env("CFLAGS", compiler.cflags_env()) - .current_dir(&src_dir) - .status() - .expect("could not execute make"); + let project_dir = src_dir.join("zlib"); - assert!(status.success(), "make failed"); - - let status = process::Command::new("make") - .arg("install") - .arg("-j") - .arg(&format!("{}", num_cpus())) - .current_dir(&src_dir) - .status() - .expect("could not execute make"); + let file = std::fs::File::open(project_dir.join("README")).unwrap(); + let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap(); - assert!(status.success(), "make failed"); + let project_dir = project_dir.to_str().unwrap(); + + let zlib_sources = [ + "adler32.c", + "compress.c", + "crc32.c", + "deflate.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "uncompr.c", + "zutil.c", + ]; + + // These flags are only used in Android + // ref: https://android.googlesource.com/platform/external/zlib/+/refs/tags/android-11.0.0_r48/Android.bp + let android_cflags = [ + // We do support hidden visibility, so turn that on. + "-DHAVE_HIDDEN", + // We do support const, so turn that on. + "-DZLIB_CONST", + // Enable -O3 as per chromium. + "-O3", + // "-Wall", + // "-Werror", + // "-Wno-deprecated-non-prototype", + // "-Wno-unused", + // "-Wno-unused-parameter", + ]; + + configure(project_dir, vec![]); + + let mut builder = cc::Build::new(); + + builder.include(project_dir).files({ + zlib_sources + .iter() + .map(|source| format!("{project_dir}/{source}")) + }); + + if build_android() { + for flag in android_cflags { + builder.flag(flag); + } + } else { + for flag in compiler.args() { + builder.flag(flag); + } + } - let status = process::Command::new("make") - .arg("distclean") - .current_dir(&src_dir) - .status() - .expect("could not execute make"); + builder.flag_if_supported("-w").warnings(false).compile("z"); - assert!(status.success(), "make failed"); emit_rerun_directives_for_contents(&src_dir); } -fn make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) { +fn make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, _: &path::Path) { // lock README such that if two crates are trying to compile // this at the same time (eg libbpf-rs libbpf-cargo) // they wont trample each other - let file = std::fs::File::open(src_dir.join("elfutils/README")).unwrap(); - let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap(); - - let flags = compiler - .cflags_env() - .into_string() - .expect("failed to get cflags"); - let mut cflags: String = flags - .split_whitespace() - .filter_map(|arg| { - if arg != "-static" { - // compilation fails with -static flag - Some(format!(" {arg}")) - } else { - None - } - }) - .collect(); + let project_dir = src_dir.join("elfutils"); - #[cfg(target_arch = "aarch64")] - cflags.push_str(" -Wno-error=stringop-overflow"); - cflags.push_str(&format!(" -I{}/zlib/", src_dir.display())); + let file = std::fs::File::open(project_dir.join("README")).unwrap(); + let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap(); - let status = process::Command::new("autoreconf") - .arg("--install") - .arg("--force") - .current_dir(&src_dir.join("elfutils")) - .status() - .expect("could not execute make"); - - assert!(status.success(), "make failed"); - - // location of libz.a - let out_lib = format!("-L{}", out_dir.display()); - let status = process::Command::new("./configure") - .arg("--enable-maintainer-mode") - .arg("--disable-debuginfod") - .arg("--disable-libdebuginfod") - .arg("--disable-demangler") - .arg("--without-zstd") - .arg("--prefix") - .arg(&src_dir.join("elfutils/prefix_dir")) - .arg("--host") - .arg({ + let libelf_dir = project_dir.join("libelf"); + let project_dir = project_dir.to_str().unwrap(); + + let mut build_options = vec![ + "--enable-maintainer-mode", + "--disable-debuginfod", + "--disable-libdebuginfod", + "--without-lzma", + "--without-bzlib", + "--without-zstd", + ] + .into_iter() + .map(|s| s.to_string()) + .collect::>(); + + if !build_android() { + build_options.push("--host".to_owned()); + build_options.push({ let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let arch = match arch.as_str() { "riscv64gc" => "riscv64", @@ -308,91 +320,163 @@ fn make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path let env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); format!("{arch}-{vendor}-{os}-{env}") - }) - .arg("--libdir") - .arg(out_dir) - .env("CC", compiler.path()) - .env("CXX", compiler.path()) - .env("CFLAGS", &cflags) - .env("CXXFLAGS", &cflags) - .env("LDFLAGS", &out_lib) - .current_dir(&src_dir.join("elfutils")) - .status() - .expect("could not execute make"); + }); + } - assert!(status.success(), "make failed"); + autoconf(project_dir); - // Build in elfutils/lib because building libelf requires it. - let status = process::Command::new("make") - .arg("-j") - .arg(&format!("{}", num_cpus())) - .arg("BUILD_STATIC_ONLY=y") - .current_dir(&src_dir.join("elfutils/lib")) - .status() - .expect("could not execute make"); + configure(project_dir, build_options.iter().map(|s| s.as_str())); - assert!(status.success(), "make failed"); + let mut builder = cc::Build::new(); - // Build libelf only - let status = process::Command::new("make") - .arg("install") - .arg("-j") - .arg(&format!("{}", num_cpus())) - .arg("BUILD_STATIC_ONLY=y") - .current_dir(&src_dir.join("elfutils/libelf")) - .status() - .expect("could not execute make"); + builder + .flag("-DHAVE_CONFIG_H") + .flag("-D_GNU_SOURCE") + .include(project_dir) + .include(src_dir.join("zlib")) + .include(format!("{project_dir}/lib")) + .include(format!("{project_dir}/include")) + .include(format!("{project_dir}/libelf")); - assert!(status.success(), "make failed"); + if build_android() { + builder + .flag("-DNAMES=1000") + .flag("-std=gnu99") + .flag("-D_FILE_OFFSET_BITS=64") + .flag("-includeAndroidFixup.h") + .include(src_dir.join("android")); + } else { + #[cfg(target_arch = "aarch64")] + builder.flag("-Wno-error=stringop-overflow"); - let status = process::Command::new("make") - .arg("distclean") - .current_dir(&src_dir.join("elfutils")) - .status() - .expect("could not execute make"); + builder.compiler(compiler.path()); + + for flag in compiler.args() { + if flag.ne("-static") { + builder.flag(flag); + } + } + } + + for entry in std::fs::read_dir(libelf_dir).expect("Failed to `read_dir`") { + let entry = entry.expect("Failed to `read_dir`"); + if entry.file_type().unwrap().is_file() + && entry.file_name().to_str().unwrap().ends_with(".c") + { + builder.file(entry.path()); + } + } + + builder + .flag_if_supported("-w") + .warnings(false) + .compile("elf"); - assert!(status.success(), "make failed"); emit_rerun_directives_for_contents(&src_dir.join("elfutils").join("src")); } -fn make_libbpf( - compiler: &cc::Tool, - cflags: &ffi::OsStr, - src_dir: &path::Path, - out_dir: &path::Path, -) { - let src_dir = src_dir.join("libbpf/src"); - // create obj_dir if it doesn't exist - let obj_dir = path::PathBuf::from(&out_dir.join("obj").into_os_string()); - let _ = fs::create_dir(&obj_dir); - - let status = process::Command::new("make") - .arg("install") - .arg("-j") - .arg(&format!("{}", num_cpus())) - .env("BUILD_STATIC_ONLY", "y") - .env("PREFIX", "/") - .env("LIBDIR", "") - .env("OBJDIR", &obj_dir) - .env("DESTDIR", out_dir) - .env("CC", compiler.path()) - .env("CFLAGS", cflags) - .current_dir(&src_dir) - .status() - .expect("could not execute make"); +fn make_libbpf(compiler: &cc::Tool, _: &ffi::OsStr, src_dir: &path::Path, _: &path::Path) { + let project_dir = src_dir.join("libbpf"); - assert!(status.success(), "make failed"); + let project = project_dir.to_str().unwrap(); - let status = process::Command::new("make") - .arg("clean") - .current_dir(&src_dir) - .status() - .expect("could not execute make"); + let mut builder = cc::Build::new(); + + builder + .include(src_dir) + .include(src_dir.join("zlib")) + .include(src_dir.join("elfutils").join("libelf")) + .include(format!("{project}/src")) + .include(format!("{project}/include")) + .include(format!("{project}/include/uapi")); + + if build_android() { + let cflags = ["-DCOMPAT_NEED_REALLOCARRAY"]; + + builder.flag("-includeandroid/android.h"); + + for flag in cflags { + builder.flag(flag); + } + } else { + builder.compiler(compiler.path()); + + for flag in compiler.args() { + builder.flag(flag); + } + } + + for entry in std::fs::read_dir(project_dir.join("src")).expect("Failed to `read_dir`") { + let entry = entry.unwrap(); + if entry.file_type().unwrap().is_file() + && entry.file_name().to_str().unwrap().ends_with(".c") + { + builder.file(entry.path()); + } + } + + builder + .flag_if_supported("-w") + .warnings(false) + .compile("bpf"); - assert!(status.success(), "make failed"); emit_rerun_directives_for_contents(&src_dir); } -fn num_cpus() -> usize { - std::thread::available_parallelism().map_or(1, |count| count.get()) +fn build_android() -> bool { + env::var("CARGO_CFG_TARGET_OS") + .expect("CARGO_CFG_TARGET_OS not set") + .eq("android") +} + +fn configure<'a, P, A>(project_dir: P, args: A) +where + P: AsRef, + A: IntoIterator, +{ + let project_dir = project_dir.as_ref(); + + let prog = format!("{project_dir}/configure"); + + unsafe { + let prog = std::ffi::CString::new(prog.as_bytes()).unwrap(); + nix::libc::chmod(prog.as_ptr(), 0o755); + } + + let status = subproc(prog, project_dir, args); + + assert!( + status.success(), + "configure({}) failed: {}", + project_dir, + status + ); +} + +fn subproc<'a, P, A>(prog: P, workdir: &str, args: A) -> ExitStatus +where + P: AsRef, + A: IntoIterator, +{ + process::Command::new(prog.as_ref()) + .current_dir(workdir) + .args(args) + .status() + .expect(&format!("could not execute `{}`", prog.as_ref())) +} + +fn autoconf

(project_dir: P) +where + P: AsRef, +{ + let project_dir = project_dir.as_ref(); + + let status = subproc("autoreconf", project_dir, ["--install", "--force"]); + + assert!( + status.success(), + "autoreconf({}) failed: {}", + project_dir, + status + ); } diff --git a/src/bindings.rs b/src/bindings.rs index 3e35881..27c170f 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -5487,7 +5487,6 @@ extern "C" { ) -> ::std::os::raw::c_int; } pub type va_list = __builtin_va_list; -pub type __gnuc_va_list = __builtin_va_list; #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct btf_header { @@ -6046,13 +6045,6 @@ extern "C" { opts: *const btf_dump_type_data_opts, ) -> ::std::os::raw::c_int; } -extern "C" { - pub fn vdprintf( - __fd: ::std::os::raw::c_int, - __fmt: *const ::std::os::raw::c_char, - __arg: *mut __va_list_tag, - ) -> ::std::os::raw::c_int; -} pub type pid_t = __pid_t; extern "C" { pub fn libbpf_major_version() -> __u32; @@ -6086,16 +6078,6 @@ pub const LIBBPF_WARN: libbpf_print_level = 0; pub const LIBBPF_INFO: libbpf_print_level = 1; pub const LIBBPF_DEBUG: libbpf_print_level = 2; pub type libbpf_print_level = ::std::os::raw::c_uint; -pub type libbpf_print_fn_t = ::std::option::Option< - unsafe extern "C" fn( - level: libbpf_print_level, - arg1: *const ::std::os::raw::c_char, - ap: *mut __va_list_tag, - ) -> ::std::os::raw::c_int, ->; -extern "C" { - pub fn libbpf_set_print(fn_: libbpf_print_fn_t) -> libbpf_print_fn_t; -} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct bpf_object_open_opts { diff --git a/src/lib.rs b/src/lib.rs index a65ab4c..f4cf707 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,26 @@ macro_rules! header { }; } +pub type libbpf_print_fn_t = ::std::option::Option< + unsafe extern "C" fn( + level: libbpf_print_level, + arg1: *const ::std::os::raw::c_char, + ap: *mut __va_list_tag, + ) -> ::std::os::raw::c_int, +>; + + +extern "C" { + + pub fn vdprintf( + __fd: ::std::os::raw::c_int, + __fmt: *const ::std::os::raw::c_char, + __args: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; + + pub fn libbpf_set_print(fn_: libbpf_print_fn_t) -> libbpf_print_fn_t; +} + /// Vendored libbpf headers /// /// Tuple format is: (header filename, header contents)