From 98d4adadc01c386cefa12efc33825d7a74abb0da Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Wed, 17 Dec 2025 16:43:05 +0000 Subject: [PATCH 01/18] First pass, need to isolate only collector from test harness --- bin_tests/Cargo.toml | 1 + bin_tests/build.rs | 30 ++++ bin_tests/preload/malloc_logger.c | 129 ++++++++++++++++++ bin_tests/src/bin/crashtracker_bin_test.rs | 28 +++- bin_tests/src/modes/behavior.rs | 1 + bin_tests/src/modes/unix/mod.rs | 1 + .../unix/test_016_runtime_malloc_logger.rs | 47 +++++++ bin_tests/src/test_types.rs | 4 + bin_tests/tests/crashtracker_bin_test.rs | 14 ++ 9 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 bin_tests/build.rs create mode 100644 bin_tests/preload/malloc_logger.c create mode 100644 bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs diff --git a/bin_tests/Cargo.toml b/bin_tests/Cargo.toml index 7e5e9c3c45..4e91b94665 100644 --- a/bin_tests/Cargo.toml +++ b/bin_tests/Cargo.toml @@ -6,6 +6,7 @@ name = "bin_tests" version = "0.1.0" edition = "2021" publish = false +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/bin_tests/build.rs b/bin_tests/build.rs new file mode 100644 index 0000000000..e9cf2f333d --- /dev/null +++ b/bin_tests/build.rs @@ -0,0 +1,30 @@ +// Copyright 2025-Present Datadog, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(unix)] +fn main() { + use std::env; + use std::path::PathBuf; + use std::process::Command; + + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); + let src = PathBuf::from("preload/malloc_logger.c"); + let so_path = out_dir.join("libmalloc_logger.so"); + + let status = Command::new("cc") + .args(["-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) + .arg(&so_path) + .arg(&src) + .status() + .expect("failed to spawn cc"); + + if !status.success() { + panic!("compiling malloc_logger.c failed with status {status}"); + } + + // Make the built shared object path available at compile time for tests/tools. + println!("cargo:rustc-env=MALLOC_LOGGER_SO={}", so_path.display()); +} + +#[cfg(not(unix))] +fn main() {} diff --git a/bin_tests/preload/malloc_logger.c b/bin_tests/preload/malloc_logger.c new file mode 100644 index 0000000000..17d262c60d --- /dev/null +++ b/bin_tests/preload/malloc_logger.c @@ -0,0 +1,129 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void *(*real_malloc)(size_t) = NULL; +static void (*real_free)(void *) = NULL; +static void *(*real_calloc)(size_t, size_t) = NULL; +static void *(*real_realloc)(void *, size_t) = NULL; +static int log_fd = -1; +static pthread_once_t init_once = PTHREAD_ONCE_INIT; + +static int is_enabled(void) { + const char *v = getenv("MALLOC_LOG_ENABLED"); + return v && v[0] == '1'; +} + +static void init_logger(void) { + const char *path = getenv("MALLOC_LOG_PATH"); + if (path == NULL || path[0] == '\0') { + path = "/tmp/malloc_logger.log"; + } + + log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + real_malloc = dlsym(RTLD_NEXT, "malloc"); + real_free = dlsym(RTLD_NEXT, "free"); + real_calloc = dlsym(RTLD_NEXT, "calloc"); + real_realloc = dlsym(RTLD_NEXT, "realloc"); + + // No logging here; deferred until enabled. +} + +__attribute__((constructor)) static void preload_ctor(void) { + pthread_once(&init_once, init_logger); +} + +__attribute__((destructor)) static void preload_dtor(void) { + if (log_fd >= 0) { + close(log_fd); + } +} + +static void log_line(const char *tag, size_t size, void *ptr) { + if (log_fd < 0) { + return; + } + + if (!is_enabled()) { + return; + } + + char buf[200]; + pid_t pid = getpid(); + long tid = syscall(SYS_gettid); + int len; + + if (strcmp(tag, "malloc") == 0) { + len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld malloc size=%zu ptr=%p\n", pid, tid, size, ptr); + } else if (strcmp(tag, "calloc") == 0) { + len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld calloc size=%zu ptr=%p\n", pid, tid, size, ptr); + } else if (strcmp(tag, "realloc") == 0) { + len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld realloc size=%zu ptr=%p\n", pid, tid, size, ptr); + } else { // free + len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld free ptr=%p\n", pid, tid, ptr); + } + + if (len > 0) { + (void)write(log_fd, buf, (size_t)len); + } +} + +void *malloc(size_t size) { + pthread_once(&init_once, init_logger); + + if (real_malloc == NULL) { + errno = ENOMEM; + return NULL; + } + + void *ptr = real_malloc(size); + log_line("malloc", size, ptr); + return ptr; +} + +void free(void *ptr) { + pthread_once(&init_once, init_logger); + + if (real_free == NULL) { + return; + } + + log_line("free", 0, ptr); + real_free(ptr); +} + +void *calloc(size_t nmemb, size_t size) { + pthread_once(&init_once, init_logger); + + if (real_calloc == NULL) { + errno = ENOMEM; + return NULL; + } + + void *ptr = real_calloc(nmemb, size); + log_line("calloc", nmemb * size, ptr); + return ptr; +} + +void *realloc(void *ptr, size_t size) { + pthread_once(&init_once, init_logger); + + if (real_realloc == NULL) { + errno = ENOMEM; + return NULL; + } + + void *new_ptr = real_realloc(ptr, size); + log_line("realloc", size, new_ptr); + return new_ptr; +} diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 2de19292db..d6eb7f682d 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -19,6 +19,7 @@ mod unix { }; use std::env; use std::path::Path; + use std::process; use std::time::Duration; use libdd_common::{tag, Endpoint}; @@ -43,7 +44,8 @@ mod unix { } pub fn main() -> anyhow::Result<()> { - let mut args = env::args().skip(1); + let raw_args: Vec = env::args().collect(); + let mut args = raw_args.iter().skip(1); let output_url = args.next().context("Unexpected number of arguments")?; let receiver_binary = args.next().context("Unexpected number of arguments")?; let output_dir = args.next().context("Unexpected number of arguments")?; @@ -51,6 +53,28 @@ mod unix { let crash_typ = args.next().context("Missing crash type")?; anyhow::ensure!(args.next().is_none(), "unexpected extra arguments"); + // For malloc logger mode, re-exec with LD_PRELOAD but keep logging disabled + // until the Behavior explicitly enables it (MALLOC_LOG_ENABLED=1). This + // ensures the logger is loaded before any allocator calls while avoiding + // harness noise. + if mode_str == "runtime_malloc_logger" + && env::var_os("LD_PRELOAD").is_none() + && env::var_os("MALLOC_LOGGER_BOOTSTRAPPED").is_none() + { + if let Some(so_path) = option_env!("MALLOC_LOGGER_SO") { + let status = process::Command::new(&raw_args[0]) + .args(&raw_args[1..]) + .env("LD_PRELOAD", so_path) + .env("MALLOC_LOGGER_BOOTSTRAPPED", "1") + .env("MALLOC_LOG_ENABLED", "0") + .status() + .context("failed to re-exec with LD_PRELOAD")?; + + let code = status.code().unwrap_or(1); + process::exit(code); + } + } + let stderr_filename = format!("{output_dir}/out.stderr"); let stdout_filename = format!("{output_dir}/out.stdout"); let output_dir: &Path = output_dir.as_ref(); @@ -101,7 +125,7 @@ mod unix { CrashtrackerReceiverConfig::new( vec![], env::vars().collect(), - receiver_binary, + receiver_binary.to_string(), Some(stderr_filename), Some(stdout_filename), )?, diff --git a/bin_tests/src/modes/behavior.rs b/bin_tests/src/modes/behavior.rs index c5bdc51102..17e7dc2913 100644 --- a/bin_tests/src/modes/behavior.rs +++ b/bin_tests/src/modes/behavior.rs @@ -137,6 +137,7 @@ pub fn get_behavior(mode_str: &str) -> Box { "panic_hook_after_fork" => Box::new(test_013_panic_hook_after_fork::Test), "panic_hook_string" => Box::new(test_014_panic_hook_string::Test), "panic_hook_unknown_type" => Box::new(test_015_panic_hook_unknown_type::Test), + "runtime_malloc_logger" => Box::new(test_016_runtime_malloc_logger::Test), _ => panic!("Unknown mode: {mode_str}"), } } diff --git a/bin_tests/src/modes/unix/mod.rs b/bin_tests/src/modes/unix/mod.rs index 2b8c2b0f2d..08f33bd95d 100644 --- a/bin_tests/src/modes/unix/mod.rs +++ b/bin_tests/src/modes/unix/mod.rs @@ -16,3 +16,4 @@ pub mod test_012_runtime_callback_frame_invalid_utf8; 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_runtime_malloc_logger; diff --git a/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs b/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs new file mode 100644 index 0000000000..a4d34d956c --- /dev/null +++ b/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs @@ -0,0 +1,47 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 +// +// Exercise the collector under an LD_PRELOAD malloc logger. + +use crate::modes::behavior::Behavior; +use libdd_crashtracker::CrashtrackerConfiguration; +use std::path::Path; + +pub struct Test; + +impl Behavior for Test { + fn setup( + &self, + output_dir: &Path, + _config: &mut CrashtrackerConfiguration, + ) -> anyhow::Result<()> { + use anyhow::Context; + + // Ensure the collector loads the malloc logger via LD_PRELOAD. + if std::env::var("LD_PRELOAD").is_err() { + let so_path = option_env!("MALLOC_LOGGER_SO") + .context("MALLOC_LOGGER_SO not set; rebuild bin_tests?")?; + std::env::set_var("LD_PRELOAD", so_path); + } + + // Direct malloc log into the test output directory. + let log_path = output_dir.join("malloc_logger.log"); + std::env::set_var("MALLOC_LOG_PATH", &log_path); + // Enable logging now that path is set. + std::env::set_var("MALLOC_LOG_ENABLED", "1"); + let _ = std::fs::remove_file(&log_path); + Ok(()) + } + + fn pre(&self, _output_dir: &Path) -> anyhow::Result<()> { + // The collector (this process) should already be running with LD_PRELOAD. + // Drop LD_PRELOAD before spawning the receiver to keep the preload scoped + // to the collector only. + std::env::remove_var("LD_PRELOAD"); + Ok(()) + } + + fn post(&self, _output_dir: &Path) -> anyhow::Result<()> { + Ok(()) + } +} diff --git a/bin_tests/src/test_types.rs b/bin_tests/src/test_types.rs index 26331c8551..c169ef53c7 100644 --- a/bin_tests/src/test_types.rs +++ b/bin_tests/src/test_types.rs @@ -18,6 +18,7 @@ pub enum TestMode { RuntimeCallbackFrame, RuntimeCallbackString, RuntimeCallbackFrameInvalidUtf8, + RuntimeMallocLogger, } impl TestMode { @@ -37,6 +38,7 @@ impl TestMode { Self::RuntimeCallbackFrame => "runtime_callback_frame", Self::RuntimeCallbackString => "runtime_callback_string", Self::RuntimeCallbackFrameInvalidUtf8 => "runtime_callback_frame_invalid_utf8", + Self::RuntimeMallocLogger => "runtime_malloc_logger", } } @@ -56,6 +58,7 @@ impl TestMode { Self::RuntimeCallbackFrame, Self::RuntimeCallbackString, Self::RuntimeCallbackFrameInvalidUtf8, + Self::RuntimeMallocLogger, ] } } @@ -84,6 +87,7 @@ impl std::str::FromStr for TestMode { "runtime_callback_frame" => Ok(Self::RuntimeCallbackFrame), "runtime_callback_string" => Ok(Self::RuntimeCallbackString), "runtime_callback_frame_invalid_utf8" => Ok(Self::RuntimeCallbackFrameInvalidUtf8), + "runtime_malloc_logger" => Ok(Self::RuntimeMallocLogger), _ => Err(format!("Unknown test mode: {}", s)), } } diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 887f9362f0..4e2966250a 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -180,6 +180,20 @@ fn test_crash_tracking_bin_no_runtime_callback() { run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); } +// Manual/diagnostic malloc logger check. This is ignored by default to keep the +// regular bin test suite fast and hermetic. Run with: +// cargo test --test crashtracker_bin_test -- --ignored manual_runtime_malloc_logger +// Log output is written to tmp/malloc_logger.log. +#[test] +#[ignore] +fn manual_runtime_malloc_logger() { + run_standard_crash_test_refactored( + BuildProfile::Release, + TestMode::RuntimeMallocLogger, + CrashType::NullDeref, + ); +} + #[test] #[cfg_attr(miri, ignore)] fn test_crash_tracking_bin_runtime_callback_frame_invalid_utf8() { From a1b87bc4218533328168678527d4810847a26ca5 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Tue, 6 Jan 2026 15:22:37 +0000 Subject: [PATCH 02/18] Fix clippy and rename from malloc -> preload --- bin_tests/build.rs | 4 ++-- bin_tests/preload/{malloc_logger.c => preload.c} | 14 +------------- bin_tests/src/bin/crashtracker_bin_test.rs | 4 ++-- 3 files changed, 5 insertions(+), 17 deletions(-) rename bin_tests/preload/{malloc_logger.c => preload.c} (90%) diff --git a/bin_tests/build.rs b/bin_tests/build.rs index e9cf2f333d..09e6e35422 100644 --- a/bin_tests/build.rs +++ b/bin_tests/build.rs @@ -8,7 +8,7 @@ fn main() { use std::process::Command; let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); - let src = PathBuf::from("preload/malloc_logger.c"); + let src = PathBuf::from("preload/preload.c"); let so_path = out_dir.join("libmalloc_logger.so"); let status = Command::new("cc") @@ -19,7 +19,7 @@ fn main() { .expect("failed to spawn cc"); if !status.success() { - panic!("compiling malloc_logger.c failed with status {status}"); + panic!("compiling preload.c failed with status {status}"); } // Make the built shared object path available at compile time for tests/tools. diff --git a/bin_tests/preload/malloc_logger.c b/bin_tests/preload/preload.c similarity index 90% rename from bin_tests/preload/malloc_logger.c rename to bin_tests/preload/preload.c index 17d262c60d..6722fca5d5 100644 --- a/bin_tests/preload/malloc_logger.c +++ b/bin_tests/preload/preload.c @@ -35,18 +35,6 @@ static void init_logger(void) { real_free = dlsym(RTLD_NEXT, "free"); real_calloc = dlsym(RTLD_NEXT, "calloc"); real_realloc = dlsym(RTLD_NEXT, "realloc"); - - // No logging here; deferred until enabled. -} - -__attribute__((constructor)) static void preload_ctor(void) { - pthread_once(&init_once, init_logger); -} - -__attribute__((destructor)) static void preload_dtor(void) { - if (log_fd >= 0) { - close(log_fd); - } } static void log_line(const char *tag, size_t size, void *ptr) { @@ -69,7 +57,7 @@ static void log_line(const char *tag, size_t size, void *ptr) { len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld calloc size=%zu ptr=%p\n", pid, tid, size, ptr); } else if (strcmp(tag, "realloc") == 0) { len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld realloc size=%zu ptr=%p\n", pid, tid, size, ptr); - } else { // free + } else if (strcmp(tag, "free") == 0) { len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld free ptr=%p\n", pid, tid, ptr); } diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index d6eb7f682d..7cdfd4176f 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -82,7 +82,7 @@ mod unix { let endpoint = if output_url.is_empty() { None } else { - Some(Endpoint::from_slice(&output_url)) + Some(Endpoint::from_slice(output_url)) }; // The configuration can be modified by a Behavior (testing plan), so it is mut here. @@ -116,7 +116,7 @@ mod unix { }; // Set the behavior of the test, run setup, and do the pre-init test - let behavior = get_behavior(&mode_str); + let behavior = get_behavior(mode_str); behavior.setup(output_dir, &mut config)?; behavior.pre(output_dir)?; From 60ac3f14fc220b8916dbe6bd8c2f80f4153e069f Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Tue, 6 Jan 2026 15:25:57 +0000 Subject: [PATCH 03/18] Add license header --- bin_tests/preload/preload.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 6722fca5d5..ecf7693648 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -1,3 +1,6 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + #define _GNU_SOURCE #include #include From 236b4248f7447f4395edc89d761f161f2981b33b Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Tue, 6 Jan 2026 15:36:26 +0000 Subject: [PATCH 04/18] Fix docker cache bake for build.rs under bin_tests --- tools/docker/Dockerfile.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/docker/Dockerfile.build b/tools/docker/Dockerfile.build index e0246a59c5..7ce7eeadf1 100644 --- a/tools/docker/Dockerfile.build +++ b/tools/docker/Dockerfile.build @@ -112,6 +112,8 @@ COPY "datadog-ipc/plugins/Cargo.toml" "datadog-ipc/plugins/" COPY "libdd-data-pipeline/Cargo.toml" "libdd-data-pipeline/" COPY "libdd-data-pipeline-ffi/Cargo.toml" "libdd-data-pipeline-ffi/" COPY "bin_tests/Cargo.toml" "bin_tests/" +COPY "bin_tests/build.rs" "bin_tests/" +COPY "bin_tests/preload/preload.c" "bin_tests/preload/" COPY "libdd-tinybytes/Cargo.toml" "libdd-tinybytes/" COPY "builder/Cargo.toml" "builder/" COPY "datadog-ffe/Cargo.toml" "datadog-ffe/" From b0d9bac44f6ab3f5900a82e8e4eabd1828c97901 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Mon, 12 Jan 2026 02:22:25 +0000 Subject: [PATCH 05/18] Use thread local var to scope to collector, general clean up --- bin_tests/build.rs | 4 +- bin_tests/preload/preload.c | 17 +++---- bin_tests/src/bin/crashtracker_bin_test.rs | 14 ++---- bin_tests/src/modes/behavior.rs | 2 +- bin_tests/src/modes/unix/mod.rs | 2 +- .../unix/test_016_runtime_malloc_logger.rs | 47 ------------------- .../unix/test_016_runtime_preload_logger.rs | 31 ++++++++++++ bin_tests/src/test_types.rs | 8 ++-- bin_tests/tests/crashtracker_bin_test.rs | 10 ++-- libdd-crashtracker/src/collector/api.rs | 20 ++++++++ 10 files changed, 77 insertions(+), 78 deletions(-) delete mode 100644 bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs create mode 100644 bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs diff --git a/bin_tests/build.rs b/bin_tests/build.rs index 09e6e35422..bcc0fdbaa6 100644 --- a/bin_tests/build.rs +++ b/bin_tests/build.rs @@ -9,7 +9,7 @@ fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); let src = PathBuf::from("preload/preload.c"); - let so_path = out_dir.join("libmalloc_logger.so"); + let so_path = out_dir.join("libpreload_logger.so"); let status = Command::new("cc") .args(["-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) @@ -23,7 +23,7 @@ fn main() { } // Make the built shared object path available at compile time for tests/tools. - println!("cargo:rustc-env=MALLOC_LOGGER_SO={}", so_path.display()); + println!("cargo:rustc-env=PRELOAD_LOGGER_SO={}", so_path.display()); } #[cfg(not(unix))] diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index ecf7693648..3efa67f81f 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -21,16 +21,17 @@ static void *(*real_calloc)(size_t, size_t) = NULL; static void *(*real_realloc)(void *, size_t) = NULL; static int log_fd = -1; static pthread_once_t init_once = PTHREAD_ONCE_INIT; +// Per-thread flag to indicate this thread belongs to the collector; only those +// threads should produce malloc logs. +static __thread int dd_is_collector_thread = 0; -static int is_enabled(void) { - const char *v = getenv("MALLOC_LOG_ENABLED"); - return v && v[0] == '1'; -} +// Called by the collector process to scope logging to its thread only. +void dd_preload_logger_mark_collector_thread(void) { dd_is_collector_thread = 1; } static void init_logger(void) { - const char *path = getenv("MALLOC_LOG_PATH"); + const char *path = getenv("PRELOAD_LOG_PATH"); if (path == NULL || path[0] == '\0') { - path = "/tmp/malloc_logger.log"; + path = "/tmp/preload_logger.log"; } log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); @@ -41,11 +42,11 @@ static void init_logger(void) { } static void log_line(const char *tag, size_t size, void *ptr) { - if (log_fd < 0) { + if (!dd_is_collector_thread) { return; } - if (!is_enabled()) { + if (log_fd < 0) { return; } diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 7cdfd4176f..cb41c43f9e 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -53,20 +53,14 @@ mod unix { let crash_typ = args.next().context("Missing crash type")?; anyhow::ensure!(args.next().is_none(), "unexpected extra arguments"); - // For malloc logger mode, re-exec with LD_PRELOAD but keep logging disabled - // until the Behavior explicitly enables it (MALLOC_LOG_ENABLED=1). This - // ensures the logger is loaded before any allocator calls while avoiding - // harness noise. - if mode_str == "runtime_malloc_logger" - && env::var_os("LD_PRELOAD").is_none() - && env::var_os("MALLOC_LOGGER_BOOTSTRAPPED").is_none() - { + // For preload logger mode, ensure we actually start with LD_PRELOAD applied. + // Setting LD_PRELOAD after startup has no effect on the current process, + // so re-exec only if we weren't born with it + if mode_str == "runtime_preload_logger" && env::var_os("LD_PRELOAD").is_none() { if let Some(so_path) = option_env!("MALLOC_LOGGER_SO") { let status = process::Command::new(&raw_args[0]) .args(&raw_args[1..]) .env("LD_PRELOAD", so_path) - .env("MALLOC_LOGGER_BOOTSTRAPPED", "1") - .env("MALLOC_LOG_ENABLED", "0") .status() .context("failed to re-exec with LD_PRELOAD")?; diff --git a/bin_tests/src/modes/behavior.rs b/bin_tests/src/modes/behavior.rs index 17e7dc2913..250e3422a8 100644 --- a/bin_tests/src/modes/behavior.rs +++ b/bin_tests/src/modes/behavior.rs @@ -137,7 +137,7 @@ pub fn get_behavior(mode_str: &str) -> Box { "panic_hook_after_fork" => Box::new(test_013_panic_hook_after_fork::Test), "panic_hook_string" => Box::new(test_014_panic_hook_string::Test), "panic_hook_unknown_type" => Box::new(test_015_panic_hook_unknown_type::Test), - "runtime_malloc_logger" => Box::new(test_016_runtime_malloc_logger::Test), + "runtime_preload_logger" => Box::new(test_016_runtime_preload_logger::Test), _ => panic!("Unknown mode: {mode_str}"), } } diff --git a/bin_tests/src/modes/unix/mod.rs b/bin_tests/src/modes/unix/mod.rs index 08f33bd95d..e52884988e 100644 --- a/bin_tests/src/modes/unix/mod.rs +++ b/bin_tests/src/modes/unix/mod.rs @@ -16,4 +16,4 @@ pub mod test_012_runtime_callback_frame_invalid_utf8; 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_runtime_malloc_logger; +pub mod test_016_runtime_preload_logger; diff --git a/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs b/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs deleted file mode 100644 index a4d34d956c..0000000000 --- a/bin_tests/src/modes/unix/test_016_runtime_malloc_logger.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ -// SPDX-License-Identifier: Apache-2.0 -// -// Exercise the collector under an LD_PRELOAD malloc logger. - -use crate::modes::behavior::Behavior; -use libdd_crashtracker::CrashtrackerConfiguration; -use std::path::Path; - -pub struct Test; - -impl Behavior for Test { - fn setup( - &self, - output_dir: &Path, - _config: &mut CrashtrackerConfiguration, - ) -> anyhow::Result<()> { - use anyhow::Context; - - // Ensure the collector loads the malloc logger via LD_PRELOAD. - if std::env::var("LD_PRELOAD").is_err() { - let so_path = option_env!("MALLOC_LOGGER_SO") - .context("MALLOC_LOGGER_SO not set; rebuild bin_tests?")?; - std::env::set_var("LD_PRELOAD", so_path); - } - - // Direct malloc log into the test output directory. - let log_path = output_dir.join("malloc_logger.log"); - std::env::set_var("MALLOC_LOG_PATH", &log_path); - // Enable logging now that path is set. - std::env::set_var("MALLOC_LOG_ENABLED", "1"); - let _ = std::fs::remove_file(&log_path); - Ok(()) - } - - fn pre(&self, _output_dir: &Path) -> anyhow::Result<()> { - // The collector (this process) should already be running with LD_PRELOAD. - // Drop LD_PRELOAD before spawning the receiver to keep the preload scoped - // to the collector only. - std::env::remove_var("LD_PRELOAD"); - Ok(()) - } - - fn post(&self, _output_dir: &Path) -> anyhow::Result<()> { - Ok(()) - } -} diff --git a/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs b/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs new file mode 100644 index 0000000000..8c2b77cf85 --- /dev/null +++ b/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs @@ -0,0 +1,31 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 +// +// Exercise the collector under an LD_PRELOAD preload logger. + +use crate::modes::behavior::Behavior; +use libdd_crashtracker::CrashtrackerConfiguration; +use std::path::Path; + +pub struct Test; + +impl Behavior for Test { + fn setup( + &self, + output_dir: &Path, + _config: &mut CrashtrackerConfiguration, + ) -> anyhow::Result<()> { + let log_path = output_dir.join("preload_logger.log"); + std::env::set_var("PRELOAD_LOG_PATH", &log_path); + let _ = std::fs::remove_file(&log_path); + Ok(()) + } + + fn pre(&self, _output_dir: &Path) -> anyhow::Result<()> { + Ok(()) + } + + fn post(&self, _output_dir: &Path) -> anyhow::Result<()> { + Ok(()) + } +} diff --git a/bin_tests/src/test_types.rs b/bin_tests/src/test_types.rs index c169ef53c7..1666264f77 100644 --- a/bin_tests/src/test_types.rs +++ b/bin_tests/src/test_types.rs @@ -18,7 +18,7 @@ pub enum TestMode { RuntimeCallbackFrame, RuntimeCallbackString, RuntimeCallbackFrameInvalidUtf8, - RuntimeMallocLogger, + RuntimePreloadLogger, } impl TestMode { @@ -38,7 +38,7 @@ impl TestMode { Self::RuntimeCallbackFrame => "runtime_callback_frame", Self::RuntimeCallbackString => "runtime_callback_string", Self::RuntimeCallbackFrameInvalidUtf8 => "runtime_callback_frame_invalid_utf8", - Self::RuntimeMallocLogger => "runtime_malloc_logger", + Self::RuntimePreloadLogger => "runtime_preload_logger", } } @@ -58,7 +58,7 @@ impl TestMode { Self::RuntimeCallbackFrame, Self::RuntimeCallbackString, Self::RuntimeCallbackFrameInvalidUtf8, - Self::RuntimeMallocLogger, + Self::RuntimePreloadLogger, ] } } @@ -87,7 +87,7 @@ impl std::str::FromStr for TestMode { "runtime_callback_frame" => Ok(Self::RuntimeCallbackFrame), "runtime_callback_string" => Ok(Self::RuntimeCallbackString), "runtime_callback_frame_invalid_utf8" => Ok(Self::RuntimeCallbackFrameInvalidUtf8), - "runtime_malloc_logger" => Ok(Self::RuntimeMallocLogger), + "runtime_preload_logger" => Ok(Self::RuntimePreloadLogger), _ => Err(format!("Unknown test mode: {}", s)), } } diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 4e2966250a..189af98393 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -180,16 +180,16 @@ fn test_crash_tracking_bin_no_runtime_callback() { run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); } -// Manual/diagnostic malloc logger check. This is ignored by default to keep the +// Manual/diagnostic preload logger check. This is ignored by default to keep the // regular bin test suite fast and hermetic. Run with: -// cargo test --test crashtracker_bin_test -- --ignored manual_runtime_malloc_logger -// Log output is written to tmp/malloc_logger.log. +// cargo test --test crashtracker_bin_test -- --ignored manual_runtime_preload_logger +// Log output is written to tmp/preload_logger.log. #[test] #[ignore] -fn manual_runtime_malloc_logger() { +fn manual_runtime_preload_logger() { run_standard_crash_test_refactored( BuildProfile::Release, - TestMode::RuntimeMallocLogger, + TestMode::RuntimePreloadLogger, CrashType::NullDeref, ); } diff --git a/libdd-crashtracker/src/collector/api.rs b/libdd-crashtracker/src/collector/api.rs index 91e1e78a10..06847699a6 100644 --- a/libdd-crashtracker/src/collector/api.rs +++ b/libdd-crashtracker/src/collector/api.rs @@ -17,6 +17,24 @@ pub fn default_signals() -> Vec { Vec::from(DEFAULT_SYMBOLS) } +#[cfg(target_os = "linux")] +fn mark_malloc_logger_collector_thread() { + // This function is specific only for LD_PRELOAD testing + // Best effort; this symbol exists only when the malloc logger preload is present. + // When found, it flips the thread local var in the collector so the preload will emit logs. + const SYMBOL: &[u8] = b"dd_preload_logger_mark_collector_thread\0"; + unsafe { + let sym = libc::dlsym(libc::RTLD_DEFAULT, SYMBOL.as_ptr() as *const _); + if !sym.is_null() { + let func: extern "C" fn() = std::mem::transmute(sym); + func(); + } + } +} + +#[cfg(not(target_os = "linux"))] +fn mark_malloc_logger_collector_thread() {} + /// Reinitialize the crash-tracking infrastructure after a fork. /// This should be one of the first things done after a fork, to minimize the /// chance that a crash occurs between the fork, and this call. @@ -36,6 +54,7 @@ pub fn on_fork( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { + mark_malloc_logger_collector_thread(); clear_spans()?; clear_traces()?; reset_counters()?; @@ -67,6 +86,7 @@ pub fn init( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { + mark_malloc_logger_collector_thread(); update_metadata(metadata)?; update_config(config.clone())?; Receiver::update_stored_config(receiver_config)?; From eb0f2345c8ce1b41507eb4315e633692f5298601 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Mon, 12 Jan 2026 15:25:38 +0000 Subject: [PATCH 06/18] Clean up --- bin_tests/preload/preload.c | 8 ++++---- bin_tests/src/bin/crashtracker_bin_test.rs | 2 +- libdd-crashtracker/src/collector/api.rs | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 3efa67f81f..8b27141064 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -23,10 +23,10 @@ static int log_fd = -1; static pthread_once_t init_once = PTHREAD_ONCE_INIT; // Per-thread flag to indicate this thread belongs to the collector; only those // threads should produce malloc logs. -static __thread int dd_is_collector_thread = 0; +static __thread int dd_is_collector = 0; -// Called by the collector process to scope logging to its thread only. -void dd_preload_logger_mark_collector_thread(void) { dd_is_collector_thread = 1; } +// Called by the collector process to scope logging to the collector only +void dd_preload_logger_mark_collector(void) { dd_is_collector = 1; } static void init_logger(void) { const char *path = getenv("PRELOAD_LOG_PATH"); @@ -42,7 +42,7 @@ static void init_logger(void) { } static void log_line(const char *tag, size_t size, void *ptr) { - if (!dd_is_collector_thread) { + if (!dd_is_collector) { return; } diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index cb41c43f9e..2f9e00d471 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -57,7 +57,7 @@ mod unix { // Setting LD_PRELOAD after startup has no effect on the current process, // so re-exec only if we weren't born with it if mode_str == "runtime_preload_logger" && env::var_os("LD_PRELOAD").is_none() { - if let Some(so_path) = option_env!("MALLOC_LOGGER_SO") { + if let Some(so_path) = option_env!("PRELOAD_LOGGER_SO") { let status = process::Command::new(&raw_args[0]) .args(&raw_args[1..]) .env("LD_PRELOAD", so_path) diff --git a/libdd-crashtracker/src/collector/api.rs b/libdd-crashtracker/src/collector/api.rs index 06847699a6..c540fccf84 100644 --- a/libdd-crashtracker/src/collector/api.rs +++ b/libdd-crashtracker/src/collector/api.rs @@ -18,11 +18,11 @@ pub fn default_signals() -> Vec { } #[cfg(target_os = "linux")] -fn mark_malloc_logger_collector_thread() { +fn mark_preload_logger_collector() { // This function is specific only for LD_PRELOAD testing - // Best effort; this symbol exists only when the malloc logger preload is present. + // Best effort; this symbol exists only when the preload logger preload is present. // When found, it flips the thread local var in the collector so the preload will emit logs. - const SYMBOL: &[u8] = b"dd_preload_logger_mark_collector_thread\0"; + const SYMBOL: &[u8] = b"dd_preload_logger_mark_collector\0"; unsafe { let sym = libc::dlsym(libc::RTLD_DEFAULT, SYMBOL.as_ptr() as *const _); if !sym.is_null() { @@ -33,7 +33,7 @@ fn mark_malloc_logger_collector_thread() { } #[cfg(not(target_os = "linux"))] -fn mark_malloc_logger_collector_thread() {} +fn mark_preload_logger_collector() {} /// Reinitialize the crash-tracking infrastructure after a fork. /// This should be one of the first things done after a fork, to minimize the @@ -54,7 +54,7 @@ pub fn on_fork( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { - mark_malloc_logger_collector_thread(); + mark_preload_logger_collector(); clear_spans()?; clear_traces()?; reset_counters()?; @@ -86,7 +86,7 @@ pub fn init( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { - mark_malloc_logger_collector_thread(); + mark_preload_logger_collector(); update_metadata(metadata)?; update_config(config.clone())?; Receiver::update_stored_config(receiver_config)?; From 0cedb4274f128bea084426586a7fb1b95d89e652 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Mon, 12 Jan 2026 19:51:25 +0000 Subject: [PATCH 07/18] Actually only log in collector child --- bin_tests/preload/preload.c | 77 +++++++++++++------ bin_tests/src/bin/crashtracker_bin_test.rs | 21 ++--- bin_tests/src/modes/behavior.rs | 2 +- libdd-crashtracker/src/collector/api.rs | 6 +- .../src/collector/collector_manager.rs | 6 ++ 5 files changed, 75 insertions(+), 37 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 8b27141064..3ac635f2cf 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -21,39 +21,64 @@ static void *(*real_calloc)(size_t, size_t) = NULL; static void *(*real_realloc)(void *, size_t) = NULL; static int log_fd = -1; static pthread_once_t init_once = PTHREAD_ONCE_INIT; -// Per-thread flag to indicate this thread belongs to the collector; only those -// threads should produce malloc logs. -static __thread int dd_is_collector = 0; +// Flag to indicate we are currently in the collector; We should only +// log when we are in the collector. +static int collector_marked = 0; -// Called by the collector process to scope logging to the collector only -void dd_preload_logger_mark_collector(void) { dd_is_collector = 1; } +static void init_function_ptrs(void) { + if (real_malloc == NULL) { + real_malloc = dlsym(RTLD_NEXT, "malloc"); + real_free = dlsym(RTLD_NEXT, "free"); + real_calloc = dlsym(RTLD_NEXT, "calloc"); + real_realloc = dlsym(RTLD_NEXT, "realloc"); + } +} static void init_logger(void) { + if (log_fd >= 0 || !collector_marked) { + // Already initialized or not a collector + return; + } + const char *path = getenv("PRELOAD_LOG_PATH"); if (path == NULL || path[0] == '\0') { path = "/tmp/preload_logger.log"; } log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); - real_malloc = dlsym(RTLD_NEXT, "malloc"); - real_free = dlsym(RTLD_NEXT, "free"); - real_calloc = dlsym(RTLD_NEXT, "calloc"); - real_realloc = dlsym(RTLD_NEXT, "realloc"); + if (log_fd >= 0) { + char buf[256]; + pid_t pid = getpid(); + int len = snprintf(buf, sizeof(buf), "[DEBUG] Collector logger initialized pid=%d, fd=%d, path=%s\n", + pid, log_fd, path); + write(log_fd, buf, len); + } } -static void log_line(const char *tag, size_t size, void *ptr) { - if (!dd_is_collector) { - return; +// Called by the collector process to scope logging to the collector only +void dd_preload_logger_mark_collector(void) { + collector_marked = 1; + + init_logger(); + + if (log_fd >= 0) { + char buf[256]; + pid_t pid = getpid(); + int len = snprintf(buf, sizeof(buf), "[DEBUG] Marked as collector, pid=%d, fd=%d\n", pid, log_fd); + write(log_fd, buf, len); } +} + +static void log_line(const char *tag, size_t size, void *ptr) { - if (log_fd < 0) { + if (log_fd < 0 || !collector_marked) { return; } char buf[200]; pid_t pid = getpid(); long tid = syscall(SYS_gettid); - int len; + int len = 0; if (strcmp(tag, "malloc") == 0) { len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld malloc size=%zu ptr=%p\n", pid, tid, size, ptr); @@ -71,7 +96,7 @@ static void log_line(const char *tag, size_t size, void *ptr) { } void *malloc(size_t size) { - pthread_once(&init_once, init_logger); + pthread_once(&init_once, init_function_ptrs); if (real_malloc == NULL) { errno = ENOMEM; @@ -79,23 +104,27 @@ void *malloc(size_t size) { } void *ptr = real_malloc(size); - log_line("malloc", size, ptr); + if (collector_marked) { + log_line("malloc", size, ptr); + } return ptr; } void free(void *ptr) { - pthread_once(&init_once, init_logger); + pthread_once(&init_once, init_function_ptrs); if (real_free == NULL) { return; } - log_line("free", 0, ptr); + if (collector_marked) { + log_line("free", 0, ptr); + } real_free(ptr); } void *calloc(size_t nmemb, size_t size) { - pthread_once(&init_once, init_logger); + pthread_once(&init_once, init_function_ptrs); if (real_calloc == NULL) { errno = ENOMEM; @@ -103,12 +132,14 @@ void *calloc(size_t nmemb, size_t size) { } void *ptr = real_calloc(nmemb, size); - log_line("calloc", nmemb * size, ptr); + if (collector_marked) { + log_line("calloc", nmemb * size, ptr); + } return ptr; } void *realloc(void *ptr, size_t size) { - pthread_once(&init_once, init_logger); + pthread_once(&init_once, init_function_ptrs); if (real_realloc == NULL) { errno = ENOMEM; @@ -116,6 +147,8 @@ void *realloc(void *ptr, size_t size) { } void *new_ptr = real_realloc(ptr, size); - log_line("realloc", size, new_ptr); + if (collector_marked) { + log_line("realloc", size, new_ptr); + } return new_ptr; } diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 2f9e00d471..7cfcef3322 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -56,16 +56,17 @@ mod unix { // For preload logger mode, ensure we actually start with LD_PRELOAD applied. // Setting LD_PRELOAD after startup has no effect on the current process, // so re-exec only if we weren't born with it - if mode_str == "runtime_preload_logger" && env::var_os("LD_PRELOAD").is_none() { - if let Some(so_path) = option_env!("PRELOAD_LOGGER_SO") { - let status = process::Command::new(&raw_args[0]) - .args(&raw_args[1..]) - .env("LD_PRELOAD", so_path) - .status() - .context("failed to re-exec with LD_PRELOAD")?; - - let code = status.code().unwrap_or(1); - process::exit(code); + if mode_str == "runtime_preload_logger" { + if env::var_os("LD_PRELOAD").is_none() { + if let Some(so_path) = option_env!("PRELOAD_LOGGER_SO") { + let status = process::Command::new(&raw_args[0]) + .args(&raw_args[1..]) + .env("LD_PRELOAD", so_path) + .status() + .context("failed to re-exec with LD_PRELOAD")?; + let code = status.code().unwrap_or(1); + process::exit(code); + } } } diff --git a/bin_tests/src/modes/behavior.rs b/bin_tests/src/modes/behavior.rs index 250e3422a8..3099f49678 100644 --- a/bin_tests/src/modes/behavior.rs +++ b/bin_tests/src/modes/behavior.rs @@ -137,7 +137,7 @@ pub fn get_behavior(mode_str: &str) -> Box { "panic_hook_after_fork" => Box::new(test_013_panic_hook_after_fork::Test), "panic_hook_string" => Box::new(test_014_panic_hook_string::Test), "panic_hook_unknown_type" => Box::new(test_015_panic_hook_unknown_type::Test), - "runtime_preload_logger" => Box::new(test_016_runtime_preload_logger::Test), + "runtime_preload_logger" => Box::new(test_000_donothing::Test), _ => panic!("Unknown mode: {mode_str}"), } } diff --git a/libdd-crashtracker/src/collector/api.rs b/libdd-crashtracker/src/collector/api.rs index c540fccf84..432737b96f 100644 --- a/libdd-crashtracker/src/collector/api.rs +++ b/libdd-crashtracker/src/collector/api.rs @@ -18,7 +18,7 @@ pub fn default_signals() -> Vec { } #[cfg(target_os = "linux")] -fn mark_preload_logger_collector() { +pub(super) fn mark_preload_logger_collector() { // This function is specific only for LD_PRELOAD testing // Best effort; this symbol exists only when the preload logger preload is present. // When found, it flips the thread local var in the collector so the preload will emit logs. @@ -33,7 +33,7 @@ fn mark_preload_logger_collector() { } #[cfg(not(target_os = "linux"))] -fn mark_preload_logger_collector() {} +pub(super) fn mark_preload_logger_collector() {} /// Reinitialize the crash-tracking infrastructure after a fork. /// This should be one of the first things done after a fork, to minimize the @@ -54,7 +54,6 @@ pub fn on_fork( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { - mark_preload_logger_collector(); clear_spans()?; clear_traces()?; reset_counters()?; @@ -86,7 +85,6 @@ pub fn init( receiver_config: CrashtrackerReceiverConfig, metadata: Metadata, ) -> anyhow::Result<()> { - mark_preload_logger_collector(); update_metadata(metadata)?; update_config(config.clone())?; Receiver::update_stored_config(receiver_config)?; diff --git a/libdd-crashtracker/src/collector/collector_manager.rs b/libdd-crashtracker/src/collector/collector_manager.rs index a3e3fb1e65..44d691dfe3 100644 --- a/libdd-crashtracker/src/collector/collector_manager.rs +++ b/libdd-crashtracker/src/collector/collector_manager.rs @@ -42,6 +42,12 @@ impl Collector { match fork_result { 0 => { // Child (does not exit from this function) + // Mark this process as a collector for the preload logger + #[cfg(target_os = "linux")] + { + super::api::mark_preload_logger_collector(); + } + run_collector_child( config, config_str, From 63790c07c342494aa426a07c91facc10b5a2db20 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Mon, 12 Jan 2026 21:14:20 +0000 Subject: [PATCH 08/18] clean --- bin_tests/preload/preload.c | 8 +------- bin_tests/src/bin/crashtracker_bin_test.rs | 20 +++++++++----------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 3ac635f2cf..fb0c665a1f 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -6,10 +6,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -40,11 +38,7 @@ static void init_logger(void) { return; } - const char *path = getenv("PRELOAD_LOG_PATH"); - if (path == NULL || path[0] == '\0') { - path = "/tmp/preload_logger.log"; - } - + const char *path = "/tmp/preload_logger.log"; log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (log_fd >= 0) { char buf[256]; diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 7cfcef3322..01b2b4f5d5 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -56,17 +56,15 @@ mod unix { // For preload logger mode, ensure we actually start with LD_PRELOAD applied. // Setting LD_PRELOAD after startup has no effect on the current process, // so re-exec only if we weren't born with it - if mode_str == "runtime_preload_logger" { - if env::var_os("LD_PRELOAD").is_none() { - if let Some(so_path) = option_env!("PRELOAD_LOGGER_SO") { - let status = process::Command::new(&raw_args[0]) - .args(&raw_args[1..]) - .env("LD_PRELOAD", so_path) - .status() - .context("failed to re-exec with LD_PRELOAD")?; - let code = status.code().unwrap_or(1); - process::exit(code); - } + if mode_str == "runtime_preload_logger" && env::var_os("LD_PRELOAD").is_none() { + if let Some(so_path) = option_env!("PRELOAD_LOGGER_SO") { + let status = process::Command::new(&raw_args[0]) + .args(&raw_args[1..]) + .env("LD_PRELOAD", so_path) + .status() + .context("failed to re-exec with LD_PRELOAD")?; + let code = status.code().unwrap_or(1); + process::exit(code); } } From ff763bd82f5307e3a93276ebc57255632ffc40b2 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Mon, 12 Jan 2026 21:47:44 +0000 Subject: [PATCH 09/18] Clean API by loading real symbols on load --- bin_tests/preload/preload.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index fb0c665a1f..f2bd882d6a 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -17,12 +17,9 @@ static void *(*real_malloc)(size_t) = NULL; static void (*real_free)(void *) = NULL; static void *(*real_calloc)(size_t, size_t) = NULL; static void *(*real_realloc)(void *, size_t) = NULL; -static int log_fd = -1; static pthread_once_t init_once = PTHREAD_ONCE_INIT; -// Flag to indicate we are currently in the collector; We should only -// log when we are in the collector. -static int collector_marked = 0; +// We should load all the real symbols on library load static void init_function_ptrs(void) { if (real_malloc == NULL) { real_malloc = dlsym(RTLD_NEXT, "malloc"); @@ -32,6 +29,16 @@ static void init_function_ptrs(void) { } } +__attribute__((constructor)) static void preload_ctor(void) { + pthread_once(&init_once, init_function_ptrs); +} + +static int log_fd = -1; +// Flag to indicate we are currently in the collector; We should only +// log when we are in the collector. +static int collector_marked = 0; + + static void init_logger(void) { if (log_fd >= 0 || !collector_marked) { // Already initialized or not a collector @@ -64,12 +71,11 @@ void dd_preload_logger_mark_collector(void) { } static void log_line(const char *tag, size_t size, void *ptr) { - - if (log_fd < 0 || !collector_marked) { + if (log_fd < 0) { return; } - char buf[200]; + char buf[256]; pid_t pid = getpid(); long tid = syscall(SYS_gettid); int len = 0; @@ -90,8 +96,6 @@ static void log_line(const char *tag, size_t size, void *ptr) { } void *malloc(size_t size) { - pthread_once(&init_once, init_function_ptrs); - if (real_malloc == NULL) { errno = ENOMEM; return NULL; @@ -105,8 +109,6 @@ void *malloc(size_t size) { } void free(void *ptr) { - pthread_once(&init_once, init_function_ptrs); - if (real_free == NULL) { return; } @@ -118,8 +120,6 @@ void free(void *ptr) { } void *calloc(size_t nmemb, size_t size) { - pthread_once(&init_once, init_function_ptrs); - if (real_calloc == NULL) { errno = ENOMEM; return NULL; @@ -133,8 +133,6 @@ void *calloc(size_t nmemb, size_t size) { } void *realloc(void *ptr, size_t size) { - pthread_once(&init_once, init_function_ptrs); - if (real_realloc == NULL) { errno = ENOMEM; return NULL; From 1e98ec338d5fc68ca77c56ba6291c87fd63ffc6d Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Wed, 14 Jan 2026 18:02:56 +0000 Subject: [PATCH 10/18] We can just use donothing test behavior --- bin_tests/src/modes/unix/mod.rs | 1 - .../unix/test_016_runtime_preload_logger.rs | 31 ------------------- 2 files changed, 32 deletions(-) delete mode 100644 bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs diff --git a/bin_tests/src/modes/unix/mod.rs b/bin_tests/src/modes/unix/mod.rs index e52884988e..2b8c2b0f2d 100644 --- a/bin_tests/src/modes/unix/mod.rs +++ b/bin_tests/src/modes/unix/mod.rs @@ -16,4 +16,3 @@ pub mod test_012_runtime_callback_frame_invalid_utf8; 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_runtime_preload_logger; diff --git a/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs b/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs deleted file mode 100644 index 8c2b77cf85..0000000000 --- a/bin_tests/src/modes/unix/test_016_runtime_preload_logger.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ -// SPDX-License-Identifier: Apache-2.0 -// -// Exercise the collector under an LD_PRELOAD preload logger. - -use crate::modes::behavior::Behavior; -use libdd_crashtracker::CrashtrackerConfiguration; -use std::path::Path; - -pub struct Test; - -impl Behavior for Test { - fn setup( - &self, - output_dir: &Path, - _config: &mut CrashtrackerConfiguration, - ) -> anyhow::Result<()> { - let log_path = output_dir.join("preload_logger.log"); - std::env::set_var("PRELOAD_LOG_PATH", &log_path); - let _ = std::fs::remove_file(&log_path); - Ok(()) - } - - fn pre(&self, _output_dir: &Path) -> anyhow::Result<()> { - Ok(()) - } - - fn post(&self, _output_dir: &Path) -> anyhow::Result<()> { - Ok(()) - } -} From 5cf9c7d37e859929482bee76c0aee4449c31f3e2 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Wed, 14 Jan 2026 19:32:28 +0000 Subject: [PATCH 11/18] Fail test on any detection and print out stacktrace --- bin_tests/build.rs | 2 +- bin_tests/preload/preload.c | 130 +++++++++++++---------- bin_tests/tests/crashtracker_bin_test.rs | 46 ++++++-- libdd-crashtracker/src/collector/api.rs | 1 - 4 files changed, 112 insertions(+), 67 deletions(-) diff --git a/bin_tests/build.rs b/bin_tests/build.rs index bcc0fdbaa6..eda63cde5d 100644 --- a/bin_tests/build.rs +++ b/bin_tests/build.rs @@ -12,7 +12,7 @@ fn main() { let so_path = out_dir.join("libpreload_logger.so"); let status = Command::new("cc") - .args(["-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) + .args(["-std=gnu99", "-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) .arg(&so_path) .arg(&src) .status() diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index f2bd882d6a..c1324aa98e 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -6,13 +6,22 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#ifdef __GLIBC__ +#include +#define DD_HAVE_EXECINFO 1 +#else +#define DD_HAVE_EXECINFO 0 +#endif + static void *(*real_malloc)(size_t) = NULL; static void (*real_free)(void *) = NULL; static void *(*real_calloc)(size_t, size_t) = NULL; @@ -35,64 +44,70 @@ __attribute__((constructor)) static void preload_ctor(void) { static int log_fd = -1; // Flag to indicate we are currently in the collector; We should only -// log when we are in the collector. +// detect allocations when we are in the collector. static int collector_marked = 0; +// Flag to track if we've already detected and reported an allocation +// This guards against reentrancy of the detection logic. +static int allocation_detected = 0; - -static void init_logger(void) { +// Called by the collector process to enable detection in the collector only +void dd_preload_logger_mark_collector(void) { + collector_marked = 1; if (log_fd >= 0 || !collector_marked) { // Already initialized or not a collector return; } - - const char *path = "/tmp/preload_logger.log"; - log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (log_fd >= 0) { - char buf[256]; - pid_t pid = getpid(); - int len = snprintf(buf, sizeof(buf), "[DEBUG] Collector logger initialized pid=%d, fd=%d, path=%s\n", - pid, log_fd, path); - write(log_fd, buf, len); - } -} - -// Called by the collector process to scope logging to the collector only -void dd_preload_logger_mark_collector(void) { - collector_marked = 1; - - init_logger(); - - if (log_fd >= 0) { - char buf[256]; - pid_t pid = getpid(); - int len = snprintf(buf, sizeof(buf), "[DEBUG] Marked as collector, pid=%d, fd=%d\n", pid, log_fd); - write(log_fd, buf, len); - } } -static void log_line(const char *tag, size_t size, void *ptr) { - if (log_fd < 0) { - return; - } - - char buf[256]; - pid_t pid = getpid(); - long tid = syscall(SYS_gettid); - int len = 0; - - if (strcmp(tag, "malloc") == 0) { - len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld malloc size=%zu ptr=%p\n", pid, tid, size, ptr); - } else if (strcmp(tag, "calloc") == 0) { - len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld calloc size=%zu ptr=%p\n", pid, tid, size, ptr); - } else if (strcmp(tag, "realloc") == 0) { - len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld realloc size=%zu ptr=%p\n", pid, tid, size, ptr); - } else if (strcmp(tag, "free") == 0) { - len = snprintf(buf, sizeof(buf), "pid=%d tid=%ld free ptr=%p\n", pid, tid, ptr); +static void capture_and_report_allocation(const char *func_name) { + // Only report once using atomic compare-and-swap + if (__sync_bool_compare_and_swap(&allocation_detected, 0, 1)) { + const char *path = "/tmp/preload_detector.log"; + log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (log_fd >= 0) { + char buf[4096]; + pid_t pid = getpid(); + long tid = syscall(SYS_gettid); + + // Log the detection + int len = snprintf(buf, sizeof(buf), + "[FATAL] Dangerous allocation detected in collector!\n" + " Function: %s\n" + " PID: %d\n" + " TID: %ld\n" + " Stacktrace:\n", + func_name, pid, tid); + write(log_fd, buf, len); + + // Capture and log stacktrace (glibc only; musl lacks execinfo) +#if DD_HAVE_EXECINFO + void *array[100]; + int size = backtrace(array, 100); + char **strings = backtrace_symbols(array, size); + + if (strings != NULL) { + for (int i = 0; i < size; i++) { + len = snprintf(buf, sizeof(buf), " #%d %s\n", i, strings[i]); + write(log_fd, buf, len); + } + // backtrace_symbols uses malloc internally, so we have a small leak + // but this is acceptable since this only happens once and we guard + // against it anyways + } +#else + len = snprintf(buf, sizeof(buf), + " [backtrace unavailable: execinfo.h not present on this platform (likely musl)]\n"); + write(log_fd, buf, len); +#endif + + fsync(log_fd); + close(log_fd); + log_fd = -1; + } } - if (len > 0) { - (void)write(log_fd, buf, (size_t)len); - } + // Don't abort. let the collector continue so it can finish writing the crash report + // The test will check for the log file and fail if allocations were detected } void *malloc(size_t size) { @@ -101,10 +116,11 @@ void *malloc(size_t size) { return NULL; } - void *ptr = real_malloc(size); if (collector_marked) { - log_line("malloc", size, ptr); + capture_and_report_allocation("malloc"); } + + void *ptr = real_malloc(size); return ptr; } @@ -113,9 +129,7 @@ void free(void *ptr) { return; } - if (collector_marked) { - log_line("free", 0, ptr); - } + // free is generally safe; we'll allow free operations without failing real_free(ptr); } @@ -125,10 +139,11 @@ void *calloc(size_t nmemb, size_t size) { return NULL; } - void *ptr = real_calloc(nmemb, size); if (collector_marked) { - log_line("calloc", nmemb * size, ptr); + capture_and_report_allocation("calloc"); } + + void *ptr = real_calloc(nmemb, size); return ptr; } @@ -138,9 +153,10 @@ void *realloc(void *ptr, size_t size) { return NULL; } - void *new_ptr = real_realloc(ptr, size); if (collector_marked) { - log_line("realloc", size, new_ptr); + capture_and_report_allocation("realloc"); } + + void *new_ptr = real_realloc(ptr, size); return new_ptr; } diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 189af98393..82f7be692c 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -180,18 +180,48 @@ fn test_crash_tracking_bin_no_runtime_callback() { run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); } -// Manual/diagnostic preload logger check. This is ignored by default to keep the -// regular bin test suite fast and hermetic. Run with: -// cargo test --test crashtracker_bin_test -- --ignored manual_runtime_preload_logger -// Log output is written to tmp/preload_logger.log. +// Test that verifies the collector process doesn't perform dangerous allocations. +// Uses LD_PRELOAD to detect malloc/calloc/realloc calls in the collector and fails +// if any are detected, capturing a stacktrace for debugging. #[test] -#[ignore] -fn manual_runtime_preload_logger() { - run_standard_crash_test_refactored( - BuildProfile::Release, +#[cfg_attr(miri, ignore)] +#[cfg(target_os = "linux")] // LD_PRELOAD is Linux-specific +fn test_collector_no_allocations() { + let config = CrashTestConfig::new( + BuildProfile::Debug, TestMode::RuntimePreloadLogger, CrashType::NullDeref, ); + let artifacts = StandardArtifacts::new(config.profile); + let artifacts_map = build_artifacts(&artifacts.as_slice()).unwrap(); + + let validator: ValidatorFn = Box::new(|payload, _fixtures| { + // First validate that the crash report was properly generated + PayloadValidator::new(payload).validate_counters()?; + + let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); + + if detector_log_path.exists() { + let log_content = fs::read_to_string(&detector_log_path) + .context("Failed to read preload detector log")?; + + // Clean up the log file first + let _ = fs::remove_file(&detector_log_path); + + eprintln!("=== DANGEROUS ALLOCATION DETECTED IN COLLECTOR ==="); + eprintln!("{}", log_content); + + anyhow::bail!( + "Collector performed dangerous allocation! Check the log above for stacktrace." + ); + } + + // If we get here, no dangerous allocations were detected, we shuld pass + Ok(()) + }); + + // Run the test - we expect it to complete normally without the collector aborting + run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); } #[test] diff --git a/libdd-crashtracker/src/collector/api.rs b/libdd-crashtracker/src/collector/api.rs index 432737b96f..d512398c58 100644 --- a/libdd-crashtracker/src/collector/api.rs +++ b/libdd-crashtracker/src/collector/api.rs @@ -21,7 +21,6 @@ pub fn default_signals() -> Vec { pub(super) fn mark_preload_logger_collector() { // This function is specific only for LD_PRELOAD testing // Best effort; this symbol exists only when the preload logger preload is present. - // When found, it flips the thread local var in the collector so the preload will emit logs. const SYMBOL: &[u8] = b"dd_preload_logger_mark_collector\0"; unsafe { let sym = libc::dlsym(libc::RTLD_DEFAULT, SYMBOL.as_ptr() as *const _); From d0bfe979692c1f26b952af7684ba14d36e2f6f23 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Wed, 14 Jan 2026 22:00:40 +0000 Subject: [PATCH 12/18] Flip logging flag in signal handler --- bin_tests/preload/preload.c | 3 ++- bin_tests/tests/crashtracker_bin_test.rs | 3 --- libdd-crashtracker/src/collector/collector_manager.rs | 6 ------ libdd-crashtracker/src/collector/crash_handler.rs | 6 ++++++ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index c1324aa98e..99b45d3d15 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -47,7 +47,8 @@ static int log_fd = -1; // detect allocations when we are in the collector. static int collector_marked = 0; // Flag to track if we've already detected and reported an allocation -// This guards against reentrancy of the detection logic. +// This guards against reentrancy of the detection logic when we capture +// stack trace, since backtrace can use malloc internally static int allocation_detected = 0; // Called by the collector process to enable detection in the collector only diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 82f7be692c..034e9542ec 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -207,10 +207,7 @@ fn test_collector_no_allocations() { // Clean up the log file first let _ = fs::remove_file(&detector_log_path); - - eprintln!("=== DANGEROUS ALLOCATION DETECTED IN COLLECTOR ==="); eprintln!("{}", log_content); - anyhow::bail!( "Collector performed dangerous allocation! Check the log above for stacktrace." ); diff --git a/libdd-crashtracker/src/collector/collector_manager.rs b/libdd-crashtracker/src/collector/collector_manager.rs index 44d691dfe3..a3e3fb1e65 100644 --- a/libdd-crashtracker/src/collector/collector_manager.rs +++ b/libdd-crashtracker/src/collector/collector_manager.rs @@ -42,12 +42,6 @@ impl Collector { match fork_result { 0 => { // Child (does not exit from this function) - // Mark this process as a collector for the preload logger - #[cfg(target_os = "linux")] - { - super::api::mark_preload_logger_collector(); - } - run_collector_child( config, config_str, diff --git a/libdd-crashtracker/src/collector/crash_handler.rs b/libdd-crashtracker/src/collector/crash_handler.rs index 413b78224e..917329295a 100644 --- a/libdd-crashtracker/src/collector/crash_handler.rs +++ b/libdd-crashtracker/src/collector/crash_handler.rs @@ -234,6 +234,12 @@ fn handle_posix_signal_impl( return Ok(()); } + // Mark this process as a collector for the preload logger + #[cfg(target_os = "linux")] + { + super::api::mark_preload_logger_collector(); + } + // If this code hits a stack overflow, then it will result in a segfault. That situation is // protected by the one-time guard. From 0a24253b3fc5a1439d7dca6a2422f931d2def92d Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 15:30:13 +0000 Subject: [PATCH 13/18] No os guard, use atomic and tl var --- bin_tests/build.rs | 2 +- bin_tests/preload/preload.c | 18 ++++++++++++++---- bin_tests/tests/crashtracker_bin_test.rs | 5 +++-- libdd-crashtracker/src/collector/api.rs | 4 ---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/bin_tests/build.rs b/bin_tests/build.rs index eda63cde5d..ee292834f9 100644 --- a/bin_tests/build.rs +++ b/bin_tests/build.rs @@ -12,7 +12,7 @@ fn main() { let so_path = out_dir.join("libpreload_logger.so"); let status = Command::new("cc") - .args(["-std=gnu99", "-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) + .args(["-std=gnu11", "-fPIC", "-shared", "-Wall", "-Wextra", "-o"]) .arg(&so_path) .arg(&src) .status() diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 99b45d3d15..6457e725ec 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -43,13 +44,16 @@ __attribute__((constructor)) static void preload_ctor(void) { } static int log_fd = -1; -// Flag to indicate we are currently in the collector; We should only +// Flag to indicate we are currently in the collector; we should only // detect allocations when we are in the collector. -static int collector_marked = 0; +// Must be thread-local: the collector work runs on a single thread; other +// threads in the process should not be considered "collector" and should +// not trip the detector. +static _Thread_local int collector_marked = 0; // Flag to track if we've already detected and reported an allocation // This guards against reentrancy of the detection logic when we capture // stack trace, since backtrace can use malloc internally -static int allocation_detected = 0; +static _Atomic int allocation_detected = 0; // Called by the collector process to enable detection in the collector only void dd_preload_logger_mark_collector(void) { @@ -62,7 +66,13 @@ void dd_preload_logger_mark_collector(void) { static void capture_and_report_allocation(const char *func_name) { // Only report once using atomic compare-and-swap - if (__sync_bool_compare_and_swap(&allocation_detected, 0, 1)) { + int expected = 0; + if (atomic_compare_exchange_strong_explicit( + &allocation_detected, + &expected, + 1, + memory_order_relaxed, + memory_order_relaxed)) { const char *path = "/tmp/preload_detector.log"; log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (log_fd >= 0) { diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 034e9542ec..9362f49523 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -202,8 +202,9 @@ fn test_collector_no_allocations() { let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); if detector_log_path.exists() { - let log_content = fs::read_to_string(&detector_log_path) - .context("Failed to read preload detector log")?; + let log_bytes = + fs::read(&detector_log_path).context("Failed to read preload detector log")?; + let log_content = String::from_utf8_lossy(&log_bytes); // Clean up the log file first let _ = fs::remove_file(&detector_log_path); diff --git a/libdd-crashtracker/src/collector/api.rs b/libdd-crashtracker/src/collector/api.rs index d512398c58..72789d311e 100644 --- a/libdd-crashtracker/src/collector/api.rs +++ b/libdd-crashtracker/src/collector/api.rs @@ -17,7 +17,6 @@ pub fn default_signals() -> Vec { Vec::from(DEFAULT_SYMBOLS) } -#[cfg(target_os = "linux")] pub(super) fn mark_preload_logger_collector() { // This function is specific only for LD_PRELOAD testing // Best effort; this symbol exists only when the preload logger preload is present. @@ -31,9 +30,6 @@ pub(super) fn mark_preload_logger_collector() { } } -#[cfg(not(target_os = "linux"))] -pub(super) fn mark_preload_logger_collector() {} - /// Reinitialize the crash-tracking infrastructure after a fork. /// This should be one of the first things done after a fork, to minimize the /// chance that a crash occurs between the fork, and this call. From ba9d0c2ed89d19ea3e5b13b58d36736f997dc135 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 18:06:25 +0000 Subject: [PATCH 14/18] Don't use sprintf, dont print backtrace --- bin_tests/preload/preload.c | 73 +++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 6457e725ec..3d4025cb55 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -64,8 +64,31 @@ void dd_preload_logger_mark_collector(void) { } } +static void write_int(int fd, long value) { + char buf[32]; + int i = 0; + + if (value == 0) { + write(fd, "0", 1); + return; + } + + if (value < 0) { + write(fd, "-", 1); + value = -value; + } + + while (value > 0 && i < (int)sizeof(buf)) { + buf[i++] = '0' + (value % 10); + value /= 10; + } + + for (int j = i - 1; j >= 0; j--) { + write(fd, &buf[j], 1); + } +} + static void capture_and_report_allocation(const char *func_name) { - // Only report once using atomic compare-and-swap int expected = 0; if (atomic_compare_exchange_strong_explicit( &allocation_detected, @@ -73,52 +96,32 @@ static void capture_and_report_allocation(const char *func_name) { 1, memory_order_relaxed, memory_order_relaxed)) { + const char *path = "/tmp/preload_detector.log"; log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (log_fd >= 0) { - char buf[4096]; pid_t pid = getpid(); long tid = syscall(SYS_gettid); - // Log the detection - int len = snprintf(buf, sizeof(buf), - "[FATAL] Dangerous allocation detected in collector!\n" - " Function: %s\n" - " PID: %d\n" - " TID: %ld\n" - " Stacktrace:\n", - func_name, pid, tid); - write(log_fd, buf, len); - - // Capture and log stacktrace (glibc only; musl lacks execinfo) -#if DD_HAVE_EXECINFO - void *array[100]; - int size = backtrace(array, 100); - char **strings = backtrace_symbols(array, size); - - if (strings != NULL) { - for (int i = 0; i < size; i++) { - len = snprintf(buf, sizeof(buf), " #%d %s\n", i, strings[i]); - write(log_fd, buf, len); - } - // backtrace_symbols uses malloc internally, so we have a small leak - // but this is acceptable since this only happens once and we guard - // against it anyways - } -#else - len = snprintf(buf, sizeof(buf), - " [backtrace unavailable: execinfo.h not present on this platform (likely musl)]\n"); - write(log_fd, buf, len); -#endif + write(log_fd, + "[FATAL] Dangerous allocation detected in collector!\n", + 52); + + write(log_fd, " Function: ", 12); + write(log_fd, func_name, strlen(func_name)); + + write(log_fd, "\n PID: ", 8); + write_int(log_fd, pid); + + write(log_fd, "\n TID: ", 8); + write_int(log_fd, tid); - fsync(log_fd); close(log_fd); log_fd = -1; } } - // Don't abort. let the collector continue so it can finish writing the crash report - // The test will check for the log file and fail if allocations were detected + // Don't abort. Let the collector continue so it can finish writing the crash report } void *malloc(size_t size) { From 8c304ef7e842f1bcef0fc695953d042489de7373 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 18:28:55 +0000 Subject: [PATCH 15/18] Fail fast --- bin_tests/preload/preload.c | 1 + bin_tests/src/test_runner.rs | 14 ++++++ bin_tests/tests/crashtracker_bin_test.rs | 49 ++++++++++---------- libdd-crashtracker/src/collector/emitters.rs | 3 ++ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index 3d4025cb55..d3557b514f 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -118,6 +118,7 @@ static void capture_and_report_allocation(const char *func_name) { close(log_fd); log_fd = -1; + abort(); } } diff --git a/bin_tests/src/test_runner.rs b/bin_tests/src/test_runner.rs index 1f20dcb53b..3bc0a536c2 100644 --- a/bin_tests/src/test_runner.rs +++ b/bin_tests/src/test_runner.rs @@ -6,6 +6,7 @@ //! across different test scenarios. use crate::{ + build_artifacts, test_types::{CrashType, TestMode}, validation::{read_and_parse_crash_payload, validate_std_outputs, PayloadValidator}, ArtifactType, ArtifactsBuild, BuildProfile, @@ -270,6 +271,19 @@ where Ok(()) } +/// Convenience helper that builds the standard artifacts for the given config +/// and runs a crash test with the provided validator. Use this when a test +/// doesn't care about customizing artifacts and just needs to run validation +/// on the crash payload/fixtures. +pub fn run_crash_test_with_validator(config: &CrashTestConfig, validator: F) -> Result<()> +where + F: FnOnce(&Value, &TestFixtures) -> Result<()>, +{ + let artifacts = StandardArtifacts::new(config.profile); + let artifacts_map = build_artifacts(&artifacts.as_slice())?; + run_crash_test_with_artifacts(config, &artifacts_map, &artifacts, validator) +} + /// Validates the process exit status matches expectations for the crash type. fn assert_exit_status(exit_status: process::ExitStatus, crash_type: CrashType) -> Result<()> { let expected_success = crash_type.expects_success(); diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 9362f49523..4781a64f07 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -12,7 +12,10 @@ use std::{fs, path::PathBuf}; use anyhow::Context; use bin_tests::{ build_artifacts, - test_runner::{run_crash_test_with_artifacts, CrashTestConfig, StandardArtifacts, ValidatorFn}, + test_runner::{ + run_crash_test_with_artifacts, run_crash_test_with_validator, CrashTestConfig, + StandardArtifacts, ValidatorFn, + }, test_types::{CrashType, TestMode}, validation::PayloadValidator, ArtifactType, ArtifactsBuild, BuildProfile, @@ -187,39 +190,37 @@ fn test_crash_tracking_bin_no_runtime_callback() { #[cfg_attr(miri, ignore)] #[cfg(target_os = "linux")] // LD_PRELOAD is Linux-specific fn test_collector_no_allocations() { - let config = CrashTestConfig::new( - BuildProfile::Debug, - TestMode::RuntimePreloadLogger, - CrashType::NullDeref, - ); - let artifacts = StandardArtifacts::new(config.profile); - let artifacts_map = build_artifacts(&artifacts.as_slice()).unwrap(); - - let validator: ValidatorFn = Box::new(|payload, _fixtures| { - // First validate that the crash report was properly generated - PayloadValidator::new(payload).validate_counters()?; + let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); - let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); - - if detector_log_path.exists() { - let log_bytes = - fs::read(&detector_log_path).context("Failed to read preload detector log")?; + let fail_if_log_exists = |path: &PathBuf| -> anyhow::Result<()> { + if path.exists() { + let log_bytes = fs::read(path).context("Failed to read preload detector log")?; let log_content = String::from_utf8_lossy(&log_bytes); // Clean up the log file first - let _ = fs::remove_file(&detector_log_path); + let _ = fs::remove_file(path); eprintln!("{}", log_content); anyhow::bail!( - "Collector performed dangerous allocation! Check the log above for stacktrace." + "Collector performed dangerous allocation!" ); } - - // If we get here, no dangerous allocations were detected, we shuld pass Ok(()) - }); + }; - // Run the test - we expect it to complete normally without the collector aborting - run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); + // Fail fast if a previous run already produced a detector log. + fail_if_log_exists(&detector_log_path).unwrap(); + + let config = CrashTestConfig::new( + BuildProfile::Debug, + TestMode::RuntimePreloadLogger, + CrashType::NullDeref, + ); + + let validator_log_path = detector_log_path.clone(); + let validator: ValidatorFn = + Box::new(move |_payload, _fixtures| fail_if_log_exists(&validator_log_path)); + + run_crash_test_with_validator(&config, validator).unwrap(); } #[test] diff --git a/libdd-crashtracker/src/collector/emitters.rs b/libdd-crashtracker/src/collector/emitters.rs index 35e061d4e6..8a46ffdf6d 100644 --- a/libdd-crashtracker/src/collector/emitters.rs +++ b/libdd-crashtracker/src/collector/emitters.rs @@ -143,6 +143,9 @@ pub(crate) fn emit_crashreport( ucontext: *const ucontext_t, ppid: i32, ) -> Result<(), EmitterError> { + let mut test_allocation = Vec::new(); + test_allocation.push(config_str); + test_allocation.push(metadata_string); // The following order is important in order to emit the crash ping: // - receiver expects the config // - then message if any From 5a28686e4bf890f86fd92884b1fc1b38b8edc3a7 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 18:48:55 +0000 Subject: [PATCH 16/18] Fail fast fix --- bin_tests/src/test_runner.rs | 27 +++++++++++++++++--- bin_tests/tests/crashtracker_bin_test.rs | 13 ++++------ libdd-crashtracker/src/collector/emitters.rs | 3 --- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/bin_tests/src/test_runner.rs b/bin_tests/src/test_runner.rs index 3bc0a536c2..35bbf59a11 100644 --- a/bin_tests/src/test_runner.rs +++ b/bin_tests/src/test_runner.rs @@ -274,14 +274,33 @@ where /// Convenience helper that builds the standard artifacts for the given config /// and runs a crash test with the provided validator. Use this when a test /// doesn't care about customizing artifacts and just needs to run validation -/// on the crash payload/fixtures. -pub fn run_crash_test_with_validator(config: &CrashTestConfig, validator: F) -> Result<()> +/// independently of the crash report. +pub fn run_crash_test_with_validator_no_crash_report( + config: &CrashTestConfig, + validator: F, +) -> Result<()> where - F: FnOnce(&Value, &TestFixtures) -> Result<()>, + F: FnOnce() -> Result<()>, { let artifacts = StandardArtifacts::new(config.profile); let artifacts_map = build_artifacts(&artifacts.as_slice())?; - run_crash_test_with_artifacts(config, &artifacts_map, &artifacts, validator) + let fixtures = TestFixtures::new()?; + + let mut cmd = process::Command::new(&artifacts_map[&artifacts.crashtracker_bin]); + cmd.arg(format!("file://{}", fixtures.crash_profile_path.display())) + .arg(&artifacts_map[&artifacts.crashtracker_receiver]) + .arg(&fixtures.output_dir) + .arg(config.mode.as_str()) + .arg(config.crash_type.as_str()); + + for (key, val) in &config.env_vars { + cmd.env(key, val); + } + + cmd.spawn().context("Failed to spawn test process")?; + + validator()?; + Ok(()) } /// Validates the process exit status matches expectations for the crash type. diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 4781a64f07..17388a80c3 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -13,8 +13,8 @@ use anyhow::Context; use bin_tests::{ build_artifacts, test_runner::{ - run_crash_test_with_artifacts, run_crash_test_with_validator, CrashTestConfig, - StandardArtifacts, ValidatorFn, + run_crash_test_with_artifacts, run_crash_test_with_validator_no_crash_report, + CrashTestConfig, StandardArtifacts, ValidatorFn, }, test_types::{CrashType, TestMode}, validation::PayloadValidator, @@ -200,9 +200,7 @@ fn test_collector_no_allocations() { // Clean up the log file first let _ = fs::remove_file(path); eprintln!("{}", log_content); - anyhow::bail!( - "Collector performed dangerous allocation!" - ); + anyhow::bail!("Collector performed dangerous allocation!"); } Ok(()) }; @@ -217,10 +215,9 @@ fn test_collector_no_allocations() { ); let validator_log_path = detector_log_path.clone(); - let validator: ValidatorFn = - Box::new(move |_payload, _fixtures| fail_if_log_exists(&validator_log_path)); + let validator = move || fail_if_log_exists(&validator_log_path); - run_crash_test_with_validator(&config, validator).unwrap(); + run_crash_test_with_validator_no_crash_report(&config, validator).unwrap(); } #[test] diff --git a/libdd-crashtracker/src/collector/emitters.rs b/libdd-crashtracker/src/collector/emitters.rs index 8a46ffdf6d..35e061d4e6 100644 --- a/libdd-crashtracker/src/collector/emitters.rs +++ b/libdd-crashtracker/src/collector/emitters.rs @@ -143,9 +143,6 @@ pub(crate) fn emit_crashreport( ucontext: *const ucontext_t, ppid: i32, ) -> Result<(), EmitterError> { - let mut test_allocation = Vec::new(); - test_allocation.push(config_str); - test_allocation.push(metadata_string); // The following order is important in order to emit the crash ping: // - receiver expects the config // - then message if any From b0c0ffcdd7ce6a7afe2e8ce90d18231f92e5af03 Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 20:31:11 +0000 Subject: [PATCH 17/18] All stacktrace modes --- bin_tests/preload/preload.c | 2 - bin_tests/src/bin/crashtracker_bin_test.rs | 17 ++++- bin_tests/src/test_runner.rs | 3 +- bin_tests/tests/crashtracker_bin_test.rs | 73 +++++++++++++--------- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index d3557b514f..c9d457ebd5 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -121,8 +121,6 @@ static void capture_and_report_allocation(const char *func_name) { abort(); } } - - // Don't abort. Let the collector continue so it can finish writing the crash report } void *malloc(size_t size) { diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 01b2b4f5d5..6ab95b6512 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -81,12 +81,27 @@ mod unix { // The configuration can be modified by a Behavior (testing plan), so it is mut here. // Unlike a normal harness, in this harness tests are run in individual processes, so race // issues are avoided. + let stacktrace_collection = match env::var("DD_TEST_STACKTRACE_COLLECTION") { + Ok(val) => match val.as_str() { + "disabled" => crashtracker::StacktraceCollection::Disabled, + "without_symbols" => crashtracker::StacktraceCollection::WithoutSymbols, + "inprocess_symbols" => { + crashtracker::StacktraceCollection::EnabledWithInprocessSymbols + } + "receiver_symbols" => { + crashtracker::StacktraceCollection::EnabledWithSymbolsInReceiver + } + _ => crashtracker::StacktraceCollection::WithoutSymbols, + }, + Err(_) => crashtracker::StacktraceCollection::WithoutSymbols, + }; + let mut config = CrashtrackerConfiguration::new( vec![], true, true, endpoint, - crashtracker::StacktraceCollection::WithoutSymbols, + stacktrace_collection, crashtracker::default_signals(), Some(TEST_COLLECTOR_TIMEOUT), Some("".to_string()), diff --git a/bin_tests/src/test_runner.rs b/bin_tests/src/test_runner.rs index 35bbf59a11..917033c169 100644 --- a/bin_tests/src/test_runner.rs +++ b/bin_tests/src/test_runner.rs @@ -297,7 +297,8 @@ where cmd.env(key, val); } - cmd.spawn().context("Failed to spawn test process")?; + let mut child = cmd.spawn().context("Failed to spawn test process")?; + let _ = child.wait(); validator()?; Ok(()) diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 17388a80c3..056721c814 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -183,41 +183,56 @@ fn test_crash_tracking_bin_no_runtime_callback() { run_crash_test_with_artifacts(&config, &artifacts_map, &artifacts, validator).unwrap(); } -// Test that verifies the collector process doesn't perform dangerous allocations. -// Uses LD_PRELOAD to detect malloc/calloc/realloc calls in the collector and fails -// if any are detected, capturing a stacktrace for debugging. #[test] #[cfg_attr(miri, ignore)] -#[cfg(target_os = "linux")] // LD_PRELOAD is Linux-specific -fn test_collector_no_allocations() { - let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); - - let fail_if_log_exists = |path: &PathBuf| -> anyhow::Result<()> { - if path.exists() { - let log_bytes = fs::read(path).context("Failed to read preload detector log")?; - let log_content = String::from_utf8_lossy(&log_bytes); - - // Clean up the log file first - let _ = fs::remove_file(path); - eprintln!("{}", log_content); - anyhow::bail!("Collector performed dangerous allocation!"); - } - Ok(()) - }; +#[cfg(unix)] +fn test_collector_no_allocations_stacktrace_modes() { + // (env_value, should_expect_log) + let cases = [ + ("disabled", false), + ("without_symbols", false), + ("receiver_symbols", false), + ("inprocess_symbols", true), + ]; - // Fail fast if a previous run already produced a detector log. - fail_if_log_exists(&detector_log_path).unwrap(); + for (env_value, expect_log) in cases { + let detector_log_path = PathBuf::from("/tmp/preload_detector.log"); - let config = CrashTestConfig::new( - BuildProfile::Debug, - TestMode::RuntimePreloadLogger, - CrashType::NullDeref, - ); + // Clean up + let _ = fs::remove_file(&detector_log_path); + + let config = CrashTestConfig::new( + BuildProfile::Debug, + TestMode::RuntimePreloadLogger, + CrashType::NullDeref, + ) + .with_env("DD_TEST_STACKTRACE_COLLECTION", env_value); - let validator_log_path = detector_log_path.clone(); - let validator = move || fail_if_log_exists(&validator_log_path); + // Validator does nothing; we inspect the log after the run. + let validator = || Ok(()); - run_crash_test_with_validator_no_crash_report(&config, validator).unwrap(); + let result = run_crash_test_with_validator_no_crash_report(&config, validator); + + let log_exists = detector_log_path.exists(); + + if expect_log { + assert!( + log_exists, + "Expected allocation detection log for mode {env_value}" + ); + if log_exists { + if let Ok(bytes) = fs::read(&detector_log_path) { + eprintln!("{}", String::from_utf8_lossy(&bytes)); + } + } + } else { + result.unwrap(); + assert!( + !log_exists, + "Did not expect allocation detection log for mode {env_value}" + ); + } + } } #[test] From 6fed7337aea9fe13260dc0b8bb79459276324f3c Mon Sep 17 00:00:00 2001 From: Gyuheon Oh Date: Thu, 15 Jan 2026 20:38:03 +0000 Subject: [PATCH 18/18] Skeleton test runner --- bin_tests/preload/preload.c | 61 ++++++++++-------------- bin_tests/src/test_runner.rs | 15 ++---- bin_tests/tests/crashtracker_bin_test.rs | 11 ++--- 3 files changed, 31 insertions(+), 56 deletions(-) diff --git a/bin_tests/preload/preload.c b/bin_tests/preload/preload.c index c9d457ebd5..677a2a2596 100644 --- a/bin_tests/preload/preload.c +++ b/bin_tests/preload/preload.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -49,11 +48,7 @@ static int log_fd = -1; // Must be thread-local: the collector work runs on a single thread; other // threads in the process should not be considered "collector" and should // not trip the detector. -static _Thread_local int collector_marked = 0; -// Flag to track if we've already detected and reported an allocation -// This guards against reentrancy of the detection logic when we capture -// stack trace, since backtrace can use malloc internally -static _Atomic int allocation_detected = 0; +static __thread int collector_marked = 0; // Called by the collector process to enable detection in the collector only void dd_preload_logger_mark_collector(void) { @@ -88,38 +83,30 @@ static void write_int(int fd, long value) { } } +// This function MUST be async signal safe static void capture_and_report_allocation(const char *func_name) { - int expected = 0; - if (atomic_compare_exchange_strong_explicit( - &allocation_detected, - &expected, - 1, - memory_order_relaxed, - memory_order_relaxed)) { - - const char *path = "/tmp/preload_detector.log"; - log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (log_fd >= 0) { - pid_t pid = getpid(); - long tid = syscall(SYS_gettid); - - write(log_fd, - "[FATAL] Dangerous allocation detected in collector!\n", - 52); - - write(log_fd, " Function: ", 12); - write(log_fd, func_name, strlen(func_name)); - - write(log_fd, "\n PID: ", 8); - write_int(log_fd, pid); - - write(log_fd, "\n TID: ", 8); - write_int(log_fd, tid); - - close(log_fd); - log_fd = -1; - abort(); - } + const char *path = "/tmp/preload_detector.log"; + log_fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (log_fd >= 0) { + pid_t pid = getpid(); + long tid = syscall(SYS_gettid); + + write(log_fd, + "[FATAL] Dangerous allocation detected in collector!\n", + 52); + + write(log_fd, " Function: ", 12); + write(log_fd, func_name, strlen(func_name)); + + write(log_fd, "\n PID: ", 8); + write_int(log_fd, pid); + + write(log_fd, "\n TID: ", 8); + write_int(log_fd, tid); + + close(log_fd); + log_fd = -1; + abort(); } } diff --git a/bin_tests/src/test_runner.rs b/bin_tests/src/test_runner.rs index 917033c169..af47bcc63d 100644 --- a/bin_tests/src/test_runner.rs +++ b/bin_tests/src/test_runner.rs @@ -271,17 +271,9 @@ where Ok(()) } -/// Convenience helper that builds the standard artifacts for the given config -/// and runs a crash test with the provided validator. Use this when a test -/// doesn't care about customizing artifacts and just needs to run validation -/// independently of the crash report. -pub fn run_crash_test_with_validator_no_crash_report( - config: &CrashTestConfig, - validator: F, -) -> Result<()> -where - F: FnOnce() -> Result<()>, -{ +/// Minimal runner for scenarios where the process may not emit a crash report +/// (preload allocation detector). It just runs the binary and waits. +pub fn run_crash_no_op(config: &CrashTestConfig) -> Result<()> { let artifacts = StandardArtifacts::new(config.profile); let artifacts_map = build_artifacts(&artifacts.as_slice())?; let fixtures = TestFixtures::new()?; @@ -300,7 +292,6 @@ where let mut child = cmd.spawn().context("Failed to spawn test process")?; let _ = child.wait(); - validator()?; Ok(()) } diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index 056721c814..78cb44eabd 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -13,8 +13,8 @@ use anyhow::Context; use bin_tests::{ build_artifacts, test_runner::{ - run_crash_test_with_artifacts, run_crash_test_with_validator_no_crash_report, - CrashTestConfig, StandardArtifacts, ValidatorFn, + run_crash_no_op, run_crash_test_with_artifacts, CrashTestConfig, StandardArtifacts, + ValidatorFn, }, test_types::{CrashType, TestMode}, validation::PayloadValidator, @@ -185,7 +185,7 @@ fn test_crash_tracking_bin_no_runtime_callback() { #[test] #[cfg_attr(miri, ignore)] -#[cfg(unix)] +#[cfg(target_os = "linux")] fn test_collector_no_allocations_stacktrace_modes() { // (env_value, should_expect_log) let cases = [ @@ -208,10 +208,7 @@ fn test_collector_no_allocations_stacktrace_modes() { ) .with_env("DD_TEST_STACKTRACE_COLLECTION", env_value); - // Validator does nothing; we inspect the log after the run. - let validator = || Ok(()); - - let result = run_crash_test_with_validator_no_crash_report(&config, validator); + let result = run_crash_no_op(&config); let log_exists = detector_log_path.exists();