From b42e8b4dec33daec51f40936c81cfe922931f431 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Mon, 9 May 2022 14:00:47 -0700 Subject: [PATCH] Add option to force memunreachable check. The new option is named check_unreachable_on_signal. It is meant to duplicate dumpsys meminfo --unreachable for non-java processes. When enabled, a user can send a signal to a process which will trigger the unreachable check on the next allocation call. Added new unit tests. Test: New unit tests pass. Test: Enabled for the entire system, then dumped on the netd Test: process and also system_server. Change-Id: I73561b408a947a11ce21a211b065d59fcc39097b --- libc/malloc_debug/Android.bp | 2 + libc/malloc_debug/Config.cpp | 62 +++++++++----- libc/malloc_debug/Config.h | 5 ++ libc/malloc_debug/README.md | 29 ++++++- libc/malloc_debug/Unreachable.cpp | 80 +++++++++++++++++++ libc/malloc_debug/Unreachable.h | 49 ++++++++++++ libc/malloc_debug/malloc_debug.cpp | 25 +++++- .../tests/malloc_debug_config_tests.cpp | 18 +++++ .../tests/malloc_debug_unit_tests.cpp | 46 ++++++++++- 9 files changed, 291 insertions(+), 25 deletions(-) create mode 100644 libc/malloc_debug/Unreachable.cpp create mode 100644 libc/malloc_debug/Unreachable.h diff --git a/libc/malloc_debug/Android.bp b/libc/malloc_debug/Android.bp index 7ff3db204..344f9a783 100644 --- a/libc/malloc_debug/Android.bp +++ b/libc/malloc_debug/Android.bp @@ -64,6 +64,7 @@ cc_library { "malloc_debug.cpp", "PointerData.cpp", "RecordData.cpp", + "Unreachable.cpp", "UnwindBacktrace.cpp", ], @@ -73,6 +74,7 @@ cc_library { "libasync_safe", "libbase", "libc_malloc_debug_backtrace", + "libmemunreachable", ], shared_libs: [ diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index 11887e295..5fd511f15 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -70,17 +70,21 @@ static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/reco const std::unordered_map Config::kOptions = { { - "guard", {FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, &Config::SetGuard}, + "guard", + {FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, &Config::SetGuard}, }, { - "front_guard", {FRONT_GUARD | TRACK_ALLOCS, &Config::SetFrontGuard}, + "front_guard", + {FRONT_GUARD | TRACK_ALLOCS, &Config::SetFrontGuard}, }, { - "rear_guard", {REAR_GUARD | TRACK_ALLOCS, &Config::SetRearGuard}, + "rear_guard", + {REAR_GUARD | TRACK_ALLOCS, &Config::SetRearGuard}, }, { - "backtrace", {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktrace}, + "backtrace", + {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktrace}, }, { "backtrace_enable_on_signal", @@ -88,55 +92,74 @@ const std::unordered_map Config::kOptions = { }, { - "backtrace_dump_on_exit", {0, &Config::SetBacktraceDumpOnExit}, + "backtrace_dump_on_exit", + {0, &Config::SetBacktraceDumpOnExit}, }, { - "backtrace_dump_prefix", {0, &Config::SetBacktraceDumpPrefix}, + "backtrace_dump_prefix", + {0, &Config::SetBacktraceDumpPrefix}, }, { - "backtrace_full", {BACKTRACE_FULL, &Config::VerifyValueEmpty}, + "backtrace_full", + {BACKTRACE_FULL, &Config::VerifyValueEmpty}, }, { - "fill", {FILL_ON_ALLOC | FILL_ON_FREE, &Config::SetFill}, + "fill", + {FILL_ON_ALLOC | FILL_ON_FREE, &Config::SetFill}, }, { - "fill_on_alloc", {FILL_ON_ALLOC, &Config::SetFillOnAlloc}, + "fill_on_alloc", + {FILL_ON_ALLOC, &Config::SetFillOnAlloc}, }, { - "fill_on_free", {FILL_ON_FREE, &Config::SetFillOnFree}, + "fill_on_free", + {FILL_ON_FREE, &Config::SetFillOnFree}, }, { - "expand_alloc", {EXPAND_ALLOC, &Config::SetExpandAlloc}, + "expand_alloc", + {EXPAND_ALLOC, &Config::SetExpandAlloc}, }, { - "free_track", {FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, &Config::SetFreeTrack}, + "free_track", + {FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, &Config::SetFreeTrack}, }, { - "free_track_backtrace_num_frames", {0, &Config::SetFreeTrackBacktraceNumFrames}, + "free_track_backtrace_num_frames", + {0, &Config::SetFreeTrackBacktraceNumFrames}, }, { - "leak_track", {LEAK_TRACK | TRACK_ALLOCS, &Config::VerifyValueEmpty}, + "leak_track", + {LEAK_TRACK | TRACK_ALLOCS, &Config::VerifyValueEmpty}, }, { - "record_allocs", {RECORD_ALLOCS, &Config::SetRecordAllocs}, + "record_allocs", + {RECORD_ALLOCS, &Config::SetRecordAllocs}, }, { - "record_allocs_file", {0, &Config::SetRecordAllocsFile}, + "record_allocs_file", + {0, &Config::SetRecordAllocsFile}, }, { - "verify_pointers", {TRACK_ALLOCS, &Config::VerifyValueEmpty}, + "verify_pointers", + {TRACK_ALLOCS, &Config::VerifyValueEmpty}, }, { - "abort_on_error", {ABORT_ON_ERROR, &Config::VerifyValueEmpty}, + "abort_on_error", + {ABORT_ON_ERROR, &Config::VerifyValueEmpty}, }, { - "verbose", {VERBOSE, &Config::VerifyValueEmpty}, + "verbose", + {VERBOSE, &Config::VerifyValueEmpty}, + }, + { + "check_unreachable_on_signal", + {CHECK_UNREACHABLE_ON_SIGNAL, &Config::VerifyValueEmpty}, }, }; @@ -380,6 +403,7 @@ bool Config::Init(const char* options_str) { backtrace_enabled_ = false; backtrace_dump_on_exit_ = false; backtrace_dump_prefix_ = DEFAULT_BACKTRACE_DUMP_PREFIX; + check_unreachable_signal_ = SIGRTMAX - 16; // Process each option name we can find. std::string option; diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index 1b5c748e9..5e3ff7169 100644 --- a/libc/malloc_debug/Config.h +++ b/libc/malloc_debug/Config.h @@ -46,6 +46,7 @@ constexpr uint64_t RECORD_ALLOCS = 0x200; constexpr uint64_t BACKTRACE_FULL = 0x400; constexpr uint64_t ABORT_ON_ERROR = 0x800; constexpr uint64_t VERBOSE = 0x1000; +constexpr uint64_t CHECK_UNREACHABLE_ON_SIGNAL = 0x2000; // In order to guarantee posix compliance, set the minimum alignment // to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems. @@ -93,6 +94,8 @@ class Config { size_t record_allocs_num_entries() const { return record_allocs_num_entries_; } const std::string& record_allocs_file() const { return record_allocs_file_; } + int check_unreachable_signal() const { return check_unreachable_signal_; } + private: struct OptionInfo { uint64_t option; @@ -160,4 +163,6 @@ class Config { uint8_t fill_free_value_; uint8_t front_guard_value_; uint8_t rear_guard_value_; + + int check_unreachable_signal_ = 0; }; diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index 662f5f897..6b046db26 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -114,7 +114,7 @@ the backtrace and information about the original allocation. After that, this option will not add a special header. As of P, this option will also enable dumping backtrace heap data to a -file when the process receives the signal SIGRTMAX - 17 ( which is 47 on most +file when the process receives the signal SIGRTMAX - 17 ( which is 47 on Android devices). The format of this dumped data is the same format as that dumped when running am dumpheap -n. The default is to dump this data to the file /data/local/tmp/backtrace\_heap.**PID**.txt. This is useful when @@ -127,7 +127,7 @@ malloc/free occurs. ### backtrace\_enable\_on\_signal[=MAX\_FRAMES] Enable capturing the backtrace of each allocation site. If the backtrace capture is toggled when the process receives the signal -SIGRTMAX - 19 (which is 45 on most Android devices). When this +SIGRTMAX - 19 (which is 45 on Android devices). When this option is used alone, backtrace capture starts out disabled until the signal is received. If both this option and the backtrace option are set, then backtrace capture is enabled until the signal is received. @@ -165,6 +165,29 @@ As of Q, any time that a backtrace is gathered, a different algorithm is used that is extra thorough and can unwind through Java frames. This will run slower than the normal backtracing function. +### check\_unreachable\_on\_signal +As of Android U, this option will trigger a check for unreachable memory +in a process. Specifically, if the signal SIGRTMAX - 16 (which is 48 on +Android devices). The best way to see the exact signal being used is to +enable the verbose option then look at the log for the message: + + Run: 'kill -48 ' to check for unreachable memory. + +When the signal is received, the actual unreachable check only triggers +on the next allocation that happens in the process (malloc/free, etc). + +If a process is not doing any allocations, it can be forced to trigger when +running: + + debuggerd -b + +**NOTE**: The unreachable check can fail for protected processes, so it +might be necessary to run: + + setenforce 0 + +To get the unreachable data. + ### fill\_on\_alloc[=MAX\_FILLED\_BYTES] Any allocation routine, other than calloc, will result in the allocation being filled with the value 0xeb. When doing a realloc to a larger size, the bytes @@ -270,7 +293,7 @@ Example leak error found in the log: ### record\_allocs[=TOTAL\_ENTRIES] Keep track of every allocation/free made on every thread and dump them -to a file when the signal SIGRTMAX - 18 (which is 46 on most Android devices) +to a file when the signal SIGRTMAX - 18 (which is 46 on Android devices) is received. If TOTAL\_ENTRIES is set, then it indicates the total number of diff --git a/libc/malloc_debug/Unreachable.cpp b/libc/malloc_debug/Unreachable.cpp new file mode 100644 index 000000000..af4725746 --- /dev/null +++ b/libc/malloc_debug/Unreachable.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Config.h" +#include "Unreachable.h" +#include "debug_log.h" + +std::atomic_bool Unreachable::do_check_; + +static void EnableUnreachableCheck(int, struct siginfo*, void*) { + Unreachable::EnableCheck(); +} + +void Unreachable::CheckIfRequested(const Config& config) { + if ((config.options() & CHECK_UNREACHABLE_ON_SIGNAL) && do_check_.exchange(false)) { + info_log("Starting to check for unreachable memory."); + if (!LogUnreachableMemory(false, 100)) { + error_log("Unreachable check failed, run setenforce 0 and try again."); + } + } +} + +bool Unreachable::Initialize(const Config& config) { + if (!(config.options() & CHECK_UNREACHABLE_ON_SIGNAL)) { + return true; + } + + struct sigaction64 unreachable_act = {}; + unreachable_act.sa_sigaction = EnableUnreachableCheck; + unreachable_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + if (sigaction64(config.check_unreachable_signal(), &unreachable_act, nullptr) != 0) { + error_log("Unable to set up check unreachable signal function: %s", strerror(errno)); + return false; + } + + if (config.options() & VERBOSE) { + info_log("%s: Run: 'kill -%d %d' to check for unreachable memory.", getprogname(), + config.check_unreachable_signal(), getpid()); + } + + return true; +} diff --git a/libc/malloc_debug/Unreachable.h b/libc/malloc_debug/Unreachable.h new file mode 100644 index 000000000..36c0bdb56 --- /dev/null +++ b/libc/malloc_debug/Unreachable.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#pragma once + +#include + +#include + +// Forward declarations +class ConfigData; + +class Unreachable { + public: + static bool Initialize(const Config& config); + static void CheckIfRequested(const Config& config); + + static void EnableCheck() { do_check_ = true; } + + private: + static std::atomic_bool do_check_; + + BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(Unreachable); +}; diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index d608f5d02..35cda838c 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -53,11 +53,12 @@ #include "Config.h" #include "DebugData.h" +#include "Unreachable.h" +#include "UnwindBacktrace.h" #include "backtrace.h" #include "debug_disable.h" #include "debug_log.h" #include "malloc_debug.h" -#include "UnwindBacktrace.h" // ------------------------------------------------------------------------ // Global Data @@ -315,7 +316,7 @@ bool debug_initialize(const MallocDispatch* malloc_dispatch, bool* zygote_child, } DebugData* debug = new DebugData(); - if (!debug->Initialize(options)) { + if (!debug->Initialize(options) || !Unreachable::Initialize(debug->config())) { delete debug; DebugDisableFinalize(); return false; @@ -402,6 +403,8 @@ void debug_free_malloc_leak_info(uint8_t* info) { } size_t debug_malloc_usable_size(void* pointer) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->malloc_usable_size(pointer); } @@ -467,6 +470,8 @@ static void* InternalMalloc(size_t size) { } void* debug_malloc(size_t size) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->malloc(size); } @@ -544,6 +549,8 @@ static void InternalFree(void* pointer) { } void debug_free(void* pointer) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->free(pointer); } @@ -563,6 +570,8 @@ void debug_free(void* pointer) { } void* debug_memalign(size_t alignment, size_t bytes) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->memalign(alignment, bytes); } @@ -643,6 +652,8 @@ void* debug_memalign(size_t alignment, size_t bytes) { } void* debug_realloc(void* pointer, size_t bytes) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->realloc(pointer, bytes); } @@ -763,6 +774,8 @@ void* debug_realloc(void* pointer, size_t bytes) { } void* debug_calloc(size_t nmemb, size_t bytes) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->calloc(nmemb, bytes); } @@ -862,6 +875,8 @@ int debug_malloc_info(int options, FILE* fp) { } void* debug_aligned_alloc(size_t alignment, size_t size) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->aligned_alloc(alignment, size); } @@ -873,6 +888,8 @@ void* debug_aligned_alloc(size_t alignment, size_t size) { } int debug_posix_memalign(void** memptr, size_t alignment, size_t size) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->posix_memalign(memptr, alignment, size); } @@ -934,6 +951,8 @@ ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t max_fram #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) void* debug_pvalloc(size_t bytes) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->pvalloc(bytes); } @@ -949,6 +968,8 @@ void* debug_pvalloc(size_t bytes) { } void* debug_valloc(size_t size) { + Unreachable::CheckIfRequested(g_debug->config()); + if (DebugCallsDisabled()) { return g_dispatch->valloc(size); } diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index 42d1415ac..d08ba98f6 100644 --- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp @@ -761,3 +761,21 @@ TEST_F(MallocDebugConfigTest, trigger_verbose_fail) { "which does not take a value\n"); ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); } + +TEST_F(MallocDebugConfigTest, check_unreachable_on_signal) { + ASSERT_TRUE(InitConfig("check_unreachable_on_signal")) << getFakeLogPrint(); + ASSERT_EQ(CHECK_UNREACHABLE_ON_SIGNAL, config->options()); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, trigger_check_unreachable_on_signal_fail) { + ASSERT_FALSE(InitConfig("check_unreachable_on_signal=200")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg( + "6 malloc_debug malloc_testing: value set for option 'check_unreachable_on_signal' " + "which does not take a value\n"); + ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); +} diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index ea2dc7880..ff743ac82 100644 --- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp @@ -227,6 +227,9 @@ void VerifyAllocCalls(bool all_options) { expected_log += android::base::StringPrintf( "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n", SIGRTMAX - 18, getpid()); + expected_log += android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to check for unreachable memory.\n", + SIGRTMAX - 16, getpid()); } expected_log += "4 malloc_debug malloc_testing: malloc debug enabled\n"; ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); @@ -296,6 +299,16 @@ TEST_F(MallocDebugTest, verbose_record_allocs) { ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } +TEST_F(MallocDebugTest, verbose_check_unreachable_on_signal) { + Init("verbose check_unreachable_on_signal"); + + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to check for unreachable memory.\n", + SIGRTMAX - 16, getpid()); + expected_log += "4 malloc_debug malloc_testing: malloc debug enabled\n"; + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + TEST_F(MallocDebugTest, fill_on_free) { Init("fill_on_free free_track free_track_backtrace_num_frames=0"); @@ -359,7 +372,7 @@ TEST_F(MallocDebugTest, free_track_partial) { TEST_F(MallocDebugTest, all_options) { Init( "guard backtrace backtrace_enable_on_signal fill expand_alloc free_track leak_track " - "record_allocs verify_pointers abort_on_error verbose"); + "record_allocs verify_pointers abort_on_error verbose check_unreachable_on_signal"); VerifyAllocCalls(true); } @@ -2695,3 +2708,34 @@ TEST_F(MallocDebugTest, dump_heap) { std::string expected_log = std::string("6 malloc_debug Dumping to file: ") + tf.path + "\n\n"; ASSERT_EQ(expected_log, getFakeLogPrint()); } + +extern "C" bool LogUnreachableMemory(bool, size_t) { + static bool return_value = false; + return_value = !return_value; + return return_value; +} + +TEST_F(MallocDebugTest, check_unreachable_on_signal) { + Init("check_unreachable_on_signal"); + + ASSERT_TRUE(kill(getpid(), SIGRTMAX - 16) == 0); + sleep(1); + + // The first unreachable check will pass. + void* pointer = debug_malloc(110); + ASSERT_TRUE(pointer != nullptr); + + ASSERT_TRUE(kill(getpid(), SIGRTMAX - 16) == 0); + sleep(1); + + // The second unreachable check will fail. + debug_free(pointer); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = "4 malloc_debug Starting to check for unreachable memory.\n"; + ASSERT_STREQ( + "4 malloc_debug Starting to check for unreachable memory.\n" + "4 malloc_debug Starting to check for unreachable memory.\n" + "6 malloc_debug Unreachable check failed, run setenforce 0 and try again.\n", + getFakeLogPrint().c_str()); +}