Merge "Add option to force memunreachable check."

This commit is contained in:
Christopher Ferris 2022-05-11 23:28:21 +00:00 committed by Gerrit Code Review
commit bcd8f15e27
9 changed files with 291 additions and 25 deletions

View file

@ -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: [

View file

@ -70,17 +70,21 @@ static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/reco
const std::unordered_map<std::string, Config::OptionInfo> 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<std::string, Config::OptionInfo> 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;

View file

@ -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;
};

View file

@ -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 <PID>' 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 <PID>
**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

View file

@ -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 <errno.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <atomic>
#include <string>
#include <memunreachable/memunreachable.h>
#include <platform/bionic/macros.h>
#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;
}

View file

@ -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 <stdint.h>
#include <atomic>
// 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);
};

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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());
}