diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 121f1e9aba..4ee60bdf40 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -234,6 +234,22 @@ jobs: cmake -S .. -DDatadog_ROOT=$LIBDD_OUTPUT_FOLDER cmake --build . fi + - name: "Test building CXX bindings (Unix)" + shell: bash + if: matrix.platform != 'windows-latest' + run: | + 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' + run: | + cd examples/cxx + .\build-and-run-crashinfo.ps1 cross-centos7: name: build and test using cross - on centos7 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..68ea54112f --- /dev/null +++ b/examples/cxx/README.md @@ -0,0 +1,129 @@ +# 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 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:** +- `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:** +- `add_frame(Box, bool)` - Add a frame (bool = incomplete) +- `mark_complete()` - Mark trace as complete + +**Building & Output:** +```cpp +auto crash_info = crashinfo_build(std::move(builder)); +auto json = crash_info->to_json(); +``` + +## Building and Running + +### Unix (Linux/macOS) + +The `build-and-run-crashinfo.sh` script handles the entire build process: + +```bash +./examples/cxx/build-and-run-crashinfo.sh +``` + +### Windows + +The `build-and-run-crashinfo.ps1` PowerShell script handles the build process on Windows: + +```powershell +.\examples\cxx\build-and-run-crashinfo.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 (automatically detects MSVC or MinGW/Clang) +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++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-crashinfo.ps1 b/examples/cxx/build-and-run-crashinfo.ps1 new file mode 100644 index 0000000000..bd920608c5 --- /dev/null +++ b/examples/cxx/build-and-run-crashinfo.ps1 @@ -0,0 +1,159 @@ +# 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 + +# 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 + +# Auto-detect which toolchain Rust used by checking which library exists +# 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")) + +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) { + $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 library naming varies by platform and toolchain +if ($USE_MSVC) { + # 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 + $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 ($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" -ForegroundColor Red + Write-Host "Searched for: libdd-crashtracker-cxx.a and liblibdd-crashtracker-cxx.a" -ForegroundColor Yellow + } + 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 + +if ($USE_MSVC) { + Write-Host "Using MSVC compiler" -ForegroundColor Yellow + + # MSVC compilation + cl.exe /std:c++20 /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 - 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 ` + "$CXX_BRIDGE_LIB" ` + "$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 + } +} + +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/examples/cxx/build-and-run-crashinfo.sh b/examples/cxx/build-and-run-crashinfo.sh new file mode 100755 index 0000000000..acc1548889 --- /dev/null +++ b/examples/cxx/build-and-run-crashinfo.sh @@ -0,0 +1,69 @@ +#!/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..." +# Platform-specific linker flags +if [[ "$OSTYPE" == "darwin"* ]]; then + PLATFORM_LIBS="-framework Security -framework CoreFoundation" +else + PLATFORM_LIBS="" +fi + +c++ -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" \ + -lpthread -ldl $PLATFORM_LIBS \ + -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..2c62dd301e --- /dev/null +++ b/examples/cxx/crashinfo.cpp @@ -0,0 +1,85 @@ +#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(CxxErrorKind::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); + + builder->set_metadata(Metadata{ + .library_name = "libdatadog", + .library_version = "1.0.0", + .family = "rust", + .tags = {"service:example", "env:dev"} + }); + + builder->set_proc_info(ProcInfo{.pid = 12345}); + + 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(); + + // 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(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); + + // 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(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); + + 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-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"] 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..f2ca83ab4d 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++20") + .compile("libdd-crashtracker-cxx"); + + println!("cargo:rerun-if-changed=src/crash_info/cxx.rs"); +} + #[cfg(unix)] fn build_shared_libs() { build_c_file(); @@ -103,6 +113,10 @@ fn main() { .file("src/crash_info/emit_sicodes.c") .compile("emit_sicodes"); + // Build CXX bridge if feature is enabled + #[cfg(feature = "cxx")] + build_cxx_bridge(); + // 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") @@ -119,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(); +} 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..3690e27acd --- /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 (renamed to avoid cbindgen conflicts) + #[repr(u32)] + enum CxxErrorKind { + Panic = 0, + UnhandledException = 1, + UnixSignal = 2, + } + + enum CxxBuildIdType { + GNU, + GO, + PDB, + SHA1, + } + + enum CxxFileType { + 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: 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<()>; + 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: CxxBuildIdType); + fn file_type(self: &mut StackFrame, file_type: CxxFileType); + + // 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::CxxErrorKind) -> anyhow::Result<()> { + let internal_kind = match kind { + 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) + } + + 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::CxxBuildIdType) { + let internal_type = match build_id_type { + 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::CxxFileType) { + let internal_type = match file_type { + ffi::CxxFileType::APK => FileType::APK, + ffi::CxxFileType::ELF => FileType::ELF, + ffi::CxxFileType::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 }