diff options
author | Christopher Ferris <cferris@google.com> | 2016-04-20 12:30:58 -0700 |
---|---|---|
committer | Christopher Ferris <cferris@google.com> | 2016-07-08 14:05:17 -0700 |
commit | 7bd01783a830f72c1245c262a7fd9a199e90aed1 (patch) | |
tree | 5bd657e6c452d5f9d81a6c9b156ee748c8a8f070 /libc | |
parent | a0e205e3b4f7993affb864260e924ce9f20573d0 (diff) |
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
Diffstat (limited to 'libc')
-rw-r--r-- | libc/malloc_debug/Android.mk | 2 | ||||
-rw-r--r-- | libc/malloc_debug/Config.cpp | 370 | ||||
-rw-r--r-- | libc/malloc_debug/Config.h | 7 | ||||
-rw-r--r-- | libc/malloc_debug/DebugData.cpp | 7 | ||||
-rw-r--r-- | libc/malloc_debug/DebugData.h | 2 | ||||
-rw-r--r-- | libc/malloc_debug/README.md | 102 | ||||
-rw-r--r-- | libc/malloc_debug/RecordData.cpp | 231 | ||||
-rw-r--r-- | libc/malloc_debug/RecordData.h | 177 | ||||
-rw-r--r-- | libc/malloc_debug/malloc_debug.cpp | 39 | ||||
-rw-r--r-- | libc/malloc_debug/tests/malloc_debug_config_tests.cpp | 129 | ||||
-rw-r--r-- | libc/malloc_debug/tests/malloc_debug_unit_tests.cpp | 239 |
11 files changed, 1126 insertions, 179 deletions
diff --git a/libc/malloc_debug/Android.mk b/libc/malloc_debug/Android.mk index 3576611f5..00f5f89c8 100644 --- a/libc/malloc_debug/Android.mk +++ b/libc/malloc_debug/Android.mk @@ -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 \ diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp index 6220a233a..885542e1e 100644 --- a/libc/malloc_debug/Config.cpp +++ b/libc/malloc_debug/Config.cpp @@ -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) {} +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"; + +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; - size_t default_value = 0; - size_t min_value = 0; - size_t max_value = 0; - uint64_t option = 0; - size_t* value = nullptr; - bool* config = nullptr; + 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; - } - 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; + // 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); } - *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), - - // 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), - - 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), - - // 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), - - // 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), - - // Enable printing leaked allocations. - Feature("leak_track", 0, 0, 0, LEAK_TRACK | TRACK_ALLOCS, nullptr, nullptr, false), + // 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. + 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); + + 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. + 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. + 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. + 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(); } diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h index 3ee93b23d..ac620adfe 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 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; diff --git a/libc/malloc_debug/DebugData.cpp b/libc/malloc_debug/DebugData.cpp index 58cbbcb47..fdc28100f 100644 --- a/libc/malloc_debug/DebugData.cpp +++ b/libc/malloc_debug/DebugData.cpp @@ -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; } diff --git a/libc/malloc_debug/DebugData.h b/libc/malloc_debug/DebugData.h index 7e55512e2..7e2df0c36 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 @@ 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; diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md index b3742cb4f..984430b3a 100644 --- a/libc/malloc_debug/README.md +++ b/libc/malloc_debug/README.md @@ -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. diff --git a/libc/malloc_debug/RecordData.cpp b/libc/malloc_debug/RecordData.cpp new file mode 100644 index 000000000..c0f348660 --- /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 000000000..741afd5fe --- /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 5da5b88e4..d2bcf9967 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -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() { diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp index 85d5cb582..b8cf7cf23 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 @@ 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()); +} diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp index 014b91352..edb03f6de 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 @@ 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()); +} |