From 6e8eba7502706ec83ab0be26fe5cfd59b0494e39 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Wed, 3 Dec 2025 00:00:42 -0500 Subject: [PATCH 01/11] crashtracker: support cxx bindings for crashinfo --- .gitignore | 1 + Cargo.lock | 98 ++++++- examples/cxx/README.md | 102 +++++++ examples/cxx/build-and-run.sh | 62 +++++ examples/cxx/crashinfo.cpp | 91 ++++++ libdd-crashtracker/Cargo.toml | 6 +- libdd-crashtracker/build.rs | 10 + libdd-crashtracker/src/crash_info/builder.rs | 167 +++++------ libdd-crashtracker/src/crash_info/cxx.rs | 260 ++++++++++++++++++ libdd-crashtracker/src/crash_info/mod.rs | 2 + .../src/crash_info/stacktrace.rs | 52 ++++ .../src/receiver/receive_report.rs | 9 +- 12 files changed, 762 insertions(+), 98 deletions(-) create mode 100644 examples/cxx/README.md create mode 100755 examples/cxx/build-and-run.sh create mode 100644 examples/cxx/crashinfo.cpp create mode 100644 libdd-crashtracker/src/crash_info/cxx.rs diff --git a/.gitignore b/.gitignore index bafe4bc49c..07fa526c83 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ Cargo.lock # this is ignored so you don't update by accident, but is committed. docker-sync.yml libtest.so libtest_cpp.so +examples/cxx/crashinfo diff --git a/Cargo.lock b/Cargo.lock index cb752a87c3..af1ddb3ad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,6 +838,17 @@ dependencies = [ "cc", ] +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -1106,6 +1117,68 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc" +[[package]] +name = "cxx" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7620f6cfc4dcca21f2b085b7a890e16c60fd66f560cd69ee60594908dc72ab1" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash 0.2.0", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9bc1a22964ff6a355fbec24cf68266a0ed28f8b84c0864c386474ea3d0e479" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap 2.12.0", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.110", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f29a879d35f7906e3c9b77d7a1005a6a0787d330c09dfe4ffb5f617728cb44" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap 2.12.0", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67109015f93f683e364085aa6489a5b2118b4a40058482101d699936a7836d6" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d187e019e7b05a1f3e69a8396b70800ee867aa9fc2ab972761173ccee03742df" +dependencies = [ + "indexmap 2.12.0", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "darling" version = "0.21.3" @@ -1700,6 +1773,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1983,7 +2062,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -2601,6 +2680,8 @@ dependencies = [ "cc", "chrono", "criterion", + "cxx", + "cxx-build", "goblin", "http", "libc", @@ -3021,6 +3102,15 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -4490,6 +4580,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + [[package]] name = "scroll" version = "0.12.0" diff --git a/examples/cxx/README.md b/examples/cxx/README.md new file mode 100644 index 0000000000..87f5dffd17 --- /dev/null +++ b/examples/cxx/README.md @@ -0,0 +1,102 @@ +# CXX Bindings Example for libdd-crashtracker + +This example demonstrates how to use the CXX bindings for the libdd-crashtracker crate, providing a safer and more idiomatic C++ API compared to the traditional C FFI. + +## Features + +The CXX bindings provide access to: + +### Core Types +- `CrashInfoBuilder` - Builder for constructing crash information +- `StackFrame` - Individual stack frame with debug info and addresses +- `StackTrace` - Collection of stack frames +- `CrashInfo` - Final crash information object +- `Metadata` - Library metadata (name, version, tags) +- `ProcInfo` - Process information +- `OsInfo` - Operating system information + +### Enums +- `ErrorKind` - Type of error (Panic, UnhandledException, UnixSignal) +- `BuildIdType` - Build ID format (GNU, GO, PDB, SHA1) +- `FileType` - Binary file format (APK, ELF, PE) + +### Key Functions + +**Builder Creation:** +- `crashinfo_builder_new()` - Create a new builder +- `stackframe_new()` - Create a new stack frame +- `stacktrace_new()` - Create a new stack trace + +**Builder Methods:** +- `crashinfo_with_kind()` - Set error type +- `crashinfo_with_message()` - Set error message +- `crashinfo_with_metadata()` - Set library metadata +- `crashinfo_with_proc_info()` - Set process info +- `crashinfo_with_os_info()` - Set OS info +- `crashinfo_with_counter()` - Add a named counter +- `crashinfo_with_file()` - Add a file to the report +- `crashinfo_with_stack()` - Set the stack trace +- `crashinfo_with_timestamp_now()` - Set current timestamp +- `crashinfo_build()` - Build the final CrashInfo + +**StackFrame Methods:** +- `stackframe_with_function()`, `stackframe_with_file()`, `stackframe_with_line()`, `stackframe_with_column()` - Set debug info +- `stackframe_with_ip()`, `stackframe_with_sp()` - Set absolute addresses +- `stackframe_with_build_id()`, `stackframe_with_path()` - Set binary info +- `stackframe_with_relative_address()` - Set relative address + +**StackTrace Methods:** +- `stacktrace_push_frame()` - Add a frame to the trace +- `stacktrace_set_complete()` - Mark trace as complete + +**Output:** +- `crashinfo_to_json()` - Convert CrashInfo to JSON string + +## Building and Running + +The `build-and-run.sh` script handles the entire build process: + +```bash +./examples/cxx/build-and-run.sh +``` + +This will: +1. Build libdd-crashtracker with the `cxx` feature enabled +2. Find the CXX bridge headers and libraries +3. Compile the C++ example +4. Run the example and display the output + +## Example Output + +The example creates a crash report with: +- Error kind and message +- Library metadata with tags +- Process and OS information +- A stack trace with multiple frames (debug info + binary addresses) +- Counters and log messages +- Timestamp + +The output is a JSON object that can be sent to Datadog's crash tracking service. + +## Notes + +- The CXX bindings use `rust::String` types which need to be converted to `std::string` for use with standard C++ streams +- All functions that can fail will use exceptions (standard C++ exception handling) +- The bindings are type-safe and prevent many common C FFI errors +- Memory is managed automatically through RAII and smart pointers + +## Comparison to C FFI + +The CXX bindings provide several advantages over the traditional C FFI: + +1. **Type Safety**: No void pointers, proper type checking at compile time +2. **Memory Safety**: Automatic memory management through smart pointers +3. **Ergonomics**: More natural C++ idioms, no need for manual handle management +4. **Error Handling**: Exceptions instead of error codes +5. **String Handling**: Seamless `rust::String` ↔ C++ string interop + +## Requirements + +- C++14 or later +- Rust toolchain +- macOS (this example) or Linux diff --git a/examples/cxx/build-and-run.sh b/examples/cxx/build-and-run.sh new file mode 100755 index 0000000000..4868aed06c --- /dev/null +++ b/examples/cxx/build-and-run.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +# SPDX-License-Identifier: Apache-2.0 + +# Build and run the CXX crashinfo example +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +cd "$PROJECT_ROOT" + +echo "šŸ”Ø Building libdd-crashtracker with cxx feature..." +cargo build -p libdd-crashtracker --features cxx --release + +echo "šŸ” Finding CXX bridge headers..." +CXX_BRIDGE_INCLUDE=$(find target/release/build/libdd-crashtracker-*/out/cxxbridge/include -type d 2>/dev/null | head -n 1) +CXX_BRIDGE_CRATE=$(find target/release/build/libdd-crashtracker-*/out/cxxbridge/crate -type d 2>/dev/null | head -n 1) +RUST_CXX_INCLUDE=$(find target/release/build/cxx-*/out -type d 2>/dev/null | head -n 1) + +if [ -z "$CXX_BRIDGE_INCLUDE" ] || [ -z "$CXX_BRIDGE_CRATE" ] || [ -z "$RUST_CXX_INCLUDE" ]; then + echo "āŒ Error: Could not find CXX bridge directories" + exit 1 +fi + +echo "šŸ“ CXX include: $CXX_BRIDGE_INCLUDE" +echo "šŸ“ CXX crate: $CXX_BRIDGE_CRATE" +echo "šŸ“ Rust CXX: $RUST_CXX_INCLUDE" + +echo "šŸ”Ø Finding libraries..." +CRASHTRACKER_LIB="$PROJECT_ROOT/target/release/liblibdd_crashtracker.a" +CXX_BRIDGE_LIB=$(find target/release/build/libdd-crashtracker-*/out -name "liblibdd-crashtracker-cxx.a" | head -n 1) + +if [ ! -f "$CRASHTRACKER_LIB" ]; then + echo "āŒ Error: Could not find libdd-crashtracker library at $CRASHTRACKER_LIB" + exit 1 +fi + +if [ ! -f "$CXX_BRIDGE_LIB" ]; then + echo "āŒ Error: Could not find CXX bridge library" + exit 1 +fi + +echo "šŸ“š Crashtracker library: $CRASHTRACKER_LIB" +echo "šŸ“š CXX bridge library: $CXX_BRIDGE_LIB" + +echo "šŸ”Ø Compiling C++ example..." +c++ -std=c++14 \ + -I"$CXX_BRIDGE_INCLUDE" \ + -I"$CXX_BRIDGE_CRATE" \ + -I"$RUST_CXX_INCLUDE" \ + -I"$PROJECT_ROOT" \ + examples/cxx/crashinfo.cpp \ + "$CRASHTRACKER_LIB" \ + "$CXX_BRIDGE_LIB" \ + -lpthread -ldl -framework Security -framework CoreFoundation \ + -o examples/cxx/crashinfo + +echo "šŸš€ Running example..." +./examples/cxx/crashinfo + +echo "" +echo "āœ… Success!" diff --git a/examples/cxx/crashinfo.cpp b/examples/cxx/crashinfo.cpp new file mode 100644 index 0000000000..af486498f3 --- /dev/null +++ b/examples/cxx/crashinfo.cpp @@ -0,0 +1,91 @@ +#include +#include +#include "libdd-crashtracker/src/crash_info/cxx.rs.h" + +using namespace datadog::crashtracker; + +int main() { + try { + std::cout << "Creating CrashInfo using CXX bindings..." << std::endl; + + auto builder = CrashInfoBuilder::create(); + + builder->set_kind(ErrorKind::Panic); + builder->with_message("Example crash message"); + builder->with_counter("my_counter", 42); + builder->with_log_message("This is a log message", true); + builder->with_fingerprint("test-fingerprint-123"); + builder->with_incomplete(false); + + // Set metadata + Metadata metadata; + metadata.library_name = "libdatadog"; + metadata.library_version = "1.0.0"; + metadata.family = "rust"; + metadata.tags.push_back("service:example"); + metadata.tags.push_back("env:dev"); + builder->set_metadata(metadata); + + // Set process info + ProcInfo proc_info; + proc_info.pid = 12345; + builder->set_proc_info(proc_info); + + // Set OS info + OsInfo os_info; + os_info.architecture = "x86_64"; + os_info.bitness = "64"; + os_info.os_type = "Linux"; + os_info.version = "5.15.0"; + builder->set_os_info(os_info); + + // Create a stack trace + auto stacktrace = StackTrace::create(); + + // Pass 'true' for incomplete to allow adding more frames + for (int i = 0; i < 5; ++i) { + auto frame = StackFrame::create(); + frame->with_function("function_" + std::to_string(i)); + frame->with_file("/path/to/file_" + std::to_string(i) + ".rs"); + frame->with_line(100 + i); + frame->with_column(10 + i); + stacktrace->add_frame(std::move(frame), true); + } + + // Add a frame with address info (Windows style) + auto win_frame = StackFrame::create(); + win_frame->with_ip(0xDEADBEEF); + win_frame->with_module_base_address(0xABBABABA); + win_frame->with_build_id("abcdef123456"); + win_frame->build_id_type(BuildIdType::PDB); + win_frame->file_type(FileType::PE); + win_frame->with_path("C:/Program Files/example.exe"); + win_frame->with_relative_address(0xBABEF00D); + stacktrace->add_frame(std::move(win_frame), true); + + // Add a frame with ELF info + auto elf_frame = StackFrame::create(); + elf_frame->with_ip(0xCAFEBABE); + elf_frame->with_build_id("fedcba987654321"); + elf_frame->build_id_type(BuildIdType::GNU); + elf_frame->file_type(FileType::ELF); + elf_frame->with_path("/usr/lib/libexample.so"); + elf_frame->with_relative_address(0xF00DFACE); + stacktrace->add_frame(std::move(elf_frame), true); + + stacktrace->mark_complete(); + builder->add_stack(std::move(stacktrace)); + builder->with_timestamp_now(); + + auto crash_info = crashinfo_build(std::move(builder)); + auto json = crash_info->to_json(); + std::cout << "\nCrashInfo JSON:\n" << std::string(json) << std::endl; + + std::cout << "\nāœ… Success!" << std::endl; + return 0; + + } catch (const std::exception& e) { + std::cerr << "āŒ Exception: " << e.what() << std::endl; + return 1; + } +} diff --git a/libdd-crashtracker/Cargo.toml b/libdd-crashtracker/Cargo.toml index d3a2967da5..9e7942c3e8 100644 --- a/libdd-crashtracker/Cargo.toml +++ b/libdd-crashtracker/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true autobenches = false [lib] -crate-type = ["lib"] +crate-type = ["lib", "staticlib"] bench = false [[bin]] @@ -34,6 +34,8 @@ collector_windows = [] generate-unit-test-files = [] # Enables benchmark functionality benchmarking = ["generate-unit-test-files"] +# Enables C++ bindings via cxx +cxx = ["dep:cxx", "dep:cxx-build"] [target.'cfg(unix)'.dependencies] # Should be kept in sync with the libdatadog symbolizer crate (also using blasesym) @@ -43,6 +45,7 @@ blazesym = "=0.2.0-rc.5" anyhow = "1.0" backtrace = "=0.3.74" chrono = {version = "0.4", default-features = false, features = ["std", "clock", "serde"]} +cxx = { version = "1.0", optional = true } libdd-common = { version = "1.0.0", path = "../libdd-common" } libdd-telemetry = { version = "1.0.0", path = "../libdd-telemetry" } http = "1.0" @@ -74,4 +77,5 @@ tempfile = { version = "3.23" } [build-dependencies] # If we use a newer version of cc, CI fails on alpine. cc = "1.1.31" +cxx-build = { version = "1.0", optional = true } libdd-common = { version = "1.0.0", path = "../libdd-common" } diff --git a/libdd-crashtracker/build.rs b/libdd-crashtracker/build.rs index 1fea6e595c..ef3a215524 100644 --- a/libdd-crashtracker/build.rs +++ b/libdd-crashtracker/build.rs @@ -103,6 +103,16 @@ fn main() { .file("src/crash_info/emit_sicodes.c") .compile("emit_sicodes"); + // Build CXX bridge if feature is enabled + #[cfg(feature = "cxx")] + { + cxx_build::bridge("src/crash_info/cxx.rs") + .flag_if_supported("-std=c++14") + .compile("libdd-crashtracker-cxx"); + + println!("cargo:rerun-if-changed=src/crash_info/cxx.rs"); + } + // Don't build test libraries during `cargo publish` verification. // During verification, the package is unpacked to target/package/ and built there. let is_packaging = std::env::var("CARGO_MANIFEST_DIR") diff --git a/libdd-crashtracker/src/crash_info/builder.rs b/libdd-crashtracker/src/crash_info/builder.rs index 43c8bd0cc3..fdff37f213 100644 --- a/libdd-crashtracker/src/crash_info/builder.rs +++ b/libdd-crashtracker/src/crash_info/builder.rs @@ -46,35 +46,31 @@ impl ErrorDataBuilder { Self::default() } - pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<&mut Self> { + pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<()> { self.kind = Some(kind); - Ok(self) + Ok(()) } - pub fn with_message(&mut self, message: String) -> anyhow::Result<&mut Self> { + pub fn with_message(&mut self, message: String) -> anyhow::Result<()> { self.message = Some(message); - Ok(self) + Ok(()) } - pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<&mut Self> { + pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<()> { self.stack = Some(stack); - Ok(self) + Ok(()) } - pub fn with_stack_frame( - &mut self, - frame: StackFrame, - incomplete: bool, - ) -> anyhow::Result<&mut Self> { + pub fn with_stack_frame(&mut self, frame: StackFrame, incomplete: bool) -> anyhow::Result<()> { if let Some(stack) = &mut self.stack { stack.push_frame(frame, incomplete)?; } else { self.stack = Some(StackTrace::from_frames(vec![frame], incomplete)); } - Ok(self) + Ok(()) } - pub fn with_stack_set_complete(&mut self) -> anyhow::Result<&mut Self> { + pub fn with_stack_set_complete(&mut self) -> anyhow::Result<()> { if let Some(stack) = &mut self.stack { stack.set_complete()?; } else { @@ -82,16 +78,16 @@ impl ErrorDataBuilder { // empty on musl based Linux (Alpine) because stack unwinding may not be able to unwind // passed the signal handler. This by-passing for musl is temporary and needs a fix. #[cfg(target_env = "musl")] - return Ok(self); + return Ok(()); #[cfg(not(target_env = "musl"))] anyhow::bail!("Can't set non-existant stack complete"); } - Ok(self) + Ok(()) } - pub fn with_threads(&mut self, threads: Vec) -> anyhow::Result<&mut Self> { + pub fn with_threads(&mut self, threads: Vec) -> anyhow::Result<()> { self.threads = Some(threads); - Ok(self) + Ok(()) } } @@ -183,63 +179,63 @@ impl CrashInfoBuilder { } /// Inserts the given counter to the current set of counters in the builder. - pub fn with_counter(&mut self, name: String, value: i64) -> anyhow::Result<&mut Self> { + pub fn with_counter(&mut self, name: String, value: i64) -> anyhow::Result<()> { anyhow::ensure!(!name.is_empty(), "Empty counter name not allowed"); if let Some(ref mut counters) = &mut self.counters { counters.insert(name, value); } else { self.counters = Some(HashMap::from([(name, value)])); } - Ok(self) + Ok(()) } - pub fn with_counters(&mut self, counters: HashMap) -> anyhow::Result<&mut Self> { + pub fn with_counters(&mut self, counters: HashMap) -> anyhow::Result<()> { self.counters = Some(counters); - Ok(self) + Ok(()) } pub fn with_experimental_additional_tags( &mut self, additional_tags: Vec, - ) -> anyhow::Result<&mut Self> { + ) -> anyhow::Result<()> { if let Some(experimental) = &mut self.experimental { experimental.additional_tags = additional_tags; } else { self.experimental = Some(Experimental::new().with_additional_tags(additional_tags)); } - Ok(self) + Ok(()) } - pub fn with_experimental_ucontext(&mut self, ucontext: String) -> anyhow::Result<&mut Self> { + pub fn with_experimental_ucontext(&mut self, ucontext: String) -> anyhow::Result<()> { if let Some(experimental) = &mut self.experimental { experimental.ucontext = Some(ucontext); } else { self.experimental = Some(Experimental::new().with_ucontext(ucontext)); } - Ok(self) + Ok(()) } pub fn with_experimental_runtime_stack( &mut self, runtime_stack: RuntimeStack, - ) -> anyhow::Result<&mut Self> { + ) -> anyhow::Result<()> { if let Some(experimental) = &mut self.experimental { experimental.runtime_stack = Some(runtime_stack); } else { self.experimental = Some(Experimental::new().with_runtime_stack(runtime_stack)); } - Ok(self) + Ok(()) } - pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<&mut Self> { - self.error.with_kind(kind)?; - Ok(self) + pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<()> { + self.error.with_kind(kind) } - pub fn with_file(&mut self, filename: String) -> anyhow::Result<&mut Self> { + pub fn with_file(&mut self, filename: String) -> anyhow::Result<()> { let file = File::open(&filename).with_context(|| format!("filename: {filename}"))?; let lines: std::io::Result> = BufReader::new(file).lines().collect(); - self.with_file_and_contents(filename, lines?) + self.with_file_and_contents(filename, lines?)?; + Ok(()) } /// Appends the given file to the current set of files in the builder. @@ -247,38 +243,34 @@ impl CrashInfoBuilder { &mut self, filename: String, contents: Vec, - ) -> anyhow::Result<&mut Self> { + ) -> anyhow::Result<()> { if let Some(ref mut files) = &mut self.files { files.insert(filename, contents); } else { self.files = Some(HashMap::from([(filename, contents)])); } - Ok(self) + Ok(()) } /// Sets the current set of files in the builder. - pub fn with_files(&mut self, files: HashMap>) -> anyhow::Result<&mut Self> { + pub fn with_files(&mut self, files: HashMap>) -> anyhow::Result<()> { self.files = Some(files); - Ok(self) + Ok(()) } - pub fn with_fingerprint(&mut self, fingerprint: String) -> anyhow::Result<&mut Self> { + pub fn with_fingerprint(&mut self, fingerprint: String) -> anyhow::Result<()> { anyhow::ensure!(!fingerprint.is_empty(), "Expect non-empty fingerprint"); self.fingerprint = Some(fingerprint); - Ok(self) + Ok(()) } - pub fn with_incomplete(&mut self, incomplete: bool) -> anyhow::Result<&mut Self> { + pub fn with_incomplete(&mut self, incomplete: bool) -> anyhow::Result<()> { self.incomplete = Some(incomplete); - Ok(self) + Ok(()) } /// Appends the given message to the current set of messages in the builder. - pub fn with_log_message( - &mut self, - message: String, - also_print: bool, - ) -> anyhow::Result<&mut Self> { + pub fn with_log_message(&mut self, message: String, also_print: bool) -> anyhow::Result<()> { if also_print { eprintln!("{message}"); } @@ -288,111 +280,104 @@ impl CrashInfoBuilder { } else { self.log_messages = Some(vec![message]); } - Ok(self) + Ok(()) } - pub fn with_log_messages(&mut self, log_messages: Vec) -> anyhow::Result<&mut Self> { + pub fn with_log_messages(&mut self, log_messages: Vec) -> anyhow::Result<()> { self.log_messages = Some(log_messages); - Ok(self) + Ok(()) } - pub fn with_message(&mut self, message: String) -> anyhow::Result<&mut Self> { - self.error.with_message(message)?; - Ok(self) + pub fn with_message(&mut self, message: String) -> anyhow::Result<()> { + self.error.with_message(message) } - pub fn with_metadata(&mut self, metadata: Metadata) -> anyhow::Result<&mut Self> { + pub fn with_metadata(&mut self, metadata: Metadata) -> anyhow::Result<()> { self.metadata = Some(metadata); - Ok(self) + Ok(()) } - pub fn with_os_info(&mut self, os_info: OsInfo) -> anyhow::Result<&mut Self> { + pub fn with_os_info(&mut self, os_info: OsInfo) -> anyhow::Result<()> { self.os_info = Some(os_info); - Ok(self) + Ok(()) } - pub fn with_os_info_this_machine(&mut self) -> anyhow::Result<&mut Self> { - self.with_os_info(::os_info::get().into()) + pub fn with_os_info_this_machine(&mut self) -> anyhow::Result<()> { + self.with_os_info(::os_info::get().into())?; + Ok(()) } - pub fn with_proc_info(&mut self, proc_info: ProcInfo) -> anyhow::Result<&mut Self> { + pub fn with_proc_info(&mut self, proc_info: ProcInfo) -> anyhow::Result<()> { self.proc_info = Some(proc_info); - Ok(self) + Ok(()) } - pub fn with_sig_info(&mut self, sig_info: SigInfo) -> anyhow::Result<&mut Self> { + pub fn with_sig_info(&mut self, sig_info: SigInfo) -> anyhow::Result<()> { self.sig_info = Some(sig_info); - Ok(self) + Ok(()) } - pub fn with_span_id(&mut self, span_id: Span) -> anyhow::Result<&mut Self> { + pub fn with_span_id(&mut self, span_id: Span) -> anyhow::Result<()> { if let Some(ref mut span_ids) = &mut self.span_ids { span_ids.push(span_id); } else { self.span_ids = Some(vec![span_id]); } - Ok(self) + Ok(()) } - pub fn with_span_ids(&mut self, span_ids: Vec) -> anyhow::Result<&mut Self> { + pub fn with_span_ids(&mut self, span_ids: Vec) -> anyhow::Result<()> { self.span_ids = Some(span_ids); - Ok(self) + Ok(()) } - pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<&mut Self> { - self.error.with_stack(stack)?; - Ok(self) + pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<()> { + self.error.with_stack(stack) } - pub fn with_stack_frame( - &mut self, - frame: StackFrame, - incomplete: bool, - ) -> anyhow::Result<&mut Self> { - self.error.with_stack_frame(frame, incomplete)?; - Ok(self) + pub fn with_stack_frame(&mut self, frame: StackFrame, incomplete: bool) -> anyhow::Result<()> { + self.error.with_stack_frame(frame, incomplete) } - pub fn with_stack_set_complete(&mut self) -> anyhow::Result<&mut Self> { - self.error.with_stack_set_complete()?; - Ok(self) + pub fn with_stack_set_complete(&mut self) -> anyhow::Result<()> { + self.error.with_stack_set_complete() } - pub fn with_thread(&mut self, thread: ThreadData) -> anyhow::Result<&mut Self> { + pub fn with_thread(&mut self, thread: ThreadData) -> anyhow::Result<()> { if let Some(ref mut threads) = &mut self.error.threads { threads.push(thread); } else { self.error.threads = Some(vec![thread]); } - Ok(self) + Ok(()) } - pub fn with_threads(&mut self, threads: Vec) -> anyhow::Result<&mut Self> { - self.error.with_threads(threads)?; - Ok(self) + pub fn with_threads(&mut self, threads: Vec) -> anyhow::Result<()> { + self.error.with_threads(threads) } - pub fn with_timestamp(&mut self, timestamp: DateTime) -> anyhow::Result<&mut Self> { + pub fn with_timestamp(&mut self, timestamp: DateTime) -> anyhow::Result<()> { self.timestamp = Some(timestamp); - Ok(self) + Ok(()) } - pub fn with_timestamp_now(&mut self) -> anyhow::Result<&mut Self> { - self.with_timestamp(Utc::now()) + pub fn with_timestamp_now(&mut self) -> anyhow::Result<()> { + self.with_timestamp(Utc::now())?; + Ok(()) } - pub fn with_trace_id(&mut self, trace_id: Span) -> anyhow::Result<&mut Self> { + pub fn with_trace_id(&mut self, trace_id: Span) -> anyhow::Result<()> { if let Some(ref mut trace_ids) = &mut self.trace_ids { trace_ids.push(trace_id); } else { self.trace_ids = Some(vec![trace_id]); } - Ok(self) + Ok(()) } - pub fn with_trace_ids(&mut self, trace_ids: Vec) -> anyhow::Result<&mut Self> { + pub fn with_trace_ids(&mut self, trace_ids: Vec) -> anyhow::Result<()> { self.trace_ids = Some(trace_ids); - Ok(self) + Ok(()) } /// This method requires that the builder has a UUID and metadata set. diff --git a/libdd-crashtracker/src/crash_info/cxx.rs b/libdd-crashtracker/src/crash_info/cxx.rs new file mode 100644 index 0000000000..dc24812bbf --- /dev/null +++ b/libdd-crashtracker/src/crash_info/cxx.rs @@ -0,0 +1,260 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! CXX bindings for crash_info module - provides a safe and idiomatic C++ API + +use super::builder::CrashInfoBuilder; +use super::stacktrace::{StackFrame, StackTrace}; +use super::CrashInfo; +use crate::{BuildIdType, FileType, Metadata}; + +// ============================================================================ +// CXX Bridge - C++ Bindings +// ============================================================================ + +#[cxx::bridge(namespace = "datadog::crashtracker")] +pub mod ffi { + // Shared enums + #[repr(u32)] + enum ErrorKind { + Panic = 0, + UnhandledException = 1, + UnixSignal = 2, + } + + enum BuildIdType { + GNU, + GO, + PDB, + SHA1, + } + + enum FileType { + APK, + ELF, + PE, + } + + // Shared structs + struct Metadata { + library_name: String, + library_version: String, + family: String, + tags: Vec, + } + + struct ProcInfo { + pid: u32, + } + + struct OsInfo { + architecture: String, + bitness: String, + os_type: String, + version: String, + } + + // Opaque Rust types + extern "Rust" { + type CrashInfoBuilder; + type StackFrame; + type StackTrace; + type CrashInfo; + + // Static factory methods + #[Self = "CrashInfoBuilder"] + fn create() -> Box; + + #[Self = "StackFrame"] + fn create() -> Box; + + #[Self = "StackTrace"] + fn create() -> Box; + + // CrashInfoBuilder methods - need wrappers for type conversion + fn set_kind(self: &mut CrashInfoBuilder, kind: ErrorKind) -> Result<()>; + fn set_metadata(self: &mut CrashInfoBuilder, metadata: Metadata) -> Result<()>; + fn set_proc_info(self: &mut CrashInfoBuilder, proc_info: ProcInfo) -> Result<()>; + fn set_os_info(self: &mut CrashInfoBuilder, os_info: OsInfo) -> Result<()>; + fn add_stack(self: &mut CrashInfoBuilder, stack: Box) -> Result<()>; + fn add_stack_frame( + self: &mut CrashInfoBuilder, + frame: Box, + incomplete: bool, + ) -> Result<()>; + + // CrashInfoBuilder methods - exposed directly (no conversion needed) + fn with_message(self: &mut CrashInfoBuilder, message: String) -> Result<()>; + fn with_fingerprint(self: &mut CrashInfoBuilder, fingerprint: String) -> Result<()>; + fn with_incomplete(self: &mut CrashInfoBuilder, incomplete: bool) -> Result<()>; + fn with_counter(self: &mut CrashInfoBuilder, name: String, value: i64) -> Result<()>; + fn with_log_message( + self: &mut CrashInfoBuilder, + message: String, + also_print: bool, + ) -> Result<()>; + fn with_file(self: &mut CrashInfoBuilder, filename: String) -> Result<()>; + fn with_timestamp_now(self: &mut CrashInfoBuilder) -> Result<()>; + fn with_os_info_this_machine(self: &mut CrashInfoBuilder) -> Result<()>; + fn with_stack_set_complete(self: &mut CrashInfoBuilder) -> Result<()>; + + // Build function + fn crashinfo_build(builder: Box) -> Result>; + + // StackFrame methods - wrappers for FFI type conversion + fn build_id_type(self: &mut StackFrame, build_id_type: BuildIdType); + fn file_type(self: &mut StackFrame, file_type: FileType); + + // StackFrame methods - exposed directly + fn with_ip(self: &mut StackFrame, ip: usize); + fn with_module_base_address(self: &mut StackFrame, addr: usize); + fn with_sp(self: &mut StackFrame, sp: usize); + fn with_symbol_address(self: &mut StackFrame, addr: usize); + fn with_build_id(self: &mut StackFrame, build_id: String); + fn with_path(self: &mut StackFrame, path: String); + fn with_relative_address(self: &mut StackFrame, addr: usize); + fn with_function(self: &mut StackFrame, function: String); + fn with_file(self: &mut StackFrame, file: String); + fn with_line(self: &mut StackFrame, line: u32); + fn with_column(self: &mut StackFrame, column: u32); + + // StackTrace methods + fn add_frame(self: &mut StackTrace, frame: Box, incomplete: bool) + -> Result<()>; + #[cxx_name = "mark_complete"] + fn set_complete(self: &mut StackTrace) -> Result<()>; + + // CrashInfo methods + fn to_json(self: &CrashInfo) -> Result; + } +} + +// ============================================================================ +// Static Factory Methods +// ============================================================================ + +impl CrashInfoBuilder { + pub fn create() -> Box { + Box::new(CrashInfoBuilder::default()) + } +} + +impl StackFrame { + pub fn create() -> Box { + Box::new(StackFrame::new()) + } +} + +impl StackTrace { + pub fn create() -> Box { + Box::new(StackTrace::new_incomplete()) + } +} + +// ============================================================================ +// CrashInfoBuilder - Type Conversion Wrappers +// ============================================================================ + +impl CrashInfoBuilder { + pub fn set_kind(&mut self, kind: ffi::ErrorKind) -> anyhow::Result<()> { + let internal_kind = match kind { + ffi::ErrorKind::Panic => crate::ErrorKind::Panic, + ffi::ErrorKind::UnhandledException => crate::ErrorKind::UnhandledException, + ffi::ErrorKind::UnixSignal => crate::ErrorKind::UnixSignal, + _ => anyhow::bail!("Unknown error kind"), + }; + self.with_kind(internal_kind) + } + + pub fn set_metadata(&mut self, metadata: ffi::Metadata) -> anyhow::Result<()> { + let internal_metadata = Metadata::new( + metadata.library_name, + metadata.library_version, + metadata.family, + metadata.tags, + ); + self.with_metadata(internal_metadata) + } + + pub fn set_proc_info(&mut self, proc_info: ffi::ProcInfo) -> anyhow::Result<()> { + let internal_proc_info = crate::ProcInfo { pid: proc_info.pid }; + self.with_proc_info(internal_proc_info) + } + + pub fn set_os_info(&mut self, os_info: ffi::OsInfo) -> anyhow::Result<()> { + let internal_os_info = crate::OsInfo { + architecture: os_info.architecture, + bitness: os_info.bitness, + os_type: os_info.os_type, + version: os_info.version, + }; + self.with_os_info(internal_os_info) + } + + #[allow(clippy::boxed_local)] + pub fn add_stack(&mut self, stack: Box) -> anyhow::Result<()> { + self.with_stack(*stack) + } + + #[allow(clippy::boxed_local)] + pub fn add_stack_frame( + &mut self, + frame: Box, + incomplete: bool, + ) -> anyhow::Result<()> { + self.with_stack_frame(*frame, incomplete) + } +} + +pub fn crashinfo_build(builder: Box) -> anyhow::Result> { + Ok(Box::new(builder.build()?)) +} + +// ============================================================================ +// StackFrame - Type Conversion Wrappers +// ============================================================================ + +impl StackFrame { + pub fn build_id_type(&mut self, build_id_type: ffi::BuildIdType) { + let internal_type = match build_id_type { + ffi::BuildIdType::GNU => BuildIdType::GNU, + ffi::BuildIdType::GO => BuildIdType::GO, + ffi::BuildIdType::PDB => BuildIdType::PDB, + ffi::BuildIdType::SHA1 => BuildIdType::SHA1, + _ => return, + }; + self.set_build_id_type(internal_type); + } + + pub fn file_type(&mut self, file_type: ffi::FileType) { + let internal_type = match file_type { + ffi::FileType::APK => FileType::APK, + ffi::FileType::ELF => FileType::ELF, + ffi::FileType::PE => FileType::PE, + _ => return, + }; + self.set_file_type(internal_type); + } +} + +// ============================================================================ +// StackTrace - Wrapper Methods +// ============================================================================ + +impl StackTrace { + // Wrapper to unbox the frame parameter since CXX requires Box for opaque types + #[allow(clippy::boxed_local)] + pub fn add_frame(&mut self, frame: Box, incomplete: bool) -> anyhow::Result<()> { + StackTrace::push_frame(self, *frame, incomplete) + } +} + +// ============================================================================ +// CrashInfo - Utility Functions +// ============================================================================ + +impl CrashInfo { + pub fn to_json(&self) -> anyhow::Result { + Ok(serde_json::to_string_pretty(self)?) + } +} diff --git a/libdd-crashtracker/src/crash_info/mod.rs b/libdd-crashtracker/src/crash_info/mod.rs index b1cbe5900b..acb5df93c3 100644 --- a/libdd-crashtracker/src/crash_info/mod.rs +++ b/libdd-crashtracker/src/crash_info/mod.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 mod builder; +#[cfg(feature = "cxx")] +pub mod cxx; mod error_data; mod errors_intake; mod experimental; diff --git a/libdd-crashtracker/src/crash_info/stacktrace.rs b/libdd-crashtracker/src/crash_info/stacktrace.rs index f4972c50c9..337c908c57 100644 --- a/libdd-crashtracker/src/crash_info/stacktrace.rs +++ b/libdd-crashtracker/src/crash_info/stacktrace.rs @@ -249,6 +249,58 @@ impl StackFrame { } Ok(()) } + + pub fn set_build_id_type(&mut self, build_id_type: BuildIdType) { + self.build_id_type = Some(build_id_type); + } + + pub fn set_file_type(&mut self, file_type: FileType) { + self.file_type = Some(file_type); + } + + pub fn with_ip(&mut self, ip: usize) { + self.ip = Some(format!("0x{:x}", ip)); + } + + pub fn with_module_base_address(&mut self, addr: usize) { + self.module_base_address = Some(format!("0x{:x}", addr)); + } + + pub fn with_sp(&mut self, sp: usize) { + self.sp = Some(format!("0x{:x}", sp)); + } + + pub fn with_symbol_address(&mut self, addr: usize) { + self.symbol_address = Some(format!("0x{:x}", addr)); + } + + pub fn with_build_id(&mut self, build_id: String) { + self.build_id = Some(build_id); + } + + pub fn with_path(&mut self, path: String) { + self.path = Some(path); + } + + pub fn with_relative_address(&mut self, addr: usize) { + self.relative_address = Some(format!("0x{:x}", addr)); + } + + pub fn with_function(&mut self, function: String) { + self.function = Some(function); + } + + pub fn with_file(&mut self, file: String) { + self.file = Some(file); + } + + pub fn with_line(&mut self, line: u32) { + self.line = Some(line); + } + + pub fn with_column(&mut self, column: u32) { + self.column = Some(column); + } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] diff --git a/libdd-crashtracker/src/receiver/receive_report.rs b/libdd-crashtracker/src/receiver/receive_report.rs index f16a24e176..a976b9e2f5 100644 --- a/libdd-crashtracker/src/receiver/receive_report.rs +++ b/libdd-crashtracker/src/receiver/receive_report.rs @@ -189,11 +189,10 @@ fn process_line( sig_info.si_code_human_readable, sig_info.si_signo_human_readable ); - builder - .with_timestamp_now()? - .with_sig_info(sig_info)? - .with_incomplete(true)? - .with_message(message)?; + builder.with_timestamp_now()?; + builder.with_sig_info(sig_info)?; + builder.with_incomplete(true)?; + builder.with_message(message)?; StdinState::SigInfo } From 299a56767ec3bf0726524414a5a500ca03465921 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Wed, 3 Dec 2025 02:08:43 -0500 Subject: [PATCH 02/11] fix cbindgen issue --- libdd-crashtracker-ffi/cbindgen.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libdd-crashtracker-ffi/cbindgen.toml b/libdd-crashtracker-ffi/cbindgen.toml index 50cba8f0ad..773064854a 100644 --- a/libdd-crashtracker-ffi/cbindgen.toml +++ b/libdd-crashtracker-ffi/cbindgen.toml @@ -71,3 +71,4 @@ must_use = "DDOG_CHECK_RETURN" [parse] parse_deps = true include = ["libdd-common", "libdd-common-ffi", "libdd-crashtracker", "ux"] +exclude = ["libdd_crashtracker::crash_info::cxx"] From f1bb1b2a6135cd3e1d34c9a414cb47ace1e01513 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Wed, 3 Dec 2025 13:00:49 -0500 Subject: [PATCH 03/11] rename enums to avoid CI issue --- examples/cxx/crashinfo.cpp | 10 +++--- libdd-crashtracker/src/crash_info/cxx.rs | 40 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/cxx/crashinfo.cpp b/examples/cxx/crashinfo.cpp index af486498f3..03f7ae1db3 100644 --- a/examples/cxx/crashinfo.cpp +++ b/examples/cxx/crashinfo.cpp @@ -10,7 +10,7 @@ int main() { auto builder = CrashInfoBuilder::create(); - builder->set_kind(ErrorKind::Panic); + builder->set_kind(CxxErrorKind::Panic); builder->with_message("Example crash message"); builder->with_counter("my_counter", 42); builder->with_log_message("This is a log message", true); @@ -57,8 +57,8 @@ int main() { win_frame->with_ip(0xDEADBEEF); win_frame->with_module_base_address(0xABBABABA); win_frame->with_build_id("abcdef123456"); - win_frame->build_id_type(BuildIdType::PDB); - win_frame->file_type(FileType::PE); + win_frame->build_id_type(CxxBuildIdType::PDB); + win_frame->file_type(CxxFileType::PE); win_frame->with_path("C:/Program Files/example.exe"); win_frame->with_relative_address(0xBABEF00D); stacktrace->add_frame(std::move(win_frame), true); @@ -67,8 +67,8 @@ int main() { auto elf_frame = StackFrame::create(); elf_frame->with_ip(0xCAFEBABE); elf_frame->with_build_id("fedcba987654321"); - elf_frame->build_id_type(BuildIdType::GNU); - elf_frame->file_type(FileType::ELF); + elf_frame->build_id_type(CxxBuildIdType::GNU); + elf_frame->file_type(CxxFileType::ELF); elf_frame->with_path("/usr/lib/libexample.so"); elf_frame->with_relative_address(0xF00DFACE); stacktrace->add_frame(std::move(elf_frame), true); diff --git a/libdd-crashtracker/src/crash_info/cxx.rs b/libdd-crashtracker/src/crash_info/cxx.rs index dc24812bbf..3690e27acd 100644 --- a/libdd-crashtracker/src/crash_info/cxx.rs +++ b/libdd-crashtracker/src/crash_info/cxx.rs @@ -14,22 +14,22 @@ use crate::{BuildIdType, FileType, Metadata}; #[cxx::bridge(namespace = "datadog::crashtracker")] pub mod ffi { - // Shared enums + // Shared enums (renamed to avoid cbindgen conflicts) #[repr(u32)] - enum ErrorKind { + enum CxxErrorKind { Panic = 0, UnhandledException = 1, UnixSignal = 2, } - enum BuildIdType { + enum CxxBuildIdType { GNU, GO, PDB, SHA1, } - enum FileType { + enum CxxFileType { APK, ELF, PE, @@ -72,7 +72,7 @@ pub mod ffi { fn create() -> Box; // CrashInfoBuilder methods - need wrappers for type conversion - fn set_kind(self: &mut CrashInfoBuilder, kind: ErrorKind) -> Result<()>; + fn set_kind(self: &mut CrashInfoBuilder, kind: CxxErrorKind) -> Result<()>; fn set_metadata(self: &mut CrashInfoBuilder, metadata: Metadata) -> Result<()>; fn set_proc_info(self: &mut CrashInfoBuilder, proc_info: ProcInfo) -> Result<()>; fn set_os_info(self: &mut CrashInfoBuilder, os_info: OsInfo) -> Result<()>; @@ -102,8 +102,8 @@ pub mod ffi { fn crashinfo_build(builder: Box) -> Result>; // StackFrame methods - wrappers for FFI type conversion - fn build_id_type(self: &mut StackFrame, build_id_type: BuildIdType); - fn file_type(self: &mut StackFrame, file_type: FileType); + fn build_id_type(self: &mut StackFrame, build_id_type: CxxBuildIdType); + fn file_type(self: &mut StackFrame, file_type: CxxFileType); // StackFrame methods - exposed directly fn with_ip(self: &mut StackFrame, ip: usize); @@ -156,11 +156,11 @@ impl StackTrace { // ============================================================================ impl CrashInfoBuilder { - pub fn set_kind(&mut self, kind: ffi::ErrorKind) -> anyhow::Result<()> { + pub fn set_kind(&mut self, kind: ffi::CxxErrorKind) -> anyhow::Result<()> { let internal_kind = match kind { - ffi::ErrorKind::Panic => crate::ErrorKind::Panic, - ffi::ErrorKind::UnhandledException => crate::ErrorKind::UnhandledException, - ffi::ErrorKind::UnixSignal => crate::ErrorKind::UnixSignal, + ffi::CxxErrorKind::Panic => crate::ErrorKind::Panic, + ffi::CxxErrorKind::UnhandledException => crate::ErrorKind::UnhandledException, + ffi::CxxErrorKind::UnixSignal => crate::ErrorKind::UnixSignal, _ => anyhow::bail!("Unknown error kind"), }; self.with_kind(internal_kind) @@ -215,22 +215,22 @@ pub fn crashinfo_build(builder: Box) -> anyhow::Result BuildIdType::GNU, - ffi::BuildIdType::GO => BuildIdType::GO, - ffi::BuildIdType::PDB => BuildIdType::PDB, - ffi::BuildIdType::SHA1 => BuildIdType::SHA1, + ffi::CxxBuildIdType::GNU => BuildIdType::GNU, + ffi::CxxBuildIdType::GO => BuildIdType::GO, + ffi::CxxBuildIdType::PDB => BuildIdType::PDB, + ffi::CxxBuildIdType::SHA1 => BuildIdType::SHA1, _ => return, }; self.set_build_id_type(internal_type); } - pub fn file_type(&mut self, file_type: ffi::FileType) { + pub fn file_type(&mut self, file_type: ffi::CxxFileType) { let internal_type = match file_type { - ffi::FileType::APK => FileType::APK, - ffi::FileType::ELF => FileType::ELF, - ffi::FileType::PE => FileType::PE, + ffi::CxxFileType::APK => FileType::APK, + ffi::CxxFileType::ELF => FileType::ELF, + ffi::CxxFileType::PE => FileType::PE, _ => return, }; self.set_file_type(internal_type); From e9244861e52f16f6f6e7f1ac7dfb2d8e550ac5d2 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Wed, 3 Dec 2025 14:42:47 -0500 Subject: [PATCH 04/11] run in ci --- .github/workflows/test.yml | 7 +++++++ examples/cxx/build-and-run.sh | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 121f1e9aba..e2b6064d6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -234,6 +234,13 @@ jobs: cmake -S .. -DDatadog_ROOT=$LIBDD_OUTPUT_FOLDER cmake --build . fi + - name: "Test building CXX bindings" + shell: bash + if: matrix.platform != 'windows-latest' + run: | + set -e + cd examples/cxx + ./build-and-run.sh cross-centos7: name: build and test using cross - on centos7 diff --git a/examples/cxx/build-and-run.sh b/examples/cxx/build-and-run.sh index 4868aed06c..70fe424e19 100755 --- a/examples/cxx/build-and-run.sh +++ b/examples/cxx/build-and-run.sh @@ -44,6 +44,13 @@ echo "šŸ“š Crashtracker library: $CRASHTRACKER_LIB" echo "šŸ“š CXX bridge library: $CXX_BRIDGE_LIB" echo "šŸ”Ø Compiling C++ example..." +# Platform-specific linker flags +if [[ "$OSTYPE" == "darwin"* ]]; then + PLATFORM_LIBS="-framework Security -framework CoreFoundation" +else + PLATFORM_LIBS="" +fi + c++ -std=c++14 \ -I"$CXX_BRIDGE_INCLUDE" \ -I"$CXX_BRIDGE_CRATE" \ @@ -52,7 +59,7 @@ c++ -std=c++14 \ examples/cxx/crashinfo.cpp \ "$CRASHTRACKER_LIB" \ "$CXX_BRIDGE_LIB" \ - -lpthread -ldl -framework Security -framework CoreFoundation \ + -lpthread -ldl $PLATFORM_LIBS \ -o examples/cxx/crashinfo echo "šŸš€ Running example..." From c6440a6c71a96c6275be2cc99a89e6fa5820a918 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 11:52:07 -0500 Subject: [PATCH 05/11] windows ci --- .github/workflows/test.yml | 8 ++- examples/cxx/README.md | 22 ++++++- examples/cxx/build-and-run.ps1 | 106 +++++++++++++++++++++++++++++++++ libdd-crashtracker/build.rs | 24 +++++--- 4 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 examples/cxx/build-and-run.ps1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e2b6064d6e..4e7ba2d3af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -234,13 +234,19 @@ jobs: cmake -S .. -DDatadog_ROOT=$LIBDD_OUTPUT_FOLDER cmake --build . fi - - name: "Test building CXX bindings" + - name: "Test building CXX bindings (Unix)" shell: bash if: matrix.platform != 'windows-latest' run: | set -e cd examples/cxx ./build-and-run.sh + - name: "Test building CXX bindings (Windows)" + shell: pwsh + if: matrix.platform == 'windows-latest' + run: | + cd examples/cxx + .\build-and-run.ps1 cross-centos7: name: build and test using cross - on centos7 diff --git a/examples/cxx/README.md b/examples/cxx/README.md index 87f5dffd17..713f637535 100644 --- a/examples/cxx/README.md +++ b/examples/cxx/README.md @@ -54,16 +54,31 @@ The CXX bindings provide access to: ## Building and Running +### Unix (Linux/macOS) + The `build-and-run.sh` script handles the entire build process: ```bash ./examples/cxx/build-and-run.sh ``` -This will: +### Windows + +The `build-and-run.ps1` PowerShell script handles the build process on Windows: + +```powershell +.\examples\cxx\build-and-run.ps1 +``` + +**Prerequisites for Windows:** +- Either MSVC (via Visual Studio) or MinGW/LLVM with C++ compiler +- PowerShell 5.0 or later (comes with Windows 10+) +- Rust toolchain + +The build script will: 1. Build libdd-crashtracker with the `cxx` feature enabled 2. Find the CXX bridge headers and libraries -3. Compile the C++ example +3. Compile the C++ example (automatically detects MSVC or MinGW/Clang) 4. Run the example and display the output ## Example Output @@ -99,4 +114,5 @@ The CXX bindings provide several advantages over the traditional C FFI: - C++14 or later - Rust toolchain -- macOS (this example) or Linux +- Platform: macOS, Linux, or Windows + - Windows: Requires MSVC (via Visual Studio) or MinGW/LLVM diff --git a/examples/cxx/build-and-run.ps1 b/examples/cxx/build-and-run.ps1 new file mode 100644 index 0000000000..284a90f094 --- /dev/null +++ b/examples/cxx/build-and-run.ps1 @@ -0,0 +1,106 @@ +# Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +# SPDX-License-Identifier: Apache-2.0 + +# Build and run the CXX crashinfo example on Windows +$ErrorActionPreference = "Stop" + +$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path +$PROJECT_ROOT = (Get-Item (Join-Path $SCRIPT_DIR ".." "..")).FullName +Set-Location $PROJECT_ROOT + +Write-Host "šŸ”Ø Building libdd-crashtracker with cxx feature..." -ForegroundColor Cyan +cargo build -p libdd-crashtracker --features cxx --release + +Write-Host "šŸ” Finding CXX bridge headers..." -ForegroundColor Cyan +$CXX_BRIDGE_INCLUDE = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out\cxxbridge\include" -Directory -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName +$CXX_BRIDGE_CRATE = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out\cxxbridge\crate" -Directory -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName +$RUST_CXX_INCLUDE = Get-ChildItem -Path "target\release\build\cxx-*\out" -Directory -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + +if (-not $CXX_BRIDGE_INCLUDE -or -not $CXX_BRIDGE_CRATE -or -not $RUST_CXX_INCLUDE) { + Write-Host "āŒ Error: Could not find CXX bridge directories" -ForegroundColor Red + exit 1 +} + +Write-Host "šŸ“ CXX include: $CXX_BRIDGE_INCLUDE" -ForegroundColor Green +Write-Host "šŸ“ CXX crate: $CXX_BRIDGE_CRATE" -ForegroundColor Green +Write-Host "šŸ“ Rust CXX: $RUST_CXX_INCLUDE" -ForegroundColor Green + +Write-Host "šŸ”Ø Finding libraries..." -ForegroundColor Cyan +$CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.lib" +$CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.lib" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + +if (-not (Test-Path $CRASHTRACKER_LIB)) { + Write-Host "āŒ Error: Could not find libdd-crashtracker library at $CRASHTRACKER_LIB" -ForegroundColor Red + exit 1 +} + +if (-not $CXX_BRIDGE_LIB) { + Write-Host "āŒ Error: Could not find CXX bridge library" -ForegroundColor Red + exit 1 +} + +Write-Host "šŸ“š Crashtracker library: $CRASHTRACKER_LIB" -ForegroundColor Green +Write-Host "šŸ“š CXX bridge library: $CXX_BRIDGE_LIB" -ForegroundColor Green + +Write-Host "šŸ”Ø Compiling C++ example..." -ForegroundColor Cyan + +# Check if we have MSVC (cl.exe) or MinGW (g++/clang++) +$MSVC = Get-Command cl.exe -ErrorAction SilentlyContinue +$GPP = Get-Command g++.exe -ErrorAction SilentlyContinue +$CLANGPP = Get-Command clang++.exe -ErrorAction SilentlyContinue + +if ($MSVC) { + Write-Host "Using MSVC compiler" -ForegroundColor Yellow + + # MSVC compilation + cl.exe /std:c++14 /EHsc ` + /I"$CXX_BRIDGE_INCLUDE" ` + /I"$CXX_BRIDGE_CRATE" ` + /I"$RUST_CXX_INCLUDE" ` + /I"$PROJECT_ROOT" ` + examples\cxx\crashinfo.cpp ` + "$CRASHTRACKER_LIB" ` + "$CXX_BRIDGE_LIB" ` + ws2_32.lib advapi32.lib userenv.lib ntdll.lib bcrypt.lib ` + /Fe:examples\cxx\crashinfo.exe + + if ($LASTEXITCODE -ne 0) { + Write-Host "āŒ Compilation failed" -ForegroundColor Red + exit 1 + } +} elseif ($GPP -or $CLANGPP) { + $COMPILER = if ($GPP) { "g++" } else { "clang++" } + Write-Host "Using $COMPILER compiler" -ForegroundColor Yellow + + # MinGW/Clang compilation + & $COMPILER -std=c++14 ` + -I"$CXX_BRIDGE_INCLUDE" ` + -I"$CXX_BRIDGE_CRATE" ` + -I"$RUST_CXX_INCLUDE" ` + -I"$PROJECT_ROOT" ` + examples/cxx/crashinfo.cpp ` + "$CRASHTRACKER_LIB" ` + "$CXX_BRIDGE_LIB" ` + -lws2_32 -ladvapi32 -luserenv -lntdll -lbcrypt ` + -o examples/cxx/crashinfo.exe + + if ($LASTEXITCODE -ne 0) { + Write-Host "āŒ Compilation failed" -ForegroundColor Red + exit 1 + } +} else { + Write-Host "āŒ Error: No C++ compiler found. Please install MSVC (via Visual Studio) or MinGW/LLVM" -ForegroundColor Red + exit 1 +} + +Write-Host "šŸš€ Running example..." -ForegroundColor Cyan +& ".\examples\cxx\crashinfo.exe" + +if ($LASTEXITCODE -ne 0) { + Write-Host "āŒ Example failed with exit code $LASTEXITCODE" -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "āœ… Success!" -ForegroundColor Green + diff --git a/libdd-crashtracker/build.rs b/libdd-crashtracker/build.rs index ef3a215524..8b017a2edc 100644 --- a/libdd-crashtracker/build.rs +++ b/libdd-crashtracker/build.rs @@ -12,6 +12,16 @@ use unix_imports::*; pub use libdd_common::cc_utils::cc; +// Build CXX bridge - cross-platform function +#[cfg(feature = "cxx")] +fn build_cxx_bridge() { + cxx_build::bridge("src/crash_info/cxx.rs") + .flag_if_supported("-std=c++14") + .compile("libdd-crashtracker-cxx"); + + println!("cargo:rerun-if-changed=src/crash_info/cxx.rs"); +} + #[cfg(unix)] fn build_shared_libs() { build_c_file(); @@ -105,13 +115,7 @@ fn main() { // Build CXX bridge if feature is enabled #[cfg(feature = "cxx")] - { - cxx_build::bridge("src/crash_info/cxx.rs") - .flag_if_supported("-std=c++14") - .compile("libdd-crashtracker-cxx"); - - println!("cargo:rerun-if-changed=src/crash_info/cxx.rs"); - } + build_cxx_bridge(); // Don't build test libraries during `cargo publish` verification. // During verification, the package is unpacked to target/package/ and built there. @@ -129,4 +133,8 @@ fn main() { } #[cfg(not(unix))] -fn main() {} +fn main() { + // Build CXX bridge if feature is enabled + #[cfg(feature = "cxx")] + build_cxx_bridge(); +} From aa3b9def6df8349185cd363788f9b381e8ddb098 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 12:10:29 -0500 Subject: [PATCH 06/11] C++ 20 --- .github/workflows/test.yml | 4 +-- examples/cxx/README.md | 10 +++--- ...nd-run.ps1 => build-and-run-crashinfo.ps1} | 4 +-- ...-and-run.sh => build-and-run-crashinfo.sh} | 2 +- examples/cxx/crashinfo.cpp | 32 ++++++++----------- libdd-crashtracker/build.rs | 2 +- 6 files changed, 24 insertions(+), 30 deletions(-) rename examples/cxx/{build-and-run.ps1 => build-and-run-crashinfo.ps1} (98%) rename examples/cxx/{build-and-run.sh => build-and-run-crashinfo.sh} (99%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e7ba2d3af..73dbecc689 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -240,13 +240,13 @@ jobs: run: | set -e cd examples/cxx - ./build-and-run.sh + ./build-and-run-crashinfo.sh - name: "Test building CXX bindings (Windows)" shell: pwsh if: matrix.platform == 'windows-latest' run: | cd examples/cxx - .\build-and-run.ps1 + .\build-and-run-crashinfo.ps1 cross-centos7: name: build and test using cross - on centos7 diff --git a/examples/cxx/README.md b/examples/cxx/README.md index 713f637535..82f0db4467 100644 --- a/examples/cxx/README.md +++ b/examples/cxx/README.md @@ -56,18 +56,18 @@ The CXX bindings provide access to: ### Unix (Linux/macOS) -The `build-and-run.sh` script handles the entire build process: +The `build-and-run-crashinfo.sh` script handles the entire build process: ```bash -./examples/cxx/build-and-run.sh +./examples/cxx/build-and-run-crashinfo.sh ``` ### Windows -The `build-and-run.ps1` PowerShell script handles the build process on Windows: +The `build-and-run-crashinfo.ps1` PowerShell script handles the build process on Windows: ```powershell -.\examples\cxx\build-and-run.ps1 +.\examples\cxx\build-and-run-crashinfo.ps1 ``` **Prerequisites for Windows:** @@ -112,7 +112,7 @@ The CXX bindings provide several advantages over the traditional C FFI: ## Requirements -- C++14 or later +- C++20 or later - Rust toolchain - Platform: macOS, Linux, or Windows - Windows: Requires MSVC (via Visual Studio) or MinGW/LLVM diff --git a/examples/cxx/build-and-run.ps1 b/examples/cxx/build-and-run-crashinfo.ps1 similarity index 98% rename from examples/cxx/build-and-run.ps1 rename to examples/cxx/build-and-run-crashinfo.ps1 index 284a90f094..4a2672a165 100644 --- a/examples/cxx/build-and-run.ps1 +++ b/examples/cxx/build-and-run-crashinfo.ps1 @@ -53,7 +53,7 @@ if ($MSVC) { Write-Host "Using MSVC compiler" -ForegroundColor Yellow # MSVC compilation - cl.exe /std:c++14 /EHsc ` + cl.exe /std:c++20 /EHsc ` /I"$CXX_BRIDGE_INCLUDE" ` /I"$CXX_BRIDGE_CRATE" ` /I"$RUST_CXX_INCLUDE" ` @@ -73,7 +73,7 @@ if ($MSVC) { Write-Host "Using $COMPILER compiler" -ForegroundColor Yellow # MinGW/Clang compilation - & $COMPILER -std=c++14 ` + & $COMPILER -std=c++20 ` -I"$CXX_BRIDGE_INCLUDE" ` -I"$CXX_BRIDGE_CRATE" ` -I"$RUST_CXX_INCLUDE" ` diff --git a/examples/cxx/build-and-run.sh b/examples/cxx/build-and-run-crashinfo.sh similarity index 99% rename from examples/cxx/build-and-run.sh rename to examples/cxx/build-and-run-crashinfo.sh index 70fe424e19..acc1548889 100755 --- a/examples/cxx/build-and-run.sh +++ b/examples/cxx/build-and-run-crashinfo.sh @@ -51,7 +51,7 @@ else PLATFORM_LIBS="" fi -c++ -std=c++14 \ +c++ -std=c++20 \ -I"$CXX_BRIDGE_INCLUDE" \ -I"$CXX_BRIDGE_CRATE" \ -I"$RUST_CXX_INCLUDE" \ diff --git a/examples/cxx/crashinfo.cpp b/examples/cxx/crashinfo.cpp index 03f7ae1db3..2c62dd301e 100644 --- a/examples/cxx/crashinfo.cpp +++ b/examples/cxx/crashinfo.cpp @@ -17,27 +17,21 @@ int main() { builder->with_fingerprint("test-fingerprint-123"); builder->with_incomplete(false); - // Set metadata - Metadata metadata; - metadata.library_name = "libdatadog"; - metadata.library_version = "1.0.0"; - metadata.family = "rust"; - metadata.tags.push_back("service:example"); - metadata.tags.push_back("env:dev"); - builder->set_metadata(metadata); + builder->set_metadata(Metadata{ + .library_name = "libdatadog", + .library_version = "1.0.0", + .family = "rust", + .tags = {"service:example", "env:dev"} + }); - // Set process info - ProcInfo proc_info; - proc_info.pid = 12345; - builder->set_proc_info(proc_info); + builder->set_proc_info(ProcInfo{.pid = 12345}); - // Set OS info - OsInfo os_info; - os_info.architecture = "x86_64"; - os_info.bitness = "64"; - os_info.os_type = "Linux"; - os_info.version = "5.15.0"; - builder->set_os_info(os_info); + builder->set_os_info(OsInfo{ + .architecture = "x86_64", + .bitness = "64", + .os_type = "Linux", + .version = "5.15.0" + }); // Create a stack trace auto stacktrace = StackTrace::create(); diff --git a/libdd-crashtracker/build.rs b/libdd-crashtracker/build.rs index 8b017a2edc..f2ca83ab4d 100644 --- a/libdd-crashtracker/build.rs +++ b/libdd-crashtracker/build.rs @@ -16,7 +16,7 @@ pub use libdd_common::cc_utils::cc; #[cfg(feature = "cxx")] fn build_cxx_bridge() { cxx_build::bridge("src/crash_info/cxx.rs") - .flag_if_supported("-std=c++14") + .flag_if_supported("-std=c++20") .compile("libdd-crashtracker-cxx"); println!("cargo:rerun-if-changed=src/crash_info/cxx.rs"); From f9494bd4c7e520af5d2ad617b77efaba2643322d Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 12:13:10 -0500 Subject: [PATCH 07/11] try to fix windows ci --- examples/cxx/build-and-run-crashinfo.ps1 | 47 +++++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/examples/cxx/build-and-run-crashinfo.ps1 b/examples/cxx/build-and-run-crashinfo.ps1 index 4a2672a165..7445d70ec9 100644 --- a/examples/cxx/build-and-run-crashinfo.ps1 +++ b/examples/cxx/build-and-run-crashinfo.ps1 @@ -25,9 +25,32 @@ Write-Host "šŸ“ CXX include: $CXX_BRIDGE_INCLUDE" -ForegroundColor Green Write-Host "šŸ“ CXX crate: $CXX_BRIDGE_CRATE" -ForegroundColor Green Write-Host "šŸ“ Rust CXX: $RUST_CXX_INCLUDE" -ForegroundColor Green +# Check if we have MSVC (cl.exe) or MinGW (g++/clang++) +$MSVC = Get-Command cl.exe -ErrorAction SilentlyContinue +$GPP = Get-Command g++.exe -ErrorAction SilentlyContinue +$CLANGPP = Get-Command clang++.exe -ErrorAction SilentlyContinue + +# Determine library extension based on compiler +if ($MSVC) { + $LIB_EXT = ".lib" + $LIB_PREFIX = "" +} elseif ($GPP -or $CLANGPP) { + $LIB_EXT = ".a" + $LIB_PREFIX = "lib" +} else { + Write-Host "āŒ Error: No C++ compiler found. Please install MSVC (via Visual Studio) or MinGW/LLVM" -ForegroundColor Red + exit 1 +} + Write-Host "šŸ”Ø Finding libraries..." -ForegroundColor Cyan -$CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.lib" -$CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.lib" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName +# Note: Rust always prefixes with 'lib' in the source, but Windows MSVC strips it +if ($MSVC) { + $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\dd_crashtracker.lib" + $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.lib" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName +} else { + $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\liblibdd_crashtracker.a" + $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "liblibdd-crashtracker-cxx.a" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName +} if (-not (Test-Path $CRASHTRACKER_LIB)) { Write-Host "āŒ Error: Could not find libdd-crashtracker library at $CRASHTRACKER_LIB" -ForegroundColor Red @@ -35,7 +58,11 @@ if (-not (Test-Path $CRASHTRACKER_LIB)) { } if (-not $CXX_BRIDGE_LIB) { - Write-Host "āŒ Error: Could not find CXX bridge library" -ForegroundColor Red + if ($MSVC) { + Write-Host "āŒ Error: Could not find CXX bridge library (looking for libdd-crashtracker-cxx.lib)" -ForegroundColor Red + } else { + Write-Host "āŒ Error: Could not find CXX bridge library (looking for liblibdd-crashtracker-cxx.a)" -ForegroundColor Red + } exit 1 } @@ -44,11 +71,6 @@ Write-Host "šŸ“š CXX bridge library: $CXX_BRIDGE_LIB" -ForegroundColor Green Write-Host "šŸ”Ø Compiling C++ example..." -ForegroundColor Cyan -# Check if we have MSVC (cl.exe) or MinGW (g++/clang++) -$MSVC = Get-Command cl.exe -ErrorAction SilentlyContinue -$GPP = Get-Command g++.exe -ErrorAction SilentlyContinue -$CLANGPP = Get-Command clang++.exe -ErrorAction SilentlyContinue - if ($MSVC) { Write-Host "Using MSVC compiler" -ForegroundColor Yellow @@ -72,25 +94,22 @@ if ($MSVC) { $COMPILER = if ($GPP) { "g++" } else { "clang++" } Write-Host "Using $COMPILER compiler" -ForegroundColor Yellow - # MinGW/Clang compilation + # MinGW/Clang compilation - needs proper library ordering and Rust std lib & $COMPILER -std=c++20 ` -I"$CXX_BRIDGE_INCLUDE" ` -I"$CXX_BRIDGE_CRATE" ` -I"$RUST_CXX_INCLUDE" ` -I"$PROJECT_ROOT" ` examples/cxx/crashinfo.cpp ` - "$CRASHTRACKER_LIB" ` "$CXX_BRIDGE_LIB" ` - -lws2_32 -ladvapi32 -luserenv -lntdll -lbcrypt ` + "$CRASHTRACKER_LIB" ` + -lws2_32 -ladvapi32 -luserenv -lntdll -lbcrypt -lgcc_eh -lpthread ` -o examples/cxx/crashinfo.exe if ($LASTEXITCODE -ne 0) { Write-Host "āŒ Compilation failed" -ForegroundColor Red exit 1 } -} else { - Write-Host "āŒ Error: No C++ compiler found. Please install MSVC (via Visual Studio) or MinGW/LLVM" -ForegroundColor Red - exit 1 } Write-Host "šŸš€ Running example..." -ForegroundColor Cyan From 4860b1b1e24a90cb52fbed07838b96ad70eb74b8 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 12:14:51 -0500 Subject: [PATCH 08/11] fix readme --- examples/cxx/README.md | 63 +++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/examples/cxx/README.md b/examples/cxx/README.md index 82f0db4467..68ea54112f 100644 --- a/examples/cxx/README.md +++ b/examples/cxx/README.md @@ -20,37 +20,48 @@ The CXX bindings provide access to: - `BuildIdType` - Build ID format (GNU, GO, PDB, SHA1) - `FileType` - Binary file format (APK, ELF, PE) -### Key Functions - -**Builder Creation:** -- `crashinfo_builder_new()` - Create a new builder -- `stackframe_new()` - Create a new stack frame -- `stacktrace_new()` - Create a new stack trace - -**Builder Methods:** -- `crashinfo_with_kind()` - Set error type -- `crashinfo_with_message()` - Set error message -- `crashinfo_with_metadata()` - Set library metadata -- `crashinfo_with_proc_info()` - Set process info -- `crashinfo_with_os_info()` - Set OS info -- `crashinfo_with_counter()` - Add a named counter -- `crashinfo_with_file()` - Add a file to the report -- `crashinfo_with_stack()` - Set the stack trace -- `crashinfo_with_timestamp_now()` - Set current timestamp -- `crashinfo_build()` - Build the final CrashInfo +### Key API + +**Object Creation:** +```cpp +auto builder = CrashInfoBuilder::create(); +auto frame = StackFrame::create(); +auto stacktrace = StackTrace::create(); +``` + +**CrashInfoBuilder Methods:** +- `set_kind(CxxErrorKind)` - Set error type (Panic, UnhandledException, UnixSignal) +- `with_message(String)` - Set error message +- `with_counter(String, i64)` - Add a named counter +- `with_log_message(String, bool)` - Add a log message +- `with_fingerprint(String)` - Set crash fingerprint +- `with_incomplete(bool)` - Mark as incomplete +- `set_metadata(Metadata)` - Set library metadata +- `set_proc_info(ProcInfo)` - Set process information +- `set_os_info(OsInfo)` - Set OS information +- `add_stack(Box)` - Add a stack trace +- `with_timestamp_now()` - Set current timestamp +- `with_file(String)` - Add a file to the report **StackFrame Methods:** -- `stackframe_with_function()`, `stackframe_with_file()`, `stackframe_with_line()`, `stackframe_with_column()` - Set debug info -- `stackframe_with_ip()`, `stackframe_with_sp()` - Set absolute addresses -- `stackframe_with_build_id()`, `stackframe_with_path()` - Set binary info -- `stackframe_with_relative_address()` - Set relative address +- `with_function(String)`, `with_file(String)`, `with_line(u32)`, `with_column(u32)` - Set debug info +- `with_ip(usize)`, `with_sp(usize)` - Set instruction/stack pointers +- `with_module_base_address(usize)`, `with_symbol_address(usize)` - Set base addresses +- `with_build_id(String)` - Set build ID +- `build_id_type(CxxBuildIdType)` - Set build ID format (GNU, GO, PDB, SHA1) +- `file_type(CxxFileType)` - Set binary format (APK, ELF, PE) +- `with_path(String)` - Set module path +- `with_relative_address(usize)` - Set relative address **StackTrace Methods:** -- `stacktrace_push_frame()` - Add a frame to the trace -- `stacktrace_set_complete()` - Mark trace as complete +- `add_frame(Box, bool)` - Add a frame (bool = incomplete) +- `mark_complete()` - Mark trace as complete -**Output:** -- `crashinfo_to_json()` - Convert CrashInfo to JSON string +**Building & Output:** +```cpp +auto crash_info = crashinfo_build(std::move(builder)); +auto json = crash_info->to_json(); +``` ## Building and Running From 8a759d8bd27a0826ffdf6196cd458cb0b6592cae Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 12:31:10 -0500 Subject: [PATCH 09/11] another try at windows --- examples/cxx/build-and-run-crashinfo.ps1 | 59 ++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/examples/cxx/build-and-run-crashinfo.ps1 b/examples/cxx/build-and-run-crashinfo.ps1 index 7445d70ec9..ea29765009 100644 --- a/examples/cxx/build-and-run-crashinfo.ps1 +++ b/examples/cxx/build-and-run-crashinfo.ps1 @@ -26,42 +26,75 @@ Write-Host "šŸ“ CXX crate: $CXX_BRIDGE_CRATE" -ForegroundColor Green Write-Host "šŸ“ Rust CXX: $RUST_CXX_INCLUDE" -ForegroundColor Green # Check if we have MSVC (cl.exe) or MinGW (g++/clang++) +# Note: Prefer MSVC on Windows as it's the default Rust toolchain $MSVC = Get-Command cl.exe -ErrorAction SilentlyContinue $GPP = Get-Command g++.exe -ErrorAction SilentlyContinue $CLANGPP = Get-Command clang++.exe -ErrorAction SilentlyContinue -# Determine library extension based on compiler -if ($MSVC) { - $LIB_EXT = ".lib" - $LIB_PREFIX = "" +# Auto-detect which toolchain Rust used by checking which library exists +$HAS_MSVC_LIB = Test-Path (Join-Path $PROJECT_ROOT "target\release\dd_crashtracker.lib") +$HAS_GNU_LIB = (Test-Path (Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.a")) -or ` + (Test-Path (Join-Path $PROJECT_ROOT "target\release\liblibdd_crashtracker.a")) + +if ($HAS_MSVC_LIB -and $MSVC) { + $USE_MSVC = $true + Write-Host "Detected MSVC Rust toolchain" -ForegroundColor Cyan +} elseif ($HAS_GNU_LIB -and ($GPP -or $CLANGPP)) { + $USE_MSVC = $false + Write-Host "Detected GNU Rust toolchain" -ForegroundColor Cyan +} elseif ($MSVC) { + $USE_MSVC = $true + Write-Host "Defaulting to MSVC (library not found yet, will check after)" -ForegroundColor Yellow } elseif ($GPP -or $CLANGPP) { - $LIB_EXT = ".a" - $LIB_PREFIX = "lib" + $USE_MSVC = $false + Write-Host "Defaulting to GNU toolchain (library not found yet, will check after)" -ForegroundColor Yellow } else { Write-Host "āŒ Error: No C++ compiler found. Please install MSVC (via Visual Studio) or MinGW/LLVM" -ForegroundColor Red exit 1 } Write-Host "šŸ”Ø Finding libraries..." -ForegroundColor Cyan -# Note: Rust always prefixes with 'lib' in the source, but Windows MSVC strips it -if ($MSVC) { +# Note: Rust library naming varies by platform and toolchain +if ($USE_MSVC) { + # MSVC: dd_crashtracker.lib (no lib prefix) $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\dd_crashtracker.lib" $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.lib" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName } else { - $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\liblibdd_crashtracker.a" - $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "liblibdd-crashtracker-cxx.a" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + # MinGW: Try both possible naming patterns + $CRASHTRACKER_LIB_1 = Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.a" + $CRASHTRACKER_LIB_2 = Join-Path $PROJECT_ROOT "target\release\liblibdd_crashtracker.a" + + if (Test-Path $CRASHTRACKER_LIB_1) { + $CRASHTRACKER_LIB = $CRASHTRACKER_LIB_1 + } elseif (Test-Path $CRASHTRACKER_LIB_2) { + $CRASHTRACKER_LIB = $CRASHTRACKER_LIB_2 + } else { + $CRASHTRACKER_LIB = $CRASHTRACKER_LIB_1 # Use this for error message + } + + # Try both naming patterns for CXX bridge + $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.a" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + if (-not $CXX_BRIDGE_LIB) { + $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "liblibdd-crashtracker-cxx.a" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + } } if (-not (Test-Path $CRASHTRACKER_LIB)) { Write-Host "āŒ Error: Could not find libdd-crashtracker library at $CRASHTRACKER_LIB" -ForegroundColor Red + if (-not $MSVC) { + Write-Host "Searched for: libdd_crashtracker.a and liblibdd_crashtracker.a" -ForegroundColor Yellow + Write-Host "Files in target/release/:" -ForegroundColor Yellow + Get-ChildItem -Path "target\release" -Filter "*crashtracker*" | Select-Object -First 10 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } + } exit 1 } if (-not $CXX_BRIDGE_LIB) { - if ($MSVC) { + if ($USE_MSVC) { Write-Host "āŒ Error: Could not find CXX bridge library (looking for libdd-crashtracker-cxx.lib)" -ForegroundColor Red } else { - Write-Host "āŒ Error: Could not find CXX bridge library (looking for liblibdd-crashtracker-cxx.a)" -ForegroundColor Red + Write-Host "āŒ Error: Could not find CXX bridge library" -ForegroundColor Red + Write-Host "Searched for: libdd-crashtracker-cxx.a and liblibdd-crashtracker-cxx.a" -ForegroundColor Yellow } exit 1 } @@ -71,7 +104,7 @@ Write-Host "šŸ“š CXX bridge library: $CXX_BRIDGE_LIB" -ForegroundColor Green Write-Host "šŸ”Ø Compiling C++ example..." -ForegroundColor Cyan -if ($MSVC) { +if ($USE_MSVC) { Write-Host "Using MSVC compiler" -ForegroundColor Yellow # MSVC compilation From ef45ce2b9ecdb8e01a81bb7afd6b3f846e0f2088 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 15:05:24 -0500 Subject: [PATCH 10/11] even more windows --- examples/cxx/build-and-run-crashinfo.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/cxx/build-and-run-crashinfo.ps1 b/examples/cxx/build-and-run-crashinfo.ps1 index ea29765009..bd920608c5 100644 --- a/examples/cxx/build-and-run-crashinfo.ps1 +++ b/examples/cxx/build-and-run-crashinfo.ps1 @@ -32,7 +32,8 @@ $GPP = Get-Command g++.exe -ErrorAction SilentlyContinue $CLANGPP = Get-Command clang++.exe -ErrorAction SilentlyContinue # Auto-detect which toolchain Rust used by checking which library exists -$HAS_MSVC_LIB = Test-Path (Join-Path $PROJECT_ROOT "target\release\dd_crashtracker.lib") +# Note: On Windows, Rust still uses 'lib' prefix even for MSVC .lib files +$HAS_MSVC_LIB = Test-Path (Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.lib") $HAS_GNU_LIB = (Test-Path (Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.a")) -or ` (Test-Path (Join-Path $PROJECT_ROOT "target\release\liblibdd_crashtracker.a")) @@ -56,8 +57,8 @@ if ($HAS_MSVC_LIB -and $MSVC) { Write-Host "šŸ”Ø Finding libraries..." -ForegroundColor Cyan # Note: Rust library naming varies by platform and toolchain if ($USE_MSVC) { - # MSVC: dd_crashtracker.lib (no lib prefix) - $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\dd_crashtracker.lib" + # MSVC: libdd_crashtracker.lib (Rust keeps the lib prefix even on Windows) + $CRASHTRACKER_LIB = Join-Path $PROJECT_ROOT "target\release\libdd_crashtracker.lib" $CXX_BRIDGE_LIB = Get-ChildItem -Path "target\release\build\libdd-crashtracker-*\out" -Filter "libdd-crashtracker-cxx.lib" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName } else { # MinGW: Try both possible naming patterns From d4524450a2898b40037cf6497f6ea337a5527f50 Mon Sep 17 00:00:00 2001 From: Daniel Schwartz-Narbonne Date: Thu, 4 Dec 2025 15:30:34 -0500 Subject: [PATCH 11/11] try windows again --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73dbecc689..4ee60bdf40 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -241,6 +241,9 @@ jobs: set -e cd examples/cxx ./build-and-run-crashinfo.sh + - name: "Setup MSVC (Windows)" + if: matrix.platform == 'windows-latest' + uses: ilammy/msvc-dev-cmd@v1 - name: "Test building CXX bindings (Windows)" shell: pwsh if: matrix.platform == 'windows-latest'