From f001add98c974cc1948cb0d046ca8f48b0ac881b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 16 Nov 2023 13:55:59 -0800 Subject: [PATCH] Introduce capi crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change moves all C API functionality into the cblazesym crate in the existing workspace. Doing so ensures better isolation between the core Rust part and the C API and it will allow us to version the C API different from the main Rust crate. Closes: #344 Signed-off-by: Daniel Müller --- .github/workflows/test.yml | 8 +- CHANGELOG.md | 1 + Cargo.toml | 12 +- README.md | 24 +--- capi/.cargo | 1 + capi/Cargo.toml | 33 ++++++ capi/README.md | 35 ++++++ capi/build.rs | 170 +++++++++++++++++++++++++++ cbindgen.toml => capi/cbindgen.toml | 8 +- {include => capi/include}/blazesym.h | 70 +++-------- {src/c_api => capi/src}/inspect.rs | 33 +++--- capi/src/lib.rs | 35 ++++++ {src/c_api => capi/src}/normalize.rs | 41 ++++--- {src/c_api => capi/src}/symbolize.rs | 43 +++---- tests/c_api.rs => capi/tests/capi.rs | 59 ++++++---- src/c_api/mod.rs | 12 -- src/lib.rs | 13 +- src/util.rs | 13 -- 18 files changed, 396 insertions(+), 215 deletions(-) create mode 120000 capi/.cargo create mode 100644 capi/Cargo.toml create mode 100644 capi/README.md create mode 100644 capi/build.rs rename cbindgen.toml => capi/cbindgen.toml (71%) rename {include => capi/include}/blazesym.h (89%) rename {src/c_api => capi/src}/inspect.rs (96%) create mode 100644 capi/src/lib.rs rename {src/c_api => capi/src}/normalize.rs (96%) rename {src/c_api => capi/src}/symbolize.rs (96%) rename tests/c_api.rs => capi/tests/capi.rs (91%) delete mode 100644 src/c_api/mod.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5547ca4c..cd7186cf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,7 +80,7 @@ jobs: - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Test and gather coverage - run: cargo llvm-cov --all-targets --features=nightly,generate-large-test-files --lcov --output-path lcov.info + run: cargo llvm-cov --workspace --all-targets --features=nightly,generate-large-test-files --lcov --output-path lcov.info - name: Upload code coverage results uses: codecov/codecov-action@v3 with: @@ -117,7 +117,7 @@ jobs: RUSTFLAGS: "-Zsanitizer=${{ matrix.sanitizer }}" ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" LSAN_OPTIONS: "" - run: cargo test --lib --tests --target x86_64-unknown-linux-gnu + run: cargo test --workspace --lib --tests --target x86_64-unknown-linux-gnu test-release: name: Test with release build runs-on: ubuntu-22.04 @@ -162,7 +162,7 @@ jobs: toolchain: stable profile: minimal override: true - - run: cargo check --workspace --features=generate-c-header + - run: cargo check --package=cblazesym --features=generate-c-header - name: Check that C header is up-to-date run: git diff --exit-code || (echo "!!!! CHECKED IN C HEADER IS OUTDATED !!!!" && false) @@ -234,4 +234,4 @@ jobs: toolchain: stable profile: minimal override: true - - run: cargo doc --no-deps + - run: cargo doc --workspace --no-deps diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d010e2f..cd757a055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Unreleased - Adjusted various symbolization code paths to stop heap-allocating - Handled potential numeric overflow in Gsym inlined function parser more gracefully +- Moved C API into `cblazesym` crate 0.2.0-alpha.8 diff --git a/Cargo.toml b/Cargo.toml index d5359f5a8..103ad155d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ ".", + "capi", "cli", ] @@ -33,10 +34,6 @@ autobenches = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "blazesym" -crate-type = ["lib", "cdylib", "staticlib"] - [features] default = ["backtrace", "demangle", "dwarf"] # Enable this feature to compile in support for capturing backtraces in errors. @@ -47,11 +44,6 @@ backtrace = [] dwarf = ["gimli"] # Enable this feature to get transparent symbol demangling. demangle = ["cpp_demangle", "rustc-demangle"] -# Enable this feature to re-generate the library's C header file. An -# up-to-date version of this header should already be available in the -# include/ directory, so this feature is only necessary when APIs are -# changed. -generate-c-header = ["cbindgen", "which"] # Enable this feature to opt in to the generation of unit test files. # Having these test files created is necessary for running tests. generate-unit-test-files = ["xz2", "zip"] @@ -96,9 +88,7 @@ test-log = {version = "0.2", default-features = false, features = ["trace"]} tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]} [build-dependencies] -cbindgen = {version = "0.26", optional = true} libc = "0.2.137" reqwest = {version = "0.11.18", optional = true, features = ["blocking"]} xz2 = {version = "0.1.7", optional = true} -which = {version = "5.0.0", optional = true} zip = {version = "0.6.4", optional = true, default-features = false} diff --git a/README.md b/README.md index 5df9d7eb6..9914ad310 100644 --- a/README.md +++ b/README.md @@ -91,27 +91,9 @@ comprehensive explanation of individual types and functions. ### C -For C interoperability, the aforementioned build will produce `libblazesym.a` as -well as `libblazesym.so` in the respective target folder (e.g., -`/target/debug/`). - -In your C programs include [`blazesym.h`](include/blazesym.h) (provided as part -of the library) from your source code and then link against the static or shared -library, respectively. When linking statically, you may also need to link: -```text --lrt -ldl -lpthread -lm -``` - -An example of usage of the C API is in available in **libbpf-bootstrap**: - - -This example periodically samples the running process of every processor -in a system and prints their stack traces. - -A detailed [documentation of the C API](https://docs.rs/blazesym/latest/blazesym/c_api) -is available as part of the Rust documentation or can be generated locally from -the current repository snapshot using `cargo doc` (grouped under the `c_api` -module). +The companion crate [`cblazesym`](capi/) provides the means for +interfacing with the library from C. Please refer to its +[`README`](capi/README.md) for usage details. ### Command-line diff --git a/capi/.cargo b/capi/.cargo new file mode 120000 index 000000000..ca32a9856 --- /dev/null +++ b/capi/.cargo @@ -0,0 +1 @@ +../.cargo \ No newline at end of file diff --git a/capi/Cargo.toml b/capi/Cargo.toml new file mode 100644 index 000000000..313e2cebd --- /dev/null +++ b/capi/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "cblazesym" +version = "0.0.0" +edition = "2021" +rust-version = "1.64" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "cblazesym" +# `lib` is necessary for end-to-end tests. +crate-type = ["lib", "cdylib", "staticlib"] + +[features] +# Enable this feature to re-generate the library's C header file. An +# up-to-date version of this header should already be available in the +# include/ directory, so this feature is only necessary when APIs are +# changed. +generate-c-header = ["cbindgen", "which"] + +[build-dependencies] +cbindgen = {version = "0.26", optional = true} +which = {version = "5.0.0", optional = true} + +[dependencies] +# Pinned, because we use #[doc(hidden)] APIs. +blazesym = {version = "=0.2.0-alpha.8", path = "../"} +libc = "0.2.137" + +[dev-dependencies] +env_logger = "0.10" +test-log = {version = "0.2.13", default-features = false, features = ["trace"]} +tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]} diff --git a/capi/README.md b/capi/README.md new file mode 100644 index 000000000..e7e5c5018 --- /dev/null +++ b/capi/README.md @@ -0,0 +1,35 @@ +[![pipeline](https://github.com/libbpf/blazesym/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/libbpf/blazesym/actions/workflows/test.yml) +[![rustc](https://img.shields.io/badge/rustc-1.65+-blue.svg)](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html) + +cblazesym +========= + +**cblazesym** provides C language bindings for the +[**blazesym**][blazesym] library. + +## Build & Use +**cblazesym** requires a standard Rust toolchain and can be built using +the Cargo project manager (e.g., `cargo build`). + +The build will produce `libcblazesym.a` as well as `libcblazesym.so` in +the respective target folder (e.g., `/target/debug/`). + +In your C programs include [`blazesym.h`](include/blazesym.h) (provided as part +of the crate) from your source code and then link against the static or +shared library, respectively. When linking statically, you may also need +to link: +```text +-lrt -ldl -lpthread -lm +``` + +An example of usage of the C API is in available in **libbpf-bootstrap**: + + +This example periodically samples the running process of every processor +in a system and prints their stack traces. + +A detailed [documentation of the C API](https://docs.rs/cblazesym/latest/) +is available as part of the Rust documentation or can be generated locally from +the current repository snapshot using `cargo doc`. + +[blazesym]: https://crates.io/crates/blazesym diff --git a/capi/build.rs b/capi/build.rs new file mode 100644 index 000000000..af59bdb97 --- /dev/null +++ b/capi/build.rs @@ -0,0 +1,170 @@ +#![allow(clippy::let_unit_value)] + +use std::env; +use std::ffi::OsStr; +use std::io::Error; +use std::io::ErrorKind; +use std::io::Result; +use std::ops::Deref as _; +use std::path::Path; +use std::process::Command; +use std::process::Stdio; + + +/// Format a command with the given list of arguments as a string. +fn format_command(command: C, args: A) -> String +where + C: AsRef, + A: IntoIterator, + S: AsRef, +{ + args.into_iter().fold( + command.as_ref().to_string_lossy().into_owned(), + |mut cmd, arg| { + cmd += " "; + cmd += arg.as_ref().to_string_lossy().deref(); + cmd + }, + ) +} + +/// Run a command with the provided arguments. +fn run(command: C, args: A) -> Result<()> +where + C: AsRef, + A: IntoIterator + Clone, + S: AsRef, +{ + let instance = Command::new(command.as_ref()) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .env_clear() + .envs(env::vars().filter(|(k, _)| k == "PATH")) + .args(args.clone()) + .output() + .map_err(|err| { + Error::new( + ErrorKind::Other, + format!( + "failed to run `{}`: {err}", + format_command(command.as_ref(), args.clone()) + ), + ) + })?; + + if !instance.status.success() { + let code = if let Some(code) = instance.status.code() { + format!(" ({code})") + } else { + " (terminated by signal)".to_string() + }; + + let stderr = String::from_utf8_lossy(&instance.stderr); + let stderr = stderr.trim_end(); + let stderr = if !stderr.is_empty() { + format!(": {stderr}") + } else { + String::new() + }; + + Err(Error::new( + ErrorKind::Other, + format!( + "`{}` reported non-zero exit-status{code}{stderr}", + format_command(command, args) + ), + )) + } else { + Ok(()) + } +} + +/// Compile `src` into `dst` using the provided compiler. +fn compile(compiler: &str, src: &Path, dst: &str, options: &[&str]) { + let dst = src.with_file_name(dst); + println!("cargo:rerun-if-changed={}", src.display()); + println!("cargo:rerun-if-changed={}", dst.display()); + + let () = run( + compiler, + options + .iter() + .map(OsStr::new) + .chain([src.as_os_str(), "-o".as_ref(), dst.as_os_str()]), + ) + .unwrap_or_else(|err| panic!("failed to run `{compiler}`: {err}")); +} + +/// Compile `src` into `dst` using `cc`. +#[cfg_attr(not(feature = "generate-c-header"), allow(dead_code))] +fn cc(src: &Path, dst: &str, options: &[&str]) { + compile("cc", src, dst, options) +} + +fn main() { + #[cfg(feature = "generate-c-header")] + { + use std::fs::copy; + use std::fs::write; + + let crate_dir = env!("CARGO_MANIFEST_DIR"); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(cbindgen::Config::from_root_or_default(crate_dir)) + .generate() + .expect("Unable to generate bindings") + .write_to_file(Path::new(crate_dir).join("include").join("blazesym.h")); + + // Generate a C program that just included blazesym.h as a basic + // smoke test that cbindgen didn't screw up completely. + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_dir = Path::new(&out_dir); + let blaze_src_c = out_dir.join("blazesym.c"); + let () = write( + &blaze_src_c, + r#" +#include + +int main() { + return 0; +} +"#, + ) + .unwrap(); + + let blaze_src_cxx = out_dir.join("blazesym.cpp"); + let _bytes = copy(&blaze_src_c, &blaze_src_cxx).expect("failed to copy file"); + + cc( + &blaze_src_c, + "blazesym.bin", + &[ + "-Wall", + "-Wextra", + "-Werror", + "-I", + Path::new(crate_dir).join("include").to_str().unwrap(), + ], + ); + + // Best-effort check that C++ can compile the thing as well. Hopefully + // all flags are supported... + for cxx in ["clang++", "g++"] { + if which::which(cxx).is_ok() { + compile( + cxx, + &blaze_src_cxx, + &format!("blazesym_cxx_{cxx}.bin"), + &[ + "-Wall", + "-Wextra", + "-Werror", + "-I", + Path::new(crate_dir).join("include").to_str().unwrap(), + ], + ); + } + } + } +} diff --git a/cbindgen.toml b/capi/cbindgen.toml similarity index 71% rename from cbindgen.toml rename to capi/cbindgen.toml index b7d9f6440..e9cf7e765 100644 --- a/cbindgen.toml +++ b/capi/cbindgen.toml @@ -1,4 +1,4 @@ -# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml language = "C" cpp_compat = true @@ -11,9 +11,9 @@ exclude = ["Addr"] [export.rename] "Addr" = "uintptr_t" -"Inspector" = "blaze_inspector" -"Normalizer" = "blaze_normalizer" -"Symbolizer" = "blaze_symbolizer" +"Inspector" = "struct blaze_inspector" +"Normalizer" = "struct blaze_normalizer" +"Symbolizer" = "struct blaze_symbolizer" [fn] args = "Vertical" diff --git a/include/blazesym.h b/capi/include/blazesym.h similarity index 89% rename from include/blazesym.h rename to capi/include/blazesym.h index fef5d9280..40e3a7969 100644 --- a/include/blazesym.h +++ b/capi/include/blazesym.h @@ -44,50 +44,6 @@ typedef enum blaze_user_meta_kind { BLAZE_USER_META_ELF, } blaze_user_meta_kind; -/** - * An inspector of various "sources". - * - * Object of this type can be used to perform inspections of supported sources. - * E.g., using an ELF file as a source, information about a symbol can be - * inquired based on its name. - * - * An instance of this type is the unit at which inspection inputs are cached. - * That is to say, source files (such as ELF) and the parsed data structures - * may be kept around in memory for the lifetime of this object to speed up - * future inspection requests. - * If you are working with large input sources and/or do not intend to perform - * multiple inspection requests for the same symbolization source, you may want - * to consider creating a new `Inspector` instance regularly. - */ -typedef struct blaze_inspector blaze_inspector; - -/** - * A normalizer for addresses. - * - * Address normalization is the process of taking virtual absolute - * addresses as they are seen by, say, a process (which include - * relocation and process specific layout randomizations, among other - * things) and converting them to "normalized" virtual addresses as - * they are present in, say, an ELF binary or a DWARF debug info file, - * and one would be able to see them using tools such as readelf(1). - */ -typedef struct blaze_normalizer blaze_normalizer; - -/** - * Symbolizer provides an interface to symbolize addresses. - * - * An instance of this type is the unit at which symbolization inputs are - * cached. That is to say, source files (DWARF, ELF, ...) and the parsed data - * structures may be kept around in memory for the lifetime of this object to - * speed up future symbolization requests. If you are working with large input - * sources and/or do not intend to perform multiple symbolization requests - * (i.e., [`symbolize`][Symbolizer::symbolize] or - * [`symbolize_single`][Symbolizer::symbolize_single] calls) for the same - * symbolization source, you may want to consider creating a new `Symbolizer` - * instance regularly. - */ -typedef struct blaze_symbolizer blaze_symbolizer; - /** * Information about a looked up symbol. */ @@ -118,6 +74,11 @@ typedef struct blaze_sym_info { enum blaze_sym_type sym_type; } blaze_sym_info; +/** + * C ABI compatible version of [`blazesym::inspect::Inspector`]. + */ +typedef struct blaze_inspector blaze_inspector; + /** * An object representing an ELF inspection source. * @@ -135,6 +96,11 @@ typedef struct blaze_inspect_elf_src { bool debug_info; } blaze_inspect_elf_src; +/** + * C ABI compatible version of [`blazesym::normalize::Normalizer`]. + */ +typedef struct blaze_normalizer blaze_normalizer; + /** * C compatible version of [`Apk`]. */ @@ -247,7 +213,7 @@ typedef struct blaze_normalized_user_output { } blaze_normalized_user_output; /** - * A placeholder symbolizer for C API. + * C ABI compatible version of [`blazesym::symbolize::Symbolizer`]. * * It is returned by [`blaze_symbolizer_new`] and should be free by * [`blaze_symbolizer_free`]. @@ -497,7 +463,7 @@ extern "C" { * [`blaze_inspector_new`], `src` needs to point to a valid object, and `names` * needs to be a valid pointer to `name_cnt` strings. */ -const struct blaze_sym_info *const *blaze_inspect_syms_elf(const struct blaze_inspector *inspector, +const struct blaze_sym_info *const *blaze_inspect_syms_elf(const blaze_inspector *inspector, const struct blaze_inspect_elf_src *src, const char *const *names, size_t name_cnt); @@ -518,7 +484,7 @@ void blaze_inspect_syms_free(const struct blaze_sym_info *const *syms); * The returned pointer should be released using * [`blaze_inspector_free`] once it is no longer needed. */ -struct blaze_inspector *blaze_inspector_new(void); +blaze_inspector *blaze_inspector_new(void); /** * Free a blazesym inspector. @@ -530,7 +496,7 @@ struct blaze_inspector *blaze_inspector_new(void); * The provided inspector should have been created by * [`blaze_inspector_new`]. */ -void blaze_inspector_free(struct blaze_inspector *inspector); +void blaze_inspector_free(blaze_inspector *inspector); /** * Create an instance of a blazesym normalizer. @@ -538,7 +504,7 @@ void blaze_inspector_free(struct blaze_inspector *inspector); * The returned pointer should be released using * [`blaze_normalizer_free`] once it is no longer needed. */ -struct blaze_normalizer *blaze_normalizer_new(void); +blaze_normalizer *blaze_normalizer_new(void); /** * Free a blazesym normalizer. @@ -550,7 +516,7 @@ struct blaze_normalizer *blaze_normalizer_new(void); * The provided normalizer should have been created by * [`blaze_normalizer_new`]. */ -void blaze_normalizer_free(struct blaze_normalizer *normalizer); +void blaze_normalizer_free(blaze_normalizer *normalizer); /** * Normalize a list of user space addresses. @@ -569,7 +535,7 @@ void blaze_normalizer_free(struct blaze_normalizer *normalizer); * Callers need to pass in a valid `addrs` pointer, pointing to memory of * `addr_cnt` addresses. */ -struct blaze_normalized_user_output *blaze_normalize_user_addrs(const struct blaze_normalizer *normalizer, +struct blaze_normalized_user_output *blaze_normalize_user_addrs(const blaze_normalizer *normalizer, const uintptr_t *addrs, size_t addr_cnt, uint32_t pid); @@ -593,7 +559,7 @@ struct blaze_normalized_user_output *blaze_normalize_user_addrs(const struct bla * Callers need to pass in a valid `addrs` pointer, pointing to memory of * `addr_cnt` addresses. */ -struct blaze_normalized_user_output *blaze_normalize_user_addrs_sorted(const struct blaze_normalizer *normalizer, +struct blaze_normalized_user_output *blaze_normalize_user_addrs_sorted(const blaze_normalizer *normalizer, const uintptr_t *addrs, size_t addr_cnt, uint32_t pid); diff --git a/src/c_api/inspect.rs b/capi/src/inspect.rs similarity index 96% rename from src/c_api/inspect.rs rename to capi/src/inspect.rs index f2b405deb..902cfd149 100644 --- a/src/c_api/inspect.rs +++ b/capi/src/inspect.rs @@ -16,15 +16,19 @@ use std::path::PathBuf; use std::ptr; #[cfg(doc)] -use crate::inspect; -use crate::inspect::Elf; -use crate::inspect::Inspector; -use crate::inspect::Source; -use crate::inspect::SymInfo; -use crate::inspect::SymType; -use crate::log::error; -use crate::util::slice_from_user_array; -use crate::Addr; +use blazesym::inspect; +use blazesym::inspect::Elf; +use blazesym::inspect::Inspector; +use blazesym::inspect::Source; +use blazesym::inspect::SymInfo; +use blazesym::inspect::SymType; +use blazesym::Addr; + +use crate::slice_from_user_array; + + +/// C ABI compatible version of [`blazesym::inspect::Inspector`]. +pub type blaze_inspector = Inspector; /// An object representing an ELF inspection source. @@ -240,7 +244,7 @@ fn convert_syms_list_to_c(syms_list: Vec>) -> *const *const blaze_s /// needs to be a valid pointer to `name_cnt` strings. #[no_mangle] pub unsafe extern "C" fn blaze_inspect_syms_elf( - inspector: *const Inspector, + inspector: *const blaze_inspector, src: *const blaze_inspect_elf_src, names: *const *const c_char, name_cnt: usize, @@ -262,10 +266,7 @@ pub unsafe extern "C" fn blaze_inspect_syms_elf( let result = inspector.lookup(&names, &src); match result { Ok(syms) => convert_syms_list_to_c(syms), - Err(err) => { - error!("failed to lookup symbols: {err}"); - ptr::null() - } + Err(_err) => ptr::null(), } } @@ -293,7 +294,7 @@ pub unsafe extern "C" fn blaze_inspect_syms_free(syms: *const *const blaze_sym_i /// The returned pointer should be released using /// [`blaze_inspector_free`] once it is no longer needed. #[no_mangle] -pub extern "C" fn blaze_inspector_new() -> *mut Inspector { +pub extern "C" fn blaze_inspector_new() -> *mut blaze_inspector { let inspector = Inspector::new(); let inspector_box = Box::new(inspector); Box::into_raw(inspector_box) @@ -309,7 +310,7 @@ pub extern "C" fn blaze_inspector_new() -> *mut Inspector { /// The provided inspector should have been created by /// [`blaze_inspector_new`]. #[no_mangle] -pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut Inspector) { +pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut blaze_inspector) { if !inspector.is_null() { // SAFETY: The caller needs to ensure that `inspector` is a // valid pointer. diff --git a/capi/src/lib.rs b/capi/src/lib.rs new file mode 100644 index 000000000..384fcc48e --- /dev/null +++ b/capi/src/lib.rs @@ -0,0 +1,35 @@ +//! C API bindings for the library. + +#![allow( + clippy::collapsible_if, + clippy::fn_to_numeric_cast, + clippy::let_and_return, + clippy::let_unit_value +)] + +#[allow(non_camel_case_types)] +mod inspect; +#[allow(non_camel_case_types)] +mod normalize; +#[allow(non_camel_case_types)] +mod symbolize; + +use std::ptr::NonNull; +use std::slice; + +pub use inspect::*; +pub use normalize::*; +pub use symbolize::*; + + +/// "Safely" create a slice from a user provided array. +pub(crate) unsafe fn slice_from_user_array<'t, T>(items: *const T, num_items: usize) -> &'t [T] { + let items = if items.is_null() { + // `slice::from_raw_parts` requires a properly aligned non-NULL pointer. + // Craft one. + NonNull::dangling().as_ptr() + } else { + items + }; + unsafe { slice::from_raw_parts(items, num_items) } +} diff --git a/src/c_api/normalize.rs b/capi/src/normalize.rs similarity index 96% rename from src/c_api/normalize.rs rename to capi/src/normalize.rs index 0c3faea2a..b7c0e48de 100644 --- a/src/c_api/normalize.rs +++ b/capi/src/normalize.rs @@ -10,15 +10,19 @@ use std::path::PathBuf; use std::ptr; use std::slice; -use crate::log::error; -use crate::normalize::Apk; -use crate::normalize::Elf; -use crate::normalize::Normalizer; -use crate::normalize::Unknown; -use crate::normalize::UserMeta; -use crate::normalize::UserOutput; -use crate::util::slice_from_user_array; -use crate::Addr; +use blazesym::normalize::Apk; +use blazesym::normalize::Elf; +use blazesym::normalize::Normalizer; +use blazesym::normalize::Unknown; +use blazesym::normalize::UserMeta; +use blazesym::normalize::UserOutput; +use blazesym::Addr; + +use crate::slice_from_user_array; + + +/// C ABI compatible version of [`blazesym::normalize::Normalizer`]. +pub type blaze_normalizer = Normalizer; /// Create an instance of a blazesym normalizer. @@ -26,7 +30,7 @@ use crate::Addr; /// The returned pointer should be released using /// [`blaze_normalizer_free`] once it is no longer needed. #[no_mangle] -pub extern "C" fn blaze_normalizer_new() -> *mut Normalizer { +pub extern "C" fn blaze_normalizer_new() -> *mut blaze_normalizer { let normalizer = Normalizer::new(); let normalizer_box = Box::new(normalizer); Box::into_raw(normalizer_box) @@ -42,7 +46,7 @@ pub extern "C" fn blaze_normalizer_new() -> *mut Normalizer { /// The provided normalizer should have been created by /// [`blaze_normalizer_new`]. #[no_mangle] -pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut Normalizer) { +pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) { if !normalizer.is_null() { // SAFETY: The caller needs to ensure that `normalizer` is a // valid pointer. @@ -260,6 +264,7 @@ impl From for blaze_user_meta { unknown: ManuallyDrop::new(blaze_user_meta_unknown::from(unknown)), }, }, + _ => unreachable!(), } } } @@ -356,7 +361,7 @@ impl From for blaze_normalized_user_output { /// `addr_cnt` addresses. #[no_mangle] pub unsafe extern "C" fn blaze_normalize_user_addrs( - normalizer: *const Normalizer, + normalizer: *const blaze_normalizer, addrs: *const Addr, addr_cnt: usize, pid: u32, @@ -370,10 +375,7 @@ pub unsafe extern "C" fn blaze_normalize_user_addrs( let result = normalizer.normalize_user_addrs(addrs, pid.into()); match result { Ok(addrs) => Box::into_raw(Box::new(blaze_normalized_user_output::from(addrs))), - Err(err) => { - error!("failed to normalize user addresses: {err}"); - ptr::null_mut() - } + Err(_err) => ptr::null_mut(), } } @@ -397,7 +399,7 @@ pub unsafe extern "C" fn blaze_normalize_user_addrs( /// `addr_cnt` addresses. #[no_mangle] pub unsafe extern "C" fn blaze_normalize_user_addrs_sorted( - normalizer: *const Normalizer, + normalizer: *const blaze_normalizer, addrs: *const Addr, addr_cnt: usize, pid: u32, @@ -411,10 +413,7 @@ pub unsafe extern "C" fn blaze_normalize_user_addrs_sorted( let result = normalizer.normalize_user_addrs_sorted(addrs, pid.into()); match result { Ok(addrs) => Box::into_raw(Box::new(blaze_normalized_user_output::from(addrs))), - Err(err) => { - error!("failed to normalize user addresses: {err}"); - ptr::null_mut() - } + Err(_err) => ptr::null_mut(), } } diff --git a/src/c_api/symbolize.rs b/capi/src/symbolize.rs similarity index 96% rename from src/c_api/symbolize.rs rename to capi/src/symbolize.rs index 5daaf0518..210549f56 100644 --- a/src/c_api/symbolize.rs +++ b/capi/src/symbolize.rs @@ -11,22 +11,21 @@ use std::path::Path; use std::path::PathBuf; use std::ptr; -use crate::log::error; -use crate::log::warn; -use crate::symbolize::CodeInfo; -use crate::symbolize::Elf; -use crate::symbolize::GsymData; -use crate::symbolize::GsymFile; -use crate::symbolize::InlinedFn; -use crate::symbolize::Input; -use crate::symbolize::Kernel; -use crate::symbolize::Process; -use crate::symbolize::Source; -use crate::symbolize::Sym; -use crate::symbolize::Symbolized; -use crate::symbolize::Symbolizer; -use crate::util::slice_from_user_array; -use crate::Addr; +use blazesym::symbolize::CodeInfo; +use blazesym::symbolize::Elf; +use blazesym::symbolize::GsymData; +use blazesym::symbolize::GsymFile; +use blazesym::symbolize::InlinedFn; +use blazesym::symbolize::Input; +use blazesym::symbolize::Kernel; +use blazesym::symbolize::Process; +use blazesym::symbolize::Source; +use blazesym::symbolize::Sym; +use blazesym::symbolize::Symbolized; +use blazesym::symbolize::Symbolizer; +use blazesym::Addr; + +use crate::slice_from_user_array; /// The parameters to load symbols and debug information from an ELF. @@ -159,7 +158,7 @@ impl From<&blaze_symbolize_src_gsym_file> for GsymFile { } -/// A placeholder symbolizer for C API. +/// C ABI compatible version of [`blazesym::symbolize::Symbolizer`]. /// /// It is returned by [`blaze_symbolizer_new`] and should be free by /// [`blaze_symbolizer_free`]. @@ -503,15 +502,9 @@ unsafe fn blaze_symbolize_impl( let result = symbolizer.symbolize(&src, input); match result { - Ok(results) if results.is_empty() => { - warn!("empty result symbolizing {input_cnt} inputs"); - ptr::null() - } + Ok(results) if results.is_empty() => ptr::null(), Ok(results) => convert_symbolizedresults_to_c(results), - Err(_err) => { - error!("failed to symbolize {input_cnt} inputs: {_err}"); - ptr::null() - } + Err(_err) => ptr::null(), } } diff --git a/tests/c_api.rs b/capi/tests/capi.rs similarity index 91% rename from tests/c_api.rs rename to capi/tests/capi.rs index 36c352af2..284ca348e 100644 --- a/tests/c_api.rs +++ b/capi/tests/capi.rs @@ -13,34 +13,34 @@ use std::slice; use blazesym::inspect; use blazesym::symbolize; - -use blazesym::c_api::blaze_inspect_elf_src; -use blazesym::c_api::blaze_inspect_syms_elf; -use blazesym::c_api::blaze_inspect_syms_free; -use blazesym::c_api::blaze_inspector_free; -use blazesym::c_api::blaze_inspector_new; -use blazesym::c_api::blaze_normalize_user_addrs; -use blazesym::c_api::blaze_normalize_user_addrs_sorted; -use blazesym::c_api::blaze_normalizer_free; -use blazesym::c_api::blaze_normalizer_new; -use blazesym::c_api::blaze_result; -use blazesym::c_api::blaze_result_free; -use blazesym::c_api::blaze_symbolize_elf_file_addrs; -use blazesym::c_api::blaze_symbolize_gsym_data_file_addrs; -use blazesym::c_api::blaze_symbolize_gsym_file_file_addrs; -use blazesym::c_api::blaze_symbolize_process_virt_addrs; -use blazesym::c_api::blaze_symbolize_src_elf; -use blazesym::c_api::blaze_symbolize_src_gsym_data; -use blazesym::c_api::blaze_symbolize_src_gsym_file; -use blazesym::c_api::blaze_symbolize_src_process; -use blazesym::c_api::blaze_symbolizer; -use blazesym::c_api::blaze_symbolizer_free; -use blazesym::c_api::blaze_symbolizer_new; -use blazesym::c_api::blaze_symbolizer_new_opts; -use blazesym::c_api::blaze_symbolizer_opts; -use blazesym::c_api::blaze_user_output_free; use blazesym::Addr; +use cblazesym::blaze_inspect_elf_src; +use cblazesym::blaze_inspect_syms_elf; +use cblazesym::blaze_inspect_syms_free; +use cblazesym::blaze_inspector_free; +use cblazesym::blaze_inspector_new; +use cblazesym::blaze_normalize_user_addrs; +use cblazesym::blaze_normalize_user_addrs_sorted; +use cblazesym::blaze_normalizer_free; +use cblazesym::blaze_normalizer_new; +use cblazesym::blaze_result; +use cblazesym::blaze_result_free; +use cblazesym::blaze_symbolize_elf_file_addrs; +use cblazesym::blaze_symbolize_gsym_data_file_addrs; +use cblazesym::blaze_symbolize_gsym_file_file_addrs; +use cblazesym::blaze_symbolize_process_virt_addrs; +use cblazesym::blaze_symbolize_src_elf; +use cblazesym::blaze_symbolize_src_gsym_data; +use cblazesym::blaze_symbolize_src_gsym_file; +use cblazesym::blaze_symbolize_src_process; +use cblazesym::blaze_symbolizer; +use cblazesym::blaze_symbolizer_free; +use cblazesym::blaze_symbolizer_new; +use cblazesym::blaze_symbolizer_new_opts; +use cblazesym::blaze_symbolizer_opts; +use cblazesym::blaze_user_output_free; + /// Make sure that we can create and free a symbolizer instance. #[test] @@ -109,6 +109,7 @@ fn symbolize_elf_dwarf_gsym() { } let path = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-stable-addresses-no-dwarf.bin"); let path_c = CString::new(path.to_str().unwrap()).unwrap(); @@ -121,6 +122,7 @@ fn symbolize_elf_dwarf_gsym() { test(symbolize, false); let path = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-stable-addresses-dwarf-only.bin"); let path_c = CString::new(path.to_str().unwrap()).unwrap(); @@ -133,6 +135,7 @@ fn symbolize_elf_dwarf_gsym() { test(symbolize, true); let path = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-stable-addresses.gsym"); let path_c = CString::new(path.to_str().unwrap()).unwrap(); @@ -145,6 +148,7 @@ fn symbolize_elf_dwarf_gsym() { test(symbolize, true); let path = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-stable-addresses.gsym"); let data = read_file(path).unwrap(); @@ -247,6 +251,7 @@ fn symbolize_dwarf_demangle() { } let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-rs.bin"); let elf = inspect::Elf::new(&test_dwarf); @@ -388,6 +393,7 @@ fn inspector_creation() { #[test] fn lookup_dwarf() { let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR")) + .join("..") .join("data") .join("test-stable-addresses-dwarf-only.bin"); @@ -398,6 +404,7 @@ fn lookup_dwarf() { let inspector = blaze_inspector_new(); let result = unsafe { blaze_inspect_syms_elf(inspector, &src, names.as_ptr(), names.len()) }; let _src = inspect::Elf::from(src); + assert!(!result.is_null()); let sym_infos = unsafe { slice::from_raw_parts(result, names.len()) }; let sym_info = unsafe { &*sym_infos[0] }; diff --git a/src/c_api/mod.rs b/src/c_api/mod.rs deleted file mode 100644 index 82d5c65b8..000000000 --- a/src/c_api/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! C API bindings for the library. - -#[allow(non_camel_case_types)] -mod inspect; -#[allow(non_camel_case_types)] -mod normalize; -#[allow(non_camel_case_types)] -mod symbolize; - -pub use inspect::*; -pub use normalize::*; -pub use symbolize::*; diff --git a/src/lib.rs b/src/lib.rs index 26447c118..9d2667893 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,9 @@ //! - [`normalize`] exposes address normalization functionality //! //! C API bindings are defined in a cross-cutting manner as part of the -//! [`c_api`] module (note that Rust code should not have to consume these -//! functions and on the ABI level this module organization has no relevance for -//! C). +//! `cblazesym` crate (note that Rust code should not have to consume +//! these functions and on the ABI level this module organization has no +//! relevance for C). #![allow( clippy::collapsible_if, @@ -40,7 +40,6 @@ #[cfg(feature = "nightly")] extern crate test; -pub mod c_api; #[cfg(feature = "dwarf")] mod dwarf; mod elf; @@ -69,12 +68,6 @@ use std::result; use resolver::SymResolver; -// We import all C API items during doc creation to not have to mention the -// `c_api` module in, say, the README. -#[cfg(doc)] -use c_api::*; - - pub use crate::error::Error; pub use crate::error::ErrorExt; pub use crate::error::ErrorKind; diff --git a/src/util.rs b/src/util.rs index 15790b1ad..6cb802055 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,7 +7,6 @@ use std::mem::align_of; use std::mem::size_of; use std::mem::MaybeUninit; use std::os::unix::io::RawFd; -use std::ptr::NonNull; use std::slice; @@ -55,18 +54,6 @@ where } -/// "Safely" create a slice from a user provided array. -pub(crate) unsafe fn slice_from_user_array<'t, T>(items: *const T, num_items: usize) -> &'t [T] { - let items = if items.is_null() { - // `slice::from_raw_parts` requires a properly aligned non-NULL pointer. - // Craft one. - NonNull::dangling().as_ptr() - } else { - items - }; - unsafe { slice::from_raw_parts(items, num_items) } -} - pub(crate) fn fstat(fd: RawFd) -> io::Result { let mut dst = MaybeUninit::uninit(); let rc = unsafe { libc::fstat(fd, dst.as_mut_ptr()) };