diff options
author | Christopher Ferris <cferris@google.com> | 2018-03-07 13:38:48 -0800 |
---|---|---|
committer | Christopher Ferris <cferris@google.com> | 2018-04-03 13:13:53 -0700 |
commit | 8b70a0266d42297e9b38e6209588eb3621843e95 (patch) | |
tree | d73242853197da6f154149580fc3afa35f13d84d | |
parent | 622a36923ec2778dec0f195fec5cdc2056e65fca (diff) | |
download | bionic-8b70a0266d42297e9b38e6209588eb3621843e95.tar.gz |
Refactor malloc debug.
Changes
- Refactor the code so that only guards require creating a special header
for every pointer allocated.
- Store only a single copy of every backtrace. This saves memory so that
turning on the backtrace option doesn't result in 10X memory usage.
- Added new option track_allocs that only verifies pointers are valid for
free/malloc_usable_size/realloc.
- Remove suffix from test names.
- Add the TRACK_ALLOCS options to all guard options.
- Add new option verify_pointers that is a lightweight way to verify
pointers that are passed to allocation routines.
- Do auto-formatting of the code.
- Updated documentation for all of these changes.
Bug: 74361929
Test: Ran unit tests.
Test: Ran libmemunreachable unit tests.
Test: Ran an app with backtrace enabled.
Change-Id: I3246c48ae4f9811f64622d90d0a9b4d9d818702c
(cherry picked from commit 4da2503d70dc4bc1444454876e3794b69227d90d)
25 files changed, 1548 insertions, 1167 deletions
diff --git a/libc/malloc_debug/Android.bp b/libc/malloc_debug/Android.bp index ede24315f..899987c42 100644 --- a/libc/malloc_debug/Android.bp +++ b/libc/malloc_debug/Android.bp @@ -42,15 +42,13 @@ cc_library { name: "libc_malloc_debug", srcs: [ - "BacktraceData.cpp", "Config.cpp", "DebugData.cpp", "debug_disable.cpp", - "FreeTrackData.cpp", "GuardData.cpp", "malloc_debug.cpp", + "PointerData.cpp", "RecordData.cpp", - "TrackData.cpp", ], stl: "libc++_static", @@ -91,6 +89,7 @@ cc_library { "-Werror", "-fno-stack-protector", "-Wno-error=format-zero-length", + "-Wthread-safety", ], } @@ -98,16 +97,7 @@ cc_library { // Unit Tests // ============================================================== cc_test { - name: "malloc_debug_unit_tests", - multilib: { - lib32: { - suffix: "32", - }, - lib64: { - suffix: "64", - }, - }, srcs: [ "tests/backtrace_fake.cpp", diff --git a/libc/malloc_debug/BacktraceData.cpp b/libc/malloc_debug/BacktraceData.cpp deleted file mode 100644 index 57d8f2afe..000000000 --- a/libc/malloc_debug/BacktraceData.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 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 <stdlib.h> -#include <sys/types.h> -#include <unistd.h> - -#include <private/bionic_macros.h> - -#include "BacktraceData.h" -#include "Config.h" -#include "DebugData.h" -#include "debug_log.h" -#include "malloc_debug.h" - -static void ToggleBacktraceEnable(int, siginfo_t*, void*) { - g_debug->backtrace->ToggleBacktraceEnabled(); -} - -static void EnableDump(int, siginfo_t*, void*) { - g_debug->backtrace->EnableDumping(); -} - -BacktraceData::BacktraceData(DebugData* debug_data, const Config& config, size_t* offset) - : OptionData(debug_data) { - size_t hdr_len = sizeof(BacktraceHeader) + sizeof(uintptr_t) * config.backtrace_frames(); - alloc_offset_ = *offset; - *offset += __BIONIC_ALIGN(hdr_len, MINIMUM_ALIGNMENT_BYTES); -} - -bool BacktraceData::Initialize(const Config& config) { - enabled_ = config.backtrace_enabled(); - if (config.backtrace_enable_on_signal()) { - struct sigaction64 enable_act = {}; - enable_act.sa_sigaction = ToggleBacktraceEnable; - enable_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; - if (sigaction64(config.backtrace_signal(), &enable_act, nullptr) != 0) { - error_log("Unable to set up backtrace signal enable function: %s", strerror(errno)); - return false; - } - info_log("%s: Run: 'kill -%d %d' to enable backtracing.", getprogname(), - config.backtrace_signal(), getpid()); - } - - struct sigaction64 act = {}; - act.sa_sigaction = EnableDump; - act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; - if (sigaction64(config.backtrace_dump_signal(), &act, nullptr) != 0) { - error_log("Unable to set up backtrace dump signal function: %s", strerror(errno)); - return false; - } - info_log("%s: Run: 'kill -%d %d' to dump the backtrace.", getprogname(), - config.backtrace_dump_signal(), getpid()); - - dump_ = false; - return true; -} diff --git a/libc/malloc_debug/BacktraceData.h b/libc/malloc_debug/BacktraceData.h deleted file mode 100644 index 3c153484d..000000000 --- a/libc/malloc_debug/BacktraceData.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 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> - -#include <private/bionic_macros.h> - -#include "OptionData.h" - -// Forward declarations. -class Config; - -class BacktraceData : public OptionData { - public: - BacktraceData(DebugData* debug_data, const Config& config, size_t* offset); - virtual ~BacktraceData() = default; - - bool Initialize(const Config& config); - - inline size_t alloc_offset() { return alloc_offset_; } - - bool ShouldBacktrace() { return enabled_ == 1; } - void ToggleBacktraceEnabled() { enabled_.fetch_xor(1); } - - void EnableDumping() { dump_ = true; } - bool ShouldDumpAndReset() { - bool expected = true; - return dump_.compare_exchange_strong(expected, false); - } - - private: - size_t alloc_offset_ = 0; - - std::atomic_uint8_t enabled_; - - std::atomic_bool dump_; - - DISALLOW_COPY_AND_ASSIGN(BacktraceData); -}; diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index 3cecf9b57..2c94fe81f 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -69,65 +69,70 @@ static constexpr size_t MAX_RECORD_ALLOCS = 50000000; static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt"; const std::unordered_map<std::string, Config::OptionInfo> Config::kOptions = { - {"guard", - {FRONT_GUARD | REAR_GUARD, &Config::SetGuard}, + { + "guard", {FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, &Config::SetGuard}, }, - {"front_guard", - {FRONT_GUARD, &Config::SetFrontGuard}, + { + "front_guard", {FRONT_GUARD | TRACK_ALLOCS, &Config::SetFrontGuard}, }, - {"rear_guard", - {REAR_GUARD, &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", - {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktraceEnableOnSignal}, + { + "backtrace_enable_on_signal", + {BACKTRACE | TRACK_ALLOCS, &Config::SetBacktraceEnableOnSignal}, }, - {"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}, }, - {"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, &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}, }, }; -bool Config::ParseValue(const std::string& option, const std::string& value, - size_t min_value, size_t max_value, size_t* parsed_value) const { +bool Config::ParseValue(const std::string& option, const std::string& value, size_t min_value, + size_t max_value, size_t* parsed_value) const { assert(!value.empty()); // Parse the value into a size_t value. @@ -135,8 +140,7 @@ bool Config::ParseValue(const std::string& option, const std::string& value, char* end; long long_value = strtol(value.c_str(), &end, 10); if (errno != 0) { - error_log("%s: bad value for option '%s': %s", getprogname(), option.c_str(), - strerror(errno)); + error_log("%s: bad value for option '%s': %s", getprogname(), option.c_str(), strerror(errno)); return false; } if (end == value.c_str()) { @@ -144,24 +148,24 @@ bool Config::ParseValue(const std::string& option, const std::string& value, return false; } if (static_cast<size_t>(end - value.c_str()) != value.size()) { - error_log("%s: bad value for option '%s', non space found after option: %s", - getprogname(), option.c_str(), end); + error_log("%s: bad value for option '%s', non space found after option: %s", getprogname(), + option.c_str(), end); return false; } if (long_value < 0) { - error_log("%s: bad value for option '%s', value cannot be negative: %ld", - getprogname(), option.c_str(), long_value); + error_log("%s: bad value for option '%s', value cannot be negative: %ld", getprogname(), + option.c_str(), long_value); return false; } if (static_cast<size_t>(long_value) < min_value) { - error_log("%s: bad value for option '%s', value must be >= %zu: %ld", - getprogname(), option.c_str(), min_value, long_value); + error_log("%s: bad value for option '%s', value must be >= %zu: %ld", getprogname(), + option.c_str(), min_value, long_value); return false; } if (static_cast<size_t>(long_value) > max_value) { - error_log("%s: bad value for option '%s', value must be <= %zu: %ld", - getprogname(), option.c_str(), max_value, long_value); + error_log("%s: bad value for option '%s', value must be <= %zu: %ld", getprogname(), + option.c_str(), max_value, long_value); return false; } *parsed_value = static_cast<size_t>(long_value); @@ -261,7 +265,6 @@ bool Config::SetBacktraceDumpPrefix(const std::string&, const std::string& value return true; } - bool Config::SetExpandAlloc(const std::string& option, const std::string& value) { return ParseValue(option, value, DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, &expand_alloc_bytes_); } @@ -305,14 +308,13 @@ bool Config::SetRecordAllocsFile(const std::string&, const std::string& value) { bool Config::VerifyValueEmpty(const std::string& option, const std::string& value) { if (!value.empty()) { // This is not valid. - error_log("%s: value set for option '%s' which does not take a value", - getprogname(), option.c_str()); + error_log("%s: value set for option '%s' which does not take a value", getprogname(), + option.c_str()); return false; } return true; } - void Config::LogUsage() const { error_log("malloc debug options usage:"); error_log(""); @@ -329,8 +331,8 @@ void Config::LogUsage() const { error_log(" guard[=XX]"); error_log(" Enables both a front guard and a rear guard on all allocations."); error_log(" If XX is set it sets the number of bytes in both guards."); - error_log(" The default is %zu bytes, the max bytes is %zu.", - DEFAULT_GUARD_BYTES, MAX_GUARD_BYTES); + error_log(" The default is %zu bytes, the max bytes is %zu.", DEFAULT_GUARD_BYTES, + MAX_GUARD_BYTES); error_log(""); error_log(" backtrace[=XX]"); error_log(" Enable capturing the backtrace at the point of allocation."); @@ -419,13 +421,16 @@ void Config::LogUsage() const { error_log(" This option only has meaning if the record_allocs options has been specified."); error_log(" This is the name of the file to which recording information will be dumped."); error_log(" The default is %s.", DEFAULT_RECORD_ALLOCS_FILE); + error_log(""); + error_log(" verify_pointers"); + error_log(" A lightweight way to verify that free/malloc_usable_size/realloc"); + error_log(" are passed valid pointers."); } bool Config::GetOption(const char** options_str, std::string* option, std::string* value) { const char* cur = *options_str; // Process each property name we can find. - while (isspace(*cur)) - ++cur; + while (isspace(*cur)) ++cur; if (*cur == '\0') { *options_str = cur; @@ -433,25 +438,21 @@ bool Config::GetOption(const char** options_str, std::string* option, std::strin } const char* start = cur; - while (!isspace(*cur) && *cur != '=' && *cur != '\0') - ++cur; + while (!isspace(*cur) && *cur != '=' && *cur != '\0') ++cur; *option = std::string(start, cur - start); // Skip any spaces after the name. - while (isspace(*cur)) - ++cur; + while (isspace(*cur)) ++cur; value->clear(); if (*cur == '=') { ++cur; // Skip the space after the equal. - while (isspace(*cur)) - ++cur; + while (isspace(*cur)) ++cur; start = cur; - while (!isspace(*cur) && *cur != '\0') - ++cur; + while (!isspace(*cur) && *cur != '\0') ++cur; if (cur != start) { *value = std::string(start, cur - start); diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index a310e09e0..3bcef0bc8 100644 --- a/libc/malloc_debug/Config.h +++ b/libc/malloc_debug/Config.h @@ -53,7 +53,7 @@ constexpr size_t MINIMUM_ALIGNMENT_BYTES = 8; #endif // If one or more of these options is set, then a special header is needed. -constexpr uint64_t HEADER_OPTIONS = FRONT_GUARD | REAR_GUARD | BACKTRACE | FREE_TRACK | LEAK_TRACK; +constexpr uint64_t HEADER_OPTIONS = FRONT_GUARD | REAR_GUARD; class Config { public: diff --git a/libc/malloc_debug/DebugData.cpp b/libc/malloc_debug/DebugData.cpp index 76f8fbb1e..44c4a10d9 100644 --- a/libc/malloc_debug/DebugData.cpp +++ b/libc/malloc_debug/DebugData.cpp @@ -28,14 +28,12 @@ #include <stdint.h> -#include "BacktraceData.h" #include "Config.h" #include "DebugData.h" -#include "debug_disable.h" -#include "FreeTrackData.h" #include "GuardData.h" +#include "PointerData.h" +#include "debug_disable.h" #include "malloc_debug.h" -#include "TrackData.h" bool DebugData::Initialize(const char* options) { if (!config_.Init(options)) { @@ -44,18 +42,9 @@ bool DebugData::Initialize(const char* options) { // Check to see if the options that require a header are enabled. if (config_.options() & HEADER_OPTIONS) { - need_header_ = true; - // Initialize all of the static header offsets. pointer_offset_ = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES); - if (config_.options() & BACKTRACE) { - backtrace.reset(new BacktraceData(this, config_, &pointer_offset_)); - if (!backtrace->Initialize(config_)) { - return false; - } - } - if (config_.options() & FRONT_GUARD) { front_guard.reset(new FrontGuardData(this, config_, &pointer_offset_)); } @@ -67,13 +56,12 @@ bool DebugData::Initialize(const char* options) { rear_guard.reset(new RearGuardData(this, config_)); extra_bytes_ += config_.rear_guard_bytes(); } + } - if (config_.options() & FREE_TRACK) { - free_track.reset(new FreeTrackData(this, config_)); - } - - if (config_.options() & TRACK_ALLOCS) { - track.reset(new TrackData(this)); + if (TrackPointers()) { + pointer.reset(new PointerData(this)); + if (!pointer->Initialize(config_)) { + return false; } } @@ -91,28 +79,19 @@ bool DebugData::Initialize(const char* options) { } void DebugData::PrepareFork() { - if (track != nullptr) { - track->PrepareFork(); - } - if (free_track != nullptr) { - free_track->PrepareFork(); + if (pointer != nullptr) { + pointer->PrepareFork(); } } void DebugData::PostForkParent() { - if (track != nullptr) { - track->PostForkParent(); - } - if (free_track != nullptr) { - free_track->PostForkParent(); + if (pointer != nullptr) { + pointer->PostForkParent(); } } void DebugData::PostForkChild() { - if (track != nullptr) { - track->PostForkChild(); - } - if (free_track != nullptr) { - free_track->PostForkChild(); + if (pointer != nullptr) { + pointer->PostForkChild(); } } diff --git a/libc/malloc_debug/DebugData.h b/libc/malloc_debug/DebugData.h index 9aece0b80..f7cf8abd1 100644 --- a/libc/malloc_debug/DebugData.h +++ b/libc/malloc_debug/DebugData.h @@ -35,13 +35,11 @@ #include <private/bionic_macros.h> -#include "BacktraceData.h" #include "Config.h" -#include "FreeTrackData.h" #include "GuardData.h" -#include "malloc_debug.h" +#include "PointerData.h" #include "RecordData.h" -#include "TrackData.h" +#include "malloc_debug.h" class DebugData { public: @@ -62,11 +60,6 @@ class DebugData { return reinterpret_cast<Header*>(value - pointer_offset_); } - BacktraceHeader* GetAllocBacktrace(const Header* header) { - uintptr_t value = reinterpret_cast<uintptr_t>(header); - return reinterpret_cast<BacktraceHeader*>(value + backtrace->alloc_offset()); - } - uint8_t* GetFrontGuard(const Header* header) { uintptr_t value = reinterpret_cast<uintptr_t>(header); return reinterpret_cast<uint8_t*>(value + front_guard->offset()); @@ -74,30 +67,30 @@ class DebugData { uint8_t* GetRearGuard(const Header* header) { uintptr_t value = reinterpret_cast<uintptr_t>(GetPointer(header)); - return reinterpret_cast<uint8_t*>(value + header->real_size()); + return reinterpret_cast<uint8_t*>(value + header->size); } const Config& config() { return config_; } size_t pointer_offset() { return pointer_offset_; } - bool need_header() { return need_header_; } size_t extra_bytes() { return extra_bytes_; } + bool TrackPointers() { return config_.options() & TRACK_ALLOCS; } + + bool HeaderEnabled() { return config_.options() & HEADER_OPTIONS; } + void PrepareFork(); void PostForkParent(); void PostForkChild(); - std::unique_ptr<BacktraceData> backtrace; - std::unique_ptr<TrackData> track; std::unique_ptr<FrontGuardData> front_guard; + std::unique_ptr<PointerData> pointer; std::unique_ptr<RearGuardData> rear_guard; - std::unique_ptr<FreeTrackData> free_track; std::unique_ptr<RecordData> record; private: size_t extra_bytes_ = 0; size_t pointer_offset_ = 0; - bool need_header_ = false; Config config_; diff --git a/libc/malloc_debug/FreeTrackData.cpp b/libc/malloc_debug/FreeTrackData.cpp deleted file mode 100644 index e8e7a6774..000000000 --- a/libc/malloc_debug/FreeTrackData.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 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 <stdint.h> - -#include "backtrace.h" -#include "Config.h" -#include "DebugData.h" -#include "debug_disable.h" -#include "debug_log.h" -#include "FreeTrackData.h" -#include "malloc_debug.h" - -FreeTrackData::FreeTrackData(DebugData* debug, const Config& config) - : OptionData(debug), backtrace_num_frames_(config.free_track_backtrace_num_frames()) { - cmp_mem_.resize(4096); - memset(cmp_mem_.data(), config.fill_free_value(), cmp_mem_.size()); -} - -void FreeTrackData::LogFreeError(const Header* header, const uint8_t* pointer) { - error_log(LOG_DIVIDER); - error_log("+++ ALLOCATION %p USED AFTER FREE", pointer); - uint8_t fill_free_value = debug_->config().fill_free_value(); - for (size_t i = 0; i < header->usable_size; i++) { - if (pointer[i] != fill_free_value) { - error_log(" allocation[%zu] = 0x%02x (expected 0x%02x)", i, pointer[i], fill_free_value); - } - } - auto back_iter = backtraces_.find(header); - if (back_iter != backtraces_.end()) { - const BacktraceHeader* back_header = back_iter->second; - error_log("Backtrace at time of free:"); - backtrace_log(&back_header->frames[0], back_header->num_frames); - } - error_log(LOG_DIVIDER); -} - -void FreeTrackData::VerifyAndFree(const Header* header) { - const void* pointer = debug_->GetPointer(header); - if (header->tag != DEBUG_FREE_TAG) { - error_log(LOG_DIVIDER); - error_log("+++ ALLOCATION %p HAS CORRUPTED HEADER TAG 0x%x AFTER FREE", pointer, header->tag); - error_log(LOG_DIVIDER); - } else { - const uint8_t* memory = reinterpret_cast<const uint8_t*>(pointer); - size_t bytes = header->usable_size; - bytes = (bytes < debug_->config().fill_on_free_bytes()) ? bytes - : debug_->config().fill_on_free_bytes(); - while (bytes > 0) { - size_t bytes_to_cmp = (bytes < cmp_mem_.size()) ? bytes : cmp_mem_.size(); - if (memcmp(memory, cmp_mem_.data(), bytes_to_cmp) != 0) { - LogFreeError(header, reinterpret_cast<const uint8_t*>(pointer)); - break; - } - bytes -= bytes_to_cmp; - memory = &memory[bytes_to_cmp]; - } - } - - auto back_iter = backtraces_.find(header); - if (back_iter != backtraces_.end()) { - g_dispatch->free(reinterpret_cast<void*>(back_iter->second)); - backtraces_.erase(header); - } - g_dispatch->free(header->orig_pointer); -} - -void FreeTrackData::Add(const Header* header) { - pthread_mutex_lock(&mutex_); - if (list_.size() == debug_->config().free_track_allocations()) { - const Header* old_header = list_.back(); - VerifyAndFree(old_header); - list_.pop_back(); - } - - if (backtrace_num_frames_ > 0) { - BacktraceHeader* back_header = reinterpret_cast<BacktraceHeader*>( - g_dispatch->malloc(sizeof(BacktraceHeader) + backtrace_num_frames_ * sizeof(uintptr_t))); - if (back_header) { - back_header->num_frames = backtrace_get(&back_header->frames[0], backtrace_num_frames_); - backtraces_[header] = back_header; - } - } - list_.push_front(header); - - pthread_mutex_unlock(&mutex_); -} - -void FreeTrackData::VerifyAll() { - for (const auto& header : list_) { - VerifyAndFree(header); - } - list_.clear(); -} - -void FreeTrackData::LogBacktrace(const Header* header) { - auto back_iter = backtraces_.find(header); - if (back_iter == backtraces_.end()) { - return; - } - - error_log("Backtrace of original free:"); - backtrace_log(&back_iter->second->frames[0], back_iter->second->num_frames); -} diff --git a/libc/malloc_debug/FreeTrackData.h b/libc/malloc_debug/FreeTrackData.h deleted file mode 100644 index bd3497d5e..000000000 --- a/libc/malloc_debug/FreeTrackData.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 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 <pthread.h> - -#include <deque> -#include <unordered_map> -#include <vector> - -#include <private/bionic_macros.h> - -#include "OptionData.h" - -// Forward declarations. -struct BacktraceHeader; -class Config; -class DebugData; -struct Header; - -class FreeTrackData : public OptionData { - public: - FreeTrackData(DebugData* debug_data, const Config& config); - virtual ~FreeTrackData() = default; - - void Add(const Header* header); - - void VerifyAll(); - - void LogBacktrace(const Header* header); - - void PrepareFork() { pthread_mutex_lock(&mutex_); } - - void PostForkParent() { pthread_mutex_unlock(&mutex_); } - - void PostForkChild() { pthread_mutex_init(&mutex_, NULL); } - - private: - void LogFreeError(const Header* header, const uint8_t* pointer); - void VerifyAndFree(const Header* header); - - pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER; - std::deque<const Header*> list_; - std::vector<uint8_t> cmp_mem_; - std::unordered_map<const Header*, BacktraceHeader*> backtraces_; - size_t backtrace_num_frames_; - - DISALLOW_COPY_AND_ASSIGN(FreeTrackData); -}; diff --git a/libc/malloc_debug/GuardData.cpp b/libc/malloc_debug/GuardData.cpp index d6cef85c1..f9a2dcaa7 100644 --- a/libc/malloc_debug/GuardData.cpp +++ b/libc/malloc_debug/GuardData.cpp @@ -31,13 +31,13 @@ #include <vector> -#include "backtrace.h" #include "Config.h" +#include "DebugData.h" +#include "GuardData.h" +#include "backtrace.h" #include "debug_disable.h" #include "debug_log.h" -#include "DebugData.h" #include "malloc_debug.h" -#include "GuardData.h" GuardData::GuardData(DebugData* debug_data, int init_value, size_t num_bytes) : OptionData(debug_data) { @@ -48,8 +48,8 @@ GuardData::GuardData(DebugData* debug_data, int init_value, size_t num_bytes) void GuardData::LogFailure(const Header* header, const void* pointer, const void* data) { error_log(LOG_DIVIDER); - error_log("+++ ALLOCATION %p SIZE %zu HAS A CORRUPTED %s GUARD", pointer, - header->real_size(), GetTypeName()); + error_log("+++ ALLOCATION %p SIZE %zu HAS A CORRUPTED %s GUARD", pointer, header->size, + GetTypeName()); // Log all of the failing bytes. const uint8_t* expected = cmp_mem_.data(); @@ -70,7 +70,7 @@ void GuardData::LogFailure(const Header* header, const void* pointer, const void } FrontGuardData::FrontGuardData(DebugData* debug_data, const Config& config, size_t* offset) - : GuardData(debug_data, config.front_guard_value(), config.front_guard_bytes()) { + : GuardData(debug_data, config.front_guard_value(), config.front_guard_bytes()) { // Create a buffer for fast comparisons of the front guard. cmp_mem_.resize(config.front_guard_bytes()); memset(cmp_mem_.data(), config.front_guard_value(), cmp_mem_.size()); @@ -88,8 +88,7 @@ void FrontGuardData::LogFailure(const Header* header) { } RearGuardData::RearGuardData(DebugData* debug_data, const Config& config) - : GuardData(debug_data, config.rear_guard_value(), config.rear_guard_bytes()) { -} + : GuardData(debug_data, config.rear_guard_value(), config.rear_guard_bytes()) {} bool RearGuardData::Valid(const Header* header) { return GuardData::Valid(debug_->GetRearGuard(header)); diff --git a/libc/malloc_debug/MapData.cpp b/libc/malloc_debug/MapData.cpp index d57017e34..060425e23 100644 --- a/libc/malloc_debug/MapData.cpp +++ b/libc/malloc_debug/MapData.cpp @@ -31,8 +31,8 @@ #include <inttypes.h> #include <link.h> #include <stdio.h> -#include <string.h> #include <stdlib.h> +#include <string.h> #include <vector> @@ -46,8 +46,8 @@ static MapEntry* parse_line(char* line) { uintptr_t offset; char permissions[5]; int name_pos; - if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n", &start, - &end, permissions, &offset, &name_pos) < 2) { + if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n", &start, &end, + permissions, &offset, &name_pos) < 2) { return nullptr; } @@ -66,13 +66,13 @@ static MapEntry* parse_line(char* line) { return entry; } -template<typename T> +template <typename T> static inline bool get_val(MapEntry* entry, uintptr_t addr, T* store) { if (addr < entry->start || addr + sizeof(T) > entry->end) { return false; } // Make sure the address is aligned properly. - if (addr & (sizeof(T)-1)) { + if (addr & (sizeof(T) - 1)) { return false; } *store = *reinterpret_cast<T*>(addr); @@ -157,7 +157,7 @@ const MapEntry* MapData::find(uintptr_t pc, uintptr_t* rel_pc) { return nullptr; } - MapEntry *entry = *it; + MapEntry* entry = *it; if (!entry->load_base_read) { read_loadbase(entry); } diff --git a/libc/malloc_debug/MapData.h b/libc/malloc_debug/MapData.h index a71f96d0d..d8398bde3 100644 --- a/libc/malloc_debug/MapData.h +++ b/libc/malloc_debug/MapData.h @@ -31,8 +31,8 @@ #include <sys/cdefs.h> #include <mutex> -#include <string> #include <set> +#include <string> #include <private/bionic_macros.h> @@ -50,12 +50,9 @@ struct MapEntry { std::string name; }; - // Ordering comparator that returns equivalence for overlapping entries struct compare_entries { - bool operator()(const MapEntry* a, const MapEntry* b) const { - return a->end <= b->start; - } + bool operator()(const MapEntry* a, const MapEntry* b) const { return a->end <= b->start; } }; class MapData { diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp new file mode 100644 index 000000000..f811a5eb4 --- /dev/null +++ b/libc/malloc_debug/PointerData.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (C) 2015 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 <inttypes.h> +#include <signal.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <mutex> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <android-base/stringprintf.h> +#include <android-base/thread_annotations.h> +#include <private/bionic_macros.h> + +#include "Config.h" +#include "DebugData.h" +#include "PointerData.h" +#include "backtrace.h" +#include "debug_log.h" +#include "malloc_debug.h" + +std::atomic_uint8_t PointerData::backtrace_enabled_; +std::atomic_bool PointerData::backtrace_dump_; + +std::mutex PointerData::pointer_mutex_; +std::unordered_map<uintptr_t, PointerInfoType> PointerData::pointers_ GUARDED_BY( + PointerData::pointer_mutex_); + +std::mutex PointerData::frame_mutex_; +std::unordered_map<FrameKeyType, size_t> PointerData::key_to_index_ GUARDED_BY( + PointerData::frame_mutex_); +std::unordered_map<size_t, FrameInfoType> PointerData::frames_ GUARDED_BY(PointerData::frame_mutex_); +constexpr size_t kBacktraceEmptyIndex = 1; +size_t PointerData::cur_hash_index_ GUARDED_BY(PointerData::frame_mutex_); + +std::mutex PointerData::free_pointer_mutex_; +std::deque<FreePointerInfoType> PointerData::free_pointers_ GUARDED_BY( + PointerData::free_pointer_mutex_); + +// Buffer to use for comparison. +static constexpr size_t kCompareBufferSize = 512 * 1024; +static std::vector<uint8_t> g_cmp_mem(0); + +static void ToggleBacktraceEnable(int, siginfo_t*, void*) { + g_debug->pointer->ToggleBacktraceEnabled(); +} + +static void EnableDump(int, siginfo_t*, void*) { + g_debug->pointer->EnableDumping(); +} + +PointerData::PointerData(DebugData* debug_data) : OptionData(debug_data) {} + +bool PointerData::Initialize(const Config& config) NO_THREAD_SAFETY_ANALYSIS { + pointers_.clear(); + key_to_index_.clear(); + frames_.clear(); + free_pointers_.clear(); + // A hash index of kBacktraceEmptyIndex indicates that we tried to get + // a backtrace, but there was nothing recorded. + cur_hash_index_ = kBacktraceEmptyIndex + 1; + + backtrace_enabled_ = config.backtrace_enabled(); + if (config.backtrace_enable_on_signal()) { + struct sigaction64 enable_act = {}; + enable_act.sa_sigaction = ToggleBacktraceEnable; + enable_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + if (sigaction64(config.backtrace_signal(), &enable_act, nullptr) != 0) { + error_log("Unable to set up backtrace signal enable function: %s", strerror(errno)); + return false; + } + info_log("%s: Run: 'kill -%d %d' to enable backtracing.", getprogname(), + config.backtrace_signal(), getpid()); + } + + if (config.options() & BACKTRACE) { + struct sigaction64 act = {}; + act.sa_sigaction = EnableDump; + act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + if (sigaction64(config.backtrace_dump_signal(), &act, nullptr) != 0) { + error_log("Unable to set up backtrace dump signal function: %s", strerror(errno)); + return false; + } + info_log("%s: Run: 'kill -%d %d' to dump the backtrace.", getprogname(), + config.backtrace_dump_signal(), getpid()); + } + + backtrace_dump_ = false; + + if (config.options() & FREE_TRACK) { + g_cmp_mem.resize(kCompareBufferSize, config.fill_free_value()); + } + return true; +} + +size_t PointerData::AddBacktrace(size_t num_frames) { + std::vector<uintptr_t> frames(num_frames); + num_frames = backtrace_get(frames.data(), frames.size()); + if (num_frames == 0) { + return kBacktraceEmptyIndex; + } + + FrameKeyType key{.num_frames = num_frames, .frames = frames.data()}; + size_t hash_index; + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + auto entry = key_to_index_.find(key); + if (entry == key_to_index_.end()) { + frames.resize(num_frames); + hash_index = cur_hash_index_++; + key.frames = frames.data(); + key_to_index_.emplace(key, hash_index); + + frames_.emplace(hash_index, FrameInfoType{.references = 1, .frames = std::move(frames)}); + } else { + hash_index = entry->second; + FrameInfoType* frame_info = &frames_[hash_index]; + frame_info->references++; + } + return hash_index; +} + +void PointerData::RemoveBacktrace(size_t hash_index) { + if (hash_index <= kBacktraceEmptyIndex) { + return; + } + + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + auto frame_entry = frames_.find(hash_index); + if (frame_entry == frames_.end()) { + error_log("hash_index %zu does not have matching frame data.", hash_index); + return; + } + FrameInfoType* frame_info = &frame_entry->second; + if (--frame_info->references == 0) { + FrameKeyType key{.num_frames = frame_info->frames.size(), .frames = frame_info->frames.data()}; + key_to_index_.erase(key); + frames_.erase(hash_index); + } +} + +void PointerData::Add(const void* ptr, size_t pointer_size) { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + size_t hash_index = 0; + if (backtrace_enabled_) { + hash_index = AddBacktrace(g_debug->config().backtrace_frames()); + } + + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + pointers_[pointer] = PointerInfoType{PointerInfoType::GetEncodedSize(pointer_size), hash_index}; +} + +void PointerData::Remove(const void* ptr) { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + size_t hash_index; + { + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + auto entry = pointers_.find(pointer); + if (entry == pointers_.end()) { + // Error. + error_log("No tracked pointer found for 0x%" PRIxPTR, pointer); + return; + } + hash_index = entry->second.hash_index; + pointers_.erase(pointer); + } + + RemoveBacktrace(hash_index); +} + +size_t PointerData::GetFrames(const void* ptr, uintptr_t* frames, size_t max_frames) { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + size_t hash_index; + { + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + auto entry = pointers_.find(pointer); + if (entry == pointers_.end()) { + return 0; + } + hash_index = entry->second.hash_index; + } + + if (hash_index <= kBacktraceEmptyIndex) { + return 0; + } + + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + auto frame_entry = frames_.find(hash_index); + if (frame_entry == frames_.end()) { + return 0; + } + FrameInfoType* frame_info = &frame_entry->second; + if (max_frames > frame_info->frames.size()) { + max_frames = frame_info->frames.size(); + } + memcpy(frames, &frame_info->frames[0], max_frames * sizeof(uintptr_t)); + + return max_frames; +} + +void PointerData::LogFreeError(const FreePointerInfoType& info, size_t usable_size) { + error_log(LOG_DIVIDER); + uint8_t* memory = reinterpret_cast<uint8_t*>(info.pointer); + error_log("+++ ALLOCATION %p USED AFTER FREE", memory); + uint8_t fill_free_value = g_debug->config().fill_free_value(); + for (size_t i = 0; i < usable_size; i++) { + if (memory[i] != fill_free_value) { + error_log(" allocation[%zu] = 0x%02x (expected 0x%02x)", i, memory[i], fill_free_value); + } + } + + if (info.hash_index > kBacktraceEmptyIndex) { + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + auto frame_entry = frames_.find(info.hash_index); + if (frame_entry != frames_.end()) { + FrameInfoType* frame_info = &frame_entry->second; + error_log("Backtrace at time of free:"); + backtrace_log(frame_info->frames.data(), frame_info->frames.size()); + } + } + + error_log(LOG_DIVIDER); +} + +void PointerData::VerifyFreedPointer(const FreePointerInfoType& info) { + size_t usable_size; + if (g_debug->HeaderEnabled()) { + // Check to see if the tag data has been damaged. + Header* header = g_debug->GetHeader(reinterpret_cast<const void*>(info.pointer)); + if (header->tag != DEBUG_FREE_TAG) { + error_log(LOG_DIVIDER); + error_log("+++ ALLOCATION 0x%" PRIxPTR " HAS CORRUPTED HEADER TAG 0x%x AFTER FREE", + info.pointer, header->tag); + error_log(LOG_DIVIDER); + + // Stop processing here, it is impossible to tell how the header + // may have been damaged. + return; + } + usable_size = header->usable_size; + } else { + usable_size = g_dispatch->malloc_usable_size(reinterpret_cast<const void*>(info.pointer)); + } + + size_t bytes = (usable_size < g_debug->config().fill_on_free_bytes()) + ? usable_size + : g_debug->config().fill_on_free_bytes(); + const uint8_t* memory = reinterpret_cast<const uint8_t*>(info.pointer); + while (bytes > 0) { + size_t bytes_to_cmp = (bytes < g_cmp_mem.size()) ? bytes : g_cmp_mem.size(); + if (memcmp(memory, g_cmp_mem.data(), bytes_to_cmp) != 0) { + LogFreeError(info, usable_size); + } + bytes -= bytes_to_cmp; + memory = &memory[bytes_to_cmp]; + } +} + +void* PointerData::AddFreed(const void* ptr) { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + + size_t hash_index = 0; + size_t num_frames = g_debug->config().free_track_backtrace_num_frames(); + if (num_frames) { + hash_index = AddBacktrace(num_frames); + } + + void* last = nullptr; + std::lock_guard<std::mutex> freed_guard(free_pointer_mutex_); + if (free_pointers_.size() == g_debug->config().free_track_allocations()) { + FreePointerInfoType info(free_pointers_.front()); + free_pointers_.pop_front(); + VerifyFreedPointer(info); + RemoveBacktrace(info.hash_index); + last = reinterpret_cast<void*>(info.pointer); + } + + free_pointers_.emplace_back(FreePointerInfoType{pointer, hash_index}); + return last; +} + +void PointerData::LogFreeBacktrace(const void* ptr) { + size_t hash_index = 0; + { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + std::lock_guard<std::mutex> freed_guard(free_pointer_mutex_); + for (const auto& info : free_pointers_) { + if (info.pointer == pointer) { + hash_index = info.hash_index; + break; + } + } + } + + if (hash_index <= kBacktraceEmptyIndex) { + return; + } + + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + auto frame_entry = frames_.find(hash_index); + if (frame_entry == frames_.end()) { + error_log("Freed pointer hash_index %zu does not have matching frame data.", hash_index); + return; + } + FrameInfoType* frame_info = &frame_entry->second; + error_log("Backtrace of original free:"); + backtrace_log(frame_info->frames.data(), frame_info->frames.size()); +} + +void PointerData::VerifyAllFreed() { + std::lock_guard<std::mutex> freed_guard(free_pointer_mutex_); + for (auto& free_info : free_pointers_) { + VerifyFreedPointer(free_info); + } +} + +void PointerData::GetList(std::vector<ListInfoType>* list, bool only_with_backtrace) + REQUIRES(pointer_mutex_, frame_mutex_) { + for (const auto& entry : pointers_) { + FrameInfoType* frame_info = nullptr; + size_t hash_index = entry.second.hash_index; + if (hash_index > kBacktraceEmptyIndex) { + frame_info = &frames_[hash_index]; + if (frame_info->references == 0) { + // Somehow wound up with a pointer with a valid hash_index, but + // no frame data. This should not be possible since adding a pointer + // occurs after the hash_index and frame data have been added. + // When removing a pointer, the pointer is deleted before the frame + // data. + frames_.erase(hash_index); + error_log("Pointer 0x%" PRIxPTR " hash_index %zu does not exist.", entry.first, hash_index); + frame_info = nullptr; + } + } + if (hash_index == 0 && only_with_backtrace) { + continue; + } + + list->emplace_back(ListInfoType{entry.first, 1, entry.second.RealSize(), + entry.second.ZygoteChildAlloc(), frame_info}); + } + + // Sort by the size of the allocation. + std::sort(list->begin(), list->end(), [](const ListInfoType& a, const ListInfoType& b) { + // Put zygote child allocations first. + bool a_zygote_child_alloc = a.zygote_child_alloc; + bool b_zygote_child_alloc = b.zygote_child_alloc; + if (a_zygote_child_alloc && !b_zygote_child_alloc) { + return false; + } + if (!a_zygote_child_alloc && b_zygote_child_alloc) { + return true; + } + + // Sort by size, descending order. + if (a.size != b.size) return a.size > b.size; + + // Put pointers with no backtrace last. + FrameInfoType* a_frame = a.frame_info; + FrameInfoType* b_frame = b.frame_info; + if (a_frame == nullptr && b_frame != nullptr) { + return false; + } + if (a_frame != nullptr && b_frame == nullptr) { + return true; + } + // Put the pointers with longest backtrace first. + if (a_frame->frames.size() != b_frame->frames.size()) { + return a_frame->frames.size() > b_frame->frames.size(); + } + + // Last sort by pointer. + return a.pointer < b.pointer; + }); +} + +void PointerData::GetUniqueList(std::vector<ListInfoType>* list, bool only_with_backtrace) + REQUIRES(pointer_mutex_, frame_mutex_) { + GetList(list, only_with_backtrace); + + // Remove duplicates of size/backtraces. + for (auto iter = list->begin(); iter != list->end();) { + auto dup_iter = iter + 1; + bool zygote_child_alloc = iter->zygote_child_alloc; + size_t size = iter->size; + FrameInfoType* frame_info = iter->frame_info; + for (; dup_iter != list->end(); ++dup_iter) { + if (zygote_child_alloc != dup_iter->zygote_child_alloc || size != dup_iter->size || + frame_info != dup_iter->frame_info) { + break; + } + iter->num_allocations++; + } + iter = list->erase(iter + 1, dup_iter); + } +} + +void PointerData::LogLeaks() { + std::vector<ListInfoType> list; + + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + GetList(&list, false); + + size_t track_count = 0; + for (const auto& list_info : list) { + error_log("+++ %s leaked block of size %zu at 0x%" PRIxPTR " (leak %zu of %zu)", getprogname(), + list_info.size, list_info.pointer, ++track_count, list.size()); + if (list_info.frame_info != nullptr) { + error_log("Backtrace at time of allocation:"); + backtrace_log(list_info.frame_info->frames.data(), list_info.frame_info->frames.size()); + } + // Do not bother to free the pointers, we are about to exit any way. + } +} + +void PointerData::GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, + size_t* total_memory, size_t* backtrace_size) { + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + + if (pointers_.empty()) { + return; + } + + std::vector<ListInfoType> list; + GetUniqueList(&list, true); + if (list.empty()) { + return; + } + + *backtrace_size = g_debug->config().backtrace_frames(); + *info_size = sizeof(size_t) * 2 + sizeof(uintptr_t) * *backtrace_size; + *overall_size = *info_size * list.size(); + *info = reinterpret_cast<uint8_t*>(g_dispatch->calloc(*info_size, list.size())); + if (*info == nullptr) { + return; + } + + uint8_t* data = *info; + *total_memory = 0; + for (const auto& list_info : list) { + FrameInfoType* frame_info = list_info.frame_info; + *total_memory += list_info.size * list_info.num_allocations; + size_t allocation_size = + PointerInfoType::GetEncodedSize(list_info.zygote_child_alloc, list_info.size); + memcpy(data, &allocation_size, sizeof(size_t)); + memcpy(&data[sizeof(size_t)], &list_info.num_allocations, sizeof(size_t)); + if (frame_info != nullptr) { + memcpy(&data[2 * sizeof(size_t)], frame_info->frames.data(), + frame_info->frames.size() * sizeof(uintptr_t)); + } + data += *info_size; + } +} + +bool PointerData::Exists(const void* ptr) { + uintptr_t pointer = reinterpret_cast<uintptr_t>(ptr); + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + return pointers_.count(pointer) != 0; +} + +void PointerData::DumpLiveToFile(FILE* fp) { + std::vector<ListInfoType> list; + + std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); + std::lock_guard<std::mutex> frame_guard(frame_mutex_); + GetUniqueList(&list, false); + + size_t total_memory = 0; + for (const auto& info : list) { + total_memory += info.size * info.num_allocations; + } + + fprintf(fp, "Total memory: %zu\n", total_memory); + fprintf(fp, "Allocation records: %zd\n", list.size()); + fprintf(fp, "Backtrace size: %zu\n", g_debug->config().backtrace_frames()); + fprintf(fp, "\n"); + + for (const auto& info : list) { + fprintf(fp, "z %d sz %8zu num %zu bt", (info.zygote_child_alloc) ? 1 : 0, info.size, + info.num_allocations); + FrameInfoType* frame_info = info.frame_info; + if (frame_info != nullptr) { + for (size_t i = 0; i < frame_info->frames.size(); i++) { + if (frame_info->frames[i] == 0) { + break; + } +#if defined(__LP64__) + fprintf(fp, " %016" PRIxPTR, frame_info->frames[i]); +#else + fprintf(fp, " %08" PRIxPTR, frame_info->frames[i]); +#endif + } + } + fprintf(fp, "\n"); + } +} + +void PointerData::PrepareFork() NO_THREAD_SAFETY_ANALYSIS { + pointer_mutex_.lock(); + frame_mutex_.lock(); + free_pointer_mutex_.lock(); +} + +void PointerData::PostForkParent() NO_THREAD_SAFETY_ANALYSIS { + frame_mutex_.unlock(); + pointer_mutex_.unlock(); + free_pointer_mutex_.unlock(); +} + +void PointerData::PostForkChild() __attribute__((no_thread_safety_analysis)) { + // Make sure that any potential mutexes have been released and are back + // to an initial state. + frame_mutex_.try_lock(); + frame_mutex_.unlock(); + pointer_mutex_.try_lock(); + pointer_mutex_.unlock(); + free_pointer_mutex_.try_lock(); + free_pointer_mutex_.unlock(); +} diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h new file mode 100644 index 000000000..7a0b3b89e --- /dev/null +++ b/libc/malloc_debug/PointerData.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 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 <stdio.h> + +#include <atomic> +#include <deque> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#include <private/bionic_macros.h> + +#include "OptionData.h" + +extern int* g_malloc_zygote_child; + +// Forward declarations. +class Config; + +struct FrameKeyType { + size_t num_frames; + uintptr_t* frames; + + bool operator==(const FrameKeyType& comp) const { + if (num_frames != comp.num_frames) return false; + for (size_t i = 0; i < num_frames; i++) { + if (frames[i] != comp.frames[i]) { + return false; + } + } + return true; + } +}; + +namespace std { +template <> +struct hash<FrameKeyType> { + std::size_t operator()(const FrameKeyType& key) const { + std::size_t cur_hash = key.frames[0]; + // Limit the number of frames to speed up hashing. + size_t max_frames = (key.num_frames > 5) ? 5 : key.num_frames; + for (size_t i = 1; i < max_frames; i++) { + cur_hash ^= key.frames[i]; + } + return cur_hash; + } +}; +}; // namespace std + +struct FrameInfoType { + size_t references = 0; + std::vector<uintptr_t> frames; +}; + +struct PointerInfoType { + size_t size; + size_t hash_index; + size_t RealSize() const { return size & ~(1U << 31); } + bool ZygoteChildAlloc() const { return size & (1U << 31); } + static size_t GetEncodedSize(size_t size) { return GetEncodedSize(*g_malloc_zygote_child, size); } + static size_t GetEncodedSize(bool child_alloc, size_t size) { + return size | ((child_alloc) ? (1U << 31) : 0); + } + static size_t MaxSize() { return (1U << 31) - 1; } +}; + +struct FreePointerInfoType { + uintptr_t pointer; + size_t hash_index; +}; + +struct ListInfoType { + uintptr_t pointer; + size_t num_allocations; + size_t size; + bool zygote_child_alloc; + FrameInfoType* frame_info; +}; + +class PointerData : public OptionData { + public: + PointerData(DebugData* debug_data); + virtual ~PointerData() = default; + + bool Initialize(const Config& config); + + inline size_t alloc_offset() { return alloc_offset_; } + + bool ShouldBacktrace() { return backtrace_enabled_ == 1; } + void ToggleBacktraceEnabled() { backtrace_enabled_.fetch_xor(1); } + + void EnableDumping() { backtrace_dump_ = true; } + bool ShouldDumpAndReset() { + bool expected = true; + return backtrace_dump_.compare_exchange_strong(expected, false); + } + + void PrepareFork(); + void PostForkParent(); + void PostForkChild(); + + static void GetList(std::vector<ListInfoType>* list, bool only_with_backtrace); + static void GetUniqueList(std::vector<ListInfoType>* list, bool only_with_backtrace); + + static size_t AddBacktrace(size_t num_frames); + static void RemoveBacktrace(size_t hash_index); + + static void Add(const void* pointer, size_t size); + static void Remove(const void* pointer); + + typedef std::unordered_map<uintptr_t, PointerInfoType>::iterator iterator; + static iterator begin() { return pointers_.begin(); } + static iterator end() { return pointers_.end(); } + + static void* AddFreed(const void* pointer); + static void LogFreeError(const FreePointerInfoType& info, size_t usable_size); + static void LogFreeBacktrace(const void* ptr); + static void VerifyFreedPointer(const FreePointerInfoType& info); + static void VerifyAllFreed(); + + static void LogLeaks(); + static void DumpLiveToFile(FILE* fp); + + static void GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, + size_t* backtrace_size); + + static size_t GetFrames(const void* pointer, uintptr_t* frames, size_t max_frames); + + static bool Exists(const void* pointer); + + private: + static std::string GetHashString(uintptr_t* frames, size_t num_frames); + + size_t alloc_offset_ = 0; + std::vector<uint8_t> cmp_mem_; + + static std::atomic_uint8_t backtrace_enabled_; + + static std::atomic_bool backtrace_dump_; + + static std::mutex pointer_mutex_; + static std::unordered_map<uintptr_t, PointerInfoType> pointers_; + + static std::mutex frame_mutex_; + static std::unordered_map<FrameKeyType, size_t> key_to_index_; + static std::unordered_map<size_t, FrameInfoType> frames_; + static size_t cur_hash_index_; + + static std::mutex free_pointer_mutex_; + static std::deque<FreePointerInfoType> free_pointers_; + + DISALLOW_COPY_AND_ASSIGN(PointerData); +}; diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index 69d064824..59ab6ad7b 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -33,6 +33,13 @@ On 32 bit systems, these two deprecated functions are also replaced: Any errors detected by the library are reported in the log. +NOTE: There is a small behavioral change beginning in P for realloc. +Before, a realloc from one size to a smaller size would not update the +backtrace related to the allocation. Starting in P, every single realloc +call changes the backtrace for the pointer no matter whether the pointer +returned has changed or not. + + Controlling Malloc Debug Behavior --------------------------------- Malloc debug is controlled by individual options. Each option can be enabled @@ -106,8 +113,9 @@ If MAX\_FRAMES is present, it indicates the maximum number of frames to capture in a backtrace. The default is 16 frames, the maximumum value this can be set to is 256. -This option adds a special header to all allocations that contains the -backtrace and information about the original allocation. +Before P, this option adds a special header to all allocations that contains +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 @@ -132,8 +140,9 @@ If MAX\_FRAMES is present, it indicates the maximum number of frames to capture in a backtrace. The default is 16 frames, the maximumum value this can be set to is 256. -This option adds a special header to all allocations that contains the -backtrace and information about the original allocation. +Before P, this option adds a special header to all allocations that contains +the backtrace and information about the original allocation. After that, this +option will not add a special header. ### backtrace\_dump\_on\_exit As of P, when the backtrace option has been enabled, this causes the backtrace @@ -201,8 +210,9 @@ If ALLOCATION\_COUNT is present, it indicates the total number of allocations in the list. The default is to record 100 freed allocations, the max allocations to record is 16384. -This option adds a special header to all allocations that contains -information about the original allocation. +Before P, this option adds a special header to all allocations that contains +the backtrace and information about the original allocation. After that, this +option will not add a special header. Example error: @@ -238,8 +248,9 @@ then the log will include the backtrace of the leaked allocations. This option is not useful when enabled globally because a lot of programs do not free everything before the program terminates. -This option adds a special header to all allocations that contains -information about the original allocation. +Before P, this option adds a special header to all allocations that contains +the backtrace and information about the original allocation. After that, this +option will not add a special header. Example leak error found in the log: @@ -362,6 +373,26 @@ will be placed. **NOTE**: This option is not available until the O release of Android. +### verify\_pointers +Track all live allocations to determine if a pointer is used that does not +exist. This option is a lightweight way to verify that all +free/malloc\_usable\_size/realloc calls are passed valid pointers. + +Example error: + + 04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 UNKNOWN POINTER (free) + 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure: + 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so + 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160) + 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so + 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so + +Where the name of the function varies depending on the function that called +with a bad pointer. Only three functions do this checking: free, +malloc\_usable\_size, realloc. + +**NOTE**: This option is not available until the P release of Android. + Additional Errors ----------------- There are a few other error messages that might appear in the log. diff --git a/libc/malloc_debug/RecordData.cpp b/libc/malloc_debug/RecordData.cpp index 8e9c6716f..aea25133e 100644 --- a/libc/malloc_debug/RecordData.cpp +++ b/libc/malloc_debug/RecordData.cpp @@ -40,10 +40,10 @@ #include <android-base/stringprintf.h> #include "Config.h" -#include "debug_disable.h" -#include "debug_log.h" #include "DebugData.h" #include "RecordData.h" +#include "debug_disable.h" +#include "debug_log.h" RecordEntry::RecordEntry() : tid_(gettid()) { } @@ -52,52 +52,45 @@ std::string ThreadCompleteEntry::GetString() const { return android::base::StringPrintf("%d: thread_done 0x0\n", tid_); } -AllocEntry::AllocEntry(void* pointer) : pointer_(pointer) { -} +AllocEntry::AllocEntry(void* pointer) : pointer_(pointer) {} -MallocEntry::MallocEntry(void* pointer, size_t size) : AllocEntry(pointer), size_(size) { -} +MallocEntry::MallocEntry(void* pointer, size_t size) : AllocEntry(pointer), size_(size) {} std::string MallocEntry::GetString() const { return android::base::StringPrintf("%d: malloc %p %zu\n", tid_, pointer_, size_); } -FreeEntry::FreeEntry(void* pointer) : AllocEntry(pointer) { -} +FreeEntry::FreeEntry(void* pointer) : AllocEntry(pointer) {} std::string FreeEntry::GetString() const { return android::base::StringPrintf("%d: free %p\n", tid_, pointer_); } CallocEntry::CallocEntry(void* pointer, size_t nmemb, size_t size) - : MallocEntry(pointer, size), nmemb_(nmemb) { -} + : MallocEntry(pointer, size), nmemb_(nmemb) {} std::string CallocEntry::GetString() const { return android::base::StringPrintf("%d: calloc %p %zu %zu\n", tid_, pointer_, nmemb_, size_); } ReallocEntry::ReallocEntry(void* pointer, size_t size, void* old_pointer) - : MallocEntry(pointer, size), old_pointer_(old_pointer) { -} + : MallocEntry(pointer, size), old_pointer_(old_pointer) {} std::string ReallocEntry::GetString() const { - return android::base::StringPrintf("%d: realloc %p %p %zu\n", tid_, pointer_, - old_pointer_, size_); + return android::base::StringPrintf("%d: realloc %p %p %zu\n", tid_, pointer_, old_pointer_, size_); } // aligned_alloc, posix_memalign, memalign, pvalloc, valloc all recorded with this class. MemalignEntry::MemalignEntry(void* pointer, size_t size, size_t alignment) - : MallocEntry(pointer, size), alignment_(alignment) { -} + : MallocEntry(pointer, size), alignment_(alignment) {} std::string MemalignEntry::GetString() const { - return android::base::StringPrintf("%d: memalign %p %zu %zu\n", tid_, pointer_, - alignment_, size_); + return android::base::StringPrintf("%d: memalign %p %zu %zu\n", tid_, pointer_, alignment_, size_); } struct ThreadData { - ThreadData(RecordData* record_data, ThreadCompleteEntry* entry) : record_data(record_data), entry(entry) {} + ThreadData(RecordData* record_data, ThreadCompleteEntry* entry) + : record_data(record_data), entry(entry) {} RecordData* record_data; ThreadCompleteEntry* entry; size_t count = 0; @@ -141,8 +134,8 @@ void RecordData::Dump() { last_entry_index = num_entries_; } - int dump_fd = open(dump_file_.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, - 0755); + int dump_fd = + open(dump_file_.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0755); if (dump_fd != -1) { for (size_t i = 0; i < last_entry_index; i++) { std::string line = entries_[i]->GetString(); @@ -201,7 +194,7 @@ bool RecordData::Initialize(const Config& config) { } RecordData::~RecordData() { - delete [] entries_; + delete[] entries_; pthread_key_delete(key_); } diff --git a/libc/malloc_debug/RecordData.h b/libc/malloc_debug/RecordData.h index 021a2993a..3e5ca029e 100644 --- a/libc/malloc_debug/RecordData.h +++ b/libc/malloc_debug/RecordData.h @@ -28,8 +28,8 @@ #pragma once -#include <stdint.h> #include <pthread.h> +#include <stdint.h> #include <unistd.h> #include <atomic> diff --git a/libc/malloc_debug/TrackData.cpp b/libc/malloc_debug/TrackData.cpp deleted file mode 100644 index 4266aa282..000000000 --- a/libc/malloc_debug/TrackData.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2015 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 <pthread.h> -#include <stdint.h> -#include <stdlib.h> - -#include <algorithm> -#include <vector> - -#include <private/ScopedPthreadMutexLocker.h> - -#include "backtrace.h" -#include "BacktraceData.h" -#include "Config.h" -#include "DebugData.h" -#include "debug_disable.h" -#include "debug_log.h" -#include "malloc_debug.h" -#include "TrackData.h" - -TrackData::TrackData(DebugData* debug_data) : OptionData(debug_data) { -} - -void TrackData::GetList(std::vector<const Header*>* list) { - for (const auto& header : headers_) { - list->push_back(header); - } - - // Sort by the size of the allocation. - std::sort(list->begin(), list->end(), [](const Header* a, const Header* b) { - if (a->size == b->size) return a < b; - return a->size > b->size; - }); -} - -void TrackData::GetListBySizeThenBacktrace(std::vector<const Header*>* list, size_t* total_memory) { - if (!(debug_->config().options() & BACKTRACE)) { - return; - } - - *total_memory = 0; - for (const auto& header : headers_) { - list->push_back(header); - *total_memory += header->real_size(); - } - - // Put all zygote allocations first by size and backtrace. - // Then all zygote child allocation by size and backtrace. - std::sort(list->begin(), list->end(), [&](const Header* a, const Header* b) { - if (a->zygote_child_alloc() && !b->zygote_child_alloc()) { - return false; - } else if (!a->zygote_child_alloc() && b->zygote_child_alloc()) { - return true; - } - if (a->real_size() != b->real_size()) return a->real_size() < b->real_size(); - // If the size is the same, compare backtrace elements. - BacktraceHeader* a_back = debug_->GetAllocBacktrace(a); - BacktraceHeader* b_back = debug_->GetAllocBacktrace(b); - for (size_t i = 0; i < a_back->num_frames; i++) { - if (i > b_back->num_frames) { - // All frames equal up to this point, but a has more frames available. - return false; - } - if (a_back->frames[i] < b_back->frames[i]) { - return false; - } else if (a_back->frames[i] > b_back->frames[i]) { - return true; - } - } - if (a_back->num_frames < b_back->num_frames) { - // All frames equal up to this point, but b has more frames available. - return true; - } - return false; - }); - -} - -void TrackData::Add(const Header* header, bool backtrace_found) { - pthread_mutex_lock(&mutex_); - if (backtrace_found) { - total_backtrace_allocs_++; - } - headers_.insert(header); - pthread_mutex_unlock(&mutex_); -} - -void TrackData::Remove(const Header* header, bool backtrace_found) { - pthread_mutex_lock(&mutex_); - headers_.erase(header); - if (backtrace_found) { - total_backtrace_allocs_--; - } - pthread_mutex_unlock(&mutex_); -} - -bool TrackData::Contains(const Header* header) { - pthread_mutex_lock(&mutex_); - bool found = headers_.count(header); - pthread_mutex_unlock(&mutex_); - return found; -} - -void TrackData::DisplayLeaks() { - std::vector<const Header*> list; - GetList(&list); - - size_t track_count = 0; - for (const auto& header : list) { - error_log("+++ %s leaked block of size %zu at %p (leak %zu of %zu)", getprogname(), - header->real_size(), debug_->GetPointer(header), ++track_count, list.size()); - if (debug_->config().options() & BACKTRACE) { - BacktraceHeader* back_header = debug_->GetAllocBacktrace(header); - if (back_header->num_frames > 0) { - error_log("Backtrace at time of allocation:"); - backtrace_log(&back_header->frames[0], back_header->num_frames); - } - } - g_dispatch->free(header->orig_pointer); - } -} - -void TrackData::GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, - size_t* total_memory, size_t* backtrace_size) { - ScopedPthreadMutexLocker scoped(&mutex_); - - if (headers_.size() == 0 || total_backtrace_allocs_ == 0) { - return; - } - - *backtrace_size = debug_->config().backtrace_frames(); - *info_size = sizeof(size_t) * 2 + sizeof(uintptr_t) * *backtrace_size; - *info = reinterpret_cast<uint8_t*>(g_dispatch->calloc(*info_size, total_backtrace_allocs_)); - if (*info == nullptr) { - return; - } - *overall_size = *info_size * total_backtrace_allocs_; - - std::vector<const Header*> list; - GetList(&list); - - uint8_t* data = *info; - size_t num_allocations = 1; - for (const auto& header : list) { - BacktraceHeader* back_header = debug_->GetAllocBacktrace(header); - if (back_header->num_frames > 0) { - memcpy(data, &header->size, sizeof(size_t)); - memcpy(&data[sizeof(size_t)], &num_allocations, sizeof(size_t)); - memcpy(&data[2 * sizeof(size_t)], &back_header->frames[0], - back_header->num_frames * sizeof(uintptr_t)); - - *total_memory += header->real_size(); - - data += *info_size; - } - } -} diff --git a/libc/malloc_debug/TrackData.h b/libc/malloc_debug/TrackData.h deleted file mode 100644 index 7201c49ec..000000000 --- a/libc/malloc_debug/TrackData.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 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 <pthread.h> - -#include <vector> -#include <unordered_set> - -#include <private/bionic_macros.h> - -#include "OptionData.h" - -// Forward declarations. -struct Header; -class Config; -class DebugData; - -class TrackData : public OptionData { - public: - explicit TrackData(DebugData* debug_data); - virtual ~TrackData() = default; - - void GetList(std::vector<const Header*>* list); - - void GetListBySizeThenBacktrace(std::vector<const Header*>* list, size_t* total_memory); - - void Add(const Header* header, bool backtrace_found); - - void Remove(const Header* header, bool backtrace_found); - - bool Contains(const Header *header); - - void GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, - size_t* total_memory, size_t* backtrace_size); - - void DisplayLeaks(); - - void PrepareFork() { pthread_mutex_lock(&mutex_); } - void PostForkParent() { pthread_mutex_unlock(&mutex_); } - void PostForkChild() { pthread_mutex_init(&mutex_, NULL); } - - private: - pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER; - std::unordered_set<const Header*> headers_; - size_t total_backtrace_allocs_ = 0; - - DISALLOW_COPY_AND_ASSIGN(TrackData); -}; diff --git a/libc/malloc_debug/backtrace.cpp b/libc/malloc_debug/backtrace.cpp index 2443ba103..cd8c3340c 100644 --- a/libc/malloc_debug/backtrace.cpp +++ b/libc/malloc_debug/backtrace.cpp @@ -38,9 +38,9 @@ #include <demangle.h> +#include "MapData.h" #include "backtrace.h" #include "debug_log.h" -#include "MapData.h" #if defined(__LP64__) #define PAD_PTR "016" PRIxPTR @@ -69,8 +69,7 @@ void backtrace_startup() { _Unwind_Backtrace(find_current_map, nullptr); } -void backtrace_shutdown() { -} +void backtrace_shutdown() {} struct stack_crawl_state_t { uintptr_t* frames; @@ -165,13 +164,13 @@ std::string backtrace_string(const uintptr_t* frames, size_t frame_count) { char buf[1024]; if (symbol != nullptr) { - async_safe_format_buffer( - buf, sizeof(buf), " #%02zd pc %" PAD_PTR " %s%s (%s+%" PRIuPTR ")\n", frame_num, - rel_pc, soname, offset_buf, demangle(symbol).c_str(), frames[frame_num] - offset); + async_safe_format_buffer(buf, sizeof(buf), + " #%02zd pc %" PAD_PTR " %s%s (%s+%" PRIuPTR ")\n", + frame_num, rel_pc, soname, offset_buf, demangle(symbol).c_str(), + frames[frame_num] - offset); } else { - async_safe_format_buffer( - buf, sizeof(buf), " #%02zd pc %" PAD_PTR " %s%s\n", frame_num, rel_pc, soname, - offset_buf); + async_safe_format_buffer(buf, sizeof(buf), " #%02zd pc %" PAD_PTR " %s%s\n", + frame_num, rel_pc, soname, offset_buf); } str += buf; } diff --git a/libc/malloc_debug/debug_log.h b/libc/malloc_debug/debug_log.h index 54dd221ec..d2257df58 100644 --- a/libc/malloc_debug/debug_log.h +++ b/libc/malloc_debug/debug_log.h @@ -33,11 +33,10 @@ // ============================================================================= // log functions // ============================================================================= -#define debug_log(format, ...) \ - async_safe_format_log(ANDROID_LOG_DEBUG, "malloc_debug", (format), ##__VA_ARGS__ ) -#define error_log(format, ...) \ - async_safe_format_log(ANDROID_LOG_ERROR, "malloc_debug", (format), ##__VA_ARGS__ ) -#define error_log_string(str) \ - async_safe_write_log(ANDROID_LOG_ERROR, "malloc_debug", (str)) -#define info_log(format, ...) \ - async_safe_format_log(ANDROID_LOG_INFO, "malloc_debug", (format), ##__VA_ARGS__ ) +#define debug_log(format, ...) \ + async_safe_format_log(ANDROID_LOG_DEBUG, "malloc_debug", (format), ##__VA_ARGS__) +#define error_log(format, ...) \ + async_safe_format_log(ANDROID_LOG_ERROR, "malloc_debug", (format), ##__VA_ARGS__) +#define error_log_string(str) async_safe_write_log(ANDROID_LOG_ERROR, "malloc_debug", (str)) +#define info_log(format, ...) \ + async_safe_format_log(ANDROID_LOG_INFO, "malloc_debug", (format), ##__VA_ARGS__) diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index ecfbd7112..6f841cab5 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -41,9 +41,9 @@ #include <android-base/stringprintf.h> #include <private/bionic_malloc_dispatch.h> -#include "backtrace.h" #include "Config.h" #include "DebugData.h" +#include "backtrace.h" #include "debug_disable.h" #include "debug_log.h" #include "malloc_debug.h" @@ -66,12 +66,11 @@ const MallocDispatch* g_dispatch; __BEGIN_DECLS bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child, - const char* options); + const char* options); void debug_finalize(); bool debug_dump_heap(const char* file_name); -void debug_get_malloc_leak_info( - uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory, - size_t* backtrace_size); +void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, + size_t* total_memory, size_t* backtrace_size); ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count); void debug_free_malloc_leak_info(uint8_t* info); size_t debug_malloc_usable_size(void* pointer); @@ -85,7 +84,7 @@ struct mallinfo debug_mallinfo(); int debug_mallopt(int param, int value); int debug_posix_memalign(void** memptr, size_t alignment, size_t size); int debug_iterate(uintptr_t base, size_t size, - void (*callback)(uintptr_t base, size_t size, void* arg), void* arg); + void (*callback)(uintptr_t base, size_t size, void* arg), void* arg); void debug_malloc_disable(); void debug_malloc_enable(); @@ -99,59 +98,92 @@ __END_DECLS static void InitAtfork() { static pthread_once_t atfork_init = PTHREAD_ONCE_INIT; - pthread_once(&atfork_init, [](){ + pthread_once(&atfork_init, []() { pthread_atfork( - [](){ + []() { if (g_debug != nullptr) { g_debug->PrepareFork(); } }, - [](){ + []() { if (g_debug != nullptr) { g_debug->PostForkParent(); } }, - [](){ + []() { if (g_debug != nullptr) { g_debug->PostForkChild(); } - } - ); + }); }); } -static void LogTagError(const Header* header, const void* pointer, const char* name) { +static void LogError(const void* pointer, const char* error_str) { error_log(LOG_DIVIDER); - if (header->tag == DEBUG_FREE_TAG) { - error_log("+++ ALLOCATION %p USED AFTER FREE (%s)", pointer, name); - if (g_debug->config().options() & FREE_TRACK) { - g_debug->free_track->LogBacktrace(header); - } + error_log("+++ ALLOCATION %p %s", pointer, error_str); + + // If we are tracking already freed pointers, check to see if this is + // one so we can print extra information. + if (g_debug->config().options() & FREE_TRACK) { + PointerData::LogFreeBacktrace(pointer); + } + + std::vector<uintptr_t> frames(128); + size_t num_frames = backtrace_get(frames.data(), frames.size()); + if (num_frames == 0) { + error_log("Backtrace failed to get any frames."); } else { - error_log("+++ ALLOCATION %p HAS INVALID TAG %" PRIx32 " (%s)", pointer, header->tag, name); + error_log("Backtrace at time of failure:"); + backtrace_log(frames.data(), num_frames); } - error_log("Backtrace at time of failure:"); - std::vector<uintptr_t> frames(64); - size_t frame_num = backtrace_get(frames.data(), frames.size()); - frames.resize(frame_num); - backtrace_log(frames.data(), frames.size()); error_log(LOG_DIVIDER); } +static bool VerifyPointer(const void* pointer, const char* function_name) { + if (g_debug->HeaderEnabled()) { + Header* header = g_debug->GetHeader(pointer); + if (header->tag != DEBUG_TAG) { + std::string error_str; + if (header->tag == DEBUG_FREE_TAG) { + error_str = std::string("USED AFTER FREE (") + function_name + ")"; + } else { + error_str = android::base::StringPrintf("HAS INVALID TAG %" PRIx32 " (%s)", header->tag, + function_name); + } + LogError(pointer, error_str.c_str()); + return false; + } + } + + if (g_debug->TrackPointers()) { + if (!PointerData::Exists(pointer)) { + std::string error_str(std::string("UNKNOWN POINTER (") + function_name + ")"); + LogError(pointer, error_str.c_str()); + return false; + } + } + return true; +} + +static size_t InternalMallocUsableSize(void* pointer) { + if (g_debug->HeaderEnabled()) { + return g_debug->GetHeader(pointer)->usable_size; + } else { + return g_dispatch->malloc_usable_size(pointer); + } +} + static void* InitHeader(Header* header, void* orig_pointer, size_t size) { header->tag = DEBUG_TAG; header->orig_pointer = orig_pointer; header->size = size; - if (*g_malloc_zygote_child) { - header->set_zygote_child_alloc(); - } header->usable_size = g_dispatch->malloc_usable_size(orig_pointer); if (header->usable_size == 0) { g_dispatch->free(orig_pointer); return nullptr; } - header->usable_size -= g_debug->pointer_offset() + - reinterpret_cast<uintptr_t>(header) - reinterpret_cast<uintptr_t>(orig_pointer); + header->usable_size -= g_debug->pointer_offset() + reinterpret_cast<uintptr_t>(header) - + reinterpret_cast<uintptr_t>(orig_pointer); if (g_debug->config().options() & FRONT_GUARD) { uint8_t* guard = g_debug->GetFrontGuard(header); @@ -163,30 +195,14 @@ static void* InitHeader(Header* header, void* orig_pointer, size_t size) { memset(guard, g_debug->config().rear_guard_value(), g_debug->config().rear_guard_bytes()); // If the rear guard is enabled, set the usable size to the exact size // of the allocation. - header->usable_size = header->real_size(); - } - - bool backtrace_found = false; - if (g_debug->config().options() & BACKTRACE) { - BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); - if (g_debug->backtrace->ShouldBacktrace()) { - back_header->num_frames = backtrace_get( - &back_header->frames[0], g_debug->config().backtrace_frames()); - backtrace_found = back_header->num_frames > 0; - } else { - back_header->num_frames = 0; - } - } - - if (g_debug->config().options() & TRACK_ALLOCS) { - g_debug->track->Add(header, backtrace_found); + header->usable_size = header->size; } return g_debug->GetPointer(header); } bool debug_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child, - const char* options) { + const char* options) { if (malloc_zygote_child == nullptr || options == nullptr) { return false; } @@ -222,18 +238,19 @@ void debug_finalize() { } if (g_debug->config().options() & FREE_TRACK) { - g_debug->free_track->VerifyAll(); + PointerData::VerifyAllFreed(); } if (g_debug->config().options() & LEAK_TRACK) { - g_debug->track->DisplayLeaks(); + PointerData::LogLeaks(); } if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) { ScopedDisableDebugCalls disable; - debug_dump_heap( - android::base::StringPrintf("%s.%d.exit.txt", - g_debug->config().backtrace_dump_prefix().c_str(), getpid()).c_str()); + debug_dump_heap(android::base::StringPrintf("%s.%d.exit.txt", + g_debug->config().backtrace_dump_prefix().c_str(), + getpid()) + .c_str()); } DebugDisableSet(true); @@ -246,13 +263,13 @@ void debug_finalize() { DebugDisableFinalize(); } -void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, - size_t* info_size, size_t* total_memory, size_t* backtrace_size) { +void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, size_t* info_size, + size_t* total_memory, size_t* backtrace_size) { ScopedDisableDebugCalls disable; // Verify the arguments. - if (info == nullptr || overall_size == nullptr || info_size == NULL || - total_memory == nullptr || backtrace_size == nullptr) { + if (info == nullptr || overall_size == nullptr || info_size == NULL || total_memory == nullptr || + backtrace_size == nullptr) { error_log("get_malloc_leak_info: At least one invalid parameter."); return; } @@ -264,47 +281,37 @@ void debug_get_malloc_leak_info(uint8_t** info, size_t* overall_size, *backtrace_size = 0; if (!(g_debug->config().options() & BACKTRACE)) { - error_log("get_malloc_leak_info: Allocations not being tracked, to enable " - "set the option 'backtrace'."); + error_log( + "get_malloc_leak_info: Allocations not being tracked, to enable " + "set the option 'backtrace'."); return; } - g_debug->track->GetInfo(info, overall_size, info_size, total_memory, backtrace_size); + PointerData::GetInfo(info, overall_size, info_size, total_memory, backtrace_size); } void debug_free_malloc_leak_info(uint8_t* info) { g_dispatch->free(info); } -static size_t internal_malloc_usable_size(void* pointer) { - if (g_debug->need_header()) { - Header* header = g_debug->GetHeader(pointer); - if (header->tag != DEBUG_TAG) { - LogTagError(header, pointer, "malloc_usable_size"); - return 0; - } - - return header->usable_size; - } else { - return g_dispatch->malloc_usable_size(pointer); - } -} - size_t debug_malloc_usable_size(void* pointer) { if (DebugCallsDisabled() || pointer == nullptr) { return g_dispatch->malloc_usable_size(pointer); } ScopedDisableDebugCalls disable; - return internal_malloc_usable_size(pointer); + if (!VerifyPointer(pointer, "malloc_usable_size")) { + return 0; + } + + return InternalMallocUsableSize(pointer); } -static void *internal_malloc(size_t size) { - if ((g_debug->config().options() & BACKTRACE) && g_debug->backtrace->ShouldDumpAndReset()) { - debug_dump_heap( - android::base::StringPrintf("%s.%d.txt", - g_debug->config().backtrace_dump_prefix().c_str(), - getpid()).c_str()); +static void* InternalMalloc(size_t size) { + if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) { + debug_dump_heap(android::base::StringPrintf( + "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid()) + .c_str()); } if (size == 0) { @@ -318,15 +325,15 @@ static void *internal_malloc(size_t size) { return nullptr; } - void* pointer; - if (g_debug->need_header()) { - if (size > Header::max_size()) { - errno = ENOMEM; - return nullptr; - } + if (size > PointerInfoType::MaxSize()) { + errno = ENOMEM; + return nullptr; + } - Header* header = reinterpret_cast<Header*>( - g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size)); + void* pointer; + if (g_debug->HeaderEnabled()) { + Header* header = + reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size)); if (header == nullptr) { return nullptr; } @@ -335,11 +342,17 @@ static void *internal_malloc(size_t size) { pointer = g_dispatch->malloc(real_size); } - if (pointer != nullptr && g_debug->config().options() & FILL_ON_ALLOC) { - size_t bytes = internal_malloc_usable_size(pointer); - size_t fill_bytes = g_debug->config().fill_on_alloc_bytes(); - bytes = (bytes < fill_bytes) ? bytes : fill_bytes; - memset(pointer, g_debug->config().fill_alloc_value(), bytes); + if (pointer != nullptr) { + if (g_debug->TrackPointers()) { + PointerData::Add(pointer, size); + } + + if (g_debug->config().options() & FILL_ON_ALLOC) { + size_t bytes = InternalMallocUsableSize(pointer); + size_t fill_bytes = g_debug->config().fill_on_alloc_bytes(); + bytes = (bytes < fill_bytes) ? bytes : fill_bytes; + memset(pointer, g_debug->config().fill_alloc_value(), bytes); + } } return pointer; } @@ -350,7 +363,7 @@ void* debug_malloc(size_t size) { } ScopedDisableDebugCalls disable; - void* pointer = internal_malloc(size); + void* pointer = InternalMalloc(size); if (g_debug->config().options() & RECORD_ALLOCS) { g_debug->record->AddEntry(new MallocEntry(pointer, size)); @@ -359,23 +372,18 @@ void* debug_malloc(size_t size) { return pointer; } -static void internal_free(void* pointer) { - if ((g_debug->config().options() & BACKTRACE) && g_debug->backtrace->ShouldDumpAndReset()) { - debug_dump_heap( - android::base::StringPrintf("%s.%d.txt", - g_debug->config().backtrace_dump_prefix().c_str(), - getpid()).c_str()); +static void InternalFree(void* pointer) { + if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) { + debug_dump_heap(android::base::StringPrintf( + "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid()) + .c_str()); } void* free_pointer = pointer; size_t bytes; Header* header; - if (g_debug->need_header()) { + if (g_debug->HeaderEnabled()) { header = g_debug->GetHeader(pointer); - if (header->tag != DEBUG_TAG) { - LogTagError(header, pointer, "free"); - return; - } free_pointer = header->orig_pointer; if (g_debug->config().options() & FRONT_GUARD) { @@ -389,14 +397,6 @@ static void internal_free(void* pointer) { } } - if (g_debug->config().options() & TRACK_ALLOCS) { - bool backtrace_found = false; - if (g_debug->config().options() & BACKTRACE) { - BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); - backtrace_found = back_header->num_frames > 0; - } - g_debug->track->Remove(header, backtrace_found); - } header->tag = DEBUG_FREE_TAG; bytes = header->usable_size; @@ -410,13 +410,23 @@ static void internal_free(void* pointer) { memset(pointer, g_debug->config().fill_free_value(), bytes); } + if (g_debug->TrackPointers()) { + PointerData::Remove(pointer); + } + if (g_debug->config().options() & FREE_TRACK) { // Do not add the allocation until we are done modifying the pointer // itself. This avoids a race if a lot of threads are all doing // frees at the same time and we wind up trying to really free this // pointer from another thread, while still trying to free it in // this function. - g_debug->free_track->Add(header); + pointer = PointerData::AddFreed(pointer); + if (pointer != nullptr) { + if (g_debug->HeaderEnabled()) { + pointer = g_debug->GetHeader(pointer)->orig_pointer; + } + g_dispatch->free(pointer); + } } else { g_dispatch->free(free_pointer); } @@ -432,7 +442,11 @@ void debug_free(void* pointer) { g_debug->record->AddEntry(new FreeEntry(pointer)); } - internal_free(pointer); + if (!VerifyPointer(pointer, "free")) { + return; + } + + InternalFree(pointer); } void* debug_memalign(size_t alignment, size_t bytes) { @@ -445,13 +459,13 @@ void* debug_memalign(size_t alignment, size_t bytes) { bytes = 1; } - void* pointer; - if (g_debug->need_header()) { - if (bytes > Header::max_size()) { - errno = ENOMEM; - return nullptr; - } + if (bytes > PointerInfoType::MaxSize()) { + errno = ENOMEM; + return nullptr; + } + void* pointer; + if (g_debug->HeaderEnabled()) { // Make the alignment a power of two. if (!powerof2(alignment)) { alignment = BIONIC_ROUND_UP_POWER_OF_2(alignment); @@ -493,15 +507,21 @@ void* debug_memalign(size_t alignment, size_t bytes) { pointer = g_dispatch->memalign(alignment, real_size); } - if (pointer != nullptr && g_debug->config().options() & FILL_ON_ALLOC) { - size_t bytes = internal_malloc_usable_size(pointer); - size_t fill_bytes = g_debug->config().fill_on_alloc_bytes(); - bytes = (bytes < fill_bytes) ? bytes : fill_bytes; - memset(pointer, g_debug->config().fill_alloc_value(), bytes); - } + if (pointer != nullptr) { + if (g_debug->TrackPointers()) { + PointerData::Add(pointer, bytes); + } - if (g_debug->config().options() & RECORD_ALLOCS) { - g_debug->record->AddEntry(new MemalignEntry(pointer, bytes, alignment)); + if (g_debug->config().options() & FILL_ON_ALLOC) { + size_t bytes = InternalMallocUsableSize(pointer); + size_t fill_bytes = g_debug->config().fill_on_alloc_bytes(); + bytes = (bytes < fill_bytes) ? bytes : fill_bytes; + memset(pointer, g_debug->config().fill_alloc_value(), bytes); + } + + if (g_debug->config().options() & RECORD_ALLOCS) { + g_debug->record->AddEntry(new MemalignEntry(pointer, bytes, alignment)); + } } return pointer; @@ -514,19 +534,23 @@ void* debug_realloc(void* pointer, size_t bytes) { ScopedDisableDebugCalls disable; if (pointer == nullptr) { - pointer = internal_malloc(bytes); + pointer = InternalMalloc(bytes); if (g_debug->config().options() & RECORD_ALLOCS) { g_debug->record->AddEntry(new ReallocEntry(pointer, bytes, nullptr)); } return pointer; } + if (!VerifyPointer(pointer, "realloc")) { + return nullptr; + } + if (bytes == 0) { if (g_debug->config().options() & RECORD_ALLOCS) { g_debug->record->AddEntry(new ReallocEntry(nullptr, bytes, pointer)); } - internal_free(pointer); + InternalFree(pointer); return nullptr; } @@ -540,45 +564,45 @@ void* debug_realloc(void* pointer, size_t bytes) { } } + if (bytes > PointerInfoType::MaxSize()) { + errno = ENOMEM; + return nullptr; + } + void* new_pointer; size_t prev_size; - if (g_debug->need_header()) { - if (bytes > Header::max_size()) { - errno = ENOMEM; - return nullptr; - } - - Header* header = g_debug->GetHeader(pointer); - if (header->tag != DEBUG_TAG) { - LogTagError(header, pointer, "realloc"); - return nullptr; - } - + if (g_debug->HeaderEnabled()) { // Same size, do nothing. - if (real_size == header->real_size()) { - // Do not bother recording, this is essentially a nop. + Header* header = g_debug->GetHeader(pointer); + if (real_size == header->size) { + if (g_debug->TrackPointers()) { + // Remove and re-add so that the backtrace is updated. + PointerData::Remove(pointer); + PointerData::Add(pointer, real_size); + } return pointer; } // Allocation is shrinking. if (real_size < header->usable_size) { header->size = real_size; - if (*g_malloc_zygote_child) { - header->set_zygote_child_alloc(); - } if (g_debug->config().options() & REAR_GUARD) { // Don't bother allocating a smaller pointer in this case, simply // change the header usable_size and reset the rear guard. - header->usable_size = header->real_size(); + header->usable_size = header->size; memset(g_debug->GetRearGuard(header), g_debug->config().rear_guard_value(), g_debug->config().rear_guard_bytes()); } - // Do not bother recording, this is essentially a nop. + if (g_debug->TrackPointers()) { + // Remove and re-add so that the backtrace is updated. + PointerData::Remove(pointer); + PointerData::Add(pointer, real_size); + } return pointer; } // Allocate the new size. - new_pointer = internal_malloc(bytes); + new_pointer = InternalMalloc(bytes); if (new_pointer == nullptr) { errno = ENOMEM; return nullptr; @@ -586,17 +610,25 @@ void* debug_realloc(void* pointer, size_t bytes) { prev_size = header->usable_size; memcpy(new_pointer, pointer, prev_size); - internal_free(pointer); + InternalFree(pointer); } else { + if (g_debug->TrackPointers()) { + PointerData::Remove(pointer); + } + prev_size = g_dispatch->malloc_usable_size(pointer); new_pointer = g_dispatch->realloc(pointer, real_size); if (new_pointer == nullptr) { return nullptr; } + + if (g_debug->TrackPointers()) { + PointerData::Add(new_pointer, real_size); + } } if (g_debug->config().options() & FILL_ON_ALLOC) { - size_t bytes = internal_malloc_usable_size(new_pointer); + size_t bytes = InternalMallocUsableSize(new_pointer); if (bytes > g_debug->config().fill_on_alloc_bytes()) { bytes = g_debug->config().fill_on_alloc_bytes(); } @@ -637,17 +669,16 @@ void* debug_calloc(size_t nmemb, size_t bytes) { return nullptr; } - void* pointer; - if (g_debug->need_header()) { - // The above check will guarantee the multiply will not overflow. - if (size > Header::max_size()) { - errno = ENOMEM; - return nullptr; - } + if (real_size > PointerInfoType::MaxSize()) { + errno = ENOMEM; + return nullptr; + } + void* pointer; + if (g_debug->HeaderEnabled()) { // Need to guarantee the alignment of the header. - Header* header = reinterpret_cast<Header*>( - g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size)); + Header* header = + reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size)); if (header == nullptr) { return nullptr; } @@ -656,9 +687,14 @@ void* debug_calloc(size_t nmemb, size_t bytes) { } else { pointer = g_dispatch->calloc(1, real_size); } + if (g_debug->config().options() & RECORD_ALLOCS) { g_debug->record->AddEntry(new CallocEntry(pointer, bytes, nmemb)); } + + if (pointer != nullptr && g_debug->TrackPointers()) { + PointerData::Add(pointer, size); + } return pointer; } @@ -695,81 +731,45 @@ int debug_posix_memalign(void** memptr, size_t alignment, size_t size) { return (*memptr != nullptr) ? 0 : ENOMEM; } -int debug_iterate(uintptr_t base, size_t size, - void (*callback)(uintptr_t base, size_t size, void* arg), void* arg) { - // Can't allocate, malloc is disabled - // Manual capture of the arguments to pass to the lambda below as void* arg - struct iterate_ctx { - decltype(callback) callback; - decltype(arg) arg; - } ctx = { callback, arg }; - - return g_dispatch->iterate(base, size, - [](uintptr_t base, size_t size, void* arg) { - const iterate_ctx* ctx = reinterpret_cast<iterate_ctx*>(arg); - const void* pointer = reinterpret_cast<void*>(base); - if (g_debug->need_header()) { - const Header* header = reinterpret_cast<const Header*>(pointer); - if (g_debug->config().options() & TRACK_ALLOCS) { - if (g_debug->track->Contains(header)) { - // Return just the body of the allocation if we're sure the header exists - ctx->callback(reinterpret_cast<uintptr_t>(g_debug->GetPointer(header)), - header->usable_size, ctx->arg); - return; - } - } - } - // Fall back to returning the whole allocation - ctx->callback(base, size, ctx->arg); - }, &ctx); +int debug_iterate(uintptr_t base, size_t size, void (*callback)(uintptr_t, size_t, void*), + void* arg) { + if (g_debug->TrackPointers()) { + // Since malloc is disabled, don't bother acquiring any locks. + for (auto it = PointerData::begin(); it != PointerData::end(); ++it) { + callback(it->first, InternalMallocUsableSize(reinterpret_cast<void*>(it->first)), arg); + } + return 0; + } + + // An option that adds a header will add pointer tracking, so no need to + // check if headers are enabled. + return g_dispatch->iterate(base, size, callback, arg); } void debug_malloc_disable() { g_dispatch->malloc_disable(); - if (g_debug->track) { - g_debug->track->PrepareFork(); + if (g_debug->pointer) { + g_debug->pointer->PrepareFork(); } } void debug_malloc_enable() { - if (g_debug->track) { - g_debug->track->PostForkParent(); + if (g_debug->pointer) { + g_debug->pointer->PostForkParent(); } g_dispatch->malloc_enable(); } -ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count) { +ssize_t debug_malloc_backtrace(void* pointer, uintptr_t* frames, size_t max_frames) { if (DebugCallsDisabled() || pointer == nullptr) { return 0; } ScopedDisableDebugCalls disable; - if (g_debug->need_header()) { - Header* header; - if (g_debug->config().options() & TRACK_ALLOCS) { - header = g_debug->GetHeader(pointer); - if (!g_debug->track->Contains(header)) { - return 0; - } - } else { - header = reinterpret_cast<Header*>(pointer); - } - if (header->tag != DEBUG_TAG) { - return 0; - } - if (g_debug->config().options() & BACKTRACE) { - BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); - if (back_header->num_frames > 0) { - if (frame_count > back_header->num_frames) { - frame_count = back_header->num_frames; - } - memcpy(frames, &back_header->frames[0], frame_count * sizeof(uintptr_t)); - return frame_count; - } - } + if (!(g_debug->config().options() & BACKTRACE)) { + return 0; } - - return 0; + return PointerData::GetFrames(pointer, frames, max_frames); } #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) @@ -821,30 +821,7 @@ bool debug_dump_heap(const char* file_name) { fprintf(fp, "Android Native Heap Dump v1.0\n\n"); - std::vector<const Header*> list; - size_t total_memory; - g_debug->track->GetListBySizeThenBacktrace(&list, &total_memory); - fprintf(fp, "Total memory: %zu\n", total_memory); - fprintf(fp, "Allocation records: %zd\n", list.size()); - fprintf(fp, "Backtrace size: %zu\n", g_debug->config().backtrace_frames()); - fprintf(fp, "\n"); - - for (const auto& header : list) { - const BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header); - fprintf(fp, "z %d sz %8zu num 1 bt", (header->zygote_child_alloc()) ? 1 : 0, - header->real_size()); - for (size_t i = 0; i < back_header->num_frames; i++) { - if (back_header->frames[i] == 0) { - break; - } -#ifdef __LP64__ - fprintf(fp, " %016" PRIxPTR, back_header->frames[i]); -#else - fprintf(fp, " %08" PRIxPTR, back_header->frames[i]); -#endif - } - fprintf(fp, "\n"); - } + PointerData::DumpLiveToFile(fp); fprintf(fp, "MAPS\n"); std::string content; diff --git a/libc/malloc_debug/malloc_debug.h b/libc/malloc_debug/malloc_debug.h index 18a6c2c7f..ac210e246 100644 --- a/libc/malloc_debug/malloc_debug.h +++ b/libc/malloc_debug/malloc_debug.h @@ -37,13 +37,10 @@ // part of the header does not exist, the other parts of the header // will still be in this order. // Header (Required) -// BacktraceHeader (Optional: For the allocation backtrace) // uint8_t data (Optional: Front guard, will be a multiple of MINIMUM_ALIGNMENT_BYTES) // allocation data // uint8_t data (Optional: End guard) // -// If backtracing is enabled, then both BacktraceHeaders will be present. -// // In the initialization function, offsets into the header will be set // for each different header location. The offsets are always from the // beginning of the Header section. @@ -52,10 +49,6 @@ struct Header { void* orig_pointer; size_t size; size_t usable_size; - size_t real_size() const { return size & ~(1U << 31); } - void set_zygote_child_alloc() { size |= 1U << 31; } - bool zygote_child_alloc() const { return size & (1U << 31); } - static size_t max_size() { return (1U << 31) - 1; } } __attribute__((packed)); struct BacktraceHeader { diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index ee8fe0691..460353583 100644 --- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp @@ -135,6 +135,10 @@ std::string usage_string( "6 malloc_debug This option only has meaning if the record_allocs options has been specified.\n" "6 malloc_debug This is the name of the file to which recording information will be dumped.\n" "6 malloc_debug The default is /data/local/tmp/record_allocs.txt.\n" + "6 malloc_debug \n" + "6 malloc_debug verify_pointers\n" + "6 malloc_debug A lightweight way to verify that free/malloc_usable_size/realloc\n" + "6 malloc_debug are passed valid pointers.\n" ); TEST_F(MallocDebugConfigTest, unknown_option) { @@ -250,15 +254,15 @@ TEST_F(MallocDebugConfigTest, multiple_options) { TEST_F(MallocDebugConfigTest, front_guard) { ASSERT_TRUE(InitConfig("front_guard=48")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(48U, config->front_guard_bytes()); ASSERT_TRUE(InitConfig("front_guard")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(32U, config->front_guard_bytes()); ASSERT_TRUE(InitConfig("front_guard=39")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | TRACK_ALLOCS, config->options()); #if defined(__LP64__) ASSERT_EQ(48U, config->front_guard_bytes()); #else @@ -266,7 +270,7 @@ TEST_F(MallocDebugConfigTest, front_guard) { #endif ASSERT_TRUE(InitConfig("front_guard=41")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(48U, config->front_guard_bytes()); ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -275,11 +279,11 @@ TEST_F(MallocDebugConfigTest, front_guard) { TEST_F(MallocDebugConfigTest, rear_guard) { ASSERT_TRUE(InitConfig("rear_guard=50")) << getFakeLogPrint(); - ASSERT_EQ(REAR_GUARD, config->options()); + ASSERT_EQ(REAR_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(50U, config->rear_guard_bytes()); ASSERT_TRUE(InitConfig("rear_guard")) << getFakeLogPrint(); - ASSERT_EQ(REAR_GUARD, config->options()); + ASSERT_EQ(REAR_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(32U, config->rear_guard_bytes()); ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -288,12 +292,12 @@ TEST_F(MallocDebugConfigTest, rear_guard) { TEST_F(MallocDebugConfigTest, guard) { ASSERT_TRUE(InitConfig("guard=32")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(32U, config->front_guard_bytes()); ASSERT_EQ(32U, config->rear_guard_bytes()); ASSERT_TRUE(InitConfig("guard")) << getFakeLogPrint(); - ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options()); + ASSERT_EQ(FRONT_GUARD | REAR_GUARD | TRACK_ALLOCS, config->options()); ASSERT_EQ(32U, config->front_guard_bytes()); ASSERT_EQ(32U, config->rear_guard_bytes()); @@ -465,13 +469,13 @@ TEST_F(MallocDebugConfigTest, expand_alloc) { TEST_F(MallocDebugConfigTest, free_track) { ASSERT_TRUE(InitConfig("free_track=1234")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(1234U, config->free_track_allocations()); ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); ASSERT_TRUE(InitConfig("free_track")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(100U, config->free_track_allocations()); ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); @@ -482,20 +486,20 @@ TEST_F(MallocDebugConfigTest, free_track) { TEST_F(MallocDebugConfigTest, free_track_and_fill_on_free) { ASSERT_TRUE(InitConfig("free_track=1234 fill_on_free=32")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(1234U, config->free_track_allocations()); ASSERT_EQ(32U, config->fill_on_free_bytes()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); ASSERT_TRUE(InitConfig("free_track fill_on_free=60")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(100U, config->free_track_allocations()); ASSERT_EQ(60U, config->fill_on_free_bytes()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); // Now reverse the arguments. ASSERT_TRUE(InitConfig("fill_on_free=32 free_track=1234")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(1234U, config->free_track_allocations()); ASSERT_EQ(32U, config->fill_on_free_bytes()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); @@ -530,11 +534,11 @@ TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_zero) { TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_and_free_track) { ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames=123")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(123U, config->free_track_backtrace_num_frames()); ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames")) << getFakeLogPrint(); - ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options()); + ASSERT_EQ(FREE_TRACK | FILL_ON_FREE | TRACK_ALLOCS, config->options()); ASSERT_EQ(16U, config->free_track_backtrace_num_frames()); ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -559,6 +563,24 @@ TEST_F(MallocDebugConfigTest, leak_track_fail) { ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); } +TEST_F(MallocDebugConfigTest, verify_pointers) { + ASSERT_TRUE(InitConfig("verify_pointers")) << getFakeLogPrint(); + ASSERT_EQ(TRACK_ALLOCS, config->options()); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugConfigTest, verify_pointers_fail) { + ASSERT_FALSE(InitConfig("verify_pointers=200")) << getFakeLogPrint(); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string log_msg( + "6 malloc_debug malloc_testing: value set for option 'verify_pointers' " + "which does not take a value\n"); + ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str()); +} + TEST_F(MallocDebugConfigTest, record_allocs) { ASSERT_TRUE(InitConfig("record_allocs=1234")) << getFakeLogPrint(); ASSERT_EQ(RECORD_ALLOCS, config->options()); diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index 0e4a7d85f..8b2818872 100644 --- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp +++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp @@ -72,15 +72,8 @@ __END_DECLS constexpr char DIVIDER[] = "6 malloc_debug *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"; -constexpr uint32_t BACKTRACE_HEADER = 0x1; - -static size_t get_tag_offset(uint32_t flags = 0, size_t backtrace_frames = 0) { - size_t offset = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES); - if (flags & BACKTRACE_HEADER) { - offset += __BIONIC_ALIGN(sizeof(BacktraceHeader) + sizeof(uintptr_t) * backtrace_frames, - MINIMUM_ALIGNMENT_BYTES); - } - return offset; +static size_t get_tag_offset() { + return __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES); } static constexpr const char RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt"; @@ -111,6 +104,10 @@ class MallocDebugTest : public ::testing::Test { void BacktraceDumpOnSignal(bool trigger_with_alloc); + static size_t GetInfoEntrySize(size_t max_frames) { + return 2 * sizeof(size_t) + max_frames * sizeof(uintptr_t); + } + bool initialized; int zygote; @@ -140,6 +137,16 @@ MallocDispatch MallocDebugTest::dispatch = { aligned_alloc, }; +std::string ShowDiffs(uint8_t* a, uint8_t* b, size_t size) { + std::string diff; + for (size_t i = 0; i < size; i++) { + if (a[i] != b[i]) { + diff += android::base::StringPrintf("Byte %zu: 0x%x 0x%x\n", i, a[i], b[i]); + } + } + return diff; +} + void VerifyAllocCalls(bool backtrace_enabled) { size_t alloc_size = 1024; @@ -335,7 +342,8 @@ TEST_F(MallocDebugTest, front_guard) { uint8_t* pointer = reinterpret_cast<uint8_t*>(debug_malloc(100)); ASSERT_TRUE(pointer != nullptr); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size()); memset(pointer, 0xff, 100); debug_free(pointer); @@ -343,7 +351,8 @@ TEST_F(MallocDebugTest, front_guard) { for (size_t alignment = 1; alignment <= 256; alignment++) { pointer = reinterpret_cast<uint8_t*>(debug_memalign(alignment, 100)); ASSERT_TRUE(pointer != nullptr); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size()); size_t alignment_mask = alignment - 1; if (!powerof2(alignment)) { alignment_mask = BIONIC_ROUND_UP_POWER_OF_2(alignment) - 1; @@ -355,7 +364,8 @@ TEST_F(MallocDebugTest, front_guard) { pointer = reinterpret_cast<uint8_t*>(debug_calloc(1, 100)); ASSERT_TRUE(pointer != nullptr); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size()); for (size_t i = 0; i < 100; i++) { ASSERT_EQ(0, pointer[i]) << "debug_calloc non-zero byte at " << i; } @@ -363,10 +373,12 @@ TEST_F(MallocDebugTest, front_guard) { pointer = reinterpret_cast<uint8_t*>(debug_realloc(nullptr, 100)); ASSERT_TRUE(pointer != nullptr); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size()); memset(pointer, 0xff, 100); pointer = reinterpret_cast<uint8_t*>(debug_realloc(pointer, 200)); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size()); memset(pointer, 0xff, 200); pointer = reinterpret_cast<uint8_t*>(debug_realloc(pointer, 0)); ASSERT_TRUE(pointer == nullptr); @@ -427,7 +439,8 @@ TEST_F(MallocDebugTest, rear_guard) { uint8_t* pointer = reinterpret_cast<uint8_t*>(debug_malloc(100)); ASSERT_TRUE(pointer != nullptr); ASSERT_EQ(100U, debug_malloc_usable_size(pointer)); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[100], buffer.size()); memset(pointer, 0xff, 100); debug_free(pointer); @@ -436,7 +449,8 @@ TEST_F(MallocDebugTest, rear_guard) { pointer = reinterpret_cast<uint8_t*>(debug_memalign(alignment, 100)); ASSERT_TRUE(pointer != nullptr); ASSERT_EQ(100U, debug_malloc_usable_size(pointer)); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[100], buffer.size()); size_t alignment_mask = alignment - 1; if (!powerof2(alignment)) { alignment_mask = BIONIC_ROUND_UP_POWER_OF_2(alignment) - 1; @@ -450,7 +464,8 @@ TEST_F(MallocDebugTest, rear_guard) { pointer = reinterpret_cast<uint8_t*>(debug_calloc(1, 100)); ASSERT_TRUE(pointer != nullptr); ASSERT_EQ(100U, debug_malloc_usable_size(pointer)); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[100], buffer.size()); for (size_t i = 0; i < 100; i++) { ASSERT_EQ(0, pointer[i]) << "debug_calloc non-zero byte at " << i; } @@ -458,10 +473,12 @@ TEST_F(MallocDebugTest, rear_guard) { pointer = reinterpret_cast<uint8_t*>(debug_realloc(nullptr, 100)); ASSERT_TRUE(pointer != nullptr); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[100], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[100], buffer.size()); memset(pointer, 0xff, 100); pointer = reinterpret_cast<uint8_t*>(debug_realloc(pointer, 200)); - ASSERT_TRUE(memcmp(buffer.data(), &pointer[200], buffer.size()) == 0); + ASSERT_TRUE(memcmp(buffer.data(), &pointer[200], buffer.size()) == 0) + << ShowDiffs(buffer.data(), &pointer[200], buffer.size()); for (size_t i = 0; i < 100; i++) { ASSERT_EQ(0xff, pointer[i]) << "debug_realloc not copied byte at " << i; } @@ -829,7 +846,7 @@ TEST_F(MallocDebugTest, free_track_use_after_free_finalize) { } TEST_F(MallocDebugTest, free_track_use_after_free_with_backtrace) { - Init("free_track=100"); + Init("free_track=100 rear_guard"); // Free backtrace. backtrace_fake_add(std::vector<uintptr_t> {0xfa, 0xeb, 0xdc}); @@ -860,7 +877,7 @@ TEST_F(MallocDebugTest, free_track_use_after_free_with_backtrace) { } TEST_F(MallocDebugTest, free_track_use_after_free_call_realloc) { - Init("free_track=100"); + Init("free_track=100 rear_guard"); // Free backtrace. backtrace_fake_add(std::vector<uintptr_t> {0xfa, 0xeb, 0xdc}); @@ -894,7 +911,7 @@ TEST_F(MallocDebugTest, free_track_use_after_free_call_realloc) { } TEST_F(MallocDebugTest, free_track_use_after_free_call_free) { - Init("free_track=100"); + Init("free_track=100 rear_guard"); // Free backtrace. backtrace_fake_add(std::vector<uintptr_t> {0xfa, 0xeb, 0xdc}); @@ -926,7 +943,7 @@ TEST_F(MallocDebugTest, free_track_use_after_free_call_free) { } TEST_F(MallocDebugTest, free_track_header_tag_corrupted) { - Init("free_track=100 free_track_backtrace_num_frames=0"); + Init("free_track=100 free_track_backtrace_num_frames=0 rear_guard"); uint8_t* pointer = reinterpret_cast<uint8_t*>(debug_malloc(100)); ASSERT_TRUE(pointer != nullptr); @@ -1053,7 +1070,7 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_single) { Init("backtrace"); // Create the expected info buffer. - size_t individual_size = 2 * sizeof(size_t) + 16 * sizeof(uintptr_t); + size_t individual_size = GetInfoEntrySize(16); std::vector<uint8_t> expected_info(individual_size); memset(expected_info.data(), 0, individual_size); @@ -1082,7 +1099,8 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_single) { ASSERT_EQ(individual_size, info_size); ASSERT_EQ(200U, total_memory); ASSERT_EQ(16U, backtrace_size); - ASSERT_TRUE(memcmp(expected_info.data(), info, overall_size) == 0); + ASSERT_TRUE(memcmp(expected_info.data(), info, overall_size) == 0) + << ShowDiffs(expected_info.data(), info, overall_size); debug_free_malloc_leak_info(info); @@ -1099,7 +1117,7 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi) { Init("backtrace=16"); // Create the expected info buffer. - size_t individual_size = 2 * sizeof(size_t) + 16 * sizeof(uintptr_t); + size_t individual_size = GetInfoEntrySize(16); std::vector<uint8_t> expected_info(individual_size * 3); memset(expected_info.data(), 0, individual_size * 3); @@ -1162,7 +1180,8 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi) { ASSERT_EQ(individual_size, info_size); ASSERT_EQ(500U + 4100U + 9000U, total_memory); ASSERT_EQ(16U, backtrace_size); - ASSERT_TRUE(memcmp(expected_info.data(), info, overall_size) == 0); + ASSERT_TRUE(memcmp(expected_info.data(), info, overall_size) == 0) + << ShowDiffs(expected_info.data(), info, overall_size); debug_free_malloc_leak_info(info); @@ -1177,52 +1196,13 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi) { ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } -TEST_F(MallocDebugTest, get_malloc_leak_info_multi_skip_empty_backtrace) { - Init("backtrace=16"); - - // Create the expected info buffer. - size_t individual_size = 2 * sizeof(size_t) + 16 * sizeof(uintptr_t); - std::vector<uint8_t> expected_info(individual_size * 2); - memset(expected_info.data(), 0, individual_size * 2); - - InfoEntry* entry0 = reinterpret_cast<InfoEntry*>(expected_info.data()); - InfoEntry* entry1 = reinterpret_cast<InfoEntry*>( - reinterpret_cast<uintptr_t>(entry0) + individual_size); - - // These values will be in the reverse order that we create. - entry1->size = 500; - entry1->num_allocations = 1; - entry1->frames[0] = 0xf; - entry1->frames[1] = 0xe; - entry1->frames[2] = 0xd; - entry1->frames[3] = 0xc; - - backtrace_fake_add(std::vector<uintptr_t> {0xf, 0xe, 0xd, 0xc}); - - uint8_t* pointers[3]; - - pointers[0] = reinterpret_cast<uint8_t*>(debug_malloc(entry1->size)); - ASSERT_TRUE(pointers[0] != nullptr); - memset(pointers[0], 0, entry1->size); - - entry0->size = 4100; - entry0->num_allocations = 1; - for (size_t i = 0; i < 16; i++) { - entry0->frames[i] = 0xbc000 + i; - } - - backtrace_fake_add( - std::vector<uintptr_t> {0xbc000, 0xbc001, 0xbc002, 0xbc003, 0xbc004, 0xbc005, - 0xbc006, 0xbc007, 0xbc008, 0xbc009, 0xbc00a, 0xbc00b, - 0xbc00c, 0xbc00d, 0xbc00e, 0xbc00f, 0xffff}); - - pointers[1] = reinterpret_cast<uint8_t*>(debug_malloc(entry0->size)); - ASSERT_TRUE(pointers[1] != nullptr); - memset(pointers[1], 0, entry0->size); +TEST_F(MallocDebugTest, get_malloc_backtrace_with_header) { + Init("backtrace=16 guard"); - pointers[2] = reinterpret_cast<uint8_t*>(debug_malloc(10000)); - ASSERT_TRUE(pointers[2] != nullptr); - memset(pointers[2], 0, 10000); + void* pointer = debug_malloc(100); + ASSERT_TRUE(pointer != nullptr); + memset(pointer, 0, 100); + EXPECT_EQ(100U, debug_malloc_usable_size(pointer)); uint8_t* info; size_t overall_size; @@ -1231,18 +1211,18 @@ TEST_F(MallocDebugTest, get_malloc_leak_info_multi_skip_empty_backtrace) { size_t backtrace_size; debug_get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size); - ASSERT_TRUE(info != nullptr); - ASSERT_EQ(individual_size * 2, overall_size); - ASSERT_EQ(individual_size, info_size); - ASSERT_EQ(500U + 4100U, total_memory); - ASSERT_EQ(16U, backtrace_size); - ASSERT_TRUE(memcmp(expected_info.data(), info, overall_size) == 0); - + EXPECT_TRUE(info != nullptr); + EXPECT_EQ(GetInfoEntrySize(16), overall_size); + EXPECT_EQ(GetInfoEntrySize(16), info_size); + EXPECT_EQ(100U, total_memory); + EXPECT_EQ(16U, backtrace_size); debug_free_malloc_leak_info(info); - debug_free(pointers[0]); - debug_free(pointers[1]); - debug_free(pointers[2]); + debug_free(pointer); + + // There should be no pointers that have leaked. + debug_finalize(); + initialized = false; ASSERT_STREQ("", getFakeLogBuf().c_str()); std::string expected_log = android::base::StringPrintf( @@ -1338,19 +1318,19 @@ void MallocDebugTest::BacktraceDumpOnSignal(bool trigger_with_alloc) { "Backtrace size: 4\n" "\n" #if defined(__LP64__) - "z 0 sz 5 num 1 bt 000000000000a300 000000000000b300\n" - "z 0 sz 10 num 1 bt 000000000000a000 000000000000b000\n" "z 0 sz 50 num 1 bt 000000000000a100 000000000000b200\n" - "z 1 sz 40 num 1 bt 0000000000000300 0000000000000400\n" - "z 1 sz 100 num 1 bt 0000000000000100 0000000000000200\n" + "z 0 sz 10 num 1 bt 000000000000a000 000000000000b000\n" + "z 0 sz 5 num 1 bt 000000000000a300 000000000000b300\n" "z 1 sz 200 num 1 bt 0000000000000500 0000000000000600\n" + "z 1 sz 100 num 1 bt 0000000000000100 0000000000000200\n" + "z 1 sz 40 num 1 bt 0000000000000300 0000000000000400\n" #else - "z 0 sz 5 num 1 bt 0000a300 0000b300\n" - "z 0 sz 10 num 1 bt 0000a000 0000b000\n" "z 0 sz 50 num 1 bt 0000a100 0000b200\n" - "z 1 sz 40 num 1 bt 00000300 00000400\n" - "z 1 sz 100 num 1 bt 00000100 00000200\n" + "z 0 sz 10 num 1 bt 0000a000 0000b000\n" + "z 0 sz 5 num 1 bt 0000a300 0000b300\n" "z 1 sz 200 num 1 bt 00000500 00000600\n" + "z 1 sz 100 num 1 bt 00000100 00000200\n" + "z 1 sz 40 num 1 bt 00000300 00000400\n" #endif "MAPS\n" "MAP_DATA\n" @@ -1410,13 +1390,64 @@ TEST_F(MallocDebugTest, backtrace_dump_on_exit) { "Backtrace size: 4\n" "\n" #if defined(__LP64__) - "z 0 sz 300 num 1 bt 0000000000000100 0000000000000200\n" - "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000\n" "z 0 sz 500 num 1 bt 000000000000a000 000000000000b000 000000000000c000\n" + "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000\n" + "z 0 sz 300 num 1 bt 0000000000000100 0000000000000200\n" #else - "z 0 sz 300 num 1 bt 00000100 00000200\n" - "z 0 sz 400 num 1 bt 0000a000 0000b000\n" "z 0 sz 500 num 1 bt 0000a000 0000b000 0000c000\n" + "z 0 sz 400 num 1 bt 0000a000 0000b000\n" + "z 0 sz 300 num 1 bt 00000100 00000200\n" +#endif + "MAPS\n" + "MAP_DATA\n" + "END\n\n"; + ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual; + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_dump_on_exit_shared_backtrace) { + pid_t pid; + if ((pid = fork()) == 0) { + Init("backtrace=4 backtrace_dump_on_exit"); + backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200}); + backtrace_fake_add(std::vector<uintptr_t> {0xa000, 0xb000, 0xc000}); + backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200}); + + std::vector<void*> pointers; + pointers.push_back(debug_malloc(300)); + pointers.push_back(debug_malloc(400)); + pointers.push_back(debug_malloc(300)); + + // Call the exit function manually. + debug_finalize(); + exit(0); + } + ASSERT_NE(-1, pid); + ASSERT_EQ(pid, TEMP_FAILURE_RETRY(waitpid(pid, nullptr, 0))); + + // Read all of the contents. + std::string actual; + std::string name = android::base::StringPrintf("%s.%d.exit.txt", BACKTRACE_DUMP_PREFIX, pid); + ASSERT_TRUE(android::base::ReadFileToString(name, &actual)); + ASSERT_EQ(0, unlink(name.c_str())); + + std::string sanitized(SanitizeHeapData(actual)); + + std::string expected = + "Android Native Heap Dump v1.0\n" + "\n" + "Total memory: 1000\n" + "Allocation records: 2\n" + "Backtrace size: 4\n" + "\n" +#if defined(__LP64__) + "z 0 sz 400 num 1 bt 000000000000a000 000000000000b000 000000000000c000\n" + "z 0 sz 300 num 2 bt 0000000000000100 0000000000000200\n" +#else + "z 0 sz 400 num 1 bt 0000a000 0000b000 0000c000\n" + "z 0 sz 300 num 2 bt 00000100 00000200\n" #endif "MAPS\n" "MAP_DATA\n" @@ -1427,6 +1458,7 @@ TEST_F(MallocDebugTest, backtrace_dump_on_exit) { ASSERT_STREQ("", getFakeLogPrint().c_str()); } + TEST_F(MallocDebugTest, realloc_usable_size) { Init("front_guard"); @@ -1453,7 +1485,7 @@ TEST_F(MallocDebugTest, realloc_usable_size) { TEST_F(MallocDebugTest, backtrace_enable_on_signal) { Init("backtrace_enable_on_signal=20"); - size_t individual_size = 2 * sizeof(size_t) + 20 * sizeof(uintptr_t); + size_t individual_size = GetInfoEntrySize(20); backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200, 0x300, 0x400}); @@ -1532,6 +1564,198 @@ TEST_F(MallocDebugTest, backtrace_enable_on_signal) { ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } +TEST_F(MallocDebugTest, backtrace_same_stack) { + Init("backtrace=4"); + + size_t individual_size = GetInfoEntrySize(4); + + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + + void* pointers[4]; + pointers[0] = debug_malloc(10); + ASSERT_TRUE(pointers[0] != nullptr); + pointers[1] = debug_malloc(10); + ASSERT_TRUE(pointers[1] != nullptr); + pointers[2] = debug_malloc(10); + ASSERT_TRUE(pointers[2] != nullptr); + pointers[3] = debug_malloc(100); + ASSERT_TRUE(pointers[3] != nullptr); + + uint8_t* info; + size_t overall_size; + size_t info_size; + size_t total_memory; + size_t backtrace_size; + + debug_get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(individual_size * 2, overall_size); + ASSERT_EQ(individual_size, info_size); + EXPECT_EQ(130U, total_memory); + EXPECT_EQ(4U, backtrace_size); + EXPECT_EQ(100U, *reinterpret_cast<size_t*>(&info[0])); + EXPECT_EQ(1U, *reinterpret_cast<size_t*>(&info[sizeof(size_t)])); + uintptr_t* ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t)]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0xecd00U, ips[1]); + EXPECT_EQ(0x12000U, ips[2]); + + EXPECT_EQ(10U, *reinterpret_cast<size_t*>(&info[individual_size])); + EXPECT_EQ(3U, *reinterpret_cast<size_t*>(&info[sizeof(size_t) + individual_size])); + ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t) + individual_size]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0xecd00U, ips[1]); + EXPECT_EQ(0x12000U, ips[2]); + + debug_free_malloc_leak_info(info); + + debug_free(pointers[0]); + debug_free(pointers[1]); + debug_free(pointers[2]); + debug_free(pointers[3]); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_same_stack_zygote) { + Init("backtrace=4"); + + size_t individual_size = GetInfoEntrySize(4); + + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000}); + + zygote = 1; + + void* pointers[4]; + pointers[0] = debug_malloc(100); + ASSERT_TRUE(pointers[0] != nullptr); + pointers[1] = debug_malloc(100); + ASSERT_TRUE(pointers[1] != nullptr); + pointers[2] = debug_malloc(100); + ASSERT_TRUE(pointers[2] != nullptr); + pointers[3] = debug_malloc(100); + ASSERT_TRUE(pointers[3] != nullptr); + + uint8_t* info; + size_t overall_size; + size_t info_size; + size_t total_memory; + size_t backtrace_size; + + debug_get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(individual_size * 2, overall_size); + EXPECT_EQ(individual_size, info_size); + EXPECT_EQ(400U, total_memory); + EXPECT_EQ(4U, backtrace_size); + + EXPECT_EQ(0x80000064U, *reinterpret_cast<size_t*>(&info[0])); + EXPECT_EQ(3U, *reinterpret_cast<size_t*>(&info[sizeof(size_t)])); + uintptr_t* ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t)]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0xecd00U, ips[1]); + EXPECT_EQ(0x12000U, ips[2]); + + EXPECT_EQ(0x80000064U, *reinterpret_cast<size_t*>(&info[individual_size])); + EXPECT_EQ(1U, *reinterpret_cast<size_t*>(&info[sizeof(size_t) + individual_size])); + ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t) + individual_size]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0U, ips[1]); + + debug_free_malloc_leak_info(info); + + debug_free(pointers[0]); + debug_free(pointers[1]); + debug_free(pointers[2]); + debug_free(pointers[3]); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + +TEST_F(MallocDebugTest, backtrace_same_stack_mix_zygote) { + Init("backtrace=4"); + + size_t individual_size = GetInfoEntrySize(4); + + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xecd00, 0x12000}); + backtrace_fake_add(std::vector<uintptr_t> {0xbc000}); + + zygote = 1; + void* pointers[4]; + pointers[0] = debug_malloc(40); + ASSERT_TRUE(pointers[0] != nullptr); + pointers[1] = debug_malloc(40); + ASSERT_TRUE(pointers[1] != nullptr); + + zygote = 0; + pointers[2] = debug_malloc(40); + ASSERT_TRUE(pointers[2] != nullptr); + pointers[3] = debug_malloc(100); + ASSERT_TRUE(pointers[3] != nullptr); + + uint8_t* info; + size_t overall_size; + size_t info_size; + size_t total_memory; + size_t backtrace_size; + + debug_get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(individual_size * 3, overall_size); + ASSERT_EQ(individual_size, info_size); + EXPECT_EQ(220U, total_memory); + EXPECT_EQ(4U, backtrace_size); + + EXPECT_EQ(100U, *reinterpret_cast<size_t*>(&info[0])); + EXPECT_EQ(1U, *reinterpret_cast<size_t*>(&info[sizeof(size_t)])); + uintptr_t* ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t)]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0U, ips[1]); + + EXPECT_EQ(40U, *reinterpret_cast<size_t*>(&info[individual_size])); + EXPECT_EQ(1U, *reinterpret_cast<size_t*>(&info[sizeof(size_t) + individual_size])); + ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t) + individual_size]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0xecd00U, ips[1]); + EXPECT_EQ(0x12000U, ips[2]); + + EXPECT_EQ(0x80000028U, *reinterpret_cast<size_t*>(&info[2 * individual_size])); + EXPECT_EQ(2U, *reinterpret_cast<size_t*>(&info[sizeof(size_t) + 2 * individual_size])); + ips = reinterpret_cast<uintptr_t*>(&info[2 * sizeof(size_t) + 2 * individual_size]); + EXPECT_EQ(0xbc000U, ips[0]); + EXPECT_EQ(0xecd00U, ips[1]); + EXPECT_EQ(0x12000U, ips[2]); + + debug_free_malloc_leak_info(info); + + debug_free(pointers[0]); + debug_free(pointers[1]); + debug_free(pointers[2]); + debug_free(pointers[3]); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string expected_log = android::base::StringPrintf( + "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the backtrace.\n", + SIGRTMAX - 17, getpid()); + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} + TEST_F(MallocDebugTest, overflow) { Init("guard fill_on_free"); @@ -1601,7 +1825,8 @@ static void VerifyZygoteSet(size_t memory_bytes) { ASSERT_EQ(expected_info_size, info_size); ASSERT_EQ(memory_bytes, total_memory); ASSERT_EQ(16U, backtrace_size); - ASSERT_TRUE(memcmp(info, expected_info.data(), expected_info_size) == 0); + ASSERT_TRUE(memcmp(info, expected_info.data(), expected_info_size) == 0) + << ShowDiffs(info, expected_info.data(), expected_info_size); debug_free_malloc_leak_info(info); } @@ -1619,6 +1844,7 @@ TEST_F(MallocDebugTest, zygote_set) { ASSERT_EQ(100U, debug_malloc_usable_size(pointer)); memset(pointer, 0, 100); VerifyZygoteSet(100); + ASSERT_FALSE(HasFatalFailure()); debug_free(pointer); backtrace_fake_add(std::vector<uintptr_t> {0x1}); @@ -1626,6 +1852,7 @@ TEST_F(MallocDebugTest, zygote_set) { ASSERT_TRUE(pointer != nullptr); ASSERT_EQ(200U, debug_malloc_usable_size(pointer)); VerifyZygoteSet(200); + ASSERT_FALSE(HasFatalFailure()); debug_free(pointer); backtrace_fake_add(std::vector<uintptr_t> {0x1}); @@ -1634,6 +1861,7 @@ TEST_F(MallocDebugTest, zygote_set) { ASSERT_EQ(300U, debug_malloc_usable_size(pointer)); memset(pointer, 0, 300); VerifyZygoteSet(300); + ASSERT_FALSE(HasFatalFailure()); debug_free(pointer); backtrace_fake_add(std::vector<uintptr_t> {0x1}); @@ -1642,12 +1870,14 @@ TEST_F(MallocDebugTest, zygote_set) { ASSERT_EQ(500U, debug_malloc_usable_size(pointer)); memset(pointer, 0, 500); VerifyZygoteSet(500); + ASSERT_FALSE(HasFatalFailure()); backtrace_fake_add(std::vector<uintptr_t> {0x1}); pointer = debug_realloc(pointer, 300); ASSERT_TRUE(pointer != nullptr); ASSERT_EQ(300U, debug_malloc_usable_size(pointer)); VerifyZygoteSet(300); + ASSERT_FALSE(HasFatalFailure()); debug_free(pointer); ASSERT_STREQ("", getFakeLogBuf().c_str()); @@ -1984,3 +2214,62 @@ TEST_F(MallocDebugTest, record_allocs_file_name_fail) { RECORD_ALLOCS_FILE); ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); } + +TEST_F(MallocDebugTest, verify_pointers) { + Init("verify_pointers"); + + void* pointer = debug_malloc(10); + memset(pointer, 0, 10); + debug_free(pointer); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + ASSERT_STREQ("", getFakeLogPrint().c_str()); + + debug_free(pointer); + ASSERT_EQ(0U, debug_malloc_usable_size(pointer)); + ASSERT_EQ(nullptr, debug_realloc(pointer, 1000)); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + std::string free_pointer_str( + android::base::StringPrintf("6 malloc_debug +++ ALLOCATION %p UNKNOWN POINTER (free)\n", + pointer)); + std::string usable_pointer_str( + android::base::StringPrintf("6 malloc_debug +++ ALLOCATION %p UNKNOWN POINTER (malloc_usable_size)\n", + pointer)); + std::string realloc_pointer_str( + android::base::StringPrintf("6 malloc_debug +++ ALLOCATION %p UNKNOWN POINTER (realloc)\n", + pointer)); + std::string backtrace_str("6 malloc_debug Backtrace failed to get any frames.\n"); + + std::string expected_log(DIVIDER + free_pointer_str + backtrace_str + DIVIDER); + expected_log += DIVIDER + usable_pointer_str + backtrace_str + DIVIDER; + expected_log += DIVIDER + realloc_pointer_str + backtrace_str + DIVIDER; + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); + + resetLogs(); + + backtrace_fake_add(std::vector<uintptr_t> {0x100, 0x200}); + backtrace_fake_add(std::vector<uintptr_t> {0x300, 0x400}); + backtrace_fake_add(std::vector<uintptr_t> {0x500, 0x600}); + debug_free(pointer); + ASSERT_EQ(0U, debug_malloc_usable_size(pointer)); + ASSERT_EQ(nullptr, debug_realloc(pointer, 1000)); + + ASSERT_STREQ("", getFakeLogBuf().c_str()); + expected_log = DIVIDER + free_pointer_str; + expected_log += "6 malloc_debug Backtrace at time of failure:\n"; + expected_log += "6 malloc_debug #00 pc 0x100\n"; + expected_log += "6 malloc_debug #01 pc 0x200\n"; + expected_log += DIVIDER; + expected_log += DIVIDER + usable_pointer_str; + expected_log += "6 malloc_debug Backtrace at time of failure:\n"; + expected_log += "6 malloc_debug #00 pc 0x300\n"; + expected_log += "6 malloc_debug #01 pc 0x400\n"; + expected_log += DIVIDER; + expected_log += DIVIDER + realloc_pointer_str; + expected_log += "6 malloc_debug Backtrace at time of failure:\n"; + expected_log += "6 malloc_debug #00 pc 0x500\n"; + expected_log += "6 malloc_debug #01 pc 0x600\n"; + expected_log += DIVIDER; + ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str()); +} |