Merge "Add the record alloc option."

This commit is contained in:
Treehugger Robot 2016-07-08 21:59:18 +00:00 committed by Gerrit Code Review
commit 779aa5ac33
11 changed files with 1122 additions and 175 deletions

View file

@ -8,6 +8,7 @@ libc_malloc_debug_src_files := \
FreeTrackData.cpp \
GuardData.cpp \
malloc_debug.cpp \
RecordData.cpp \
TrackData.cpp \
# ==============================================================
@ -58,6 +59,7 @@ LOCAL_CXX_STL := libc++_static
LOCAL_STATIC_LIBRARIES_arm := libunwind_llvm
LOCAL_STATIC_LIBRARIES += \
libbase \
libc_malloc_debug_backtrace \
libc_logging \

View file

@ -64,29 +64,126 @@ static constexpr size_t MAX_EXPAND_BYTES = 16384;
static constexpr size_t DEFAULT_FREE_TRACK_ALLOCATIONS = 100;
static constexpr size_t MAX_FREE_TRACK_ALLOCATIONS = 16384;
struct Feature {
Feature(std::string name, size_t default_value, size_t min_value, size_t max_value,
uint64_t option, size_t* value, bool* config, bool combo_option)
: name(name), default_value(default_value), min_value(min_value), max_value(max_value),
option(option), value(value), config(config), combo_option(combo_option) {}
std::string name;
size_t default_value = 0;
size_t min_value = 0;
size_t max_value = 0;
static constexpr size_t DEFAULT_RECORD_ALLOCS = 8000000;
static constexpr size_t MAX_RECORD_ALLOCS = 50000000;
static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt";
uint64_t option = 0;
size_t* value = nullptr;
bool* config = nullptr;
struct Option {
Option(std::string name, uint64_t option, bool combo_option = false, bool* config = nullptr)
: name(name), option(option), combo_option(combo_option), config(config) {}
virtual ~Option() = default;
std::string name;
uint64_t option;
// If set to true, then all of the options following are set on until
// for which the combo_option value is set.
// the combo_option value is set to false.
bool combo_option = false;
bool* config;
virtual bool ParseValue(const std::string& option_name, const std::string& value) const;
virtual void SetDefault() const { }
};
bool Option::ParseValue(const std::string& option_name, const std::string& raw_value) const {
if (!raw_value.empty()) {
error_log("%s: value set for option '%s' which does not take a value",
getprogname(), option_name.c_str());
return false;
}
return true;
}
struct OptionString : public Option {
OptionString(std::string name, uint64_t option, std::string default_value,
std::string* value, bool combo_option = false,
bool* config = nullptr)
: Option(name, option, combo_option, config), default_value(default_value), value(value) {}
virtual ~OptionString() = default;
std::string default_value;
std::string* value;
bool ParseValue(const std::string& option_name, const std::string& value) const override;
void SetDefault() const override { if (value) *value = default_value; }
};
bool OptionString::ParseValue(const std::string&, const std::string& raw_value) const {
if (!raw_value.empty()) {
*value = raw_value;
}
return true;
}
struct OptionSizeT : public Option {
OptionSizeT(std::string name, size_t default_value, size_t min_value, size_t max_value,
uint64_t option, size_t* value, bool combo_option = false, bool* config = nullptr)
: Option(name, option, combo_option, config), default_value(default_value),
min_value(min_value), max_value(max_value), value(value) {}
virtual ~OptionSizeT() = default;
size_t default_value;
size_t min_value;
size_t max_value;
size_t* value;
bool ParseValue(const std::string& option_name, const std::string& value) const override;
void SetDefault() const override { if (value) *value = default_value; }
};
bool OptionSizeT::ParseValue(const std::string& option_name, const std::string& raw_value) const {
if (raw_value.empty()) {
// Value should have been set by the SetDefault() pass.
return true;
}
// Parse the value into a size_t value.
errno = 0;
char* end;
long parsed_value = strtol(raw_value.c_str(), &end, 10);
if (errno != 0) {
error_log("%s: bad value for option '%s': %s", getprogname(), option_name.c_str(),
strerror(errno));
return false;
}
if (end == raw_value.c_str()) {
error_log("%s: bad value for option '%s'", getprogname(), option_name.c_str());
return false;
}
if (static_cast<size_t>(end - raw_value.c_str()) != raw_value.size()) {
error_log("%s: bad value for option '%s', non space found after option: %s",
getprogname(), option_name.c_str(), end);
return false;
}
if (parsed_value < 0) {
error_log("%s: bad value for option '%s', value cannot be negative: %ld",
getprogname(), option_name.c_str(), parsed_value);
return false;
}
if (static_cast<size_t>(parsed_value) < min_value) {
error_log("%s: bad value for option '%s', value must be >= %zu: %ld",
getprogname(), option_name.c_str(), min_value, parsed_value);
return false;
}
if (static_cast<size_t>(parsed_value) > max_value) {
error_log("%s: bad value for option '%s', value must be <= %zu: %ld",
getprogname(), option_name.c_str(), max_value, parsed_value);
return false;
}
*value = static_cast<size_t>(parsed_value);
return true;
}
class PropertyParser {
public:
explicit PropertyParser(const char* property) : cur_(property) {}
bool Get(std::string* property, size_t* value, bool* value_set);
bool Get(std::string* property, std::string* value);
bool Done() { return done_; }
@ -100,7 +197,7 @@ class PropertyParser {
DISALLOW_COPY_AND_ASSIGN(PropertyParser);
};
bool PropertyParser::Get(std::string* property, size_t* value, bool* value_set) {
bool PropertyParser::Get(std::string* property, std::string* value) {
// Process each property name we can find.
while (isspace(*cur_))
++cur_;
@ -110,44 +207,30 @@ bool PropertyParser::Get(std::string* property, size_t* value, bool* value_set)
return false;
}
const char* property_start = cur_;
const char* start = cur_;
while (!isspace(*cur_) && *cur_ != '=' && *cur_ != '\0')
++cur_;
*property = std::string(property_start, cur_ - property_start);
*property = std::string(start, cur_ - start);
// Skip any spaces after the name.
while (isspace(*cur_) && *cur_ != '=' && *cur_ != '\0')
while (isspace(*cur_))
++cur_;
value->clear();
if (*cur_ == '=') {
++cur_;
errno = 0;
*value_set = true;
char* end;
long read_value = strtol(cur_, const_cast<char**>(&end), 10);
if (errno != 0) {
error_log("%s: bad value for option '%s': %s", getprogname(), property->c_str(),
strerror(errno));
return false;
// Skip the space after the equal.
while (isspace(*cur_))
++cur_;
start = cur_;
while (!isspace(*cur_) && *cur_ != '\0')
++cur_;
if (cur_ != start) {
*value = std::string(start, cur_ - start);
}
if (cur_ == end || (!isspace(*end) && *end != '\0')) {
if (cur_ == end) {
error_log("%s: bad value for option '%s'", getprogname(), property->c_str());
} else {
error_log("%s: bad value for option '%s', non space found after option: %s",
getprogname(), property->c_str(), end);
}
return false;
} else if (read_value < 0) {
error_log("%s: bad value for option '%s', value cannot be negative: %ld",
getprogname(), property->c_str(), read_value);
return false;
}
*value = static_cast<size_t>(read_value);
cur_ = end;
} else {
*value_set = false;
}
return true;
}
@ -229,34 +312,19 @@ void PropertyParser::LogUsage() {
error_log("");
error_log(" leak_track");
error_log(" Enable the leak tracking of memory allocations.");
}
static bool SetFeature(
const std::string name, const Feature& feature, size_t value, bool value_set) {
if (feature.config) {
*feature.config = true;
}
if (feature.value != nullptr) {
if (value_set) {
if (value < feature.min_value) {
error_log("%s: bad value for option '%s', value must be >= %zu: %zu",
getprogname(), name.c_str(), feature.min_value, value);
return false;
} else if (value > feature.max_value) {
error_log("%s: bad value for option '%s', value must be <= %zu: %zu",
getprogname(), name.c_str(), feature.max_value, value);
return false;
}
*feature.value = value;
} else {
*feature.value = feature.default_value;
}
} else if (value_set) {
error_log("%s: value set for option '%s' which does not take a value",
getprogname(), name.c_str());
return false;
}
return true;
error_log("");
error_log(" record_allocs[=XX]");
error_log(" Record every single allocation/free call. When a specific signal");
error_log(" is sent to the process, the contents of recording are written to");
error_log(" a file (%s) and the recording is cleared.", DEFAULT_RECORD_ALLOCS_FILE);
error_log(" If XX is set, that is the total number of allocations/frees that can");
error_log(" recorded. of frames to capture. The default value is %zu.", DEFAULT_RECORD_ALLOCS);
error_log(" If the allocation list fills up, all further allocations are not recorded.");
error_log("");
error_log(" record_alloc_file[=FILE]");
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);
}
// This function is designed to be called once. A second call will not
@ -274,88 +342,123 @@ bool Config::SetFromProperties() {
front_guard_value = DEFAULT_FRONT_GUARD_VALUE;
rear_guard_value = DEFAULT_REAR_GUARD_VALUE;
backtrace_signal = SIGRTMAX - 19;
free_track_backtrace_num_frames = 16;
record_allocs_signal = SIGRTMAX - 18;
free_track_backtrace_num_frames = 0;
record_allocs_file.clear();
// Parse the options are of the format:
// option_name or option_name=XX
// Supported features:
const Feature features[] = {
Feature("guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, 0, nullptr, nullptr, true),
// Enable front guard. Value is the size of the guard.
Feature("front_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, FRONT_GUARD,
&this->front_guard_bytes, nullptr, true),
// Enable end guard. Value is the size of the guard.
Feature("rear_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, REAR_GUARD,
&this->rear_guard_bytes, nullptr, true),
// Supported options:
const OptionSizeT option_guard(
"guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, 0, nullptr, true);
// Enable front guard. Value is the size of the guard.
const OptionSizeT option_front_guard(
"front_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, FRONT_GUARD,
&this->front_guard_bytes, true);
// Enable end guard. Value is the size of the guard.
const OptionSizeT option_rear_guard(
"rear_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, REAR_GUARD, &this->rear_guard_bytes,
true);
// Enable logging the backtrace on allocation. Value is the total
// number of frames to log.
Feature("backtrace", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, &this->backtrace_enabled, false),
// Enable gathering backtrace values on a signal.
Feature("backtrace_enable_on_signal", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, &this->backtrace_enable_on_signal,
false),
// Enable logging the backtrace on allocation. Value is the total
// number of frames to log.
const OptionSizeT option_backtrace(
"backtrace", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES, BACKTRACE | TRACK_ALLOCS,
&this->backtrace_frames, false, &this->backtrace_enabled);
// Enable gathering backtrace values on a signal.
const OptionSizeT option_backtrace_enable_on_signal(
"backtrace_enable_on_signal", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, false, &this->backtrace_enable_on_signal);
Feature("fill", SIZE_MAX, 1, SIZE_MAX, 0, nullptr, nullptr, true),
// Fill the allocation with an arbitrary pattern on allocation.
// Value is the number of bytes of the allocation to fill
// (default entire allocation).
Feature("fill_on_alloc", SIZE_MAX, 1, SIZE_MAX, FILL_ON_ALLOC, &this->fill_on_alloc_bytes,
nullptr, true),
// Fill the allocation with an arbitrary pattern on free.
// Value is the number of bytes of the allocation to fill
// (default entire allocation).
Feature("fill_on_free", SIZE_MAX, 1, SIZE_MAX, FILL_ON_FREE, &this->fill_on_free_bytes, nullptr, true),
const OptionSizeT option_fill("fill", SIZE_MAX, 1, SIZE_MAX, 0, nullptr, true);
// Fill the allocation with an arbitrary pattern on allocation.
// Value is the number of bytes of the allocation to fill
// (default entire allocation).
const OptionSizeT option_fill_on_alloc(
"fill_on_alloc", SIZE_MAX, 1, SIZE_MAX, FILL_ON_ALLOC, &this->fill_on_alloc_bytes, true);
// Fill the allocation with an arbitrary pattern on free.
// Value is the number of bytes of the allocation to fill
// (default entire allocation).
const OptionSizeT option_fill_on_free(
"fill_on_free", SIZE_MAX, 1, SIZE_MAX, FILL_ON_FREE, &this->fill_on_free_bytes, true);
// Expand the size of every alloc by this number bytes. Value is
// the total number of bytes to expand every allocation by.
Feature ("expand_alloc", DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, EXPAND_ALLOC,
&this->expand_alloc_bytes, nullptr, false),
// Expand the size of every alloc by this number bytes. Value is
// the total number of bytes to expand every allocation by.
const OptionSizeT option_expand_alloc(
"expand_alloc", DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, EXPAND_ALLOC,
&this->expand_alloc_bytes);
// Keep track of the freed allocations and verify at a later date
// that they have not been used. Turning this on, also turns on
// fill on free.
Feature("free_track", DEFAULT_FREE_TRACK_ALLOCATIONS, 1, MAX_FREE_TRACK_ALLOCATIONS,
FREE_TRACK | FILL_ON_FREE, &this->free_track_allocations, nullptr, false),
// Number of backtrace frames to keep when free_track is enabled. If this
// value is set to zero, no backtrace will be kept.
Feature("free_track_backtrace_num_frames", DEFAULT_BACKTRACE_FRAMES,
0, MAX_BACKTRACE_FRAMES, 0, &this->free_track_backtrace_num_frames, nullptr, false),
// Keep track of the freed allocations and verify at a later date
// that they have not been used. Turning this on, also turns on
// fill on free.
const OptionSizeT option_free_track(
"free_track", DEFAULT_FREE_TRACK_ALLOCATIONS, 1, MAX_FREE_TRACK_ALLOCATIONS,
FREE_TRACK | FILL_ON_FREE, &this->free_track_allocations);
// Number of backtrace frames to keep when free_track is enabled. If this
// value is set to zero, no backtrace will be kept.
const OptionSizeT option_free_track_backtrace_num_frames(
"free_track_backtrace_num_frames", DEFAULT_BACKTRACE_FRAMES, 0, MAX_BACKTRACE_FRAMES, 0,
&this->free_track_backtrace_num_frames);
// Enable printing leaked allocations.
Feature("leak_track", 0, 0, 0, LEAK_TRACK | TRACK_ALLOCS, nullptr, nullptr, false),
// Enable printing leaked allocations.
const Option option_leak_track("leak_track", LEAK_TRACK | TRACK_ALLOCS);
const OptionSizeT option_record_allocs(
"record_allocs", DEFAULT_RECORD_ALLOCS, 1, MAX_RECORD_ALLOCS, RECORD_ALLOCS,
&this->record_allocs_num_entries);
const OptionString option_record_allocs_file(
"record_allocs_file", 0, DEFAULT_RECORD_ALLOCS_FILE, &this->record_allocs_file);
const Option* option_list[] = {
&option_guard, &option_front_guard, &option_rear_guard,
&option_backtrace, &option_backtrace_enable_on_signal,
&option_fill, &option_fill_on_alloc, &option_fill_on_free,
&option_expand_alloc,
&option_free_track, &option_free_track_backtrace_num_frames,
&option_leak_track,
&option_record_allocs, &option_record_allocs_file,
};
// Set defaults for all of the options.
for (size_t i = 0; i < sizeof(option_list)/sizeof(Option*); i++) {
option_list[i]->SetDefault();
}
// Process each property name we can find.
std::string property;
size_t value;
bool value_set;
PropertyParser parser(property_str);
bool valid = true;
while (valid && parser.Get(&property, &value, &value_set)) {
std::string property;
std::string value;
while (valid && parser.Get(&property, &value)) {
bool found = false;
for (size_t i = 0; i < sizeof(features)/sizeof(Feature); i++) {
if (property == features[i].name) {
if (features[i].option == 0 && features[i].combo_option) {
for (size_t i = 0; i < sizeof(option_list)/sizeof(Option*); i++) {
if (property == option_list[i]->name) {
if (option_list[i]->option == 0 && option_list[i]->combo_option) {
const std::string* option_name = &option_list[i]->name;
i++;
for (; i < sizeof(features)/sizeof(Feature) && features[i].combo_option; i++) {
if (!SetFeature(property, features[i], value, value_set)) {
for (; i < sizeof(option_list)/sizeof(Option*) && option_list[i]->combo_option; i++) {
if (!option_list[i]->ParseValue(*option_name, value)) {
valid = false;
break;
}
options |= features[i].option;
if (option_list[i]->config) {
*option_list[i]->config = true;
}
options |= option_list[i]->option;
}
if (!valid) {
break;
}
} else {
if (!SetFeature(property, features[i], value, value_set)) {
if (!option_list[i]->ParseValue(option_list[i]->name, value)) {
valid = false;
break;
}
options |= features[i].option;
if (option_list[i]->config) {
*option_list[i]->config = true;
}
options |= option_list[i]->option;
}
found = true;
break;
@ -376,13 +479,6 @@ bool Config::SetFromProperties() {
if (options & FRONT_GUARD) {
front_guard_bytes = BIONIC_ALIGN(front_guard_bytes, MINIMUM_ALIGNMENT_BYTES);
}
// This situation can occur if the free_track option is specified and
// the fill_on_free option is not. In this case, indicate the whole
// allocation should be filled.
if ((options & FILL_ON_FREE) && fill_on_free_bytes == 0) {
fill_on_free_bytes = SIZE_MAX;
}
} else {
parser.LogUsage();
}

View file

@ -31,6 +31,8 @@
#include <stdint.h>
#include <string>
constexpr uint64_t FRONT_GUARD = 0x1;
constexpr uint64_t REAR_GUARD = 0x2;
constexpr uint64_t BACKTRACE = 0x4;
@ -40,6 +42,7 @@ constexpr uint64_t EXPAND_ALLOC = 0x20;
constexpr uint64_t FREE_TRACK = 0x40;
constexpr uint64_t TRACK_ALLOCS = 0x80;
constexpr uint64_t LEAK_TRACK = 0x100;
constexpr uint64_t RECORD_ALLOCS = 0x200;
// In order to guarantee posix compliance, set the minimum alignment
// to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems.
@ -71,6 +74,10 @@ struct Config {
size_t free_track_allocations = 0;
size_t free_track_backtrace_num_frames = 0;
int record_allocs_signal = 0;
size_t record_allocs_num_entries = 0;
std::string record_allocs_file;
uint64_t options = 0;
uint8_t fill_alloc_value;
uint8_t fill_free_value;

View file

@ -77,6 +77,13 @@ bool DebugData::Initialize() {
}
}
if (config_.options & RECORD_ALLOCS) {
record.reset(new RecordData());
if (!record->Initialize(config_)) {
return false;
}
}
if (config_.options & EXPAND_ALLOC) {
extra_bytes_ += config_.expand_alloc_bytes;
}

View file

@ -41,6 +41,7 @@
#include "FreeTrackData.h"
#include "GuardData.h"
#include "malloc_debug.h"
#include "RecordData.h"
#include "TrackData.h"
class DebugData {
@ -91,6 +92,7 @@ class DebugData {
std::unique_ptr<FrontGuardData> front_guard;
std::unique_ptr<RearGuardData> rear_guard;
std::unique_ptr<FreeTrackData> free_track;
std::unique_ptr<RecordData> record;
private:
size_t extra_bytes_ = 0;

View file

@ -224,6 +224,108 @@ Example leak error found in the log:
04-15 12:35:33.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
04-15 12:35:33.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
### record\_allocs[=TOTAL\_ENTRIES]
Keep track of every allocation/free made on every thread and dump them
to a file when the signal SIGRTMAX - 18 (which is 46 on most Android devices)
is received.
If TOTAL\_ENTRIES is set, then it indicates the total number of
allocation/free records that can be retained. If the number of records
reaches the TOTAL\_ENTRIES value, then any further allocations/frees are
not recorded. The default value is 8,000,000 and the maximum value this
can be set to is 50,000,000.
Once the signal is received, and the current records are written to the
file, all current records are deleted. Any allocations/frees occuring while
the data is being dumped to the file are ignored.
**NOTE**: This option is not available until the O release of Android.
The allocation data is written in a human readable format. Every line begins
with the THREAD\_ID returned by gettid(), which is the thread that is making
the allocation/free. If a new thread is created, no special line is added
to the file. However, when a thread completes, a special entry is added to
the file indicating this.
The thread complete line is:
**THREAD\_ID**: thread\_done 0x0
Example:
187: thread_done 0x0
Below is how each type of allocation/free call ends up in the file dump.
pointer = malloc(size)
**THREAD\_ID**: malloc pointer size
Example:
186: malloc 0xb6038060 20
free(pointer)
**THREAD\_ID**: free pointer
Example:
186: free 0xb6038060
pointer = calloc(nmemb, size)
**THREAD\_ID**: calloc pointer nmemb size
Example:
186: calloc 0xb609f080 32 4
new\_pointer = realloc(old\_pointer, size)
**THREAD\_ID**: realloc new\_pointer old\_pointer size
Example:
186: realloc 0xb609f080 0xb603e9a0 12
pointer = memalign(alignment, size)
**THREAD\_ID**: memalign pointer alignment size
posix\_memalign(&pointer, alignment, size)
**THREAD\_ID**: memalign pointer alignment size
Example:
186: memalign 0x85423660 16 104
pointer = valloc(size)
**THREAD\_ID**: memalign pointer 4096 size
Example:
186: memalign 0x85423660 4096 112
pointer = pvalloc(size)
**THREAD\_ID**: memalign pointer 4096 <b>SIZE\_ROUNDED\_UP\_TO\_4096</b>
Example:
186: memalign 0x85423660 4096 8192
### record\_allocs\_file[=FILE\_NAME]
This option only has meaning if record\_allocs is set. It indicates the
file where the recorded allocations will be found.
If FILE\_NAME is set, then it indicates where the record allocation data
will be placed.
**NOTE**: This option is not available until the O release of Android.
Additional Errors
-----------------
There are a few other error messages that might appear in the log.

View file

@ -0,0 +1,231 @@
/*
* Copyright (C) 2016 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 <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <mutex>
#include <android-base/stringprintf.h>
#include "Config.h"
#include "debug_disable.h"
#include "debug_log.h"
#include "DebugData.h"
#include "RecordData.h"
RecordEntry::RecordEntry() : tid_(gettid()) {
}
std::string ThreadCompleteEntry::GetString() const {
return android::base::StringPrintf("%d: thread_done 0x0\n", tid_);
}
AllocEntry::AllocEntry(void* pointer) : pointer_(pointer) {
}
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) {
}
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) {
}
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) {
}
std::string ReallocEntry::GetString() const {
return android::base::StringPrintf("%d: realloc %p %p %zu\n", tid_, pointer_,
old_pointer_, size_);
}
// posix_memalign, memalgin, pvalloc, valloc all recorded with this class.
MemalignEntry::MemalignEntry(void* pointer, size_t size, size_t 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_);
}
struct ThreadData {
ThreadData(RecordData* record_data, ThreadCompleteEntry* entry) : record_data(record_data), entry(entry) {}
RecordData* record_data;
ThreadCompleteEntry* entry;
size_t count = 0;
};
static void ThreadKeyDelete(void* data) {
ThreadData* thread_data = reinterpret_cast<ThreadData*>(data);
thread_data->count++;
// This should be the last time we are called.
if (thread_data->count == 4) {
ScopedDisableDebugCalls disable;
thread_data->record_data->AddEntryOnly(thread_data->entry);
delete thread_data;
} else {
pthread_setspecific(thread_data->record_data->key(), data);
}
}
static void RecordDump(int, siginfo_t*, void*) {
// It's not necessarily safe to do the dump here, instead wait for the
// next allocation call to do the dump.
g_debug->record->SetToDump();
}
void RecordData::Dump() {
std::lock_guard<std::mutex> lock(dump_lock_);
// Make it so that no more entries can be added while dumping.
unsigned int last_entry_index = cur_index_.exchange(static_cast<unsigned int>(num_entries_));
if (dump_ == false) {
// Multiple Dump() calls from different threads, and we lost. Do nothing.
return;
}
// cur_index_ keeps getting incremented even if we hit the num_entries_.
// If that happens, cap the entries to dump by num_entries_.
if (last_entry_index > num_entries_) {
last_entry_index = num_entries_;
}
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();
ssize_t bytes = write(dump_fd, line.c_str(), line.length());
if (bytes == -1 || static_cast<size_t>(bytes) != line.length()) {
error_log("Failed to write record alloc information: %s", strerror(errno));
// Free all of the rest of the errors, we don't have any way
// to dump a partial list of the entries.
for (i++; i < last_entry_index; i++) {
delete entries_[i];
entries_[i] = nullptr;
}
break;
}
delete entries_[i];
entries_[i] = nullptr;
}
close(dump_fd);
// Mark the entries dumped.
cur_index_ = 0U;
} else {
error_log("Cannot create record alloc file %s: %s", dump_file_.c_str(), strerror(errno));
// Since we couldn't create the file, reset the entries dumped back
// to the original value.
cur_index_ = last_entry_index;
}
dump_ = false;
}
RecordData::RecordData() {
pthread_key_create(&key_, ThreadKeyDelete);
}
bool RecordData::Initialize(const Config& config) {
struct sigaction dump_act;
memset(&dump_act, 0, sizeof(dump_act));
dump_act.sa_sigaction = RecordDump;
dump_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
sigemptyset(&dump_act.sa_mask);
if (sigaction(config.record_allocs_signal, &dump_act, nullptr) != 0) {
error_log("Unable to set up record dump signal function: %s", strerror(errno));
return false;
}
pthread_setspecific(key_, nullptr);
info_log("%s: Run: 'kill -%d %d' to dump the allocation records.", getprogname(),
config.record_allocs_signal, getpid());
num_entries_ = config.record_allocs_num_entries;
entries_ = new const RecordEntry*[num_entries_];
cur_index_ = 0;
dump_ = false;
dump_file_ = config.record_allocs_file;
return true;
}
RecordData::~RecordData() {
delete [] entries_;
pthread_key_delete(key_);
}
void RecordData::AddEntryOnly(const RecordEntry* entry) {
unsigned int entry_index = cur_index_.fetch_add(1);
if (entry_index < num_entries_) {
entries_[entry_index] = entry;
}
}
void RecordData::AddEntry(const RecordEntry* entry) {
void* data = pthread_getspecific(key_);
if (data == nullptr) {
ThreadData* thread_data = new ThreadData(this, new ThreadCompleteEntry());
pthread_setspecific(key_, thread_data);
}
AddEntryOnly(entry);
// Check to see if it's time to dump the entries.
if (dump_) {
Dump();
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2016 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.
*/
#ifndef DEBUG_MALLOC_RECORDDATA_H
#define DEBUG_MALLOC_RECORDDATA_H
#include <stdint.h>
#include <pthread.h>
#include <unistd.h>
#include <atomic>
#include <mutex>
#include <string>
#include <private/bionic_macros.h>
class RecordEntry {
public:
RecordEntry();
virtual ~RecordEntry() = default;
virtual std::string GetString() const = 0;
protected:
pid_t tid_;
private:
DISALLOW_COPY_AND_ASSIGN(RecordEntry);
};
class ThreadCompleteEntry : public RecordEntry {
public:
ThreadCompleteEntry() = default;
virtual ~ThreadCompleteEntry() = default;
std::string GetString() const override;
private:
DISALLOW_COPY_AND_ASSIGN(ThreadCompleteEntry);
};
class AllocEntry : public RecordEntry {
public:
AllocEntry(void* pointer);
virtual ~AllocEntry() = default;
protected:
void* pointer_;
private:
DISALLOW_COPY_AND_ASSIGN(AllocEntry);
};
class MallocEntry : public AllocEntry {
public:
MallocEntry(void* pointer, size_t size);
virtual ~MallocEntry() = default;
std::string GetString() const override;
protected:
size_t size_;
private:
DISALLOW_COPY_AND_ASSIGN(MallocEntry);
};
class FreeEntry : public AllocEntry {
public:
FreeEntry(void* pointer);
virtual ~FreeEntry() = default;
std::string GetString() const override;
private:
DISALLOW_COPY_AND_ASSIGN(FreeEntry);
};
class CallocEntry : public MallocEntry {
public:
CallocEntry(void* pointer, size_t size, size_t nmemb);
virtual ~CallocEntry() = default;
std::string GetString() const override;
protected:
size_t nmemb_;
private:
DISALLOW_COPY_AND_ASSIGN(CallocEntry);
};
class ReallocEntry : public MallocEntry {
public:
ReallocEntry(void* pointer, size_t size, void* old_pointer);
virtual ~ReallocEntry() = default;
std::string GetString() const override;
protected:
void* old_pointer_;
private:
DISALLOW_COPY_AND_ASSIGN(ReallocEntry);
};
// posix_memalign, memalign, pvalloc, valloc all recorded with this class.
class MemalignEntry : public MallocEntry {
public:
MemalignEntry(void* pointer, size_t size, size_t alignment);
virtual ~MemalignEntry() = default;
std::string GetString() const override;
protected:
size_t alignment_;
private:
DISALLOW_COPY_AND_ASSIGN(MemalignEntry);
};
struct Config;
class RecordData {
public:
RecordData();
virtual ~RecordData();
bool Initialize(const Config& config);
void AddEntry(const RecordEntry* entry);
void AddEntryOnly(const RecordEntry* entry);
void SetToDump() { dump_ = true; }
pthread_key_t key() { return key_; }
private:
void Dump();
std::mutex dump_lock_;
pthread_key_t key_;
const RecordEntry** entries_ = nullptr;
size_t num_entries_ = 0;
std::atomic_uint cur_index_;
std::atomic_bool dump_;
std::string dump_file_;
DISALLOW_COPY_AND_ASSIGN(RecordData);
};
#endif // DEBUG_MALLOC_RECORDDATA_H

View file

@ -328,7 +328,13 @@ void* debug_malloc(size_t size) {
}
ScopedDisableDebugCalls disable;
return internal_malloc(size);
void* pointer = internal_malloc(size);
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new MallocEntry(pointer, size));
}
return pointer;
}
static void internal_free(void* pointer) {
@ -393,6 +399,10 @@ void debug_free(void* pointer) {
}
ScopedDisableDebugCalls disable;
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new FreeEntry(pointer));
}
internal_free(pointer);
}
@ -461,6 +471,10 @@ void* debug_memalign(size_t alignment, size_t 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;
}
@ -471,10 +485,18 @@ void* debug_realloc(void* pointer, size_t bytes) {
ScopedDisableDebugCalls disable;
if (pointer == nullptr) {
return internal_malloc(bytes);
pointer = internal_malloc(bytes);
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new ReallocEntry(pointer, bytes, nullptr));
}
return pointer;
}
if (bytes == 0) {
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new ReallocEntry(nullptr, bytes, pointer));
}
internal_free(pointer);
return nullptr;
}
@ -555,6 +577,10 @@ void* debug_realloc(void* pointer, size_t bytes) {
}
}
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new ReallocEntry(new_pointer, bytes, pointer));
}
return new_pointer;
}
@ -582,6 +608,7 @@ 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()) {
@ -596,10 +623,14 @@ void* debug_calloc(size_t nmemb, size_t bytes) {
return nullptr;
}
memset(header, 0, g_dispatch->malloc_usable_size(header));
return InitHeader(header, header, size);
pointer = InitHeader(header, header, size);
} else {
return g_dispatch->calloc(1, real_size);
pointer = g_dispatch->calloc(1, real_size);
}
if (g_debug->config().options & RECORD_ALLOCS) {
g_debug->record->AddEntry(new CallocEntry(pointer, bytes, nmemb));
}
return pointer;
}
struct mallinfo debug_mallinfo() {

View file

@ -113,6 +113,19 @@ std::string usage_string(
"6 malloc_debug \n"
"6 malloc_debug leak_track\n"
"6 malloc_debug Enable the leak tracking of memory allocations.\n"
"6 malloc_debug \n"
"6 malloc_debug record_allocs[=XX]\n"
"6 malloc_debug Record every single allocation/free call. When a specific signal\n"
"6 malloc_debug is sent to the process, the contents of recording are written to\n"
"6 malloc_debug a file (/data/local/tmp/record_allocs.txt) and the recording is cleared.\n"
"6 malloc_debug If XX is set, that is the total number of allocations/frees that can\n"
"6 malloc_debug recorded. of frames to capture. The default value is 8000000.\n"
"6 malloc_debug If the allocation list fills up, all further allocations are not recorded.\n"
"6 malloc_debug \n"
"6 malloc_debug record_alloc_file[=FILE]\n"
"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"
);
TEST_F(MallocDebugConfigTest, unknown_option) {
@ -190,7 +203,7 @@ TEST_F(MallocDebugConfigTest, set_value_error) {
}
TEST_F(MallocDebugConfigTest, space_before_equal) {
ASSERT_TRUE(InitConfig("backtrace =10"));
ASSERT_TRUE(InitConfig("backtrace =10")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(10U, config->backtrace_frames);
@ -199,7 +212,7 @@ TEST_F(MallocDebugConfigTest, space_before_equal) {
}
TEST_F(MallocDebugConfigTest, space_after_equal) {
ASSERT_TRUE(InitConfig("backtrace= 10"));
ASSERT_TRUE(InitConfig("backtrace= 10")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(10U, config->backtrace_frames);
@ -208,7 +221,7 @@ TEST_F(MallocDebugConfigTest, space_after_equal) {
}
TEST_F(MallocDebugConfigTest, extra_space) {
ASSERT_TRUE(InitConfig(" backtrace=64 "));
ASSERT_TRUE(InitConfig(" backtrace=64 ")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(64U, config->backtrace_frames);
@ -217,7 +230,7 @@ TEST_F(MallocDebugConfigTest, extra_space) {
}
TEST_F(MallocDebugConfigTest, multiple_options) {
ASSERT_TRUE(InitConfig(" backtrace=64 front_guard=48"));
ASSERT_TRUE(InitConfig(" backtrace=64 front_guard=48")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS | FRONT_GUARD, config->options);
ASSERT_EQ(64U, config->backtrace_frames);
ASSERT_EQ(48U, config->front_guard_bytes);
@ -227,15 +240,15 @@ TEST_F(MallocDebugConfigTest, multiple_options) {
}
TEST_F(MallocDebugConfigTest, front_guard) {
ASSERT_TRUE(InitConfig("front_guard=48"));
ASSERT_TRUE(InitConfig("front_guard=48")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD, config->options);
ASSERT_EQ(48U, config->front_guard_bytes);
ASSERT_TRUE(InitConfig("front_guard"));
ASSERT_TRUE(InitConfig("front_guard")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD, config->options);
ASSERT_EQ(32U, config->front_guard_bytes);
ASSERT_TRUE(InitConfig("front_guard=39"));
ASSERT_TRUE(InitConfig("front_guard=39")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD, config->options);
#if defined(__LP64__)
ASSERT_EQ(48U, config->front_guard_bytes);
@ -243,7 +256,7 @@ TEST_F(MallocDebugConfigTest, front_guard) {
ASSERT_EQ(40U, config->front_guard_bytes);
#endif
ASSERT_TRUE(InitConfig("front_guard=41"));
ASSERT_TRUE(InitConfig("front_guard=41")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD, config->options);
ASSERT_EQ(48U, config->front_guard_bytes);
@ -252,11 +265,11 @@ TEST_F(MallocDebugConfigTest, front_guard) {
}
TEST_F(MallocDebugConfigTest, rear_guard) {
ASSERT_TRUE(InitConfig("rear_guard=50"));
ASSERT_TRUE(InitConfig("rear_guard=50")) << getFakeLogPrint();
ASSERT_EQ(REAR_GUARD, config->options);
ASSERT_EQ(50U, config->rear_guard_bytes);
ASSERT_TRUE(InitConfig("rear_guard"));
ASSERT_TRUE(InitConfig("rear_guard")) << getFakeLogPrint();
ASSERT_EQ(REAR_GUARD, config->options);
ASSERT_EQ(32U, config->rear_guard_bytes);
@ -265,12 +278,12 @@ TEST_F(MallocDebugConfigTest, rear_guard) {
}
TEST_F(MallocDebugConfigTest, guard) {
ASSERT_TRUE(InitConfig("guard=32"));
ASSERT_TRUE(InitConfig("guard=32")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options);
ASSERT_EQ(32U, config->front_guard_bytes);
ASSERT_EQ(32U, config->rear_guard_bytes);
ASSERT_TRUE(InitConfig("guard"));
ASSERT_TRUE(InitConfig("guard")) << getFakeLogPrint();
ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options);
ASSERT_EQ(32U, config->front_guard_bytes);
ASSERT_EQ(32U, config->rear_guard_bytes);
@ -280,11 +293,11 @@ TEST_F(MallocDebugConfigTest, guard) {
}
TEST_F(MallocDebugConfigTest, backtrace) {
ASSERT_TRUE(InitConfig("backtrace=64"));
ASSERT_TRUE(InitConfig("backtrace=64")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(64U, config->backtrace_frames);
ASSERT_TRUE(InitConfig("backtrace"));
ASSERT_TRUE(InitConfig("backtrace")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(16U, config->backtrace_frames);
@ -293,11 +306,11 @@ TEST_F(MallocDebugConfigTest, backtrace) {
}
TEST_F(MallocDebugConfigTest, backtrace_enable_on_signal) {
ASSERT_TRUE(InitConfig("backtrace_enable_on_signal=64"));
ASSERT_TRUE(InitConfig("backtrace_enable_on_signal=64")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(64U, config->backtrace_frames);
ASSERT_TRUE(InitConfig("backtrace_enable_on_signal"));
ASSERT_TRUE(InitConfig("backtrace_enable_on_signal")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
ASSERT_EQ(16U, config->backtrace_frames);
@ -306,11 +319,11 @@ TEST_F(MallocDebugConfigTest, backtrace_enable_on_signal) {
}
TEST_F(MallocDebugConfigTest, fill_on_alloc) {
ASSERT_TRUE(InitConfig("fill_on_alloc=64"));
ASSERT_TRUE(InitConfig("fill_on_alloc=64")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_ALLOC, config->options);
ASSERT_EQ(64U, config->fill_on_alloc_bytes);
ASSERT_TRUE(InitConfig("fill_on_alloc"));
ASSERT_TRUE(InitConfig("fill_on_alloc")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_ALLOC, config->options);
ASSERT_EQ(SIZE_MAX, config->fill_on_alloc_bytes);
@ -319,11 +332,11 @@ TEST_F(MallocDebugConfigTest, fill_on_alloc) {
}
TEST_F(MallocDebugConfigTest, fill_on_free) {
ASSERT_TRUE(InitConfig("fill_on_free=64"));
ASSERT_TRUE(InitConfig("fill_on_free=64")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_FREE, config->options);
ASSERT_EQ(64U, config->fill_on_free_bytes);
ASSERT_TRUE(InitConfig("fill_on_free"));
ASSERT_TRUE(InitConfig("fill_on_free")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_FREE, config->options);
ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
@ -332,12 +345,12 @@ TEST_F(MallocDebugConfigTest, fill_on_free) {
}
TEST_F(MallocDebugConfigTest, fill) {
ASSERT_TRUE(InitConfig("fill=64"));
ASSERT_TRUE(InitConfig("fill=64")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_ALLOC | FILL_ON_FREE, config->options);
ASSERT_EQ(64U, config->fill_on_alloc_bytes);
ASSERT_EQ(64U, config->fill_on_free_bytes);
ASSERT_TRUE(InitConfig("fill"));
ASSERT_TRUE(InitConfig("fill")) << getFakeLogPrint();
ASSERT_EQ(FILL_ON_ALLOC | FILL_ON_FREE, config->options);
ASSERT_EQ(SIZE_MAX, config->fill_on_alloc_bytes);
ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
@ -347,11 +360,11 @@ TEST_F(MallocDebugConfigTest, fill) {
}
TEST_F(MallocDebugConfigTest, expand_alloc) {
ASSERT_TRUE(InitConfig("expand_alloc=1234"));
ASSERT_TRUE(InitConfig("expand_alloc=1234")) << getFakeLogPrint();
ASSERT_EQ(EXPAND_ALLOC, config->options);
ASSERT_EQ(1234U, config->expand_alloc_bytes);
ASSERT_TRUE(InitConfig("expand_alloc"));
ASSERT_TRUE(InitConfig("expand_alloc")) << getFakeLogPrint();
ASSERT_EQ(EXPAND_ALLOC, config->options);
ASSERT_EQ(16U, config->expand_alloc_bytes);
@ -360,13 +373,13 @@ TEST_F(MallocDebugConfigTest, expand_alloc) {
}
TEST_F(MallocDebugConfigTest, free_track) {
ASSERT_TRUE(InitConfig("free_track=1234"));
ASSERT_TRUE(InitConfig("free_track=1234")) << getFakeLogPrint();
ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, 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"));
ASSERT_TRUE(InitConfig("free_track")) << getFakeLogPrint();
ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
ASSERT_EQ(100U, config->free_track_allocations);
ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
@ -377,13 +390,13 @@ TEST_F(MallocDebugConfigTest, free_track) {
}
TEST_F(MallocDebugConfigTest, free_track_and_fill_on_free) {
ASSERT_TRUE(InitConfig("free_track=1234 fill_on_free=32"));
ASSERT_TRUE(InitConfig("free_track=1234 fill_on_free=32")) << getFakeLogPrint();
ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, 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"));
ASSERT_TRUE(InitConfig("free_track fill_on_free=60")) << getFakeLogPrint();
ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
ASSERT_EQ(100U, config->free_track_allocations);
ASSERT_EQ(60U, config->fill_on_free_bytes);
@ -394,12 +407,12 @@ TEST_F(MallocDebugConfigTest, free_track_and_fill_on_free) {
}
TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames) {
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=123"));
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=123")) << getFakeLogPrint();
ASSERT_EQ(0U, config->options);
ASSERT_EQ(123U, config->free_track_backtrace_num_frames);
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames"));
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames")) << getFakeLogPrint();
ASSERT_EQ(0U, config->options);
ASSERT_EQ(16U, config->free_track_backtrace_num_frames);
@ -408,7 +421,7 @@ TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames) {
}
TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_zero) {
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=0"));
ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=0")) << getFakeLogPrint();
ASSERT_EQ(0U, config->options);
ASSERT_EQ(0U, config->free_track_backtrace_num_frames);
@ -418,11 +431,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"));
ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames=123")) << getFakeLogPrint();
ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
ASSERT_EQ(123U, config->free_track_backtrace_num_frames);
ASSERT_TRUE(InitConfig("free_track 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(16U, config->free_track_backtrace_num_frames);
@ -431,7 +444,7 @@ TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_and_free_track) {
}
TEST_F(MallocDebugConfigTest, leak_track) {
ASSERT_TRUE(InitConfig("leak_track"));
ASSERT_TRUE(InitConfig("leak_track")) << getFakeLogPrint();
ASSERT_EQ(LEAK_TRACK | TRACK_ALLOCS, config->options);
ASSERT_STREQ("", getFakeLogBuf().c_str());
@ -439,7 +452,7 @@ TEST_F(MallocDebugConfigTest, leak_track) {
}
TEST_F(MallocDebugConfigTest, leak_track_fail) {
ASSERT_FALSE(InitConfig("leak_track=100"));
ASSERT_FALSE(InitConfig("leak_track=100")) << getFakeLogPrint();
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string log_msg(
@ -448,6 +461,32 @@ TEST_F(MallocDebugConfigTest, leak_track_fail) {
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);
ASSERT_EQ(1234U, config->record_allocs_num_entries);
ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
ASSERT_TRUE(InitConfig("record_allocs")) << getFakeLogPrint();
ASSERT_EQ(RECORD_ALLOCS, config->options);
ASSERT_EQ(8000000U, config->record_allocs_num_entries);
ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
ASSERT_STREQ("", getFakeLogPrint().c_str());
}
TEST_F(MallocDebugConfigTest, record_allocs_file) {
ASSERT_TRUE(InitConfig("record_allocs=1234 record_allocs_file=/fake/file")) << getFakeLogPrint();
ASSERT_STREQ("/fake/file", config->record_allocs_file.c_str());
ASSERT_TRUE(InitConfig("record_allocs_file")) << getFakeLogPrint();
ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
ASSERT_STREQ("", getFakeLogPrint().c_str());
}
TEST_F(MallocDebugConfigTest, guard_min_error) {
ASSERT_FALSE(InitConfig("guard=0"));
@ -626,3 +665,23 @@ TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_max_error) {
"value must be <= 256: 400\n");
ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
}
TEST_F(MallocDebugConfigTest, record_alloc_min_error) {
ASSERT_FALSE(InitConfig("record_allocs=0"));
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string log_msg(
"6 malloc_debug malloc_testing: bad value for option 'record_allocs', "
"value must be >= 1: 0\n");
ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
}
TEST_F(MallocDebugConfigTest, record_allocs_max_error) {
ASSERT_FALSE(InitConfig("record_allocs=100000000"));
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string log_msg(
"6 malloc_debug malloc_testing: bad value for option 'record_allocs', "
"value must be <= 50000000: 100000000\n");
ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
}

View file

@ -30,6 +30,7 @@
#include <gtest/gtest.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <private/bionic_macros.h>
@ -79,12 +80,16 @@ static size_t get_tag_offset(uint32_t flags = 0, size_t backtrace_frames = 0) {
return offset;
}
static constexpr const char RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt";
class MallocDebugTest : public ::testing::Test {
protected:
void SetUp() override {
initialized = false;
resetLogs();
backtrace_fake_clear_all();
// Delete the record data file if it exists.
unlink(RECORD_ALLOCS_FILE);
}
void TearDown() override {
@ -1266,7 +1271,7 @@ TEST_F(MallocDebugTest, backtrace_enable_on_signal) {
debug_free_malloc_leak_info(info);
// Send the signal to enable.
ASSERT_TRUE(kill(getpid(), SIGRTMIN + 10) == 0);
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 19) == 0);
sleep(1);
pointer = debug_malloc(100);
@ -1291,7 +1296,7 @@ TEST_F(MallocDebugTest, backtrace_enable_on_signal) {
debug_free_malloc_leak_info(info);
// Send the signal to disable.
ASSERT_TRUE(kill(getpid(), SIGRTMIN + 10) == 0);
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 19) == 0);
sleep(1);
pointer = debug_malloc(200);
@ -1311,7 +1316,7 @@ TEST_F(MallocDebugTest, backtrace_enable_on_signal) {
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string expected_log = android::base::StringPrintf(
"4 malloc_debug malloc_testing: Run: 'kill -%d %d' to enable backtracing.\n",
SIGRTMIN + 10, getpid());
SIGRTMAX - 19, getpid());
ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
}
@ -1512,3 +1517,231 @@ TEST_F(MallocDebugTest, debug_valloc) {
debug_free(pointer);
}
#endif
void VerifyRecordAllocs() {
std::string expected;
void* pointer = debug_malloc(10);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
pointer = debug_calloc(1, 20);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: calloc %p 20 1\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
pointer = debug_realloc(nullptr, 30);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: realloc %p 0x0 30\n", getpid(), pointer);
void* old_pointer = pointer;
pointer = debug_realloc(pointer, 2048);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: realloc %p %p 2048\n", getpid(),
pointer, old_pointer);
debug_realloc(pointer, 0);
expected += android::base::StringPrintf("%d: realloc 0x0 %p 0\n", getpid(), pointer);
pointer = debug_memalign(16, 40);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: memalign %p 16 40\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
ASSERT_EQ(0, debug_posix_memalign(&pointer, 32, 50));
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: memalign %p 32 50\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
pointer = debug_pvalloc(60);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: memalign %p 4096 4096\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
pointer = debug_valloc(70);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: memalign %p 4096 70\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
#endif
// Dump all of the data accumulated so far.
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
sleep(1);
// This triggers the dumping.
pointer = debug_malloc(110);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 110\n", getpid(), pointer);
// Read all of the contents.
std::string actual;
ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
ASSERT_STREQ(expected.c_str(), actual.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string expected_log = android::base::StringPrintf(
"4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
SIGRTMAX - 18, getpid());
ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
debug_free(pointer);
}
TEST_F(MallocDebugTest, record_allocs_no_header) {
Init("record_allocs");
VerifyRecordAllocs();
}
TEST_F(MallocDebugTest, record_allocs_with_header) {
Init("record_allocs front_guard");
VerifyRecordAllocs();
}
TEST_F(MallocDebugTest, record_allocs_max) {
Init("record_allocs=5");
std::string expected;
void* pointer = debug_malloc(10);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
pointer = debug_malloc(20);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 20\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
pointer = debug_malloc(1024);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 1024\n", getpid(), pointer);
debug_free(pointer);
// Dump all of the data accumulated so far.
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
sleep(1);
// This triggers the dumping.
pointer = debug_malloc(110);
ASSERT_TRUE(pointer != nullptr);
// Read all of the contents.
std::string actual;
ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
ASSERT_STREQ(expected.c_str(), actual.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string expected_log = android::base::StringPrintf(
"4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
SIGRTMAX - 18, getpid());
ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
debug_free(pointer);
}
TEST_F(MallocDebugTest, record_allocs_thread_done) {
Init("record_allocs=5");
static pid_t tid = 0;
static void* pointer = nullptr;
std::thread thread([](){
tid = gettid();
pointer = debug_malloc(100);
write(0, pointer, 0);
debug_free(pointer);
});
thread.join();
std::string expected = android::base::StringPrintf("%d: malloc %p 100\n", tid, pointer);
expected += android::base::StringPrintf("%d: free %p\n", tid, pointer);
expected += android::base::StringPrintf("%d: thread_done 0x0\n", tid);
// Dump all of the data accumulated so far.
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
sleep(1);
// This triggers the dumping.
pointer = debug_malloc(23);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 23\n", getpid(), pointer);
// Read all of the contents.
std::string actual;
ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
ASSERT_STREQ(expected.c_str(), actual.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string expected_log = android::base::StringPrintf(
"4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
SIGRTMAX - 18, getpid());
ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
debug_free(pointer);
}
TEST_F(MallocDebugTest, record_allocs_file_name_fail) {
Init("record_allocs=5");
// Delete the special.txt file and create a symbolic link there to
// make sure the create file will fail.
unlink(RECORD_ALLOCS_FILE);
ASSERT_EQ(0, symlink("/data/local/tmp/does_not_exist", RECORD_ALLOCS_FILE));
std::string expected;
void* pointer = debug_malloc(10);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
// Dump all of the data accumulated so far.
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
sleep(1);
// This triggers the dumping.
pointer = debug_malloc(110);
ASSERT_TRUE(pointer != nullptr);
expected += android::base::StringPrintf("%d: malloc %p 110\n", getpid(), pointer);
// Read all of the contents.
std::string actual;
ASSERT_FALSE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
// Unlink the file so the next dump passes.
ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE));
// Dump all of the data accumulated so far.
ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
sleep(1);
// This triggers the dumping.
debug_free(pointer);
expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
ASSERT_STREQ(expected.c_str(), actual.c_str());
ASSERT_STREQ("", getFakeLogBuf().c_str());
std::string expected_log = android::base::StringPrintf(
"4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
SIGRTMAX - 18, getpid());
expected_log += android::base::StringPrintf(
"6 malloc_debug Cannot create record alloc file %s: Too many symbolic links encountered\n",
RECORD_ALLOCS_FILE);
ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
}