Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions bin_tests/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,40 @@ fn main() {

// Make the built shared object path available at compile time for tests/tools.
println!("cargo:rustc-env=PRELOAD_LOGGER_SO={}", so_path.display());

// --- trigger_assert static library (Linux only) ---
#[cfg(target_os = "linux")]
{
let assert_src = PathBuf::from("src/c/trigger_assert.c");
let assert_obj = out_dir.join("trigger_assert.o");
let assert_lib = out_dir.join("libtrigger_assert.a");

let status = Command::new("cc")
.args([
"-std=c11", "-fPIC", "-UNDEBUG", "-Wall", "-Wextra", "-c", "-o",
])
.arg(&assert_obj)
.arg(&assert_src)
.status()
.expect("failed to compile trigger_assert.c");
if !status.success() {
panic!("compiling trigger_assert.c failed with status {status}");
}

let status = Command::new("ar")
.args(["rcs"])
.arg(&assert_lib)
.arg(&assert_obj)
.status()
.expect("failed to run ar");
if !status.success() {
panic!("creating libtrigger_assert.a failed with status {status}");
}

println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=trigger_assert");
println!("cargo:rerun-if-changed=src/c/trigger_assert.c");
}
}

#[cfg(not(unix))]
Expand Down
9 changes: 9 additions & 0 deletions bin_tests/src/bin/crashtracker_bin_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ mod unix {
"raise_sigill" => raise(Signal::SIGILL)?,
"raise_sigbus" => raise(Signal::SIGBUS)?,
"raise_sigsegv" => raise(Signal::SIGSEGV)?,
#[cfg(target_os = "linux")]
"assert_fail" => {
extern "C" {
fn trigger_c_assert() -> !;
}
// SAFETY: trigger_c_assert calls the real C assert() macro,
// which expands to __assert_fail and never returns.
unsafe { trigger_c_assert() }
}
"unhandled_exception" => {
let mut stacktrace = StackTrace::new_incomplete();
let mut stackframe1 = StackFrame::new();
Expand Down
10 changes: 10 additions & 0 deletions bin_tests/src/c/trigger_assert.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

#undef NDEBUG
#include <assert.h>

void trigger_c_assert(void) {
int test_value = 0;
assert(test_value > 0);
}
8 changes: 6 additions & 2 deletions bin_tests/src/test_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ pub enum CrashType {
RaiseSigSegv,
/// Unhandled Exception
UnhandledException,
/// C assert() failure (calls __assert_fail, triggers SIGABRT)
AssertFail,
}

impl CrashType {
Expand All @@ -156,6 +158,7 @@ impl CrashType {
Self::RaiseSigBus => "raise_sigbus",
Self::RaiseSigSegv => "raise_sigsegv",
Self::UnhandledException => "unhandled_exception",
Self::AssertFail => "assert_fail",
}
}

Expand All @@ -178,7 +181,7 @@ impl CrashType {
pub const fn signal_number(self) -> i32 {
match self {
Self::NullDeref | Self::KillSigSegv | Self::RaiseSigSegv => 11, // SIGSEGV
Self::KillSigAbrt | Self::RaiseSigAbrt => 6, // SIGABRT
Self::KillSigAbrt | Self::RaiseSigAbrt | Self::AssertFail => 6, // SIGABRT
Self::KillSigIll | Self::RaiseSigIll => 4, // SIGILL
Self::KillSigBus | Self::RaiseSigBus => 7, // SIGBUS
Self::UnhandledException => 0, // no signal
Expand All @@ -189,7 +192,7 @@ impl CrashType {
pub const fn signal_name(self) -> &'static str {
match self {
Self::NullDeref | Self::KillSigSegv | Self::RaiseSigSegv => "SIGSEGV",
Self::KillSigAbrt | Self::RaiseSigAbrt => "SIGABRT",
Self::KillSigAbrt | Self::RaiseSigAbrt | Self::AssertFail => "SIGABRT",
Self::KillSigIll | Self::RaiseSigIll => "SIGILL",
Self::KillSigBus | Self::RaiseSigBus => "SIGBUS",
Self::UnhandledException => "Unhandled Exception",
Expand Down Expand Up @@ -218,6 +221,7 @@ impl std::str::FromStr for CrashType {
"raise_sigbus" => Ok(Self::RaiseSigBus),
"raise_sigsegv" => Ok(Self::RaiseSigSegv),
"unhandled_exception" => Ok(Self::UnhandledException),
"assert_fail" => Ok(Self::AssertFail),
_ => Err(format!("Unknown crash type: {}", s)),
}
}
Expand Down
44 changes: 44 additions & 0 deletions bin_tests/tests/crashtracker_bin_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,42 @@ fn test_crash_tracking_bin_unhandled_exception() {
run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap();
}

/// Tests that when a C `assert()` fails, the crash report contains the assertion
/// expression string in the error message.
#[test]
#[cfg(target_os = "linux")]
Comment thread
gyuheon0h marked this conversation as resolved.
#[cfg_attr(miri, ignore)]
fn test_crash_tracking_bin_assert_fail() {
let config = CrashTestConfig::new(
BuildProfile::Release,
TestMode::DoNothing,
CrashType::AssertFail,
);
let artifacts = StandardArtifacts::new(config.profile);
let artifacts_map = fetch_built_artifacts(&artifacts.as_slice()).unwrap();

let validator: ValidatorFn = Box::new(|payload, fixtures| {
PayloadValidator::new(payload)
.validate_error_kind("UnixSignal")?
.validate_error_message_contains("test_value > 0")?
.validate_error_message_contains("trigger_c_assert")?;

// Validate SIGABRT signal info
let sig_info = &payload["sig_info"];
let signo_hr = sig_info["si_signo_human_readable"].as_str().unwrap_or("");
anyhow::ensure!(
signo_hr.contains("SIGABRT"),
"Expected SIGABRT in signal info, got: {signo_hr}"
);

validate_telemetry(&fixtures.crash_telemetry_path, "assert_fail")?;

Ok(())
});

run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap();
}

/// Tests that when `collect_all_threads` is enabled and the crash is reported via
/// `report_unhandled_exception`, the crash report contains entries in `error.threads`
/// for background threads with valid stack traces.
Expand Down Expand Up @@ -1772,6 +1808,10 @@ fn assert_siginfo_message(sig_info: &Value, crash_typ: &str) {
|| sig_info.is_object() && sig_info.as_object().is_none_or(|m| m.is_empty())
);
}
"assert_fail" => {
assert_eq!(sig_info["si_signo"], libc::SIGABRT);
assert_eq!(sig_info["si_signo_human_readable"], "SIGABRT");
}
_ => panic!("unexpected crash_typ {crash_typ}"),
}
}
Expand Down Expand Up @@ -1906,6 +1946,10 @@ fn assert_telemetry_message(crash_telemetry: &[u8], crash_typ: &str) {
"unhandled_exception" => {
// Unhandled exceptions have no signal info tags
}
"assert_fail" => {
assert!(tags.contains("si_signo_human_readable:SIGABRT"), "{tags:?}");
assert!(tags.contains("si_signo:6"), "{tags:?}");
}
_ => panic!("{crash_typ}"),
}

Expand Down
1 change: 1 addition & 0 deletions libdd-crashtracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ cxx = ["dep:cxx", "dep:cxx-build"]
blazesym = "=0.2.3"

[target.'cfg(target_os = "linux")'.dependencies]
libdd-gotter = { version = "0.1.0", path = "../libdd-gotter" }
libdd-libunwind-sys = { version = "1.0.2" }

[dependencies]
Expand Down
2 changes: 2 additions & 0 deletions libdd-crashtracker/src/collector/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub fn init(
Receiver::update_stored_config(receiver_config)?;
register_crash_handlers(&config)?;
register_panic_hook()?;
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
super::assert_interceptor::install_assert_hook();
enable();
Ok(())
}
Expand Down
Loading
Loading