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/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();
}