Skip to content
Draft
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
3 changes: 1 addition & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin_tests/src/modes/behavior.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ pub fn get_behavior(mode_str: &str) -> Box<dyn Behavior> {
"panic_hook_string" => Box::new(test_014_panic_hook_string::Test),
"panic_hook_unknown_type" => Box::new(test_015_panic_hook_unknown_type::Test),
"errno_preservation" => Box::new(test_016_errno_preservation::Test),
"multi_thread_collection" => Box::new(test_017_multi_thread_collection::Test),
"runtime_preload_logger" => Box::new(test_000_donothing::Test),
_ => panic!("Unknown mode: {mode_str}"),
}
Expand Down
1 change: 1 addition & 0 deletions bin_tests/src/modes/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ pub mod test_013_panic_hook_after_fork;
pub mod test_014_panic_hook_string;
pub mod test_015_panic_hook_unknown_type;
pub mod test_016_errno_preservation;
pub mod test_017_multi_thread_collection;
94 changes: 94 additions & 0 deletions bin_tests/src/modes/unix/test_017_multi_thread_collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

//! Tests that the crashtracker collects stack information for all threads, not
//! just the crashing thread.
//!
//! Two named background threads are spawned with distinct, recognisable call
//! chains so that the captured stacks are visually interesting and clearly
//! distinguishable in the crash report.
//!
//! ct_worker_0: worker_entry_0 -> wait_for_work_0 -> thread::sleep
//! ct_worker_1: worker_entry_1 -> wait_for_work_1 -> thread::sleep
//!
//! All intermediate functions are #[inline(never)] so they appear as distinct
//! frames in the libunwind output.

use crate::modes::behavior::Behavior;
use libdd_crashtracker::CrashtrackerConfiguration;
use std::path::Path;
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;

pub struct Test;

#[inline(never)]
fn wait_for_work_0() {
let _ = std::hint::black_box(1u64); // prevent the compiler from optimizing out the sleep
thread::sleep(Duration::from_secs(300));
}

#[inline(never)]
fn worker_entry_0() {
let _ = std::hint::black_box(1u64);
wait_for_work_0();
}

#[inline(never)]
fn wait_for_work_1() {
let _ = std::hint::black_box(1u64); // prevent the compiler from optimizing out the sleep
thread::sleep(Duration::from_secs(300));
}

#[inline(never)]
fn worker_entry_1() {
let _ = std::hint::black_box(1u64);
wait_for_work_1();
}

impl Behavior for Test {
fn setup(
&self,
_output_dir: &Path,
config: &mut CrashtrackerConfiguration,
) -> anyhow::Result<()> {
config.set_collect_all_threads(true);
config.set_max_threads(32);
Ok(())
}

fn pre(&self, _output_dir: &Path) -> anyhow::Result<()> {
Ok(())
}

/// Spawn two named worker threads with distinct call chains, then leak the
/// handles so the threads outlive post() and are still live when the crash fires.
fn post(&self, _output_dir: &Path) -> anyhow::Result<()> {
// 2 workers + 1 for this (main) thread.
let barrier = Arc::new(Barrier::new(3));

let b0 = Arc::clone(&barrier);
let h0 = thread::Builder::new()
.name("ct_worker_0".to_string())
.spawn(move || {
b0.wait();
worker_entry_0();
})?;

let b1 = Arc::clone(&barrier);
let h1 = thread::Builder::new()
.name("ct_worker_1".to_string())
.spawn(move || {
b1.wait();
worker_entry_1();
})?;

barrier.wait();
thread::sleep(Duration::from_millis(20));

std::mem::forget(h0);
std::mem::forget(h1);
Ok(())
}
}
4 changes: 4 additions & 0 deletions bin_tests/src/test_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub enum TestMode {
RuntimeCallbackFrameInvalidUtf8,
RuntimePreloadLogger,
ErrnoPreservation,
MultiThreadCollection,
}

impl TestMode {
Expand All @@ -41,6 +42,7 @@ impl TestMode {
Self::RuntimeCallbackFrameInvalidUtf8 => "runtime_callback_frame_invalid_utf8",
Self::RuntimePreloadLogger => "runtime_preload_logger",
Self::ErrnoPreservation => "errno_preservation",
Self::MultiThreadCollection => "multi_thread_collection",
}
}

Expand All @@ -62,6 +64,7 @@ impl TestMode {
Self::RuntimeCallbackFrameInvalidUtf8,
Self::RuntimePreloadLogger,
Self::ErrnoPreservation,
Self::MultiThreadCollection,
]
}
}
Expand Down Expand Up @@ -92,6 +95,7 @@ impl std::str::FromStr for TestMode {
"runtime_callback_frame_invalid_utf8" => Ok(Self::RuntimeCallbackFrameInvalidUtf8),
"runtime_preload_logger" => Ok(Self::RuntimePreloadLogger),
"errno_preservation" => Ok(Self::ErrnoPreservation),
"multi_thread_collection" => Ok(Self::MultiThreadCollection),
_ => Err(format!("Unknown test mode: {}", s)),
}
}
Expand Down
129 changes: 129 additions & 0 deletions bin_tests/tests/crashtracker_bin_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/

Check failure on line 1 in bin_tests/tests/crashtracker_bin_test.rs

View workflow job for this annotation

GitHub Actions / [ubuntu-latest:] test report

bin_tests/tests/crashtracker_bin_test.rs.test_crash_tracking_multi_thread_collection

test_crash_tracking_multi_thread_collection
// SPDX-License-Identifier: Apache-2.0

#![cfg(unix)]
Expand Down Expand Up @@ -188,6 +188,135 @@
run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap();
}

/// Tests that when `collect_all_threads` is enabled, the crash report contains
/// entries in `error.threads` for background threads beyond the crashing thread.
///
/// The behavior (test_017_multi_thread_collection.rs) enables `collect_all_threads`,
/// spawns two named sleeping worker threads in `post()`, and then crashes the main thread.
///
/// Thread collection now happens in the receiver process using libunwind remote unwinding
/// via ptrace (_UPT_create / unw_init_remote / unw_step_remote). The parent process stays
/// alive until the receiver completes, guaranteeing threads are valid ptrace targets.
///
/// We verify:
/// - `error.threads` is non-empty.
/// - Each thread entry is well-formed: `crashed=false`, `name`, and `stack` present.
/// - None of the additional threads are marked as crashed (the crashing thread is in
/// `error.stack`, not `error.threads`).
/// - Both worker threads are present by name (ct_worker_0, ct_worker_1).
/// - Each worker has a multi-frame stack trace including their entry function, confirming that
/// libunwind remote unwinding produced a full call chain rather than a single syscall frame.
#[test]
#[cfg(target_os = "linux")]
#[cfg_attr(miri, ignore)]
fn test_crash_tracking_multi_thread_collection() {

Check failure on line 212 in bin_tests/tests/crashtracker_bin_test.rs

View workflow job for this annotation

GitHub Actions / [ubuntu-latest:${RUST_VERSION}] test report

/home/runner/work/libdatadog/libdatadog/bin_tests/tests/crashtracker_bin_test.rs.test_crash_tracking_multi_thread_collection

thread 'test_crash_tracking_multi_thread_collection' panicked at bin_tests/tests/crashtracker_bin_test.rs:308:13
Raw output
thread 'test_crash_tracking_multi_thread_collection' panicked at bin_tests/tests/crashtracker_bin_test.rs:308:13:
ct_worker_1 stack should contain a frame for 'worker_entry_1' but got: [Object {"ip": String("0x7fa4ee0ecadf"), "sp": String("0x7fa4eddfdc60"), "build_id": String("8e9fd827446c24067541ac5390e6f527fb5947bb"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/usr/lib/x86_64-linux-gnu/libc.so.6"), "relative_address": String("0x00000000000ecadf"), "function": String("clock_nanosleep")}, Object {"ip": String("0x7fa4ee0f9a27"), "sp": String("0x7fa4eddfdce0"), "build_id": String("8e9fd827446c24067541ac5390e6f527fb5947bb"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/usr/lib/x86_64-linux-gnu/libc.so.6"), "relative_address": String("0x00000000000f9a27"), "function": String("nanosleep")}, Object {"ip": String("0x560a22586fd0"), "sp": String("0x7fa4eddfdcf0"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x000000000003efd0"), "column": Number(5), "file": String("/rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/thread/mod.rs"), "function": String("std::thread::sleep"), "line": Number(888)}, Object {"ip": String("0x560a2255d30c"), "sp": String("0x7fa4eddfdd70"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x000000000001530c"), "column": Number(2), "file": String("/home/runner/work/libdatadog/libdatadog/bin_tests/src/modes/unix/test_017_multi_thread_collection.rs"), "function": String("wait_for_work_0"), "line": Number(30)}, Object {"ip": String("0x560a2255d2f3"), "sp": String("0x7fa4eddfdd80"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x00000000000152f3"), "column": Number(2), "file": String("/home/runner/work/libdatadog/libdatadog/bin_tests/src/modes/unix/test_017_multi_thread_collection.rs"), "function": String("worker_entry_0"), "line": Number(36)}, Object {"ip": String("0x560a2255d2b4"), "sp": String("0x7fa4eddfdd90"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x00000000000152b4"), "column": Number(18), "file": String("/rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs"), "function": String("__rust_begin_short_backtrace<bin_tests::modes::unix::test_017_multi_thread_collection::{impl#0}::post::{closure_env#1}, ()>"), "line": Number(154)}, Object {"ip": String("0x560a2255d62d"), "sp": String("0x7fa4eddfddb0"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x000000000001562d"), "column": Number(5), "file": String("/rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs"), "function": String("call_once<std::thread::{impl#0}::spawn_unchecked_::{closure_env#1}<bin_tests::modes::unix::test_017_multi_thread_collection::{impl#0}::post::{closure_env#1}, ()>, ()>"), "line": Number(250)}, Object {"ip": String("0x560a225b984b"), "sp": String("0x7fa4eddfde40"), "build_id": String("52eccabe73506137f30a01fe0947c8ad3ec5f02f"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/home/runner/work/libdatadog/libdatadog/target/release/crashtracker_bin_test"), "relative_address": String("0x000000000007184b"), "column": Number(17), "file": String("/rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/pal/unix/thread.rs"), "function": String("std::sys::pal::unix::thread::Thread::new::thread_start"), "line": Number(105)}, Object {"ip": String("0x7fa4ee09caa4"), "sp": String("0x7fa4eddfde90"), "build_id": String("8e9fd827446c24067541ac5390e6f527fb5947bb"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/usr/lib/x86_64-linux-gnu/libc.so.6"), "relative_address": String("0x000000000009caa4"), "comments": Array [String("resolve_names failed with Couldn't symbolize 140346344983204: symbolization source has no or no relevant symbols")]}, Object {"ip": String("0x7fa4ee129c6c"), "sp": String("0x7fa4eddfdf40"), "build_id": String("8e9fd827446c24067541ac5390e6f527fb5947bb"), "build_id_type": String("GNU"), "file_type": String("ELF"), "path": String("/usr/lib/x86_64-linux-gnu/libc.so.6"), "relative_address": String("0x0000000000129c6c"), "comments": Array [String("resolve_names failed with Couldn't symbolize 140346345561196: symbolization source has no or no relevant symbols")]}]
stack backtrace:
   0:     0x55e8bd824dea - std::backtrace_rs::backtrace::libunwind::trace::h886f3b0575353f6e
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/../../backtrace/src/backtrace/libunwind.rs:116:5
   1:     0x55e8bd824dea - std::backtrace_rs::backtrace::trace_unsynchronized::h652d1041ec67eb09
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x55e8bd824dea - std::sys::backtrace::_print_fmt::hd0317245a04c3039
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:66:9
   3:     0x55e8bd824dea - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h14b23c1989cbd5c2
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:39:26
   4:     0x55e8bd84bc93 - core::fmt::rt::Argument::fmt::h43c1e387827e30dc
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/fmt/rt.rs:177:76
   5:     0x55e8bd84bc93 - core::fmt::write::h346b5eee5ed4d7cc
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/fmt/mod.rs:1189:21
   6:     0x55e8bd821593 - std::io::Write::write_fmt::heeb48dcd4a60b46b
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/io/mod.rs:1884:15
   7:     0x55e8bd824c32 - std::sys::backtrace::BacktraceLock::print::h48db11f3fd4983ff
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:42:9
   8:     0x55e8bd82626d - std::panicking::default_hook::{{closure}}::h65db976b9c0d8674
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:268:22
   9:     0x55e8bd8260b3 - std::panicking::default_hook::h6eb3a1192db1ae36
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:295:9
  10:     0x55e8bd826847 - std::panicking::rust_panic_with_hook::h370ee1901241e459
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:801:13
  11:     0x55e8bd8266da - std::panicking::begin_panic_handler::{{closure}}::heabfe92676d6d073
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:674:13
  12:     0x55e8bd8252c9 - std::sys::backtrace::__rust_end_short_backtrace::h6e22d229d4fdf49e
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:170:18
  13:     0x55e8bd82636c - rust_begin_unwind
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:665:5
  14:     0x55e8bd382230 - core::panicking::panic_fmt::hfae270fab21da3e6
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:76:14
  15:     0x55e8bd3967af - crashtracker_bin_test::test_crash_tracking_multi_thread_collection::{{closure}}::hd8dd2a1371a3c764
                               at /home/runner/work/libdatadog/libdatadog/bin_tests/tests/crashtracker_bin_test.rs:308:13
  16:     0x55e8bd3a4742 - core::ops::function::FnOnce::call_once::he23f9a9b8aface8b
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs:250:5
  17:     0x55e8bd3a3a30 - core::ops::function::FnOnce::call_once{{vtable.shim}}::hed5d5c5a64e6bbc3
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs:250:5
  18:     0x55e8bd3b2e5c - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h0ba09cd5b864380a
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/alloc/src/boxed.rs:1972:9
  19:     0x55e8bd3a9f4d - bin_tests::test_runner::run_crash_test_with_artifacts::h0a01004f960cc0ad
                               at /home/runner/work/libdatadog/libdatadog/bin_tests/src/test_runner.rs:157:5
  20:     0x55e8bd395bc3 - crashtracker_bin_test::test_crash_tracking_multi_thread_collection::h8db31c75ef13d11e
                               at /home/runner/work/libdatadog/libdatadog/bin_tests/tests/crashtracker_bin_test.rs:317:5
  21:     0x55e8bd3958b7 - crashtracker_bin_test::test_crash_tracking_multi_thread_collection::{{closure}}::hdb92334c6ae5ece7
                               at /home/runner/work/libdatadog/libdatadog/bin_tests/tests/crashtracker_bin_test.rs:212:49
  22:     0x55e8bd3a46f6 - core::ops::function::FnOnce::call_once::he02e9b669950bb3e
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs:250:5
  23:     0x55e8bd3f227b - core::ops::function::FnOnce::call_once::hcf459d49f817e971
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs:250:5
  24:     0x55e8bd3f227b - test::__rust_begin_short_backtrace::h812692078d2a7065
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:632:18
  25:     0x55e8bd3f1bf8 - test::run_test_in_process::{{closure}}::he383169452078072
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:655:60
  26:     0x55e8bd3f1bf8 - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::hfd3325d295001553
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panic/unwind_safe.rs:272:9
  27:     0x55e8bd3f1bf8 - std::panicking::try::do_call::hd41329e64e1d34ac
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:557:40
  28:     0x55e8bd3f1bf8 - std::panicking::try::h7a44da38acd5f700
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:520:19
  29:     0x55e8bd3f1bf8 - std::panic::catch_unwind::h84ae8452b3d168cb
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panic.rs:358:14
  30:     0x55e8bd3f1bf8 - test::run_test_in_process::h0d23dc8c28915531
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:655:27
  31:     0x55e8bd3f1bf8 - test::run_test::{{closure}}::h427e5ada87ee2030
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:576:43
  32:     0x55e8bd3b56ca - test::run_test::{{closure}}::hb864816fed1c6cd7
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:606:41
  33:     0x55e8bd3b56ca - std::sys::backtrace::__rust_begin_short_backtrace::ha8680ce782bf69f7
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:154:18
  34:     0x55e8bd3b9024 - std::thread::Builder::spawn_unchecked_::{{closure}}::{{closure}}::h05b53495b5672e29
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/thread/mod.rs:561:17
  35:     0x55e8bd3b9024 - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h2a6a6bcc98d0706b
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panic/unwind_safe.rs:272:9
  36:     0x55e8bd3b9024 - std::panicking::try::do_call::hccc670c81fea40a7
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:557:40
  37:     0x55e8bd3b9024 - std::panicking::try::h755e3c65d88973b7
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:520:19
  38:     0x55e8bd3b9024 - std::panic::catch_unwind::hced8032a948c5bd0
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panic.rs:358:14
  39:     0x55e8bd3b9024 - std::thread::Builder::spawn_unchecked_::{{closure}}::h3b9e5c9edd874915
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/thread/mod.rs:559:30
  40:     0x55e8bd3b9024 - core::ops::function::FnOnce::call_once{{vtable.shim}}::hbc1380969ae25c01
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/ops/function.rs:250:5
  41:     0x55e8bd82b56b - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h0bbb114b77b490c1
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/alloc/src/boxed.rs:1972:9
  42:     0x55e8bd82b56b - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h3673811012fc0688
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/alloc/src/boxed.rs:1972:9
  43:     0x55e8bd82b56b - std::sys::pal::unix::thread::Thread::new::thread_start::h0feaf4a9a4b2ecde
                               at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/pal/unix/thread.rs:105:17
  44:     0x7f986d09caa4 - <unknown>
  45:     0x7f986d129c6c - <unknown>
  46:                0x0 - <unknown>
let config = CrashTestConfig::new(
BuildProfile::Release,
TestMode::MultiThreadCollection,
CrashType::NullDeref,
);
let artifacts = StandardArtifacts::new(config.profile);
let artifacts_map = fetch_built_artifacts(&artifacts.as_slice()).unwrap();

let validator: ValidatorFn = Box::new(|payload, _fixtures| {
let error = &payload["error"];
// assert!(
// false,
// "{}",
// serde_json::to_string_pretty(error).unwrap_or_default()
// );
let threads = error["threads"]
.as_array()
.expect("error.threads should be a JSON array");

let thread_names: Vec<&str> = threads
.iter()
.map(|t| t["name"].as_str().unwrap_or("<none>"))
.collect();

assert!(
!threads.is_empty(),
"error.threads should be non-empty when collect_all_threads is enabled; \
got payload: {}",
serde_json::to_string_pretty(payload).unwrap_or_default()
);

// Every thread entry must be structurally valid and non-crashing
for thread in threads {
assert!(
thread["name"].is_string(),
"thread entry missing 'name': {thread:?}"
);
assert!(
thread["crashed"].is_boolean(),
"thread entry missing 'crashed': {thread:?}"
);
assert!(
thread["stack"].is_object(),
"thread entry missing 'stack': {thread:?}"
);
assert!(
!thread["crashed"].as_bool().unwrap_or(true),
"threads in error.threads must have crashed=false: {thread:?}"
);
}

// Both named workers must be present; the behavior spawns exactly two
for expected in ["ct_worker_0", "ct_worker_1"] {
assert!(
thread_names.contains(&expected),
"Expected worker thread '{expected}' in error.threads; \
got: {thread_names:?}"
);
}

// Each worker must have a multi-frame stack trace.
//
// The workers sleep in thread::sleep -> wait_for_work_N -> worker_entry_N.
// With libunwind remote unwinding, we expect the full call chain rather than
// a single syscall frame. We verify:
// - More than one frame was captured.
// - At least one frame contains the worker's entry function by name.
for expected in ["ct_worker_0", "ct_worker_1"] {
let worker = threads
.iter()
.find(|t| t["name"].as_str() == Some(expected))
.unwrap_or_else(|| panic!("{expected} should be in threads"));

let frames = worker["stack"]["frames"]
.as_array()
.unwrap_or_else(|| panic!("{expected} stack.frames should be an array"));

assert!(
frames.len() > 1,
"{expected} should have a multi-frame stack trace from remote libunwind; \
got {} frame(s): {frames:?}",
frames.len()
);

let entry_fn = if expected == "ct_worker_0" {
"worker_entry_0"
} else {
"worker_entry_1"
};
let has_entry_frame = frames.iter().any(|f| {
f["function"]
.as_str()
.map(|name| name.contains(entry_fn))
.unwrap_or(false)
});
assert!(
has_entry_frame,
"{expected} stack should contain a frame for '{entry_fn}' but got: {frames:?}"
);
}

Ok(())
});

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

#[test]
#[cfg(target_os = "linux")]
#[cfg_attr(miri, ignore)]
Expand Down
2 changes: 1 addition & 1 deletion libdd-crashtracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ cxx = ["dep:cxx", "dep:cxx-build"]
blazesym = "=0.2.3"

[target.'cfg(target_os = "linux")'.dependencies]
libdd-libunwind-sys = { version = "1.0.0" }
libdd-libunwind-sys = { git = "https://github.com/DataDog/libdatadog-libunwind", branch = "gyuheon0h/remote-unwind-api" }

[dependencies]
anyhow = "1.0"
Expand Down
4 changes: 2 additions & 2 deletions libdd-crashtracker/src/collector/collector_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ impl Collector {
tid,
);
}
pid if pid > 0 => Ok(Self {
handle: ProcessHandle::new(receiver.handle.uds_fd, Some(pid)),
child_pid if child_pid > 0 => Ok(Self {
handle: ProcessHandle::new(receiver.handle.uds_fd, Some(child_pid)),
}),
code => {
// Error
Expand Down
12 changes: 12 additions & 0 deletions libdd-crashtracker/src/collector/crash_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ fn handle_posix_signal_impl(

let receiver = Receiver::from_crashtracker_config(config)?;

// Enable ptrace permissions for receiver if multi-thread collection is enabled
#[cfg(target_os = "linux")]
if config.collect_all_threads() {
if let Some(receiver_pid) = receiver.handle.pid {
// Allow the receiver to ptrace this process for thread context collection
// SAFETY: prctl is async-signal-safe and we're just setting ptrace permissions
unsafe {
libc::prctl(libc::PR_SET_PTRACER, receiver_pid as libc::c_ulong, 0, 0, 0);
}
}
}

let collector = Collector::spawn(
&receiver,
config,
Expand Down
2 changes: 2 additions & 0 deletions libdd-crashtracker/src/receiver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub use entry_points::{
async_receiver_entry_point_unix_listener, async_receiver_entry_point_unix_socket,
get_receiver_unix_socket, receiver_entry_point_stdin, receiver_entry_point_unix_socket,
};
#[cfg(target_os = "linux")]
mod ptrace_collector;
mod receive_report;

#[cfg(feature = "benchmarking")]
Expand Down
Loading
Loading