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