Add the record alloc option.
This option adds the ability to record all of the allocation requests
and dump them to a file when a signal is sent to the process.
Included in this change, redo the option processing to add a new
string option.
Bug: 27747898
Change-Id: Ida043362e38b5eb1d459c99db9c2581015dab366
diff --git a/libc/malloc_debug/Android.mk b/libc/malloc_debug/Android.mk
index 3576611..00f5f89 100644
--- a/libc/malloc_debug/Android.mk
+++ b/libc/malloc_debug/Android.mk
@@ -8,6 +8,7 @@
FreeTrackData.cpp \
GuardData.cpp \
malloc_debug.cpp \
+ RecordData.cpp \
TrackData.cpp \
# ==============================================================
@@ -58,6 +59,7 @@
LOCAL_STATIC_LIBRARIES_arm := libunwind_llvm
LOCAL_STATIC_LIBRARIES += \
+ libbase \
libc_malloc_debug_backtrace \
libc_logging \
diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp
index 6220a23..885542e 100644
--- a/libc/malloc_debug/Config.cpp
+++ b/libc/malloc_debug/Config.cpp
@@ -64,29 +64,126 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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();
}
diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h
index 3ee93b2..ac620ad 100644
--- a/libc/malloc_debug/Config.h
+++ b/libc/malloc_debug/Config.h
@@ -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 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 @@
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;
diff --git a/libc/malloc_debug/DebugData.cpp b/libc/malloc_debug/DebugData.cpp
index 58cbbcb..fdc2810 100644
--- a/libc/malloc_debug/DebugData.cpp
+++ b/libc/malloc_debug/DebugData.cpp
@@ -77,6 +77,13 @@
}
}
+ 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;
}
diff --git a/libc/malloc_debug/DebugData.h b/libc/malloc_debug/DebugData.h
index 7e55512..7e2df0c 100644
--- a/libc/malloc_debug/DebugData.h
+++ b/libc/malloc_debug/DebugData.h
@@ -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 @@
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;
diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md
index b3742cb..984430b 100644
--- a/libc/malloc_debug/README.md
+++ b/libc/malloc_debug/README.md
@@ -224,6 +224,108 @@
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.
diff --git a/libc/malloc_debug/RecordData.cpp b/libc/malloc_debug/RecordData.cpp
new file mode 100644
index 0000000..c0f3486
--- /dev/null
+++ b/libc/malloc_debug/RecordData.cpp
@@ -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();
+ }
+}
diff --git a/libc/malloc_debug/RecordData.h b/libc/malloc_debug/RecordData.h
new file mode 100644
index 0000000..741afd5
--- /dev/null
+++ b/libc/malloc_debug/RecordData.h
@@ -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
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index 5da5b88..d2bcf99 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -328,7 +328,13 @@
}
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 @@
}
ScopedDisableDebugCalls disable;
+ if (g_debug->config().options & RECORD_ALLOCS) {
+ g_debug->record->AddEntry(new FreeEntry(pointer));
+ }
+
internal_free(pointer);
}
@@ -461,6 +471,10 @@
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 @@
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 @@
}
}
+ if (g_debug->config().options & RECORD_ALLOCS) {
+ g_debug->record->AddEntry(new ReallocEntry(new_pointer, bytes, pointer));
+ }
+
return new_pointer;
}
@@ -582,6 +608,7 @@
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 @@
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() {
diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
index 85d5cb5..b8cf7cf 100644
--- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
@@ -113,6 +113,19 @@
"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, 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_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, 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, 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, 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 @@
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, 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, 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, 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_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, 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_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) {
- 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, 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, 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_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_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_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_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, 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_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 @@
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 @@
"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());
+}
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 014b913..edb03f6 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -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 @@
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 @@
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 @@
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 @@
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 @@
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());
+}