From 10d3ad8b2ebd2c5f4e873585f729cf1472129168 Mon Sep 17 00:00:00 2001 From: Judson Wilson Date: Wed, 6 May 2026 00:53:01 +0300 Subject: [PATCH 1/4] Add optional ASAN test-leak injection gated by a config file If /etc/sonic/inject_asan_test_leak_enabled exists, syncd injects a known leak, so that when sent SIGTERM the leak detector will emit an ASAN report. This can be used to verify that the ASAN build and the leak check in the signal handler are working as intended. This matches a similar addition being made in sonic-swss. How to verify it: 1. Create /etc/sonic/inject_asan_test_leak_enabled. 2. Restart the daemon once if necessary (e.g. `config reload`) so it starts with leak injection enabled. 3. Run `config reload` or reboot to send SIGTERM to the daemon. 4. After it exits, daemons that are working correctly will produce ASAN report files in /var/log/asan/. Signed-off-by: Judson Wilson --- syncd/Asan.cpp | 107 +++++++++++++++++++--- syncd/Asan.h | 45 +++++++++ syncd/AsanCtor.cpp | 39 ++++++++ syncd/Makefile.am | 2 +- unittest/syncd/Makefile.am | 4 +- unittest/syncd/TestAsan.cpp | 176 ++++++++++++++++++++++++++++++++++++ 6 files changed, 359 insertions(+), 14 deletions(-) create mode 100644 syncd/Asan.h create mode 100644 syncd/AsanCtor.cpp create mode 100644 unittest/syncd/TestAsan.cpp diff --git a/syncd/Asan.cpp b/syncd/Asan.cpp index 3bc84ff646..38d89fd3e6 100644 --- a/syncd/Asan.cpp +++ b/syncd/Asan.cpp @@ -1,32 +1,115 @@ +/* + * ASAN support helpers: + * 1. Install a SIGTERM handler that runs an injected LSan leak check. + * 2. When /etc/sonic/inject_asan_test_leak_enabled exists, inject a known test + * leak used to verify the ASAN/LSan path is working as expected. + * + * ENABLE_ASAN=y syncd builds also link AsanCtor.cpp, whose constructor calls + * asan_init_impl() before main(). Unit tests leave AsanCtor.cpp out and call + * asan_init_impl() with test-double functions for the dependencies. + */ + +#include "Asan.h" + #include "swss/logger.h" -#include -#include +#include + +#include +#include +#include +#include + +/* ASAN test-leak injection + * + * When ASAN is enabled and /etc/sonic/inject_asan_test_leak_enabled exists, + * allocate a block and deliberately never free it so LSAN has a known leak to + * report on process exit or in the SIGTERM handler. This is useful for + * verifying that the ASAN build, configuration, and SIGTERM handlers are + * working as expected. + * + * The memory block has to still look unreachable when the leak check runs, + * which is difficult. LSan scans thread stacks conservatively, and at -O2 ASAN + * moves the injector's locals into a "fake stack" frame that lives on the heap + * for the lifetime of the thread. Overwriting the real stack never reaches + * those copies, so a leak injected on the main thread stays reachable and is + * silently dropped by __lsan_do_leak_check() in the SIGTERM handler. syncd is + * long-running and is stopped via SIGTERM, so that is the path that matters. + * + * Injecting from a short-lived helper thread sidesteps that: once the thread is + * joined, both its stack and its ASAN fake stack are gone, so no stale pointer + * survives for LSan to trip over. This works at every optimization level and + * needs no ASAN_OPTIONS tuning. + * + * The intentional leak is injected at startup so it is present when the leak + * check runs in the SIGTERM handler. Do not call the LSan leak-check callback + * here: that terminates the process when leaks are present; call it only from + * the SIGTERM handler. + */ + +// Set by asan_init_impl(); invoked from the SIGTERM handler. +static AsanLsanLeakCheckFn g_lsan_leak_check = nullptr; -extern "C" { - const char* __lsan_default_suppressions() { - // SWSS_LOG_ENTER(); // disabled - return "leak:__static_initialization_and_destruction_0\n"; +__attribute__((noinline)) +void asan_inject_test_leak(AsanMallocFn malloc_fn) +{ + void *probe = malloc_fn(ASAN_TEST_LEAK_SIZE); + if (!probe) + { + SWSS_LOG_ERROR("failed to allocate %zu bytes for the ASAN test leak, no leak injected", + ASAN_TEST_LEAK_SIZE); + return; } + + std::memset(probe, 0xCD, ASAN_TEST_LEAK_SIZE); + + // Feed the pointer to an opaque asm that also reads memory, so -O2 cannot + // drop the malloc and memset as dead stores. Nothing stores the pointer, so + // the block remains unreachable. + asm volatile("" : : "r"(probe) : "memory"); } -static void sigterm_handler(int signo) +void asan_sigterm_handler(int signo) { SWSS_LOG_ENTER(); - __lsan_do_leak_check(); + if (g_lsan_leak_check) + { + g_lsan_leak_check(); + } + signal(signo, SIG_DFL); raise(signo); } -__attribute__((constructor)) -static void asan_init() +bool asan_init_impl(AsanSignalFn signal_fn, + AsanAccessFn access_fn, + AsanMallocFn malloc_fn, + AsanLsanLeakCheckFn leak_check_fn) { SWSS_LOG_ENTER(); - if (signal(SIGTERM, sigterm_handler) == SIG_ERR) + g_lsan_leak_check = leak_check_fn; + + if (signal_fn(SIGTERM, asan_sigterm_handler) == SIG_ERR) { SWSS_LOG_ERROR("failed to setup SIGTERM action"); - exit(1); + return false; } + + if (access_fn("/etc/sonic/inject_asan_test_leak_enabled", F_OK) == 0) + { + try + { + // See comment above asan_inject_test_leak() for why this must run + // in a separate thread. + std::thread(asan_inject_test_leak, malloc_fn).join(); + } + catch (const std::exception& e) + { + SWSS_LOG_ERROR("failed to inject ASAN test leak: %s", e.what()); + } + } + + return true; } diff --git a/syncd/Asan.h b/syncd/Asan.h new file mode 100644 index 0000000000..90565a7abe --- /dev/null +++ b/syncd/Asan.h @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Testable ASAN helpers for syncd. Production builds enable them via the + * constructor in AsanCtor.cpp; unit tests call asan_init_impl() with injected + * dependencies and leave AsanCtor.cpp out of the link. + */ + +#pragma once + +#include +#include + +// Unique size to identify the intentional ASAN test-leak allocation in reports. +static constexpr size_t ASAN_TEST_LEAK_SIZE = 9861842; + +// Function pointers for real implementations or test doubles. +// signal() from csignal +using AsanSignalFn = sighandler_t (*)(int, sighandler_t); +// access() from unistd.h +using AsanAccessFn = int (*)(const char *, int); +// malloc() from cstdlib +using AsanMallocFn = void *(*)(size_t); +// __lsan_do_leak_check() from sanitizer/lsan_interface.h +using AsanLsanLeakCheckFn = void (*)(void); + +// SIGTERM handler installed by asan_init_impl(). Exposed so tests can verify +// the handler pointer that was passed to signal(). +void asan_sigterm_handler(int signo); + +// Allocate (and never free) the intentional test leak via malloc_fn. +void asan_inject_test_leak(AsanMallocFn malloc_fn); + +// Set up ASAN helpers: +// - Installs a SIGTERM handler that runs leak_check_fn. +// - When /etc/sonic/inject_asan_test_leak_enabled exists, injects a known test +// leak via malloc_fn. +// - Returns false if signal-handler installation fails; true otherwise +// (including when leak injection is skipped or malloc_fn returns nullptr). +bool asan_init_impl(AsanSignalFn signal_fn, + AsanAccessFn access_fn, + AsanMallocFn malloc_fn, + AsanLsanLeakCheckFn leak_check_fn); diff --git a/syncd/AsanCtor.cpp b/syncd/AsanCtor.cpp new file mode 100644 index 0000000000..ba09e07880 --- /dev/null +++ b/syncd/AsanCtor.cpp @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * ASAN process bootstrap. Adds code for ASAN builds, including: + * - a constructor that wires asan_init_impl() into process initialization + * before main(). + * - default LSan suppressions + * + * Include this file only in ASAN builds where you want that functionality + * enabled (ENABLE_ASAN=y syncd). To run unit tests against the implementation + * in Asan.cpp in a non-ASAN build without installing a SIGTERM handler, + * injecting a test leak, or pulling in sanitizer symbols, leave this file out + * of the build and call asan_init_impl() with test doubles for the + * dependencies. + */ + +#include "Asan.h" + +#include + +#include +#include + +extern "C" { + const char* __lsan_default_suppressions() { + return "leak:__static_initialization_and_destruction_0\n"; + } +} + +__attribute__((constructor)) +static void asan_init() +{ + if (!asan_init_impl(::signal, ::access, std::malloc, __lsan_do_leak_check)) + { + exit(EXIT_FAILURE); + } +} diff --git a/syncd/Makefile.am b/syncd/Makefile.am index c5bb32f3e8..d71e71f43b 100644 --- a/syncd/Makefile.am +++ b/syncd/Makefile.am @@ -68,7 +68,7 @@ libSyncd_a_CXXFLAGS = $(DBGFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS_COMMON) $(CODE_COVER syncd_SOURCES = main.cpp if ASAN_ENABLED -syncd_SOURCES += Asan.cpp +syncd_SOURCES += Asan.cpp AsanCtor.cpp libSyncd_a_CXXFLAGS += -DASAN_ENABLED endif syncd_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS) diff --git a/unittest/syncd/Makefile.am b/unittest/syncd/Makefile.am index 49cec2fc36..586ff168e0 100644 --- a/unittest/syncd/Makefile.am +++ b/unittest/syncd/Makefile.am @@ -26,7 +26,9 @@ tests_SOURCES = main.cpp \ TestWorkaround.cpp \ TestSyncd.cpp \ TestVendorSai.cpp \ - TestFlowDump.cpp + TestFlowDump.cpp \ + TestAsan.cpp \ + $(top_srcdir)/syncd/Asan.cpp tests_CXXFLAGS = $(DBGFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS_COMMON) -fno-access-control tests_LDFLAGS = -Wl,-rpath,$(top_srcdir)/lib/.libs -Wl,-rpath,$(top_srcdir)/meta/.libs diff --git a/unittest/syncd/TestAsan.cpp b/unittest/syncd/TestAsan.cpp new file mode 100644 index 0000000000..dd35c6ea11 --- /dev/null +++ b/unittest/syncd/TestAsan.cpp @@ -0,0 +1,176 @@ +#include "Asan.h" + +#include + +#include + +#include +#include + +namespace +{ + +struct AsanTestState +{ + int signal_calls = 0; + bool signal_fail = false; + int last_sig = 0; + sighandler_t installed = SIG_DFL; + + int access_calls = 0; + int access_rc = -1; + std::string access_path; + + int malloc_calls = 0; + size_t malloc_size = 0; + bool malloc_fail = false; + std::vector storage; +}; + +AsanTestState *g_state = nullptr; + +sighandler_t mock_signal(int sig, sighandler_t handler) +{ + EXPECT_NE(g_state, nullptr); + g_state->signal_calls++; + g_state->last_sig = sig; + g_state->installed = handler; + return g_state->signal_fail ? SIG_ERR : SIG_DFL; +} + +int mock_access(const char *path, int mode) +{ + EXPECT_NE(g_state, nullptr); + EXPECT_EQ(mode, F_OK); + g_state->access_calls++; + g_state->access_path = path ? path : ""; + return g_state->access_rc; +} + +void *mock_malloc(size_t size) +{ + EXPECT_NE(g_state, nullptr); + g_state->malloc_calls++; + g_state->malloc_size = size; + if (g_state->malloc_fail) + { + return nullptr; + } + g_state->storage.assign(size, 0); + return g_state->storage.data(); +} + +void mock_leak_check(void) +{ +} + +} // namespace + +class AsanInitTest : public ::testing::Test +{ +protected: + void SetUp() override + { + state_ = {}; + g_state = &state_; + } + + void TearDown() override + { + g_state = nullptr; + } + + AsanTestState state_; +}; + +TEST_F(AsanInitTest, InstallsSigtermHandler) +{ + state_.access_rc = -1; + + ASSERT_TRUE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); + + EXPECT_EQ(state_.signal_calls, 1); + EXPECT_EQ(state_.last_sig, SIGTERM); + EXPECT_EQ(state_.installed, asan_sigterm_handler); + EXPECT_EQ(state_.access_calls, 1); + EXPECT_EQ(state_.access_path, "/etc/sonic/inject_asan_test_leak_enabled"); + EXPECT_EQ(state_.malloc_calls, 0); +} + +TEST_F(AsanInitTest, SignalFailureReturnsFalse) +{ + state_.signal_fail = true; + + EXPECT_FALSE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); + + EXPECT_EQ(state_.signal_calls, 1); + EXPECT_EQ(state_.access_calls, 0); + EXPECT_EQ(state_.malloc_calls, 0); +} + +TEST_F(AsanInitTest, SkipsLeakInjectionWhenFlagFileMissing) +{ + state_.access_rc = -1; + + ASSERT_TRUE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); + + EXPECT_EQ(state_.malloc_calls, 0); +} + +TEST_F(AsanInitTest, InjectsLeakWhenFlagFilePresent) +{ + state_.access_rc = 0; + + ASSERT_TRUE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); + + EXPECT_EQ(state_.malloc_calls, 1); + EXPECT_EQ(state_.malloc_size, ASAN_TEST_LEAK_SIZE); + ASSERT_EQ(state_.storage.size(), ASAN_TEST_LEAK_SIZE); + EXPECT_EQ(state_.storage.front(), static_cast(0xCD)); + EXPECT_EQ(state_.storage.back(), static_cast(0xCD)); + EXPECT_EQ(state_.storage[state_.storage.size() / 2], static_cast(0xCD)); +} + +TEST_F(AsanInitTest, MallocFailureStillReturnsTrue) +{ + state_.access_rc = 0; + state_.malloc_fail = true; + + // Injection failure is logged; init itself still succeeds so syncd keeps + // running with the SIGTERM handler installed. + ASSERT_TRUE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); + + EXPECT_EQ(state_.malloc_calls, 1); + EXPECT_EQ(state_.malloc_size, ASAN_TEST_LEAK_SIZE); + EXPECT_TRUE(state_.storage.empty()); +} + +TEST(AsanInjectTest, FillsAllocationViaInjectedMalloc) +{ + AsanTestState state; + g_state = &state; + + asan_inject_test_leak(mock_malloc); + + EXPECT_EQ(state.malloc_calls, 1); + EXPECT_EQ(state.malloc_size, ASAN_TEST_LEAK_SIZE); + ASSERT_EQ(state.storage.size(), ASAN_TEST_LEAK_SIZE); + EXPECT_EQ(state.storage.front(), static_cast(0xCD)); + EXPECT_EQ(state.storage.back(), static_cast(0xCD)); + + g_state = nullptr; +} + +TEST(AsanInjectTest, NullMallocIsANoOp) +{ + AsanTestState state; + state.malloc_fail = true; + g_state = &state; + + asan_inject_test_leak(mock_malloc); + + EXPECT_EQ(state.malloc_calls, 1); + EXPECT_TRUE(state.storage.empty()); + + g_state = nullptr; +} From 3067744e6bad68c7f1548c2636e9ea3f66f40f53 Mon Sep 17 00:00:00 2001 From: Judson Wilson Date: Thu, 6 Aug 2026 20:00:59 +0300 Subject: [PATCH 2/4] More unit tests Signed-off-by: Judson Wilson --- syncd/Asan.cpp | 21 +++++--- syncd/Asan.h | 20 ++++++-- unittest/syncd/TestAsan.cpp | 96 ++++++++++++++++++++++++++++++++----- 3 files changed, 114 insertions(+), 23 deletions(-) diff --git a/syncd/Asan.cpp b/syncd/Asan.cpp index 38d89fd3e6..ef98b2d97c 100644 --- a/syncd/Asan.cpp +++ b/syncd/Asan.cpp @@ -6,7 +6,8 @@ * * ENABLE_ASAN=y syncd builds also link AsanCtor.cpp, whose constructor calls * asan_init_impl() before main(). Unit tests leave AsanCtor.cpp out and call - * asan_init_impl() with test-double functions for the dependencies. + * asan_init_impl() / asan_sigterm_handler_impl() with test-double functions for + * the dependencies. */ #include "Asan.h" @@ -69,17 +70,25 @@ void asan_inject_test_leak(AsanMallocFn malloc_fn) asm volatile("" : : "r"(probe) : "memory"); } -void asan_sigterm_handler(int signo) +void asan_sigterm_handler_impl(int signo, + AsanLsanLeakCheckFn leak_check_fn, + AsanSignalFn signal_fn, + AsanRaiseFn raise_fn) { SWSS_LOG_ENTER(); - if (g_lsan_leak_check) + if (leak_check_fn) { - g_lsan_leak_check(); + leak_check_fn(); } - signal(signo, SIG_DFL); - raise(signo); + signal_fn(signo, SIG_DFL); + raise_fn(signo); +} + +void asan_sigterm_handler(int signo) +{ + asan_sigterm_handler_impl(signo, g_lsan_leak_check, ::signal, ::raise); } bool asan_init_impl(AsanSignalFn signal_fn, diff --git a/syncd/Asan.h b/syncd/Asan.h index 90565a7abe..abd97db7be 100644 --- a/syncd/Asan.h +++ b/syncd/Asan.h @@ -4,8 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 * * Testable ASAN helpers for syncd. Production builds enable them via the - * constructor in AsanCtor.cpp; unit tests call asan_init_impl() with injected - * dependencies and leave AsanCtor.cpp out of the link. + * constructor in AsanCtor.cpp; unit tests call asan_init_impl() / + * asan_sigterm_handler_impl() with injected dependencies and leave + * AsanCtor.cpp out of the link. */ #pragma once @@ -25,11 +26,22 @@ using AsanAccessFn = int (*)(const char *, int); using AsanMallocFn = void *(*)(size_t); // __lsan_do_leak_check() from sanitizer/lsan_interface.h using AsanLsanLeakCheckFn = void (*)(void); +// raise() from csignal +using AsanRaiseFn = int (*)(int); -// SIGTERM handler installed by asan_init_impl(). Exposed so tests can verify -// the handler pointer that was passed to signal(). +// SIGTERM handler installed by asan_init_impl(). Thin wrapper around +// asan_sigterm_handler_impl() that passes g_lsan_leak_check and the real +// libc entry points. Exposed so tests can verify the handler pointer that was +// passed to signal(). void asan_sigterm_handler(int signo); +// Testable SIGTERM-handler body. Production wrapper passes g_lsan_leak_check, +// ::signal, and ::raise; unit tests inject doubles. +void asan_sigterm_handler_impl(int signo, + AsanLsanLeakCheckFn leak_check_fn, + AsanSignalFn signal_fn, + AsanRaiseFn raise_fn); + // Allocate (and never free) the intentional test leak via malloc_fn. void asan_inject_test_leak(AsanMallocFn malloc_fn); diff --git a/unittest/syncd/TestAsan.cpp b/unittest/syncd/TestAsan.cpp index dd35c6ea11..542320b64d 100644 --- a/unittest/syncd/TestAsan.cpp +++ b/unittest/syncd/TestAsan.cpp @@ -10,12 +10,20 @@ namespace { -struct AsanTestState +struct AsanSignalState { - int signal_calls = 0; - bool signal_fail = false; + // When true, mock_signal returns SIG_ERR. + bool fail = false; + + int calls = 0; int last_sig = 0; - sighandler_t installed = SIG_DFL; + // Last handler passed to signal() (init install or handler restore). + sighandler_t last_set = SIG_DFL; +}; + +struct AsanTestState +{ + AsanSignalState signal; int access_calls = 0; int access_rc = -1; @@ -23,8 +31,14 @@ struct AsanTestState int malloc_calls = 0; size_t malloc_size = 0; + // When true, mock_malloc returns nullptr. Otherwise it returns storage.data(). bool malloc_fail = false; std::vector storage; + + int leak_check_calls = 0; + + int raise_calls = 0; + int raise_signo = -1; }; AsanTestState *g_state = nullptr; @@ -32,10 +46,11 @@ AsanTestState *g_state = nullptr; sighandler_t mock_signal(int sig, sighandler_t handler) { EXPECT_NE(g_state, nullptr); - g_state->signal_calls++; - g_state->last_sig = sig; - g_state->installed = handler; - return g_state->signal_fail ? SIG_ERR : SIG_DFL; + auto& sigst = g_state->signal; + sigst.calls++; + sigst.last_sig = sig; + sigst.last_set = handler; + return sigst.fail ? SIG_ERR : SIG_DFL; } int mock_access(const char *path, int mode) @@ -62,6 +77,21 @@ void *mock_malloc(size_t size) void mock_leak_check(void) { + EXPECT_NE(g_state, nullptr); + g_state->leak_check_calls++; +} + +int mock_raise(int signo) +{ + EXPECT_NE(g_state, nullptr); + g_state->raise_calls++; + g_state->raise_signo = signo; + return 0; +} + +void invoke_handler_impl() +{ + asan_sigterm_handler_impl(SIGTERM, mock_leak_check, mock_signal, mock_raise); } } // namespace @@ -89,9 +119,9 @@ TEST_F(AsanInitTest, InstallsSigtermHandler) ASSERT_TRUE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); - EXPECT_EQ(state_.signal_calls, 1); - EXPECT_EQ(state_.last_sig, SIGTERM); - EXPECT_EQ(state_.installed, asan_sigterm_handler); + EXPECT_EQ(state_.signal.calls, 1); + EXPECT_EQ(state_.signal.last_sig, SIGTERM); + EXPECT_EQ(state_.signal.last_set, asan_sigterm_handler); EXPECT_EQ(state_.access_calls, 1); EXPECT_EQ(state_.access_path, "/etc/sonic/inject_asan_test_leak_enabled"); EXPECT_EQ(state_.malloc_calls, 0); @@ -99,11 +129,11 @@ TEST_F(AsanInitTest, InstallsSigtermHandler) TEST_F(AsanInitTest, SignalFailureReturnsFalse) { - state_.signal_fail = true; + state_.signal.fail = true; EXPECT_FALSE(asan_init_impl(mock_signal, mock_access, mock_malloc, mock_leak_check)); - EXPECT_EQ(state_.signal_calls, 1); + EXPECT_EQ(state_.signal.calls, 1); EXPECT_EQ(state_.access_calls, 0); EXPECT_EQ(state_.malloc_calls, 0); } @@ -174,3 +204,43 @@ TEST(AsanInjectTest, NullMallocIsANoOp) g_state = nullptr; } + +class AsanSigtermHandlerTest : public ::testing::Test +{ +protected: + void SetUp() override + { + state_ = {}; + g_state = &state_; + } + + void TearDown() override + { + g_state = nullptr; + } + + AsanTestState state_; +}; + +TEST_F(AsanSigtermHandlerTest, RunsLeakCheckRestoresDefaultAndRaises) +{ + invoke_handler_impl(); + + EXPECT_EQ(state_.leak_check_calls, 1); + EXPECT_EQ(state_.signal.calls, 1); + EXPECT_EQ(state_.signal.last_sig, SIGTERM); + EXPECT_EQ(state_.signal.last_set, SIG_DFL); + EXPECT_EQ(state_.raise_calls, 1); + EXPECT_EQ(state_.raise_signo, SIGTERM); +} + +TEST_F(AsanSigtermHandlerTest, NullLeakCheckDoesNotCrash) +{ + asan_sigterm_handler_impl(SIGTERM, nullptr, mock_signal, mock_raise); + + EXPECT_EQ(state_.leak_check_calls, 0); + EXPECT_EQ(state_.signal.calls, 1); + EXPECT_EQ(state_.signal.last_set, SIG_DFL); + EXPECT_EQ(state_.raise_calls, 1); + EXPECT_EQ(state_.raise_signo, SIGTERM); +} From d35dfdae9e8f07b7d17312f47357e6c9c1586f7a Mon Sep 17 00:00:00 2001 From: Judson Wilson Date: Fri, 7 Aug 2026 11:12:08 +0300 Subject: [PATCH 3/4] Add SWSS_LOG_ENTER and cleanup includes. Signed-off-by: Judson Wilson --- syncd/Asan.cpp | 7 +++++-- syncd/Asan.h | 2 +- syncd/AsanCtor.cpp | 6 +++++- unittest/syncd/TestAsan.cpp | 10 +++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/syncd/Asan.cpp b/syncd/Asan.cpp index ef98b2d97c..96bffe9be6 100644 --- a/syncd/Asan.cpp +++ b/syncd/Asan.cpp @@ -14,12 +14,11 @@ #include "swss/logger.h" -#include - #include #include #include #include +#include /* ASAN test-leak injection * @@ -54,6 +53,8 @@ static AsanLsanLeakCheckFn g_lsan_leak_check = nullptr; __attribute__((noinline)) void asan_inject_test_leak(AsanMallocFn malloc_fn) { + SWSS_LOG_ENTER(); + void *probe = malloc_fn(ASAN_TEST_LEAK_SIZE); if (!probe) { @@ -88,6 +89,8 @@ void asan_sigterm_handler_impl(int signo, void asan_sigterm_handler(int signo) { + SWSS_LOG_ENTER(); + asan_sigterm_handler_impl(signo, g_lsan_leak_check, ::signal, ::raise); } diff --git a/syncd/Asan.h b/syncd/Asan.h index abd97db7be..399e344b8c 100644 --- a/syncd/Asan.h +++ b/syncd/Asan.h @@ -11,8 +11,8 @@ #pragma once -#include #include +#include // Unique size to identify the intentional ASAN test-leak allocation in reports. static constexpr size_t ASAN_TEST_LEAK_SIZE = 9861842; diff --git a/syncd/AsanCtor.cpp b/syncd/AsanCtor.cpp index ba09e07880..1b25f1ca9b 100644 --- a/syncd/AsanCtor.cpp +++ b/syncd/AsanCtor.cpp @@ -18,13 +18,15 @@ #include "Asan.h" -#include +#include "swss/logger.h" #include #include +#include extern "C" { const char* __lsan_default_suppressions() { + // SWSS_LOG_ENTER(); // disabled return "leak:__static_initialization_and_destruction_0\n"; } } @@ -32,6 +34,8 @@ extern "C" { __attribute__((constructor)) static void asan_init() { + SWSS_LOG_ENTER(); + if (!asan_init_impl(::signal, ::access, std::malloc, __lsan_do_leak_check)) { exit(EXIT_FAILURE); diff --git a/unittest/syncd/TestAsan.cpp b/unittest/syncd/TestAsan.cpp index 542320b64d..446f7e0521 100644 --- a/unittest/syncd/TestAsan.cpp +++ b/unittest/syncd/TestAsan.cpp @@ -1,11 +1,9 @@ #include "Asan.h" -#include - #include - #include #include +#include namespace { @@ -45,6 +43,7 @@ AsanTestState *g_state = nullptr; sighandler_t mock_signal(int sig, sighandler_t handler) { + // SWSS_LOG_ENTER(); // disabled EXPECT_NE(g_state, nullptr); auto& sigst = g_state->signal; sigst.calls++; @@ -55,6 +54,7 @@ sighandler_t mock_signal(int sig, sighandler_t handler) int mock_access(const char *path, int mode) { + // SWSS_LOG_ENTER(); // disabled EXPECT_NE(g_state, nullptr); EXPECT_EQ(mode, F_OK); g_state->access_calls++; @@ -64,6 +64,7 @@ int mock_access(const char *path, int mode) void *mock_malloc(size_t size) { + // SWSS_LOG_ENTER(); // disabled EXPECT_NE(g_state, nullptr); g_state->malloc_calls++; g_state->malloc_size = size; @@ -77,12 +78,14 @@ void *mock_malloc(size_t size) void mock_leak_check(void) { + // SWSS_LOG_ENTER(); // disabled EXPECT_NE(g_state, nullptr); g_state->leak_check_calls++; } int mock_raise(int signo) { + // SWSS_LOG_ENTER(); // disabled EXPECT_NE(g_state, nullptr); g_state->raise_calls++; g_state->raise_signo = signo; @@ -91,6 +94,7 @@ int mock_raise(int signo) void invoke_handler_impl() { + // SWSS_LOG_ENTER(); // disabled asan_sigterm_handler_impl(SIGTERM, mock_leak_check, mock_signal, mock_raise); } From f90198d8c7122930b53c7f65fdbf28a1835a1e78 Mon Sep 17 00:00:00 2001 From: Judson Wilson Date: Fri, 7 Aug 2026 11:28:22 +0300 Subject: [PATCH 4/4] Fix aspellcheck.pl issues. Signed-off-by: Judson Wilson --- syncd/Asan.cpp | 2 +- tests/aspell.en.pws | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/syncd/Asan.cpp b/syncd/Asan.cpp index 96bffe9be6..39c121f5bf 100644 --- a/syncd/Asan.cpp +++ b/syncd/Asan.cpp @@ -23,7 +23,7 @@ /* ASAN test-leak injection * * When ASAN is enabled and /etc/sonic/inject_asan_test_leak_enabled exists, - * allocate a block and deliberately never free it so LSAN has a known leak to + * allocate a block and deliberately never free it so LSan has a known leak to * report on process exit or in the SIGTERM handler. This is useful for * verifying that the ASAN build, configuration, and SIGTERM handlers are * working as expected. diff --git a/tests/aspell.en.pws b/tests/aspell.en.pws index 5eb0162a8d..c32fead4ea 100644 --- a/tests/aspell.en.pws +++ b/tests/aspell.en.pws @@ -7,6 +7,7 @@ AIED API APIs ASAN +Asan ASIC ASICs ATTR @@ -59,6 +60,7 @@ LLC LLR LOGLEVEL LOOPBACK +LSan MACsec MCAST MTU @@ -88,7 +90,9 @@ SCs SDK SGs SHA +SIGTERM SONiC +SPDX SRC STP SaiAttr @@ -131,6 +135,7 @@ ASICs asicSet asicview AsicView +asm async attr ATTR @@ -164,6 +169,8 @@ cpp cpu CreateObject CRM +csignal +cstdlib currentObj currentObject currentView @@ -267,6 +274,7 @@ KEYs KEYs lck lgtm +libc librediscommon libsairedis libswsscommon @@ -279,12 +287,14 @@ LOOPBACK lua macsec MACsec +malloc MCAST md mdio MDIO Mellanox memcpy +memset metadata mlnx mpls @@ -395,6 +405,7 @@ shmem sleeptime soAll SONiC +SPDX splitted src SRC @@ -412,6 +423,7 @@ struct structs structure subport +suppressions sw swid Switch