Merge "Add the record alloc option."
This commit is contained in:
commit
779aa5ac33
11 changed files with 1122 additions and 175 deletions
|
@ -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 \
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
231
libc/malloc_debug/RecordData.cpp
Normal file
231
libc/malloc_debug/RecordData.cpp
Normal 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();
|
||||
}
|
||||
}
|
177
libc/malloc_debug/RecordData.h
Normal file
177
libc/malloc_debug/RecordData.h
Normal 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
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue