From 1e886b5c05470e1bd460f32a94bb178ffa666a1c Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Feb 2026 20:21:04 +0100 Subject: [PATCH 01/21] Wrap libunwind in a crate --- .gitmodules | 4 ++++ Cargo.lock | 7 +++++++ Cargo.toml | 2 +- libdd-libunwind/Cargo.toml | 14 ++++++++++++++ libdd-libunwind/build.rs | 36 ++++++++++++++++++++++++++++++++++++ libdd-libunwind/libunwind | 1 + libdd-libunwind/src/lib.rs | 14 ++++++++++++++ 7 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 libdd-libunwind/Cargo.toml create mode 100644 libdd-libunwind/build.rs create mode 160000 libdd-libunwind/libunwind create mode 100644 libdd-libunwind/src/lib.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..d12ac270ad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "libdd-libunwind/libunwind"] + path = libdd-libunwind/libunwind + url = https://github.com/DataDog/libunwind.git + branch = kevin/v1.8.1-custom-2 diff --git a/Cargo.lock b/Cargo.lock index 519b6b857f..dc4a6aa20f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3187,6 +3187,13 @@ dependencies = [ "tempfile", ] +[[package]] +name = "libdd-libunwind" +version = "26.0.0" +dependencies = [ + "cc", +] + [[package]] name = "libdd-log" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 23cd1b87fc..f43851d976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ members = [ "libdd-tinybytes", "libdd-dogstatsd-client", "libdd-log", - "libdd-log-ffi", + "libdd-log-ffi", "libdd-libunwind", ] # https://doc.rust-lang.org/cargo/reference/resolver.html diff --git a/libdd-libunwind/Cargo.toml b/libdd-libunwind/Cargo.toml new file mode 100644 index 0000000000..652f1fc6a3 --- /dev/null +++ b/libdd-libunwind/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libdd-libunwind" +edition.workspace = true +version.workspace = true +rust-version.workspace = true +license.workspace = true +publish = false + +[lib] +crate-type = ["lib"] +bench = false + +[build-dependencies] +cc = "1.0" diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs new file mode 100644 index 0000000000..b35c222c0b --- /dev/null +++ b/libdd-libunwind/build.rs @@ -0,0 +1,36 @@ +use std::env; +use std::path::PathBuf; + +#[cfg(not(target_os = "linux"))] +fn main() { + println!("cargo:warning=non-linux platform is not supported yet"); +} + +#[cfg(target_os = "linux")] +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let build_dir = out_dir.join("libunwind_build"); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); + + std::fs::create_dir_all(&build_dir).unwrap(); + + std::process::Command::new("sh") + .current_dir(&libunwind_dir) + .args(["-c", "autoreconf -i"]) + .status() + .expect("Install autotools: apt install autoconf automake libtool"); + + std::process::Command::new("sh") + .current_dir(&build_dir) + .args(["-c", + &format!("{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", + libunwind_dir.display())]) + .status() + .expect("Install autotools: apt install autoconf automake libtool"); + + let lib_path = build_dir.join("src/.libs"); + println!("cargo:rustc-link-search=native={}", lib_path.display()); + println!("cargo:rustc-link-lib=static=unwind"); + println!("cargo:rerun-if-changed={}", libunwind_dir.display()); +} diff --git a/libdd-libunwind/libunwind b/libdd-libunwind/libunwind new file mode 160000 index 0000000000..cc1d07281b --- /dev/null +++ b/libdd-libunwind/libunwind @@ -0,0 +1 @@ +Subproject commit cc1d07281b9e034c9e088733aeb4b94ffd91db9e diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs new file mode 100644 index 0000000000..b93cf3ffd9 --- /dev/null +++ b/libdd-libunwind/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 2137bf0210412b7b168a5396c7846a19400e1fc4 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Feb 2026 21:02:05 +0100 Subject: [PATCH 02/21] Since populate git modules --- .github/workflows/coverage.yml | 2 + .github/workflows/lint.yml | 6 +++ .github/workflows/miri.yml | 2 + .github/workflows/test.yml | 8 ++++ libdd-libunwind/build.rs | 79 ++++++++++++++++++++++++++++------ 5 files changed, 84 insertions(+), 13 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c37c9c663d..f9375476f5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,6 +26,8 @@ jobs: docker-images: true swap-storage: true - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install Rust run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 - name: Install cargo-llvm-cov diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b33af83d94..a74cdabfff 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Run actionlint uses: devops-actions/actionlint@c6744a34774e4e1c1df0ff66bdb07ec7ee480ca0 # 0.1.9 with: @@ -22,6 +24,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install nightly-2026-02-08 toolchain and rustfmt run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 && rustup component add rustfmt - name: Cache [rust] @@ -41,6 +45,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install ${{ matrix.rust_version }} toolchain and clippy run: rustup install ${{ matrix.rust_version }} && rustup default ${{ matrix.rust_version }} && rustup component add clippy - name: Cache [rust] diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index ddb8bdbe69..51860ea42e 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -14,6 +14,8 @@ jobs: PROPTEST_CASES: 1 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Set up Rust run: | set -e diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 258cefff39..c7f3b0aac4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,6 +36,8 @@ jobs: swap-storage: true - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install Rust ${{ matrix.rust_version }} if: matrix.rust_version != '' run: rustup install ${{ matrix.rust_version }} && rustup default ${{ matrix.rust_version }} @@ -155,6 +157,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Setup output dir shell: bash run: | @@ -297,6 +301,8 @@ jobs: swap-storage: true - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Cache [rust] uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # 2.8.1 with: @@ -321,6 +327,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # 3.10.0 with: diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index b35c222c0b..9c6e2a58c0 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -13,24 +13,77 @@ fn main() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); + // Check if libunwind submodule is initialized + if !libunwind_dir.exists() || std::fs::read_dir(&libunwind_dir).unwrap().next().is_none() { + panic!( + "libunwind submodule not initialized!\n\ + Run: git submodule update --init --recursive\n\ + \n\ + For CI, ensure your workflow checks out submodules:\n\ + - GitHub Actions: add 'submodules: recursive' to actions/checkout\n\ + - GitLab CI: add 'GIT_SUBMODULE_STRATEGY: recursive'\n\ + \n\ + Directory checked: {}", + libunwind_dir.display() + ); + } + std::fs::create_dir_all(&build_dir).unwrap(); - std::process::Command::new("sh") - .current_dir(&libunwind_dir) - .args(["-c", "autoreconf -i"]) - .status() - .expect("Install autotools: apt install autoconf automake libtool"); + let lib_file = build_dir.join("src/.libs/libunwind.a"); + + // Only build if library doesn't exist + if !lib_file.exists() { + eprintln!("Building libunwind from source..."); + + // Only run autoreconf if configure doesn't exist + let configure_script = libunwind_dir.join("configure"); + if !configure_script.exists() { + eprintln!("Running autoreconf..."); + let status = std::process::Command::new("sh") + .current_dir(&libunwind_dir) + .args(["-c", "autoreconf -i"]) + .status() + .expect("Failed to run autoreconf. Install with: apt install autoconf automake libtool"); + + if !status.success() { + panic!("autoreconf failed with exit code: {:?}", status.code()); + } + } + + eprintln!("Configuring and building libunwind..."); + let status = std::process::Command::new("sh") + .current_dir(&build_dir) + .args([ + "-c", + &format!( + "{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", + libunwind_dir.display() + ) + ]) + .status() + .expect("Failed to run configure/make"); - std::process::Command::new("sh") - .current_dir(&build_dir) - .args(["-c", - &format!("{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", - libunwind_dir.display())]) - .status() - .expect("Install autotools: apt install autoconf automake libtool"); + if !status.success() { + panic!("libunwind build failed with exit code: {:?}", status.code()); + } + + // Verify the library was actually created + if !lib_file.exists() { + panic!("libunwind.a was not created at expected location: {}", lib_file.display()); + } + + eprintln!("libunwind built successfully at {}", lib_file.display()); + } else { + eprintln!("Using cached libunwind build"); + } let lib_path = build_dir.join("src/.libs"); println!("cargo:rustc-link-search=native={}", lib_path.display()); println!("cargo:rustc-link-lib=static=unwind"); - println!("cargo:rerun-if-changed={}", libunwind_dir.display()); + + // More specific rerun triggers + println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); + println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); + println!("cargo:rerun-if-changed=build.rs"); } From 006534ad98df30817130bb41f70839dd5f24b2de Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Feb 2026 23:16:28 +0100 Subject: [PATCH 03/21] Improve bindings --- .github/workflows/fuzz.yml | 2 + Cargo.lock | 26 +++- libdd-libunwind/Cargo.toml | 5 + libdd-libunwind/build.rs | 43 +++++++ libdd-libunwind/src/lib.rs | 189 ++++++++++++++++++++++++++++- libdd-libunwind/src/lib_aliases.rs | 37 ++++++ 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 libdd-libunwind/src/lib_aliases.rs diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index a44010700f..9ddeb2fd2e 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -12,6 +12,8 @@ jobs: CARGO_INCREMENTAL: 0 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Set up Rust run: | set -e diff --git a/Cargo.lock b/Cargo.lock index dc4a6aa20f..182bbaf65a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,7 +380,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9c2e952a1f57e8cbc78b058a968639e70c4ce8b9c0a5e6363d4e5670eed795" dependencies = [ - "bindgen", + "bindgen 0.69.5", "cc", "cmake", "dunce", @@ -405,7 +405,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" dependencies = [ - "bindgen", + "bindgen 0.69.5", "cc", "cmake", "dunce", @@ -535,6 +535,26 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.11.0", + "log 0.4.27", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.87", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -3191,7 +3211,9 @@ dependencies = [ name = "libdd-libunwind" version = "26.0.0" dependencies = [ + "bindgen 0.70.1", "cc", + "paste", ] [[package]] diff --git a/libdd-libunwind/Cargo.toml b/libdd-libunwind/Cargo.toml index 652f1fc6a3..19c1d4a939 100644 --- a/libdd-libunwind/Cargo.toml +++ b/libdd-libunwind/Cargo.toml @@ -5,10 +5,15 @@ version.workspace = true rust-version.workspace = true license.workspace = true publish = false +links = "unwind" # Tells Cargo this crate links to libunwind (prevents multiple builds) [lib] crate-type = ["lib"] bench = false +[dependencies] +paste = "1.0" + [build-dependencies] cc = "1.0" +bindgen = "0.70" diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 9c6e2a58c0..099935f7e6 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -79,9 +79,52 @@ fn main() { } let lib_path = build_dir.join("src/.libs"); + let include_path = build_dir.join("include"); + let header_path = include_path.join("libunwind.h"); + + // Link directives for this crate println!("cargo:rustc-link-search=native={}", lib_path.display()); println!("cargo:rustc-link-lib=static=unwind"); + // Export paths to dependent crates via DEP_UNWIND_* environment variables + // These are automatically passed to crates that depend on us + println!("cargo:include={}", include_path.display()); + println!("cargo:lib={}", lib_path.display()); + println!("cargo:libdir={}", lib_path.display()); // Alternative name + println!("cargo:root={}", build_dir.display()); + + let source_include_path = libunwind_dir.join("include"); + // Generate Rust bindings from libunwind.h + eprintln!("Generating Rust bindings..."); + let bindings = bindgen::Builder::default() + .header(header_path.to_str().unwrap()) + .clang_arg(format!("-I{}", include_path.display())) + .clang_arg(format!("-I{}", source_include_path.display())) + // Define UNW_LOCAL_ONLY to match how the library was compiled + .clang_arg("-DUNW_LOCAL_ONLY") + // Allow _UL* functions (with UNW_LOCAL_ONLY defined, macros should expand to these) + .allowlist_function("_UL.*") + .allowlist_function("_U.*") + .allowlist_type("unw_.*") + .allowlist_var("UNW_.*") + // Generate layout tests + .layout_tests(true) + // Derive traits + .derive_debug(true) + .derive_default(true) + // Parse callbacks for cargo integration + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + let bindings_path = out_dir.join("bindings.rs"); + bindings + .write_to_file(&bindings_path) + .expect("Couldn't write bindings!"); + + eprintln!("Bindings generated at {}", bindings_path.display()); + println!("cargo:bindings={}", bindings_path.display()); + // More specific rerun triggers println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index b93cf3ffd9..f51447b5ab 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -1,14 +1,193 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(dead_code)] + +//! Rust bindings to libunwind +//! +//! This crate provides raw FFI bindings to libunwind for stack unwinding on Linux. +//! The bindings are automatically generated using bindgen from libunwind.h. +//! +//! # Usage in other crates +//! +//! Add this to your Cargo.toml: +//! ```toml +//! [dependencies] +//! libdd-libunwind = { path = "../libdd-libunwind" } +//! ``` +//! +//! ## Using from Rust +//! +//! ```rust,ignore +//! use libdd_libunwind::*; +//! +//! fn capture_backtrace() -> Vec { +//! let mut frames = Vec::new(); +//! +//! unsafe { +//! let mut context: unw_context_t = std::mem::zeroed(); +//! let mut cursor: unw_cursor_t = std::mem::zeroed(); +//! +//! if unw_getcontext(&mut context) != 0 { +//! return frames; +//! } +//! +//! if unw_init_local(&mut cursor, &mut context) != 0 { +//! return frames; +//! } +//! +//! loop { +//! let mut ip = 0; +//! if unw_get_reg(&mut cursor, UNW_REG_IP as i32, &mut ip) == 0 { +//! frames.push(ip as usize); +//! } +//! +//! if unw_step(&mut cursor) <= 0 { +//! break; +//! } +//! } +//! } +//! +//! frames +//! } +//! ``` +//! +//! ## Using from C code via build.rs +//! +//! ```rust,ignore +//! fn main() { +//! // Get paths exported by libdd-libunwind +//! if let Ok(include_path) = std::env::var("DEP_UNWIND_INCLUDE") { +//! println!("cargo:include={}", include_path); +//! cc::Build::new() +//! .include(&include_path) +//! .file("src/my_code.c") +//! .compile("my_code"); +//! } +//! } +//! ``` + +// Include the automatically generated bindings +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + + +// Error codes (returned as negative values) +pub const UNW_ESUCCESS: i32 = 0; // no error +pub const UNW_EUNSPEC: i32 = 1; // unspecified (general) error +pub const UNW_ENOMEM: i32 = 2; // out of memory +pub const UNW_EBADREG: i32 = 3; // bad register number +pub const UNW_EREADONLYREG: i32 = 4; // attempt to write read-only register +pub const UNW_ESTOPUNWIND: i32 = 5; // stop unwinding +pub const UNW_EINVALIDIP: i32 = 6; // invalid IP +pub const UNW_EBADFRAME: i32 = 7; // bad frame +pub const UNW_EINVAL: i32 = 8; // unsupported operation or bad value +pub const UNW_EBADVERSION: i32 = 9; // unwind info has unsupported version +pub const UNW_ENOINFO: i32 = 10; // no unwind info found + +// Register numbers (architecture-specific) + +#[cfg(target_arch = "x86_64")] +pub const UNW_X86_64_RIP: i32 = 16; // Instruction pointer +#[cfg(target_arch = "x86_64")] +pub const UNW_X86_64_RSP: i32 = 7; // Stack pointer +#[cfg(target_arch = "x86_64")] +pub const UNW_REG_IP: i32 = UNW_X86_64_RIP; // Alias for IP +#[cfg(target_arch = "x86_64")] +pub const UNW_REG_SP: i32 = UNW_X86_64_RSP; // Alias for SP + +// Add other architectures as needed +#[cfg(target_arch = "x86")] +pub const UNW_REG_IP: i32 = 8; // EIP on x86 +#[cfg(target_arch = "x86")] +pub const UNW_REG_SP: i32 = 4; // ESP on x86 + +#[cfg(target_arch = "aarch64")] +pub const UNW_REG_IP: i32 = 32; // PC on ARM64 +#[cfg(target_arch = "aarch64")] +pub const UNW_REG_SP: i32 = 31; // SP on ARM64 + +// Helper function to convert error code to string +pub fn error_string(err: i32) -> &'static str { + match err { + 0 => "UNW_ESUCCESS: no error", + -1 => "UNW_EUNSPEC: unspecified (general) error", + -2 => "UNW_ENOMEM: out of memory", + -3 => "UNW_EBADREG: bad register number", + -4 => "UNW_EREADONLYREG: attempt to write read-only register", + -5 => "UNW_ESTOPUNWIND: stop unwinding", + -6 => "UNW_EINVALIDIP: invalid IP", + -7 => "UNW_EBADFRAME: bad frame", + -8 => "UNW_EINVAL: unsupported operation or bad value", + -9 => "UNW_EBADVERSION: unwind info has unsupported version", + -10 => "UNW_ENOINFO: no unwind info found", + _ => "Unknown error code", + } } +// Create architecture-neutral aliases to standard unw_* names +// Each architecture uses different prefixes for the actual symbols + +// Architecture-specific function aliases (generated via macro) +include!("lib_aliases.rs"); + #[cfg(test)] mod tests { use super::*; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn test_basic_unwind() { + unsafe { + let mut context: unw_context_t = std::mem::zeroed(); + let mut cursor: unw_cursor_t = std::mem::zeroed(); + + // Get current context + let ret = unw_getcontext(&mut context); + assert_eq!(ret, 0, "unw_getcontext failed"); + + // Initialize cursor + let ret = unw_init_local(&mut cursor, &mut context); + assert_eq!(ret, 0, "unw_init_local failed"); + + // Walk the stack + let mut frames = 0; + loop { + let ret = unw_step(&mut cursor); + if ret <= 0 { + break; + } + frames += 1; + + // Limit iterations to prevent infinite loops + if frames > 100 { + break; + } + } + + // Should have at least a few frames + assert!(frames > 0, "Expected at least one stack frame"); + } + } + + #[test] + fn test_get_register() { + unsafe { + let mut context: unw_context_t = std::mem::zeroed(); + let mut cursor: unw_cursor_t = std::mem::zeroed(); + + assert_eq!(unw_getcontext(&mut context), 0); + assert_eq!(unw_init_local(&mut cursor, &mut context), 0); + + // Get instruction pointer + let mut ip: unw_word_t = 0; + let ret = unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip); + assert_eq!(ret, 0, "Failed to get IP register"); + assert_ne!(ip, 0, "IP should not be zero"); + + // Get stack pointer + let mut sp: unw_word_t = 0; + let ret = unw_get_reg(&mut cursor, UNW_REG_SP, &mut sp); + assert_eq!(ret, 0, "Failed to get SP register"); + assert_ne!(sp, 0, "SP should not be zero"); + } } } diff --git a/libdd-libunwind/src/lib_aliases.rs b/libdd-libunwind/src/lib_aliases.rs new file mode 100644 index 0000000000..3d8bbdb807 --- /dev/null +++ b/libdd-libunwind/src/lib_aliases.rs @@ -0,0 +1,37 @@ +// Macro to generate architecture-specific function aliases +// Usage: arch_aliases!(x86_64) generates aliases for x86_64 architecture +macro_rules! arch_aliases { + ($arch:ident) => { + ::paste::paste! { + pub use { + // Functions without "L" prefix (remote/generic functions) + [<_U $arch _getcontext>] as unw_getcontext, + [<_U $arch _strerror>] as unw_strerror, + [<_U $arch _regname>] as unw_regname, + + // Functions with "L" prefix (local unwinding functions) + [<_UL $arch _init_local>] as unw_init_local, + [<_UL $arch _step>] as unw_step, + [<_UL $arch _get_reg>] as unw_get_reg, + [<_UL $arch _set_reg>] as unw_set_reg, + [<_UL $arch _get_proc_name>] as unw_get_proc_name, + [<_UL $arch _resume>] as unw_resume, + [<_UL $arch _is_signal_frame>] as unw_is_signal_frame, + [<_UL $arch _get_proc_info>] as unw_get_proc_info, + }; + } + }; +} + +// Create architecture-neutral aliases to standard unw_* names +// To add a new architecture, just add: arch_aliases!(new_arch); + +#[cfg(target_arch = "x86_64")] +arch_aliases!(x86_64); + +#[cfg(target_arch = "aarch64")] +arch_aliases!(aarch64); + +// Add more architectures as needed: +// #[cfg(target_arch = "arm")] +// arch_aliases!(arm); From 8c368746a761b928a061c68e37dbb67526c04826 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Feb 2026 23:24:11 +0100 Subject: [PATCH 04/21] Try fixing CI --- libdd-libunwind/build.rs | 21 +++++++++++++++++---- tools/docker/Dockerfile.build | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 099935f7e6..8480ed6aa5 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -96,7 +96,7 @@ fn main() { let source_include_path = libunwind_dir.join("include"); // Generate Rust bindings from libunwind.h eprintln!("Generating Rust bindings..."); - let bindings = bindgen::Builder::default() + let mut builder = bindgen::Builder::default() .header(header_path.to_str().unwrap()) .clang_arg(format!("-I{}", include_path.display())) .clang_arg(format!("-I{}", source_include_path.display())) @@ -113,9 +113,22 @@ fn main() { .derive_debug(true) .derive_default(true) // Parse callbacks for cargo integration - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .generate() - .expect("Unable to generate bindings"); + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); + + // Help clang find system headers in CI/Docker environments + if let Ok(sysroot) = std::process::Command::new("gcc") + .arg("-print-sysroot") + .output() + { + if sysroot.status.success() { + let sysroot = String::from_utf8_lossy(&sysroot.stdout).trim().to_string(); + if !sysroot.is_empty() { + builder = builder.clang_arg(format!("--sysroot={}", sysroot)); + } + } + } + + let bindings = builder.generate().expect("Unable to generate bindings"); let bindings_path = out_dir.join("bindings.rs"); bindings diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index 208a3b627d..0db2069d44 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -80,6 +80,7 @@ COPY "libdd-ddsketch/Cargo.toml" "libdd-ddsketch/" COPY "libdd-ddsketch-ffi/Cargo.toml" "libdd-ddsketch-ffi/" COPY "libdd-log/Cargo.toml" "libdd-log/" COPY "libdd-log-ffi/Cargo.toml" "libdd-log-ffi/" +COPY "libdd-libunwind/Cargo.toml" "libdd-libunwind/" COPY "libdd-dogstatsd-client/Cargo.toml" "libdd-dogstatsd-client/" COPY "libdd-library-config-ffi/Cargo.toml" "libdd-library-config-ffi/" COPY "libdd-library-config/Cargo.toml" "libdd-library-config/" From adb72ceab37ddbf639b343df522f5103acd0e6e2 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 09:52:33 +0100 Subject: [PATCH 05/21] Clean up a bit --- Cargo.lock | 26 +--- libdd-libunwind/Cargo.toml | 2 +- libdd-libunwind/build.rs | 46 +------ libdd-libunwind/src/lib.rs | 188 +++++++++++++++++++++++++++-- libdd-libunwind/src/lib_aliases.rs | 37 ------ 5 files changed, 185 insertions(+), 114 deletions(-) delete mode 100644 libdd-libunwind/src/lib_aliases.rs diff --git a/Cargo.lock b/Cargo.lock index 182bbaf65a..397d2039d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,7 +380,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9c2e952a1f57e8cbc78b058a968639e70c4ce8b9c0a5e6363d4e5670eed795" dependencies = [ - "bindgen 0.69.5", + "bindgen", "cc", "cmake", "dunce", @@ -405,7 +405,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" dependencies = [ - "bindgen 0.69.5", + "bindgen", "cc", "cmake", "dunce", @@ -535,26 +535,6 @@ dependencies = [ "which", ] -[[package]] -name = "bindgen" -version = "0.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "itertools 0.11.0", - "log 0.4.27", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.87", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -3211,8 +3191,8 @@ dependencies = [ name = "libdd-libunwind" version = "26.0.0" dependencies = [ - "bindgen 0.70.1", "cc", + "libc", "paste", ] diff --git a/libdd-libunwind/Cargo.toml b/libdd-libunwind/Cargo.toml index 19c1d4a939..02bb3f6ca7 100644 --- a/libdd-libunwind/Cargo.toml +++ b/libdd-libunwind/Cargo.toml @@ -13,7 +13,7 @@ bench = false [dependencies] paste = "1.0" +libc = "0.2" [build-dependencies] cc = "1.0" -bindgen = "0.70" diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 8480ed6aa5..4fd658cff1 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -80,7 +80,6 @@ fn main() { let lib_path = build_dir.join("src/.libs"); let include_path = build_dir.join("include"); - let header_path = include_path.join("libunwind.h"); // Link directives for this crate println!("cargo:rustc-link-search=native={}", lib_path.display()); @@ -93,50 +92,7 @@ fn main() { println!("cargo:libdir={}", lib_path.display()); // Alternative name println!("cargo:root={}", build_dir.display()); - let source_include_path = libunwind_dir.join("include"); - // Generate Rust bindings from libunwind.h - eprintln!("Generating Rust bindings..."); - let mut builder = bindgen::Builder::default() - .header(header_path.to_str().unwrap()) - .clang_arg(format!("-I{}", include_path.display())) - .clang_arg(format!("-I{}", source_include_path.display())) - // Define UNW_LOCAL_ONLY to match how the library was compiled - .clang_arg("-DUNW_LOCAL_ONLY") - // Allow _UL* functions (with UNW_LOCAL_ONLY defined, macros should expand to these) - .allowlist_function("_UL.*") - .allowlist_function("_U.*") - .allowlist_type("unw_.*") - .allowlist_var("UNW_.*") - // Generate layout tests - .layout_tests(true) - // Derive traits - .derive_debug(true) - .derive_default(true) - // Parse callbacks for cargo integration - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); - - // Help clang find system headers in CI/Docker environments - if let Ok(sysroot) = std::process::Command::new("gcc") - .arg("-print-sysroot") - .output() - { - if sysroot.status.success() { - let sysroot = String::from_utf8_lossy(&sysroot.stdout).trim().to_string(); - if !sysroot.is_empty() { - builder = builder.clang_arg(format!("--sysroot={}", sysroot)); - } - } - } - - let bindings = builder.generate().expect("Unable to generate bindings"); - - let bindings_path = out_dir.join("bindings.rs"); - bindings - .write_to_file(&bindings_path) - .expect("Couldn't write bindings!"); - - eprintln!("Bindings generated at {}", bindings_path.display()); - println!("cargo:bindings={}", bindings_path.display()); + eprintln!("libunwind library ready at {}", lib_path.display()); // More specific rerun triggers println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index f51447b5ab..a4857589ad 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -6,7 +6,7 @@ //! Rust bindings to libunwind //! //! This crate provides raw FFI bindings to libunwind for stack unwinding on Linux. -//! The bindings are automatically generated using bindgen from libunwind.h. +//! The bindings are manually defined to avoid bindgen dependencies. //! //! # Usage in other crates //! @@ -38,7 +38,7 @@ //! //! loop { //! let mut ip = 0; -//! if unw_get_reg(&mut cursor, UNW_REG_IP as i32, &mut ip) == 0 { +//! if unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip) == 0 { //! frames.push(ip as usize); //! } //! @@ -67,9 +67,141 @@ //! } //! ``` -// Include the automatically generated bindings -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +// Manual type definitions - no bindgen needed! +// These are the essential types from libunwind headers +// ============================================================================ +// Basic types +// ============================================================================ + +pub type unw_word_t = u64; +pub type unw_sword_t = i64; + +// Architecture-specific cursor size +#[cfg(target_arch = "x86_64")] +pub const UNW_TDEP_CURSOR_LEN: usize = 127; + +#[cfg(target_arch = "aarch64")] +pub const UNW_TDEP_CURSOR_LEN: usize = 4096; // ARM64 cursor size + +#[cfg(target_arch = "x86")] +pub const UNW_TDEP_CURSOR_LEN: usize = 127; + +// Opaque cursor structure +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_cursor { + pub opaque: [unw_word_t; UNW_TDEP_CURSOR_LEN], +} + +pub type unw_cursor_t = unw_cursor; + +impl Default for unw_cursor { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +// Context is platform ucontext_t (from libc) +pub type unw_context_t = libc::ucontext_t; + +// Floating point register type +pub type unw_fpreg_t = u128; + +// Address space (opaque pointer) +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_addr_space { + _unused: [u8; 0], +} + +pub type unw_addr_space_t = *mut unw_addr_space; + +// ============================================================================ +// Procedure info structures +// ============================================================================ + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct unw_tdep_proc_info_t { + pub unused: u8, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_proc_info { + pub start_ip: unw_word_t, + pub end_ip: unw_word_t, + pub lsda: unw_word_t, + pub handler: unw_word_t, + pub gp: unw_word_t, + pub flags: unw_word_t, + pub format: ::std::os::raw::c_int, + pub unwind_info_size: ::std::os::raw::c_int, + pub unwind_info: *mut ::std::os::raw::c_void, + pub extra: unw_tdep_proc_info_t, +} + +impl Default for unw_proc_info { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +pub type unw_proc_info_t = unw_proc_info; + +// Save location structure +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_save_loc { + pub type_: ::std::os::raw::c_int, + pub extra: unw_word_t, +} + +impl Default for unw_save_loc { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +pub type unw_save_loc_t = unw_save_loc; + +// ============================================================================ +// Accessors and callbacks +// ============================================================================ + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_accessors_t { + _unused: [u8; 0], +} + +pub type unw_reg_states_callback = ::std::option::Option< + unsafe extern "C" fn( + token: *mut ::std::os::raw::c_void, + reg_states_data: *mut ::std::os::raw::c_void, + reg_states_data_size: usize, + arg: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, +>; + +pub type unw_iterate_phdr_callback_t = ::std::option::Option< + unsafe extern "C" fn( + info: *mut ::std::os::raw::c_void, + size: usize, + arg: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, +>; + +// ============================================================================ +// Constants and enums +// ============================================================================ + +// Caching policy enum +pub type unw_caching_policy_t = ::std::os::raw::c_uint; +pub const UNW_CACHE_NONE: unw_caching_policy_t = 0; +pub const UNW_CACHE_GLOBAL: unw_caching_policy_t = 1; +pub const UNW_CACHE_PER_THREAD: unw_caching_policy_t = 2; // Error codes (returned as negative values) pub const UNW_ESUCCESS: i32 = 0; // no error @@ -124,11 +256,51 @@ pub fn error_string(err: i32) -> &'static str { } } -// Create architecture-neutral aliases to standard unw_* names -// Each architecture uses different prefixes for the actual symbols +// ============================================================================ +// External function declarations and aliases +// ============================================================================ +// +// libunwind uses architecture-specific function names like _ULx86_64_init_local. +// This macro both declares the extern function AND creates a standard unw_* alias. + +macro_rules! unw_functions { + ($arch:ident) => { + paste::paste! { + extern "C" { + // Generic functions (no "L" prefix in arch name) + pub fn [<_ U $arch _getcontext>](context: *mut unw_context_t) -> ::std::os::raw::c_int; + pub fn [<_ U $arch _strerror>](err: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; + pub fn [<_ U $arch _regname>](reg: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; + + // Local unwinding functions ("L" in arch prefix) + pub fn [<_ UL $arch _init_local>](cursor: *mut unw_cursor_t, context: *mut unw_context_t) -> ::std::os::raw::c_int; + pub fn [<_ UL $arch _step>](cursor: *mut unw_cursor_t) -> ::std::os::raw::c_int; + pub fn [<_ UL $arch _get_reg>](cursor: *mut unw_cursor_t, reg: ::std::os::raw::c_int, valp: *mut unw_word_t) -> ::std::os::raw::c_int; + pub fn [<_ UL $arch _get_proc_name>](cursor: *mut unw_cursor_t, buffer: *mut ::std::os::raw::c_char, len: usize, offset: *mut unw_word_t) -> ::std::os::raw::c_int; + pub fn [<_ UL $arch _get_proc_info>](cursor: *mut unw_cursor_t, pip: *mut unw_proc_info_t) -> ::std::os::raw::c_int; + } + + // Create public aliases with standard unw_* names + pub use { + [<_ U $arch _getcontext>] as unw_getcontext, + [<_ U $arch _strerror>] as unw_strerror, + [<_ U $arch _regname>] as unw_regname, + [<_ UL $arch _init_local>] as unw_init_local, + [<_ UL $arch _step>] as unw_step, + [<_ UL $arch _get_reg>] as unw_get_reg, + [<_ UL $arch _get_proc_name>] as unw_get_proc_name, + [<_ UL $arch _get_proc_info>] as unw_get_proc_info, + }; + } + }; +} + +// Invoke for each supported architecture +#[cfg(target_arch = "x86_64")] +unw_functions!(x86_64); -// Architecture-specific function aliases (generated via macro) -include!("lib_aliases.rs"); +#[cfg(target_arch = "aarch64")] +unw_functions!(aarch64); #[cfg(test)] mod tests { diff --git a/libdd-libunwind/src/lib_aliases.rs b/libdd-libunwind/src/lib_aliases.rs deleted file mode 100644 index 3d8bbdb807..0000000000 --- a/libdd-libunwind/src/lib_aliases.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Macro to generate architecture-specific function aliases -// Usage: arch_aliases!(x86_64) generates aliases for x86_64 architecture -macro_rules! arch_aliases { - ($arch:ident) => { - ::paste::paste! { - pub use { - // Functions without "L" prefix (remote/generic functions) - [<_U $arch _getcontext>] as unw_getcontext, - [<_U $arch _strerror>] as unw_strerror, - [<_U $arch _regname>] as unw_regname, - - // Functions with "L" prefix (local unwinding functions) - [<_UL $arch _init_local>] as unw_init_local, - [<_UL $arch _step>] as unw_step, - [<_UL $arch _get_reg>] as unw_get_reg, - [<_UL $arch _set_reg>] as unw_set_reg, - [<_UL $arch _get_proc_name>] as unw_get_proc_name, - [<_UL $arch _resume>] as unw_resume, - [<_UL $arch _is_signal_frame>] as unw_is_signal_frame, - [<_UL $arch _get_proc_info>] as unw_get_proc_info, - }; - } - }; -} - -// Create architecture-neutral aliases to standard unw_* names -// To add a new architecture, just add: arch_aliases!(new_arch); - -#[cfg(target_arch = "x86_64")] -arch_aliases!(x86_64); - -#[cfg(target_arch = "aarch64")] -arch_aliases!(aarch64); - -// Add more architectures as needed: -// #[cfg(target_arch = "arm")] -// arch_aliases!(arm); From e45052a2bb1d432587515535a1c4c0b4c6c2868a Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 09:59:57 +0100 Subject: [PATCH 06/21] Add unw_backtrace2 --- libdd-libunwind/src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index a4857589ad..d6a2cd42eb 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -274,6 +274,7 @@ macro_rules! unw_functions { // Local unwinding functions ("L" in arch prefix) pub fn [<_ UL $arch _init_local>](cursor: *mut unw_cursor_t, context: *mut unw_context_t) -> ::std::os::raw::c_int; + pub fn [<_ UL $arch _init_local2>](cursor: *mut unw_cursor_t, context: *mut unw_context_t, flag: ::std::os::raw::c_int) -> ::std::os::raw::c_int; pub fn [<_ UL $arch _step>](cursor: *mut unw_cursor_t) -> ::std::os::raw::c_int; pub fn [<_ UL $arch _get_reg>](cursor: *mut unw_cursor_t, reg: ::std::os::raw::c_int, valp: *mut unw_word_t) -> ::std::os::raw::c_int; pub fn [<_ UL $arch _get_proc_name>](cursor: *mut unw_cursor_t, buffer: *mut ::std::os::raw::c_char, len: usize, offset: *mut unw_word_t) -> ::std::os::raw::c_int; @@ -286,6 +287,7 @@ macro_rules! unw_functions { [<_ U $arch _strerror>] as unw_strerror, [<_ U $arch _regname>] as unw_regname, [<_ UL $arch _init_local>] as unw_init_local, + [<_ UL $arch _init_local2>] as unw_init_local2, [<_ UL $arch _step>] as unw_step, [<_ UL $arch _get_reg>] as unw_get_reg, [<_ UL $arch _get_proc_name>] as unw_get_proc_name, @@ -295,6 +297,10 @@ macro_rules! unw_functions { }; } +extern "C" { + pub fn unw_backtrace2(frames: *mut *mut ::std::os::raw::c_void, max_frames: ::std::os::raw::c_int, context: *mut unw_context_t, inner_frame_enum: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} + // Invoke for each supported architecture #[cfg(target_arch = "x86_64")] unw_functions!(x86_64); @@ -362,4 +368,28 @@ mod tests { assert_ne!(sp, 0, "SP should not be zero"); } } + + #[test] + fn test_backtrace2() { + unsafe { + let mut context: unw_context_t = std::mem::zeroed(); + assert_eq!(unw_getcontext(&mut context), 0); + + // unw_backtrace2 expects an array of void pointers + let mut frames: [*mut ::std::os::raw::c_void; 100] = [std::ptr::null_mut(); 100]; + let ret = unw_backtrace2(frames.as_mut_ptr(), 100, &mut context, 0); + + // Return value should be >= 0 (number of frames captured) + assert!(ret >= 0, "unw_backtrace2 failed with error: {}", ret); + + let frame_count = ret as usize; + assert!(frame_count > 0, "Expected at least one frame"); + + // Print captured frames + for i in 0..frame_count { + let frame_ptr = frames[i] as usize; + println!("Frame {}: 0x{:016x}", i, frame_ptr); + } + } + } } From 16f10075eb5b516e4092a31d8f675397dc725739 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 10:01:22 +0100 Subject: [PATCH 07/21] Fix formatting --- libdd-libunwind/build.rs | 27 ++++++----- libdd-libunwind/src/lib.rs | 95 ++++++++++++++++++++------------------ 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 4fd658cff1..389b05376c 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -31,11 +31,11 @@ fn main() { std::fs::create_dir_all(&build_dir).unwrap(); let lib_file = build_dir.join("src/.libs/libunwind.a"); - + // Only build if library doesn't exist if !lib_file.exists() { eprintln!("Building libunwind from source..."); - + // Only run autoreconf if configure doesn't exist let configure_script = libunwind_dir.join("configure"); if !configure_script.exists() { @@ -44,8 +44,10 @@ fn main() { .current_dir(&libunwind_dir) .args(["-c", "autoreconf -i"]) .status() - .expect("Failed to run autoreconf. Install with: apt install autoconf automake libtool"); - + .expect( + "Failed to run autoreconf. Install with: apt install autoconf automake libtool", + ); + if !status.success() { panic!("autoreconf failed with exit code: {:?}", status.code()); } @@ -67,12 +69,15 @@ fn main() { if !status.success() { panic!("libunwind build failed with exit code: {:?}", status.code()); } - + // Verify the library was actually created if !lib_file.exists() { - panic!("libunwind.a was not created at expected location: {}", lib_file.display()); + panic!( + "libunwind.a was not created at expected location: {}", + lib_file.display() + ); } - + eprintln!("libunwind built successfully at {}", lib_file.display()); } else { eprintln!("Using cached libunwind build"); @@ -80,20 +85,20 @@ fn main() { let lib_path = build_dir.join("src/.libs"); let include_path = build_dir.join("include"); - + // Link directives for this crate println!("cargo:rustc-link-search=native={}", lib_path.display()); println!("cargo:rustc-link-lib=static=unwind"); - + // Export paths to dependent crates via DEP_UNWIND_* environment variables // These are automatically passed to crates that depend on us println!("cargo:include={}", include_path.display()); println!("cargo:lib={}", lib_path.display()); println!("cargo:libdir={}", lib_path.display()); // Alternative name println!("cargo:root={}", build_dir.display()); - + eprintln!("libunwind library ready at {}", lib_path.display()); - + // More specific rerun triggers println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index d6a2cd42eb..aa7ed8dc81 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -4,23 +4,23 @@ #![allow(dead_code)] //! Rust bindings to libunwind -//! +//! //! This crate provides raw FFI bindings to libunwind for stack unwinding on Linux. //! The bindings are manually defined to avoid bindgen dependencies. -//! +//! //! # Usage in other crates -//! +//! //! Add this to your Cargo.toml: //! ```toml //! [dependencies] //! libdd-libunwind = { path = "../libdd-libunwind" } //! ``` -//! +//! //! ## Using from Rust -//! +//! //! ```rust,ignore //! use libdd_libunwind::*; -//! +//! //! fn capture_backtrace() -> Vec { //! let mut frames = Vec::new(); //! @@ -51,9 +51,9 @@ //! frames //! } //! ``` -//! +//! //! ## Using from C code via build.rs -//! +//! //! ```rust,ignore //! fn main() { //! // Get paths exported by libdd-libunwind @@ -82,7 +82,7 @@ pub type unw_sword_t = i64; pub const UNW_TDEP_CURSOR_LEN: usize = 127; #[cfg(target_arch = "aarch64")] -pub const UNW_TDEP_CURSOR_LEN: usize = 4096; // ARM64 cursor size +pub const UNW_TDEP_CURSOR_LEN: usize = 4096; // ARM64 cursor size #[cfg(target_arch = "x86")] pub const UNW_TDEP_CURSOR_LEN: usize = 127; @@ -204,39 +204,39 @@ pub const UNW_CACHE_GLOBAL: unw_caching_policy_t = 1; pub const UNW_CACHE_PER_THREAD: unw_caching_policy_t = 2; // Error codes (returned as negative values) -pub const UNW_ESUCCESS: i32 = 0; // no error -pub const UNW_EUNSPEC: i32 = 1; // unspecified (general) error -pub const UNW_ENOMEM: i32 = 2; // out of memory -pub const UNW_EBADREG: i32 = 3; // bad register number -pub const UNW_EREADONLYREG: i32 = 4; // attempt to write read-only register -pub const UNW_ESTOPUNWIND: i32 = 5; // stop unwinding -pub const UNW_EINVALIDIP: i32 = 6; // invalid IP -pub const UNW_EBADFRAME: i32 = 7; // bad frame -pub const UNW_EINVAL: i32 = 8; // unsupported operation or bad value -pub const UNW_EBADVERSION: i32 = 9; // unwind info has unsupported version -pub const UNW_ENOINFO: i32 = 10; // no unwind info found +pub const UNW_ESUCCESS: i32 = 0; // no error +pub const UNW_EUNSPEC: i32 = 1; // unspecified (general) error +pub const UNW_ENOMEM: i32 = 2; // out of memory +pub const UNW_EBADREG: i32 = 3; // bad register number +pub const UNW_EREADONLYREG: i32 = 4; // attempt to write read-only register +pub const UNW_ESTOPUNWIND: i32 = 5; // stop unwinding +pub const UNW_EINVALIDIP: i32 = 6; // invalid IP +pub const UNW_EBADFRAME: i32 = 7; // bad frame +pub const UNW_EINVAL: i32 = 8; // unsupported operation or bad value +pub const UNW_EBADVERSION: i32 = 9; // unwind info has unsupported version +pub const UNW_ENOINFO: i32 = 10; // no unwind info found // Register numbers (architecture-specific) #[cfg(target_arch = "x86_64")] -pub const UNW_X86_64_RIP: i32 = 16; // Instruction pointer +pub const UNW_X86_64_RIP: i32 = 16; // Instruction pointer #[cfg(target_arch = "x86_64")] -pub const UNW_X86_64_RSP: i32 = 7; // Stack pointer +pub const UNW_X86_64_RSP: i32 = 7; // Stack pointer #[cfg(target_arch = "x86_64")] -pub const UNW_REG_IP: i32 = UNW_X86_64_RIP; // Alias for IP +pub const UNW_REG_IP: i32 = UNW_X86_64_RIP; // Alias for IP #[cfg(target_arch = "x86_64")] -pub const UNW_REG_SP: i32 = UNW_X86_64_RSP; // Alias for SP +pub const UNW_REG_SP: i32 = UNW_X86_64_RSP; // Alias for SP // Add other architectures as needed #[cfg(target_arch = "x86")] -pub const UNW_REG_IP: i32 = 8; // EIP on x86 +pub const UNW_REG_IP: i32 = 8; // EIP on x86 #[cfg(target_arch = "x86")] -pub const UNW_REG_SP: i32 = 4; // ESP on x86 +pub const UNW_REG_SP: i32 = 4; // ESP on x86 #[cfg(target_arch = "aarch64")] -pub const UNW_REG_IP: i32 = 32; // PC on ARM64 +pub const UNW_REG_IP: i32 = 32; // PC on ARM64 #[cfg(target_arch = "aarch64")] -pub const UNW_REG_SP: i32 = 31; // SP on ARM64 +pub const UNW_REG_SP: i32 = 31; // SP on ARM64 // Helper function to convert error code to string pub fn error_string(err: i32) -> &'static str { @@ -259,7 +259,7 @@ pub fn error_string(err: i32) -> &'static str { // ============================================================================ // External function declarations and aliases // ============================================================================ -// +// // libunwind uses architecture-specific function names like _ULx86_64_init_local. // This macro both declares the extern function AND creates a standard unw_* alias. @@ -271,7 +271,7 @@ macro_rules! unw_functions { pub fn [<_ U $arch _getcontext>](context: *mut unw_context_t) -> ::std::os::raw::c_int; pub fn [<_ U $arch _strerror>](err: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; pub fn [<_ U $arch _regname>](reg: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; - + // Local unwinding functions ("L" in arch prefix) pub fn [<_ UL $arch _init_local>](cursor: *mut unw_cursor_t, context: *mut unw_context_t) -> ::std::os::raw::c_int; pub fn [<_ UL $arch _init_local2>](cursor: *mut unw_cursor_t, context: *mut unw_context_t, flag: ::std::os::raw::c_int) -> ::std::os::raw::c_int; @@ -280,7 +280,7 @@ macro_rules! unw_functions { pub fn [<_ UL $arch _get_proc_name>](cursor: *mut unw_cursor_t, buffer: *mut ::std::os::raw::c_char, len: usize, offset: *mut unw_word_t) -> ::std::os::raw::c_int; pub fn [<_ UL $arch _get_proc_info>](cursor: *mut unw_cursor_t, pip: *mut unw_proc_info_t) -> ::std::os::raw::c_int; } - + // Create public aliases with standard unw_* names pub use { [<_ U $arch _getcontext>] as unw_getcontext, @@ -298,7 +298,12 @@ macro_rules! unw_functions { } extern "C" { - pub fn unw_backtrace2(frames: *mut *mut ::std::os::raw::c_void, max_frames: ::std::os::raw::c_int, context: *mut unw_context_t, inner_frame_enum: ::std::os::raw::c_int) -> ::std::os::raw::c_int; + pub fn unw_backtrace2( + frames: *mut *mut ::std::os::raw::c_void, + max_frames: ::std::os::raw::c_int, + context: *mut unw_context_t, + inner_frame_enum: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; } // Invoke for each supported architecture @@ -317,15 +322,15 @@ mod tests { unsafe { let mut context: unw_context_t = std::mem::zeroed(); let mut cursor: unw_cursor_t = std::mem::zeroed(); - + // Get current context let ret = unw_getcontext(&mut context); assert_eq!(ret, 0, "unw_getcontext failed"); - + // Initialize cursor let ret = unw_init_local(&mut cursor, &mut context); assert_eq!(ret, 0, "unw_init_local failed"); - + // Walk the stack let mut frames = 0; loop { @@ -334,33 +339,33 @@ mod tests { break; } frames += 1; - + // Limit iterations to prevent infinite loops if frames > 100 { break; } } - + // Should have at least a few frames assert!(frames > 0, "Expected at least one stack frame"); } } - + #[test] fn test_get_register() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); let mut cursor: unw_cursor_t = std::mem::zeroed(); - + assert_eq!(unw_getcontext(&mut context), 0); assert_eq!(unw_init_local(&mut cursor, &mut context), 0); - + // Get instruction pointer let mut ip: unw_word_t = 0; let ret = unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip); assert_eq!(ret, 0, "Failed to get IP register"); assert_ne!(ip, 0, "IP should not be zero"); - + // Get stack pointer let mut sp: unw_word_t = 0; let ret = unw_get_reg(&mut cursor, UNW_REG_SP, &mut sp); @@ -374,17 +379,17 @@ mod tests { unsafe { let mut context: unw_context_t = std::mem::zeroed(); assert_eq!(unw_getcontext(&mut context), 0); - + // unw_backtrace2 expects an array of void pointers let mut frames: [*mut ::std::os::raw::c_void; 100] = [std::ptr::null_mut(); 100]; let ret = unw_backtrace2(frames.as_mut_ptr(), 100, &mut context, 0); - + // Return value should be >= 0 (number of frames captured) assert!(ret >= 0, "unw_backtrace2 failed with error: {}", ret); - + let frame_count = ret as usize; assert!(frame_count > 0, "Expected at least one frame"); - + // Print captured frames for i in 0..frame_count { let frame_ptr = frames[i] as usize; From 852f58625492ceccdbc9b7d38aeb9fde8abbd216 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 10:08:38 +0100 Subject: [PATCH 08/21] Fix clippy --- libdd-libunwind/src/lib.rs | 4 ++-- tools/docker/Dockerfile.build | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index aa7ed8dc81..8adfa11685 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -391,8 +391,8 @@ mod tests { assert!(frame_count > 0, "Expected at least one frame"); // Print captured frames - for i in 0..frame_count { - let frame_ptr = frames[i] as usize; + for (i, &frame) in frames.iter().enumerate().take(frame_count) { + let frame_ptr = frame as usize; println!("Frame {}: 0x{:016x}", i, frame_ptr); } } diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index 0db2069d44..74f9167821 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -81,6 +81,7 @@ COPY "libdd-ddsketch-ffi/Cargo.toml" "libdd-ddsketch-ffi/" COPY "libdd-log/Cargo.toml" "libdd-log/" COPY "libdd-log-ffi/Cargo.toml" "libdd-log-ffi/" COPY "libdd-libunwind/Cargo.toml" "libdd-libunwind/" +COPY "libdd-libunwind/build.rs" "libdd-libunwind/" COPY "libdd-dogstatsd-client/Cargo.toml" "libdd-dogstatsd-client/" COPY "libdd-library-config-ffi/Cargo.toml" "libdd-library-config-ffi/" COPY "libdd-library-config/Cargo.toml" "libdd-library-config/" From 995425e7b201f90f6dbf026055008fd5f3cf0bee Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 10:14:04 +0100 Subject: [PATCH 09/21] Fix Miri job --- libdd-libunwind/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index 8adfa11685..f8b533a35e 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -318,6 +318,7 @@ mod tests { use super::*; #[test] + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_basic_unwind() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); @@ -352,6 +353,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_get_register() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); @@ -375,6 +377,7 @@ mod tests { } #[test] + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_backtrace2() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); From 9c7ff56b4edb83e424b3c09bd9b761ce3017335c Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Feb 2026 11:08:21 +0100 Subject: [PATCH 10/21] still fixing CI --- libdd-libunwind/src/lib.rs | 6 +++--- tools/docker/Dockerfile.build | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index f8b533a35e..ff44fd0cdd 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -318,7 +318,7 @@ mod tests { use super::*; #[test] - #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_basic_unwind() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); @@ -353,7 +353,7 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_get_register() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); @@ -377,7 +377,7 @@ mod tests { } #[test] - #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_backtrace2() { unsafe { let mut context: unw_context_t = std::mem::zeroed(); diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index 74f9167821..57fcd3570e 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -33,6 +33,9 @@ RUN apk update \ unzip \ bash \ clang16-libclang \ + autoconf \ + automake \ + libtool \ && mkdir /usr/local/src # Tell docker to use bash as the default @@ -152,6 +155,9 @@ RUN echo \ libdd-trace-utils/benches/main.rs \ | xargs -n 1 sh -c 'mkdir -p $(dirname $1); touch $1; echo $1' create_stubs +# Copy libunwind submodule for libdd-libunwind build.rs +COPY "libdd-libunwind/libunwind" "libdd-libunwind/libunwind/" + # cache dependencies RUN cargo fetch --locked From dfc47d3541075629e27d40d93eccd6827d77e893 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Fri, 6 Feb 2026 12:03:17 +0100 Subject: [PATCH 11/21] Clean things up A LOT: do not rely on bindgen --- libdd-libunwind/build.rs | 179 +++++------ libdd-libunwind/src/lib.rs | 360 +++-------------------- libdd-libunwind/src/libunwind_aarch64.rs | 42 +++ libdd-libunwind/src/libunwind_x86_64.rs | 46 +++ 4 files changed, 226 insertions(+), 401 deletions(-) create mode 100644 libdd-libunwind/src/libunwind_aarch64.rs create mode 100644 libdd-libunwind/src/libunwind_x86_64.rs diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 389b05376c..b2909c9abc 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -1,106 +1,117 @@ -use std::env; -use std::path::PathBuf; - -#[cfg(not(target_os = "linux"))] fn main() { - println!("cargo:warning=non-linux platform is not supported yet"); + #[cfg(target_os = "linux")] + linux::main(); + #[cfg(target_os = "windows")] + windows::main(); } -#[cfg(target_os = "linux")] -fn main() { - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let build_dir = out_dir.join("libunwind_build"); - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); - - // Check if libunwind submodule is initialized - if !libunwind_dir.exists() || std::fs::read_dir(&libunwind_dir).unwrap().next().is_none() { - panic!( - "libunwind submodule not initialized!\n\ - Run: git submodule update --init --recursive\n\ - \n\ - For CI, ensure your workflow checks out submodules:\n\ - - GitHub Actions: add 'submodules: recursive' to actions/checkout\n\ - - GitLab CI: add 'GIT_SUBMODULE_STRATEGY: recursive'\n\ - \n\ - Directory checked: {}", - libunwind_dir.display() - ); +#[cfg(target_os = "windows")] +mod windows { + pub(crate) fn main() { + println!("cargo:warning=windows platform is not supported yet"); } +} - std::fs::create_dir_all(&build_dir).unwrap(); +#[cfg(target_os = "linux")] +mod linux { + use std::env; + use std::path::PathBuf; + + pub(crate) fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let build_dir = out_dir.join("libunwind_build"); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); + + // Check if libunwind submodule is initialized + if !libunwind_dir.exists() || std::fs::read_dir(&libunwind_dir).unwrap().next().is_none() { + panic!( + "libunwind submodule not initialized!\n\ + Run: git submodule update --init --recursive\n\ + \n\ + For CI, ensure your workflow checks out submodules:\n\ + - GitHub Actions: add 'submodules: recursive' to actions/checkout\n\ + - GitLab CI: add 'GIT_SUBMODULE_STRATEGY: recursive'\n\ + \n\ + Directory checked: {}", + libunwind_dir.display() + ); + } + + std::fs::create_dir_all(&build_dir).unwrap(); - let lib_file = build_dir.join("src/.libs/libunwind.a"); + let lib_file = build_dir.join("src/.libs/libunwind.a"); - // Only build if library doesn't exist - if !lib_file.exists() { - eprintln!("Building libunwind from source..."); + // Only build if library doesn't exist + if !lib_file.exists() { + eprintln!("Building libunwind from source..."); + + // Only run autoreconf if configure doesn't exist + let configure_script = libunwind_dir.join("configure"); + if !configure_script.exists() { + eprintln!("Running autoreconf..."); + let status = std::process::Command::new("sh") + .current_dir(&libunwind_dir) + .args(["-c", "autoreconf -i"]) + .status() + .expect( + "Failed to run autoreconf. Install with: apt install autoconf automake libtool", + ); + + if !status.success() { + panic!("autoreconf failed with exit code: {:?}", status.code()); + } + } - // Only run autoreconf if configure doesn't exist - let configure_script = libunwind_dir.join("configure"); - if !configure_script.exists() { - eprintln!("Running autoreconf..."); + eprintln!("Configuring and building libunwind..."); let status = std::process::Command::new("sh") - .current_dir(&libunwind_dir) - .args(["-c", "autoreconf -i"]) + .current_dir(&build_dir) + .args([ + "-c", + &format!( + "{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", + libunwind_dir.display() + ) + ]) .status() - .expect( - "Failed to run autoreconf. Install with: apt install autoconf automake libtool", - ); + .expect("Failed to run configure/make"); if !status.success() { - panic!("autoreconf failed with exit code: {:?}", status.code()); + panic!("libunwind build failed with exit code: {:?}", status.code()); } - } - eprintln!("Configuring and building libunwind..."); - let status = std::process::Command::new("sh") - .current_dir(&build_dir) - .args([ - "-c", - &format!( - "{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", - libunwind_dir.display() - ) - ]) - .status() - .expect("Failed to run configure/make"); - - if !status.success() { - panic!("libunwind build failed with exit code: {:?}", status.code()); - } + // Verify the library was actually created + if !lib_file.exists() { + panic!( + "libunwind.a was not created at expected location: {}", + lib_file.display() + ); + } - // Verify the library was actually created - if !lib_file.exists() { - panic!( - "libunwind.a was not created at expected location: {}", - lib_file.display() - ); + eprintln!("libunwind built successfully at {}", lib_file.display()); + } else { + eprintln!("Using cached libunwind build"); } - eprintln!("libunwind built successfully at {}", lib_file.display()); - } else { - eprintln!("Using cached libunwind build"); - } + let lib_path = build_dir.join("src/.libs"); + let include_path = build_dir.join("include"); - let lib_path = build_dir.join("src/.libs"); - let include_path = build_dir.join("include"); + // Link directives for this crate + println!("cargo:rustc-link-search=native={}", lib_path.display()); + println!("cargo:rustc-link-lib=static=unwind"); - // Link directives for this crate - println!("cargo:rustc-link-search=native={}", lib_path.display()); - println!("cargo:rustc-link-lib=static=unwind"); + // Export paths to dependent crates via DEP_UNWIND_* environment variables + // These are automatically passed to crates that depend on us + println!("cargo:include={}", include_path.display()); + println!("cargo:lib={}", lib_path.display()); + println!("cargo:libdir={}", lib_path.display()); // Alternative name + println!("cargo:root={}", build_dir.display()); - // Export paths to dependent crates via DEP_UNWIND_* environment variables - // These are automatically passed to crates that depend on us - println!("cargo:include={}", include_path.display()); - println!("cargo:lib={}", lib_path.display()); - println!("cargo:libdir={}", lib_path.display()); // Alternative name - println!("cargo:root={}", build_dir.display()); + eprintln!("libunwind library ready at {}", lib_path.display()); - eprintln!("libunwind library ready at {}", lib_path.display()); - - // More specific rerun triggers - println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); - println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); - println!("cargo:rerun-if-changed=build.rs"); + // More specific rerun triggers + println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); + println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); + println!("cargo:rerun-if-changed=build.rs"); + } } diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index ff44fd0cdd..b1c0fc1efd 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -1,317 +1,16 @@ -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(dead_code)] +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 -//! Rust bindings to libunwind -//! -//! This crate provides raw FFI bindings to libunwind for stack unwinding on Linux. -//! The bindings are manually defined to avoid bindgen dependencies. -//! -//! # Usage in other crates -//! -//! Add this to your Cargo.toml: -//! ```toml -//! [dependencies] -//! libdd-libunwind = { path = "../libdd-libunwind" } -//! ``` -//! -//! ## Using from Rust -//! -//! ```rust,ignore -//! use libdd_libunwind::*; -//! -//! fn capture_backtrace() -> Vec { -//! let mut frames = Vec::new(); -//! -//! unsafe { -//! let mut context: unw_context_t = std::mem::zeroed(); -//! let mut cursor: unw_cursor_t = std::mem::zeroed(); -//! -//! if unw_getcontext(&mut context) != 0 { -//! return frames; -//! } -//! -//! if unw_init_local(&mut cursor, &mut context) != 0 { -//! return frames; -//! } -//! -//! loop { -//! let mut ip = 0; -//! if unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip) == 0 { -//! frames.push(ip as usize); -//! } -//! -//! if unw_step(&mut cursor) <= 0 { -//! break; -//! } -//! } -//! } -//! -//! frames -//! } -//! ``` -//! -//! ## Using from C code via build.rs -//! -//! ```rust,ignore -//! fn main() { -//! // Get paths exported by libdd-libunwind -//! if let Ok(include_path) = std::env::var("DEP_UNWIND_INCLUDE") { -//! println!("cargo:include={}", include_path); -//! cc::Build::new() -//! .include(&include_path) -//! .file("src/my_code.c") -//! .compile("my_code"); -//! } -//! } -//! ``` - -// Manual type definitions - no bindgen needed! -// These are the essential types from libunwind headers - -// ============================================================================ -// Basic types -// ============================================================================ - -pub type unw_word_t = u64; -pub type unw_sword_t = i64; - -// Architecture-specific cursor size #[cfg(target_arch = "x86_64")] -pub const UNW_TDEP_CURSOR_LEN: usize = 127; +mod libunwind_x86_64; #[cfg(target_arch = "aarch64")] -pub const UNW_TDEP_CURSOR_LEN: usize = 4096; // ARM64 cursor size - -#[cfg(target_arch = "x86")] -pub const UNW_TDEP_CURSOR_LEN: usize = 127; - -// Opaque cursor structure -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct unw_cursor { - pub opaque: [unw_word_t; UNW_TDEP_CURSOR_LEN], -} - -pub type unw_cursor_t = unw_cursor; - -impl Default for unw_cursor { - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -// Context is platform ucontext_t (from libc) -pub type unw_context_t = libc::ucontext_t; - -// Floating point register type -pub type unw_fpreg_t = u128; - -// Address space (opaque pointer) -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct unw_addr_space { - _unused: [u8; 0], -} - -pub type unw_addr_space_t = *mut unw_addr_space; - -// ============================================================================ -// Procedure info structures -// ============================================================================ - -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct unw_tdep_proc_info_t { - pub unused: u8, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct unw_proc_info { - pub start_ip: unw_word_t, - pub end_ip: unw_word_t, - pub lsda: unw_word_t, - pub handler: unw_word_t, - pub gp: unw_word_t, - pub flags: unw_word_t, - pub format: ::std::os::raw::c_int, - pub unwind_info_size: ::std::os::raw::c_int, - pub unwind_info: *mut ::std::os::raw::c_void, - pub extra: unw_tdep_proc_info_t, -} - -impl Default for unw_proc_info { - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -pub type unw_proc_info_t = unw_proc_info; - -// Save location structure -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct unw_save_loc { - pub type_: ::std::os::raw::c_int, - pub extra: unw_word_t, -} - -impl Default for unw_save_loc { - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -pub type unw_save_loc_t = unw_save_loc; - -// ============================================================================ -// Accessors and callbacks -// ============================================================================ - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct unw_accessors_t { - _unused: [u8; 0], -} - -pub type unw_reg_states_callback = ::std::option::Option< - unsafe extern "C" fn( - token: *mut ::std::os::raw::c_void, - reg_states_data: *mut ::std::os::raw::c_void, - reg_states_data_size: usize, - arg: *mut ::std::os::raw::c_void, - ) -> ::std::os::raw::c_int, ->; +mod libunwind_aarch64; -pub type unw_iterate_phdr_callback_t = ::std::option::Option< - unsafe extern "C" fn( - info: *mut ::std::os::raw::c_void, - size: usize, - arg: *mut ::std::os::raw::c_void, - ) -> ::std::os::raw::c_int, ->; - -// ============================================================================ -// Constants and enums -// ============================================================================ - -// Caching policy enum -pub type unw_caching_policy_t = ::std::os::raw::c_uint; -pub const UNW_CACHE_NONE: unw_caching_policy_t = 0; -pub const UNW_CACHE_GLOBAL: unw_caching_policy_t = 1; -pub const UNW_CACHE_PER_THREAD: unw_caching_policy_t = 2; - -// Error codes (returned as negative values) -pub const UNW_ESUCCESS: i32 = 0; // no error -pub const UNW_EUNSPEC: i32 = 1; // unspecified (general) error -pub const UNW_ENOMEM: i32 = 2; // out of memory -pub const UNW_EBADREG: i32 = 3; // bad register number -pub const UNW_EREADONLYREG: i32 = 4; // attempt to write read-only register -pub const UNW_ESTOPUNWIND: i32 = 5; // stop unwinding -pub const UNW_EINVALIDIP: i32 = 6; // invalid IP -pub const UNW_EBADFRAME: i32 = 7; // bad frame -pub const UNW_EINVAL: i32 = 8; // unsupported operation or bad value -pub const UNW_EBADVERSION: i32 = 9; // unwind info has unsupported version -pub const UNW_ENOINFO: i32 = 10; // no unwind info found - -// Register numbers (architecture-specific) - -#[cfg(target_arch = "x86_64")] -pub const UNW_X86_64_RIP: i32 = 16; // Instruction pointer -#[cfg(target_arch = "x86_64")] -pub const UNW_X86_64_RSP: i32 = 7; // Stack pointer -#[cfg(target_arch = "x86_64")] -pub const UNW_REG_IP: i32 = UNW_X86_64_RIP; // Alias for IP -#[cfg(target_arch = "x86_64")] -pub const UNW_REG_SP: i32 = UNW_X86_64_RSP; // Alias for SP - -// Add other architectures as needed -#[cfg(target_arch = "x86")] -pub const UNW_REG_IP: i32 = 8; // EIP on x86 -#[cfg(target_arch = "x86")] -pub const UNW_REG_SP: i32 = 4; // ESP on x86 - -#[cfg(target_arch = "aarch64")] -pub const UNW_REG_IP: i32 = 32; // PC on ARM64 #[cfg(target_arch = "aarch64")] -pub const UNW_REG_SP: i32 = 31; // SP on ARM64 - -// Helper function to convert error code to string -pub fn error_string(err: i32) -> &'static str { - match err { - 0 => "UNW_ESUCCESS: no error", - -1 => "UNW_EUNSPEC: unspecified (general) error", - -2 => "UNW_ENOMEM: out of memory", - -3 => "UNW_EBADREG: bad register number", - -4 => "UNW_EREADONLYREG: attempt to write read-only register", - -5 => "UNW_ESTOPUNWIND: stop unwinding", - -6 => "UNW_EINVALIDIP: invalid IP", - -7 => "UNW_EBADFRAME: bad frame", - -8 => "UNW_EINVAL: unsupported operation or bad value", - -9 => "UNW_EBADVERSION: unwind info has unsupported version", - -10 => "UNW_ENOINFO: no unwind info found", - _ => "Unknown error code", - } -} - -// ============================================================================ -// External function declarations and aliases -// ============================================================================ -// -// libunwind uses architecture-specific function names like _ULx86_64_init_local. -// This macro both declares the extern function AND creates a standard unw_* alias. - -macro_rules! unw_functions { - ($arch:ident) => { - paste::paste! { - extern "C" { - // Generic functions (no "L" prefix in arch name) - pub fn [<_ U $arch _getcontext>](context: *mut unw_context_t) -> ::std::os::raw::c_int; - pub fn [<_ U $arch _strerror>](err: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; - pub fn [<_ U $arch _regname>](reg: ::std::os::raw::c_int) -> *const ::std::os::raw::c_char; - - // Local unwinding functions ("L" in arch prefix) - pub fn [<_ UL $arch _init_local>](cursor: *mut unw_cursor_t, context: *mut unw_context_t) -> ::std::os::raw::c_int; - pub fn [<_ UL $arch _init_local2>](cursor: *mut unw_cursor_t, context: *mut unw_context_t, flag: ::std::os::raw::c_int) -> ::std::os::raw::c_int; - pub fn [<_ UL $arch _step>](cursor: *mut unw_cursor_t) -> ::std::os::raw::c_int; - pub fn [<_ UL $arch _get_reg>](cursor: *mut unw_cursor_t, reg: ::std::os::raw::c_int, valp: *mut unw_word_t) -> ::std::os::raw::c_int; - pub fn [<_ UL $arch _get_proc_name>](cursor: *mut unw_cursor_t, buffer: *mut ::std::os::raw::c_char, len: usize, offset: *mut unw_word_t) -> ::std::os::raw::c_int; - pub fn [<_ UL $arch _get_proc_info>](cursor: *mut unw_cursor_t, pip: *mut unw_proc_info_t) -> ::std::os::raw::c_int; - } - - // Create public aliases with standard unw_* names - pub use { - [<_ U $arch _getcontext>] as unw_getcontext, - [<_ U $arch _strerror>] as unw_strerror, - [<_ U $arch _regname>] as unw_regname, - [<_ UL $arch _init_local>] as unw_init_local, - [<_ UL $arch _init_local2>] as unw_init_local2, - [<_ UL $arch _step>] as unw_step, - [<_ UL $arch _get_reg>] as unw_get_reg, - [<_ UL $arch _get_proc_name>] as unw_get_proc_name, - [<_ UL $arch _get_proc_info>] as unw_get_proc_info, - }; - } - }; -} - -extern "C" { - pub fn unw_backtrace2( - frames: *mut *mut ::std::os::raw::c_void, - max_frames: ::std::os::raw::c_int, - context: *mut unw_context_t, - inner_frame_enum: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int; -} - -// Invoke for each supported architecture +pub use libunwind_aarch64::*; #[cfg(target_arch = "x86_64")] -unw_functions!(x86_64); - -#[cfg(target_arch = "aarch64")] -unw_functions!(aarch64); +pub use libunwind_x86_64::*; #[cfg(test)] mod tests { @@ -321,16 +20,16 @@ mod tests { #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_basic_unwind() { unsafe { - let mut context: unw_context_t = std::mem::zeroed(); - let mut cursor: unw_cursor_t = std::mem::zeroed(); + let mut context: UnwContext = std::mem::zeroed(); + let mut cursor: UnwCursor = std::mem::zeroed(); // Get current context let ret = unw_getcontext(&mut context); assert_eq!(ret, 0, "unw_getcontext failed"); // Initialize cursor - let ret = unw_init_local(&mut cursor, &mut context); - assert_eq!(ret, 0, "unw_init_local failed"); + let ret = unw_init_local2(&mut cursor, &mut context, 0); + assert_eq!(ret, 0, "unw_init_local2 failed"); // Walk the stack let mut frames = 0; @@ -356,20 +55,20 @@ mod tests { #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_get_register() { unsafe { - let mut context: unw_context_t = std::mem::zeroed(); - let mut cursor: unw_cursor_t = std::mem::zeroed(); + let mut context: UnwContext = std::mem::zeroed(); + let mut cursor: UnwCursor = std::mem::zeroed(); assert_eq!(unw_getcontext(&mut context), 0); - assert_eq!(unw_init_local(&mut cursor, &mut context), 0); + assert_eq!(unw_init_local2(&mut cursor, &mut context, 0), 0); // Get instruction pointer - let mut ip: unw_word_t = 0; + let mut ip: UnwWord = 0; let ret = unw_get_reg(&mut cursor, UNW_REG_IP, &mut ip); assert_eq!(ret, 0, "Failed to get IP register"); assert_ne!(ip, 0, "IP should not be zero"); // Get stack pointer - let mut sp: unw_word_t = 0; + let mut sp: UnwWord = 0; let ret = unw_get_reg(&mut cursor, UNW_REG_SP, &mut sp); assert_eq!(ret, 0, "Failed to get SP register"); assert_ne!(sp, 0, "SP should not be zero"); @@ -380,7 +79,7 @@ mod tests { #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind fn test_backtrace2() { unsafe { - let mut context: unw_context_t = std::mem::zeroed(); + let mut context: UnwContext = std::mem::zeroed(); assert_eq!(unw_getcontext(&mut context), 0); // unw_backtrace2 expects an array of void pointers @@ -400,4 +99,31 @@ mod tests { } } } + + #[test] + #[cfg_attr(miri, ignore)] // Miri cannot execute FFI calls to libunwind + fn test_get_proc_name() { + unsafe { + let mut context: UnwContext = std::mem::zeroed(); + let mut cursor: UnwCursor = std::mem::zeroed(); + + assert_eq!(unw_getcontext(&mut context), 0); + assert_eq!( + unw_init_local2(&mut cursor, &mut context, UNW_INIT_LOCAL_ONLY_IP), + 0 + ); + + let mut name: [libc::c_char; 100] = [0; 100]; + let ret = unw_get_proc_name(&mut cursor, name.as_mut_ptr(), 100, std::ptr::null_mut()); + assert_eq!(ret, 0, "unw_get_proc_name failed"); + let fn_name = std::ffi::CStr::from_ptr(name.as_ptr()).to_string_lossy(); + assert!(!fn_name.is_empty(), "Name should not be empty"); + // name is managed: _ZN15libdd_libunwind5tests18test_get_proc_name17hec15ec5ad6978a00E + // we should just chekc that test_get_proc_name is part of it + assert!( + fn_name.contains("test_get_proc_name"), + "Name should contain 'test_get_proc_name'" + ); + } + } } diff --git a/libdd-libunwind/src/libunwind_aarch64.rs b/libdd-libunwind/src/libunwind_aarch64.rs new file mode 100644 index 0000000000..4e5f8474d3 --- /dev/null +++ b/libdd-libunwind/src/libunwind_aarch64.rs @@ -0,0 +1,42 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +pub type UnwContext = libc::ucontext_t; + +pub type UnwWord = u64; + +// Opaque cursor structure +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct UnwCursor { + pub opaque: [UnwWord; 4096], +} + +extern "C" { + #[link_name = "_Uaarch64_getcontext"] + pub fn unw_getcontext(context: *mut UnwContext) -> i32; + #[link_name = "_ULaarch64_init_local2"] + pub fn unw_init_local2(cursor: *mut UnwCursor, context: *mut UnwContext, flag: i32) -> i32; + #[link_name = "_ULaarch64_step"] + pub fn unw_step(cursor: *mut UnwCursor) -> i32; + #[link_name = "_ULaarch64_get_reg"] + pub fn unw_get_reg(cursor: *mut UnwCursor, reg: i32, valp: *mut UnwWord) -> i32; + #[link_name = "_ULaarch64_get_proc_name"] + pub fn unw_get_proc_name( + cursor: *mut UnwCursor, + name: *mut libc::c_char, + len: usize, + offset: *mut u64, + ) -> i32; + #[link_name = "unw_backtrace2"] + pub fn unw_backtrace2( + buffer: *mut *mut ::std::os::raw::c_void, + size: i32, + context: *mut UnwContext, + flag: i32, + ) -> i32; +} + +pub const UNW_REG_IP: i32 = 32; // Instruction Pointer +pub const UNW_REG_SP: i32 = 31; // Stack Pointer +pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; diff --git a/libdd-libunwind/src/libunwind_x86_64.rs b/libdd-libunwind/src/libunwind_x86_64.rs new file mode 100644 index 0000000000..4b662bd6c0 --- /dev/null +++ b/libdd-libunwind/src/libunwind_x86_64.rs @@ -0,0 +1,46 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +// Context is platform ucontext_t (from libc) +pub type UnwContext = libc::ucontext_t; + +pub type UnwWord = u64; + +// Opaque cursor structure +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct UnwCursor { + pub opaque: [UnwWord; 127], +} + +// This is a subset of the libunwind API. + +extern "C" { + #[link_name = "_Ux86_64_getcontext"] + pub fn unw_getcontext(context: *mut UnwContext) -> i32; + #[link_name = "_ULx86_64_init_local2"] + pub fn unw_init_local2(cursor: *mut UnwCursor, context: *mut UnwContext, flag: i32) -> i32; + #[link_name = "_ULx86_64_step"] + pub fn unw_step(cursor: *mut UnwCursor) -> i32; + #[link_name = "_ULx86_64_get_reg"] + pub fn unw_get_reg(cursor: *mut UnwCursor, reg: i32, valp: *mut UnwWord) -> i32; + #[link_name = "_ULx86_64_get_proc_name"] + pub fn unw_get_proc_name( + cursor: *mut UnwCursor, + name: *mut libc::c_char, + len: usize, + offset: *mut u64, + ) -> i32; + #[link_name = "unw_backtrace2"] + pub fn unw_backtrace2( + buffer: *mut *mut ::std::os::raw::c_void, + size: i32, + context: *mut UnwContext, + flag: i32, + ) -> i32; +} + +// x86_64 register definitions for libunwind +pub const UNW_REG_IP: i32 = 16; // Instruction Pointer +pub const UNW_REG_SP: i32 = 17; // Stack Pointer +pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; From 20b02477d3a5730d783391745133c0fafc91eebb Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Fri, 6 Feb 2026 13:50:31 +0100 Subject: [PATCH 12/21] Fix jobs --- .github/CODEOWNERS | 1 + LICENSE-3rdparty.yml | 2 +- libdd-libunwind/build.rs | 12 +++--------- libdd-libunwind/src/lib.rs | 10 +++++----- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 827d6168be..6d7201299a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,7 @@ libdd-data-pipeline*/ @DataDog/libdatadog-apm libdd-ddsketch*/ @DataDog/libdatadog-apm @DataDog/apm-common-components-core libdd-dogstatsd-client @DataDog/apm-common-components-core libdd-library-config*/ @DataDog/apm-sdk-capabilities +libdd-libunwind*/ @DataDog/libdatadog-profiling libdd-log*/ @DataDog/apm-common-components-core libdd-profiling*/ @DataDog/libdatadog-profiling libdd-telemetry*/ @DataDog/apm-common-components-core diff --git a/LICENSE-3rdparty.yml b/LICENSE-3rdparty.yml index 1425aa8508..ab90880dd6 100644 --- a/LICENSE-3rdparty.yml +++ b/LICENSE-3rdparty.yml @@ -1,4 +1,4 @@ -root_name: builder, build_common, tools, libdd-alloc, libdd-crashtracker, libdd-common, libdd-telemetry, libdd-ddsketch, libdd-crashtracker-ffi, libdd-common-ffi, datadog-ffe, datadog-ffe-ffi, datadog-ipc, datadog-ipc-macros, libdd-tinybytes, tarpc, tarpc-plugins, spawn_worker, cc_utils, libdd-library-config, libdd-library-config-ffi, datadog-live-debugger, libdd-data-pipeline, libdd-dogstatsd-client, libdd-trace-protobuf, libdd-trace-stats, libdd-trace-utils, libdd-trace-normalization, libdd-log, datadog-live-debugger-ffi, libdd-profiling, libdd-profiling-protobuf, libdd-profiling-ffi, libdd-data-pipeline-ffi, libdd-ddsketch-ffi, libdd-log-ffi, libdd-telemetry-ffi, symbolizer-ffi, datadog-profiling-replayer, datadog-remote-config, datadog-sidecar, datadog-sidecar-macros, datadog-sidecar-ffi, libdd-trace-obfuscation, datadog-tracer-flare, sidecar_mockgen, test_spawn_from_lib +root_name: builder, build_common, tools, libdd-alloc, libdd-crashtracker, libdd-common, libdd-telemetry, libdd-ddsketch, libdd-crashtracker-ffi, libdd-common-ffi, datadog-ffe, datadog-ffe-ffi, datadog-ipc, datadog-ipc-macros, libdd-tinybytes, tarpc, tarpc-plugins, spawn_worker, cc_utils, libdd-library-config, libdd-library-config-ffi, datadog-live-debugger, libdd-data-pipeline, libdd-dogstatsd-client, libdd-trace-protobuf, libdd-trace-stats, libdd-trace-utils, libdd-trace-normalization, libdd-log, datadog-live-debugger-ffi, libdd-profiling, libdd-profiling-protobuf, libdd-profiling-ffi, libdd-data-pipeline-ffi, libdd-ddsketch-ffi, libdd-log-ffi, libdd-telemetry-ffi, symbolizer-ffi, datadog-profiling-replayer, datadog-remote-config, datadog-sidecar, datadog-sidecar-macros, datadog-sidecar-ffi, libdd-trace-obfuscation, datadog-tracer-flare, sidecar_mockgen, test_spawn_from_lib, bin_tests third_party_libraries: - package_name: addr2line package_version: 0.24.2 diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index b2909c9abc..296afc1a4a 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -1,15 +1,9 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + fn main() { #[cfg(target_os = "linux")] linux::main(); - #[cfg(target_os = "windows")] - windows::main(); -} - -#[cfg(target_os = "windows")] -mod windows { - pub(crate) fn main() { - println!("cargo:warning=windows platform is not supported yet"); - } } #[cfg(target_os = "linux")] diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index b1c0fc1efd..e67d5bae1f 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -1,18 +1,18 @@ // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -#[cfg(target_arch = "x86_64")] +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod libunwind_x86_64; -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_os = "linux", target_arch = "aarch64"))] mod libunwind_aarch64; -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_os = "linux", target_arch = "aarch64"))] pub use libunwind_aarch64::*; -#[cfg(target_arch = "x86_64")] +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use libunwind_x86_64::*; -#[cfg(test)] +#[cfg(all(test, target_os = "linux"))] mod tests { use super::*; From 23a39ab54258e94c11e5fa6f8c626217601e83a4 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Fri, 6 Feb 2026 14:01:16 +0100 Subject: [PATCH 13/21] Fix cargo cross build --- libdd-libunwind/build.rs | 28 ++++++++++++++++++++++++---- tools/docker/Dockerfile.centos | 20 +++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 296afc1a4a..b5dcd3480c 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -57,21 +57,35 @@ mod linux { } } - eprintln!("Configuring and building libunwind..."); + eprintln!("Configuring libunwind..."); let status = std::process::Command::new("sh") .current_dir(&build_dir) .args([ "-c", &format!( - "{}/configure --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests && make -j$(nproc)", + r"{}/configure CXXFLAGS=-fPIC\ -D_GLIBCXX_USE_CXX11_ABI=0\ -O3\ -g CFLAGS=-fPIC\ -O3\ -g --disable-shared --enable-static --disable-minidebuginfo --disable-zlibdebuginfo --disable-tests", libunwind_dir.display() ) ]) .status() - .expect("Failed to run configure/make"); + .expect("Failed to run configure"); if !status.success() { - panic!("libunwind build failed with exit code: {:?}", status.code()); + panic!( + "libunwind configure failed with exit code: {:?}", + status.code() + ); + } + + eprintln!("Building libunwind..."); + let status = std::process::Command::new("sh") + .current_dir(&build_dir) + .args(["-c", "make -j$(nproc)"]) + .status() + .expect("Failed to run make"); + + if !status.success() { + panic!("libunwind make failed with exit code: {:?}", status.code()); } // Verify the library was actually created @@ -90,9 +104,15 @@ mod linux { let lib_path = build_dir.join("src/.libs"); let include_path = build_dir.join("include"); + #[cfg(target_arch = "x86_64")] + let arch = "x86_64"; + #[cfg(target_arch = "aarch64")] + let arch = "aarch64"; + // Link directives for this crate println!("cargo:rustc-link-search=native={}", lib_path.display()); println!("cargo:rustc-link-lib=static=unwind"); + println!("cargo:rustc-link-lib=static=unwind-{}", arch); // Export paths to dependent crates via DEP_UNWIND_* environment variables // These are automatically passed to crates that depend on us diff --git a/tools/docker/Dockerfile.centos b/tools/docker/Dockerfile.centos index 51b5a8bbf8..fe04347afa 100644 --- a/tools/docker/Dockerfile.centos +++ b/tools/docker/Dockerfile.centos @@ -1,12 +1,18 @@ FROM ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main-centos -# CentOS 7 is EOL -# Find and replace mirror.centos.org with vault.centos.org -# Remove the # sign at the beginning of lines containing baseurl=http to enable baseurl usage instead of disabling it -# Add a # sign to the beginning of lines containing mirrorlist=http to disable the use of mirrorlist RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo \ && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo \ - && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo + && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo \ + && yum update -y \ + && yum install -y centos-release-scl -RUN yum update -y \ - && yum install -y unzip +# Find and replace mirror.centos.org with vault.centos.org a second time +# installing centos-release-scl may have added new repos that need to be updated +RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo \ + && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo \ + && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo \ + && yum update -y \ + && yum install -y devtoolset-11 \ + && yum clean all + +ENV PATH="/opt/rh/devtoolset-11/root/usr/bin:$PATH" From e2d83aa7b8532527f6ba6b841a524cbc5e848623 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Tue, 3 Mar 2026 16:03:17 +0100 Subject: [PATCH 14/21] Fix CI --- libdd-libunwind/src/lib.rs | 11 +++---- libdd-libunwind/src/libunwind_aarch64.rs | 41 ++++++++++++++++++++++-- libdd-libunwind/src/libunwind_x86_64.rs | 36 +++++++++++++++++++-- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind/src/lib.rs index e67d5bae1f..d84797a0be 100644 --- a/libdd-libunwind/src/lib.rs +++ b/libdd-libunwind/src/lib.rs @@ -23,9 +23,8 @@ mod tests { let mut context: UnwContext = std::mem::zeroed(); let mut cursor: UnwCursor = std::mem::zeroed(); - // Get current context - let ret = unw_getcontext(&mut context); - assert_eq!(ret, 0, "unw_getcontext failed"); + let ret = getcontext(&mut context); + assert_eq!(ret, 0, "getcontext failed"); // Initialize cursor let ret = unw_init_local2(&mut cursor, &mut context, 0); @@ -58,7 +57,7 @@ mod tests { let mut context: UnwContext = std::mem::zeroed(); let mut cursor: UnwCursor = std::mem::zeroed(); - assert_eq!(unw_getcontext(&mut context), 0); + assert_eq!(getcontext(&mut context), 0); assert_eq!(unw_init_local2(&mut cursor, &mut context, 0), 0); // Get instruction pointer @@ -80,7 +79,7 @@ mod tests { fn test_backtrace2() { unsafe { let mut context: UnwContext = std::mem::zeroed(); - assert_eq!(unw_getcontext(&mut context), 0); + assert_eq!(getcontext(&mut context), 0); // unw_backtrace2 expects an array of void pointers let mut frames: [*mut ::std::os::raw::c_void; 100] = [std::ptr::null_mut(); 100]; @@ -107,7 +106,7 @@ mod tests { let mut context: UnwContext = std::mem::zeroed(); let mut cursor: UnwCursor = std::mem::zeroed(); - assert_eq!(unw_getcontext(&mut context), 0); + assert_eq!(getcontext(&mut context), 0); assert_eq!( unw_init_local2(&mut cursor, &mut context, UNW_INIT_LOCAL_ONLY_IP), 0 diff --git a/libdd-libunwind/src/libunwind_aarch64.rs b/libdd-libunwind/src/libunwind_aarch64.rs index 4e5f8474d3..44bbd79537 100644 --- a/libdd-libunwind/src/libunwind_aarch64.rs +++ b/libdd-libunwind/src/libunwind_aarch64.rs @@ -13,8 +13,6 @@ pub struct UnwCursor { } extern "C" { - #[link_name = "_Uaarch64_getcontext"] - pub fn unw_getcontext(context: *mut UnwContext) -> i32; #[link_name = "_ULaarch64_init_local2"] pub fn unw_init_local2(cursor: *mut UnwCursor, context: *mut UnwContext, flag: i32) -> i32; #[link_name = "_ULaarch64_step"] @@ -40,3 +38,42 @@ extern "C" { pub const UNW_REG_IP: i32 = 32; // Instruction Pointer pub const UNW_REG_SP: i32 = 31; // Stack Pointer pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; + +/// Saves the current CPU context into `uc_mcontext.regs`. +/// On aarch64 libunwind does not emit a callable symbol for getcontext — +/// it uses a C preprocessor macro with inline assembly. This is the Rust +/// equivalent: save all GPRs, SP, LR, and PC into `uc_mcontext`. +// This is only for testing purposes and allow the tests to work with libc and musl-libc +#[cfg(test)] +#[inline(always)] +pub unsafe fn getcontext(context: *mut UnwContext) -> i32 { + let base = core::ptr::addr_of_mut!((*context).uc_mcontext.regs) as u64; + let ret: u64; + core::arch::asm!( + "stp x0, x1, [x0, #0]", + "stp x2, x3, [x0, #16]", + "stp x4, x5, [x0, #32]", + "stp x6, x7, [x0, #48]", + "stp x8, x9, [x0, #64]", + "stp x10, x11, [x0, #80]", + "stp x12, x13, [x0, #96]", + "stp x14, x15, [x0, #112]", + "stp x16, x17, [x0, #128]", + "stp x18, x19, [x0, #144]", + "stp x20, x21, [x0, #160]", + "stp x22, x23, [x0, #176]", + "stp x24, x25, [x0, #192]", + "stp x26, x27, [x0, #208]", + "stp x28, x29, [x0, #224]", + "mov x1, sp", + "stp x30, x1, [x0, #240]", + "adr x1, 2f", + "str x1, [x0, #256]", + "mov x0, #0", + "2:", + inout("x0") base => ret, + out("x1") _, + options(nostack, preserves_flags), + ); + ret as i32 +} diff --git a/libdd-libunwind/src/libunwind_x86_64.rs b/libdd-libunwind/src/libunwind_x86_64.rs index 4b662bd6c0..621c78bdc5 100644 --- a/libdd-libunwind/src/libunwind_x86_64.rs +++ b/libdd-libunwind/src/libunwind_x86_64.rs @@ -16,8 +16,6 @@ pub struct UnwCursor { // This is a subset of the libunwind API. extern "C" { - #[link_name = "_Ux86_64_getcontext"] - pub fn unw_getcontext(context: *mut UnwContext) -> i32; #[link_name = "_ULx86_64_init_local2"] pub fn unw_init_local2(cursor: *mut UnwCursor, context: *mut UnwContext, flag: i32) -> i32; #[link_name = "_ULx86_64_step"] @@ -44,3 +42,37 @@ extern "C" { pub const UNW_REG_IP: i32 = 16; // Instruction Pointer pub const UNW_REG_SP: i32 = 17; // Stack Pointer pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; + +/// Saves the current CPU context into `uc_mcontext.gregs`. +/// gregs layout: [R8, R9, R10, R11, R12, R13, R14, R15, +/// RDI, RSI, RBP, RBX, RDX, RAX, RCX, RSP, RIP, ...] +#[cfg(test)] +#[inline(always)] +pub unsafe fn getcontext(context: *mut UnwContext) -> i32 { + let gregs = core::ptr::addr_of_mut!((*context).uc_mcontext.gregs) as u64; + core::arch::asm!( + "mov [rdi], r8", + "mov [rdi + 8], r9", + "mov [rdi + 16], r10", + "mov [rdi + 24], r11", + "mov [rdi + 32], r12", + "mov [rdi + 40], r13", + "mov [rdi + 48], r14", + "mov [rdi + 56], r15", + "mov [rdi + 64], rdi", + "mov [rdi + 72], rsi", + "mov [rdi + 80], rbp", + "mov [rdi + 88], rbx", + "mov [rdi + 96], rdx", + "mov [rdi + 104], rax", + "mov [rdi + 112], rcx", + "mov [rdi + 120], rsp", + "lea rax, [rip + 2f]", + "mov [rdi + 128], rax", + "2:", + inout("rdi") gregs => _, + out("rax") _, + options(nostack, preserves_flags), + ); + 0 +} From ce64ad20e09a55825cdc03465d2017081d91dc47 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 09:36:16 +0100 Subject: [PATCH 15/21] Fix clippy --- libdd-libunwind/src/libunwind_aarch64.rs | 3 +++ libdd-libunwind/src/libunwind_x86_64.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/libdd-libunwind/src/libunwind_aarch64.rs b/libdd-libunwind/src/libunwind_aarch64.rs index 44bbd79537..0d778a3563 100644 --- a/libdd-libunwind/src/libunwind_aarch64.rs +++ b/libdd-libunwind/src/libunwind_aarch64.rs @@ -43,6 +43,9 @@ pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; /// On aarch64 libunwind does not emit a callable symbol for getcontext — /// it uses a C preprocessor macro with inline assembly. This is the Rust /// equivalent: save all GPRs, SP, LR, and PC into `uc_mcontext`. +/// +/// # Safety +/// `context` must be a valid, non-null pointer to a zeroed or initialized `UnwContext`. // This is only for testing purposes and allow the tests to work with libc and musl-libc #[cfg(test)] #[inline(always)] diff --git a/libdd-libunwind/src/libunwind_x86_64.rs b/libdd-libunwind/src/libunwind_x86_64.rs index 621c78bdc5..8c0232a942 100644 --- a/libdd-libunwind/src/libunwind_x86_64.rs +++ b/libdd-libunwind/src/libunwind_x86_64.rs @@ -46,6 +46,10 @@ pub const UNW_INIT_LOCAL_ONLY_IP: i32 = 1; /// Saves the current CPU context into `uc_mcontext.gregs`. /// gregs layout: [R8, R9, R10, R11, R12, R13, R14, R15, /// RDI, RSI, RBP, RBX, RDX, RAX, RCX, RSP, RIP, ...] +/// +/// # Safety +/// `context` must be a valid, non-null pointer to a zeroed or initialized `UnwContext`. +// This is only for testing purposes and allow the tests to work with libc and musl-libc #[cfg(test)] #[inline(always)] pub unsafe fn getcontext(context: *mut UnwContext) -> i32 { From 41eb7dd09ccbb4aae1fd8ea8d989459052d46e92 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 09:39:28 +0100 Subject: [PATCH 16/21] Clean up PR --- .github/workflows/coverage.yml | 2 -- .github/workflows/fuzz.yml | 2 -- .github/workflows/lint.yml | 6 ------ 3 files changed, 10 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f9375476f5..c37c9c663d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,8 +26,6 @@ jobs: docker-images: true swap-storage: true - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - submodules: recursive - name: Install Rust run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 - name: Install cargo-llvm-cov diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 9ddeb2fd2e..a44010700f 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -12,8 +12,6 @@ jobs: CARGO_INCREMENTAL: 0 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - submodules: recursive - name: Set up Rust run: | set -e diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a74cdabfff..b33af83d94 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,8 +10,6 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - submodules: recursive - name: Run actionlint uses: devops-actions/actionlint@c6744a34774e4e1c1df0ff66bdb07ec7ee480ca0 # 0.1.9 with: @@ -24,8 +22,6 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - submodules: recursive - name: Install nightly-2026-02-08 toolchain and rustfmt run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 && rustup component add rustfmt - name: Cache [rust] @@ -45,8 +41,6 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - submodules: recursive - name: Install ${{ matrix.rust_version }} toolchain and clippy run: rustup install ${{ matrix.rust_version }} && rustup default ${{ matrix.rust_version }} && rustup component add clippy - name: Cache [rust] From c8ba8ff0c57c963ac0143d72634112b58a104f4b Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 10:49:19 +0100 Subject: [PATCH 17/21] Clone libunwind repo instead of embarking the whole source code --- libdd-libunwind/build.rs | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index b5dcd3480c..409622e8b1 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -9,13 +9,41 @@ fn main() { #[cfg(target_os = "linux")] mod linux { use std::env; - use std::path::PathBuf; + use std::path::{Path, PathBuf}; + + const LIBUNWIND_REPO: &str = "https://github.com/DataDog/libunwind"; + const LIBUNWIND_BRANCH: &str = "kevin/v1.8.1-custom-2"; + + fn clone_libunwind(out_dir: &Path) -> PathBuf { + let source_dir = out_dir.join("libunwind-src"); + if source_dir.exists() { + eprintln!("Using cached libunwind source"); + return source_dir; + } + + eprintln!( + "Cloning libunwind from {LIBUNWIND_REPO} (branch: {LIBUNWIND_BRANCH})..." + ); + let status = std::process::Command::new("git") + .args([ + "clone", + "--depth=1", + "--branch", + LIBUNWIND_BRANCH, + LIBUNWIND_REPO, + source_dir.to_str().unwrap(), + ]) + .status() + .expect("Failed to run git. Is git installed?"); + assert!(status.success(), "Failed to clone libunwind"); + + source_dir + } pub(crate) fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let build_dir = out_dir.join("libunwind_build"); - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); + let libunwind_dir = clone_libunwind(&out_dir); // Check if libunwind submodule is initialized if !libunwind_dir.exists() || std::fs::read_dir(&libunwind_dir).unwrap().next().is_none() { @@ -115,17 +143,13 @@ mod linux { println!("cargo:rustc-link-lib=static=unwind-{}", arch); // Export paths to dependent crates via DEP_UNWIND_* environment variables - // These are automatically passed to crates that depend on us println!("cargo:include={}", include_path.display()); println!("cargo:lib={}", lib_path.display()); - println!("cargo:libdir={}", lib_path.display()); // Alternative name + println!("cargo:libdir={}", lib_path.display()); println!("cargo:root={}", build_dir.display()); eprintln!("libunwind library ready at {}", lib_path.display()); - // More specific rerun triggers - println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); - println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); println!("cargo:rerun-if-changed=build.rs"); } } From 96d91a4fd38d7364e48a3a27c03e7869f30f1fda Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 13:47:33 +0100 Subject: [PATCH 18/21] Fix docker based tests --- libdd-libunwind/build.rs | 5 ++--- tools/docker/Dockerfile.centos | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index 409622e8b1..c6636e0807 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -21,10 +21,9 @@ mod linux { return source_dir; } - eprintln!( - "Cloning libunwind from {LIBUNWIND_REPO} (branch: {LIBUNWIND_BRANCH})..." - ); + eprintln!("Cloning libunwind from {LIBUNWIND_REPO} (branch: {LIBUNWIND_BRANCH})..."); let status = std::process::Command::new("git") + .env("HOME", out_dir) .args([ "clone", "--depth=1", diff --git a/tools/docker/Dockerfile.centos b/tools/docker/Dockerfile.centos index fe04347afa..3ecd959e52 100644 --- a/tools/docker/Dockerfile.centos +++ b/tools/docker/Dockerfile.centos @@ -12,7 +12,7 @@ RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo \ && sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo \ && sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo \ && yum update -y \ - && yum install -y devtoolset-11 \ + && yum install -y devtoolset-11 autoconf automake libtool \ && yum clean all ENV PATH="/opt/rh/devtoolset-11/root/usr/bin:$PATH" From 46ac9e5c3fcd4b6b480e560d9c2480c47e696893 Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 14:29:41 +0100 Subject: [PATCH 19/21] Go back to git submodules --- .github/workflows/coverage.yml | 2 + .github/workflows/lint.yml | 6 +++ .gitmodules | 4 +- .../libunwind | 0 libdd-libunwind/build.rs | 43 ++++++------------- tools/docker/Dockerfile.build | 2 +- 6 files changed, 24 insertions(+), 33 deletions(-) rename {libdd-libunwind => libdd-libunwind-sys}/libunwind (100%) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c37c9c663d..f9375476f5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,6 +26,8 @@ jobs: docker-images: true swap-storage: true - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install Rust run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 - name: Install cargo-llvm-cov diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b33af83d94..a74cdabfff 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Run actionlint uses: devops-actions/actionlint@c6744a34774e4e1c1df0ff66bdb07ec7ee480ca0 # 0.1.9 with: @@ -22,6 +24,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install nightly-2026-02-08 toolchain and rustfmt run: rustup install nightly-2026-02-08 && rustup default nightly-2026-02-08 && rustup component add rustfmt - name: Cache [rust] @@ -41,6 +45,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + submodules: recursive - name: Install ${{ matrix.rust_version }} toolchain and clippy run: rustup install ${{ matrix.rust_version }} && rustup default ${{ matrix.rust_version }} && rustup component add clippy - name: Cache [rust] diff --git a/.gitmodules b/.gitmodules index d12ac270ad..72f1fea8c3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "libdd-libunwind/libunwind"] - path = libdd-libunwind/libunwind +[submodule "libdd-libunwind-sys/libunwind"] + path = libdd-libunwind-sys/libunwind url = https://github.com/DataDog/libunwind.git branch = kevin/v1.8.1-custom-2 diff --git a/libdd-libunwind/libunwind b/libdd-libunwind-sys/libunwind similarity index 100% rename from libdd-libunwind/libunwind rename to libdd-libunwind-sys/libunwind diff --git a/libdd-libunwind/build.rs b/libdd-libunwind/build.rs index c6636e0807..d2ddeb779d 100644 --- a/libdd-libunwind/build.rs +++ b/libdd-libunwind/build.rs @@ -9,40 +9,21 @@ fn main() { #[cfg(target_os = "linux")] mod linux { use std::env; - use std::path::{Path, PathBuf}; - - const LIBUNWIND_REPO: &str = "https://github.com/DataDog/libunwind"; - const LIBUNWIND_BRANCH: &str = "kevin/v1.8.1-custom-2"; - - fn clone_libunwind(out_dir: &Path) -> PathBuf { - let source_dir = out_dir.join("libunwind-src"); - if source_dir.exists() { - eprintln!("Using cached libunwind source"); - return source_dir; - } - - eprintln!("Cloning libunwind from {LIBUNWIND_REPO} (branch: {LIBUNWIND_BRANCH})..."); - let status = std::process::Command::new("git") - .env("HOME", out_dir) - .args([ - "clone", - "--depth=1", - "--branch", - LIBUNWIND_BRANCH, - LIBUNWIND_REPO, - source_dir.to_str().unwrap(), - ]) - .status() - .expect("Failed to run git. Is git installed?"); - assert!(status.success(), "Failed to clone libunwind"); - - source_dir - } + use std::path::PathBuf; pub(crate) fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let build_dir = out_dir.join("libunwind_build"); - let libunwind_dir = clone_libunwind(&out_dir); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let libunwind_dir = std::path::Path::new(&manifest_dir).join("libunwind"); + + if !libunwind_dir.join("src").exists() { + panic!( + "libunwind source not found at {}. \ + Did you forget to run `git submodule update --init`?", + libunwind_dir.display() + ); + } // Check if libunwind submodule is initialized if !libunwind_dir.exists() || std::fs::read_dir(&libunwind_dir).unwrap().next().is_none() { @@ -149,6 +130,8 @@ mod linux { eprintln!("libunwind library ready at {}", lib_path.display()); + println!("cargo:rerun-if-changed={}/src", libunwind_dir.display()); + println!("cargo:rerun-if-changed={}/include", libunwind_dir.display()); println!("cargo:rerun-if-changed=build.rs"); } } diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index 57fcd3570e..cc3d007f86 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -156,7 +156,7 @@ RUN echo \ | xargs -n 1 sh -c 'mkdir -p $(dirname $1); touch $1; echo $1' create_stubs # Copy libunwind submodule for libdd-libunwind build.rs -COPY "libdd-libunwind/libunwind" "libdd-libunwind/libunwind/" +COPY "libdd-libunwind-sys/libunwind" "libdd-libunwind-sys/libunwind/" # cache dependencies RUN cargo fetch --locked From ba13f15b32f92a9332b00b98a679260794d3578b Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Wed, 4 Mar 2026 14:57:19 +0100 Subject: [PATCH 20/21] Fix CODEOWNERS validation job --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6d7201299a..efc0feb1b6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,7 @@ .gitlab-ci.yml @DataDog/apm-common-components-core .gitlab/benchmarks.yml @DataDog/apm-common-components-core .gitlab/fuzz.yml @DataDog/chaos-platform +.gitmodules @DataDog/libdatadog benchmark/ @DataDog/apm-common-components-core bin_tests/ @DataDog/libdatadog-profiling build-common/ @DataDog/apm-common-components-core From ad90dd58733dfea25e7226363c1721d5bc03843e Mon Sep 17 00:00:00 2001 From: Gregory LEOCADIE Date: Thu, 5 Mar 2026 14:23:32 +0100 Subject: [PATCH 21/21] Fix failed merged --- Cargo.lock | 2 +- Cargo.toml | 2 +- {libdd-libunwind => libdd-libunwind-sys}/Cargo.toml | 0 {libdd-libunwind => libdd-libunwind-sys}/build.rs | 0 {libdd-libunwind => libdd-libunwind-sys}/src/lib.rs | 0 .../src/libunwind_aarch64.rs | 0 .../src/libunwind_x86_64.rs | 0 tools/docker/Dockerfile.build | 4 ++-- 8 files changed, 4 insertions(+), 4 deletions(-) rename {libdd-libunwind => libdd-libunwind-sys}/Cargo.toml (100%) rename {libdd-libunwind => libdd-libunwind-sys}/build.rs (100%) rename {libdd-libunwind => libdd-libunwind-sys}/src/lib.rs (100%) rename {libdd-libunwind => libdd-libunwind-sys}/src/libunwind_aarch64.rs (100%) rename {libdd-libunwind => libdd-libunwind-sys}/src/libunwind_x86_64.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 397d2039d3..caeec13d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "libdd-libunwind" -version = "26.0.0" +version = "28.0.2" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index f43851d976..437648e77f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ members = [ "libdd-tinybytes", "libdd-dogstatsd-client", "libdd-log", - "libdd-log-ffi", "libdd-libunwind", + "libdd-log-ffi", "libdd-libunwind-sys", ] # https://doc.rust-lang.org/cargo/reference/resolver.html diff --git a/libdd-libunwind/Cargo.toml b/libdd-libunwind-sys/Cargo.toml similarity index 100% rename from libdd-libunwind/Cargo.toml rename to libdd-libunwind-sys/Cargo.toml diff --git a/libdd-libunwind/build.rs b/libdd-libunwind-sys/build.rs similarity index 100% rename from libdd-libunwind/build.rs rename to libdd-libunwind-sys/build.rs diff --git a/libdd-libunwind/src/lib.rs b/libdd-libunwind-sys/src/lib.rs similarity index 100% rename from libdd-libunwind/src/lib.rs rename to libdd-libunwind-sys/src/lib.rs diff --git a/libdd-libunwind/src/libunwind_aarch64.rs b/libdd-libunwind-sys/src/libunwind_aarch64.rs similarity index 100% rename from libdd-libunwind/src/libunwind_aarch64.rs rename to libdd-libunwind-sys/src/libunwind_aarch64.rs diff --git a/libdd-libunwind/src/libunwind_x86_64.rs b/libdd-libunwind-sys/src/libunwind_x86_64.rs similarity index 100% rename from libdd-libunwind/src/libunwind_x86_64.rs rename to libdd-libunwind-sys/src/libunwind_x86_64.rs diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index cc3d007f86..b6871a1101 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -83,8 +83,8 @@ COPY "libdd-ddsketch/Cargo.toml" "libdd-ddsketch/" COPY "libdd-ddsketch-ffi/Cargo.toml" "libdd-ddsketch-ffi/" COPY "libdd-log/Cargo.toml" "libdd-log/" COPY "libdd-log-ffi/Cargo.toml" "libdd-log-ffi/" -COPY "libdd-libunwind/Cargo.toml" "libdd-libunwind/" -COPY "libdd-libunwind/build.rs" "libdd-libunwind/" +COPY "libdd-libunwind-sys/Cargo.toml" "libdd-libunwind-sys/" +COPY "libdd-libunwind-sys/build.rs" "libdd-libunwind-sys/" COPY "libdd-dogstatsd-client/Cargo.toml" "libdd-dogstatsd-client/" COPY "libdd-library-config-ffi/Cargo.toml" "libdd-library-config-ffi/" COPY "libdd-library-config/Cargo.toml" "libdd-library-config/"